Merge branch 'jc/attr'
* 'jc/attr': (28 commits) lockfile: record the primary process. convert.c: restructure the attribute checking part. Fix bogus linked-list management for user defined merge drivers. Simplify calling of CR/LF conversion routines Document gitattributes(5) Update 'crlf' attribute semantics. Documentation: support manual section (5) - file formats. Simplify code to find recursive merge driver. Counto-fix in merge-recursive Fix funny types used in attribute value representation Allow low-level driver to specify different behaviour during internal merge. Custom low-level merge driver: change the configuration scheme. Allow the default low-level merge driver to be configured. Custom low-level merge driver support. Add a demonstration/test of customized merge. Allow specifying specialized merge-backend per path. merge-recursive: separate out xdl_merge() interface. Allow more than true/false to attributes. Document git-check-attr Change attribute negation marker from '!' to '-'. ...
This commit is contained in:
commit
a2d7c6c620
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ git-blame
|
|||||||
git-branch
|
git-branch
|
||||||
git-bundle
|
git-bundle
|
||||||
git-cat-file
|
git-cat-file
|
||||||
|
git-check-attr
|
||||||
git-check-ref-format
|
git-check-ref-format
|
||||||
git-checkout
|
git-checkout
|
||||||
git-checkout-index
|
git-checkout-index
|
||||||
|
@ -2,9 +2,10 @@ MAN1_TXT= \
|
|||||||
$(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
|
$(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
|
||||||
$(wildcard git-*.txt)) \
|
$(wildcard git-*.txt)) \
|
||||||
gitk.txt
|
gitk.txt
|
||||||
|
MAN5_TXT=gitattributes.txt
|
||||||
MAN7_TXT=git.txt
|
MAN7_TXT=git.txt
|
||||||
|
|
||||||
DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN7_TXT))
|
DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT))
|
||||||
|
|
||||||
ARTICLES = tutorial
|
ARTICLES = tutorial
|
||||||
ARTICLES += tutorial-2
|
ARTICLES += tutorial-2
|
||||||
@ -23,12 +24,14 @@ SP_ARTICLES = howto/revert-branch-rebase user-manual
|
|||||||
DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
|
DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
|
||||||
|
|
||||||
DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
|
DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
|
||||||
|
DOC_MAN5=$(patsubst %.txt,%.5,$(MAN1_TXT))
|
||||||
DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
|
DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
|
||||||
|
|
||||||
prefix?=$(HOME)
|
prefix?=$(HOME)
|
||||||
bindir?=$(prefix)/bin
|
bindir?=$(prefix)/bin
|
||||||
mandir?=$(prefix)/man
|
mandir?=$(prefix)/man
|
||||||
man1dir=$(mandir)/man1
|
man1dir=$(mandir)/man1
|
||||||
|
man5dir=$(mandir)/man5
|
||||||
man7dir=$(mandir)/man7
|
man7dir=$(mandir)/man7
|
||||||
# DESTDIR=
|
# DESTDIR=
|
||||||
|
|
||||||
@ -53,15 +56,19 @@ all: html man
|
|||||||
|
|
||||||
html: $(DOC_HTML)
|
html: $(DOC_HTML)
|
||||||
|
|
||||||
$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN7): asciidoc.conf
|
$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7): asciidoc.conf
|
||||||
|
|
||||||
man: man1 man7
|
man: man1 man5 man7
|
||||||
man1: $(DOC_MAN1)
|
man1: $(DOC_MAN1)
|
||||||
|
man5: $(DOC_MAN5)
|
||||||
man7: $(DOC_MAN7)
|
man7: $(DOC_MAN7)
|
||||||
|
|
||||||
install: man
|
install: man
|
||||||
$(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir)
|
$(INSTALL) -d -m755 $(DESTDIR)$(man1dir)
|
||||||
|
$(INSTALL) -d -m755 $(DESTDIR)$(man5dir)
|
||||||
|
$(INSTALL) -d -m755 $(DESTDIR)$(man7dir)
|
||||||
$(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
|
$(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
|
||||||
|
$(INSTALL) -m644 $(DOC_MAN5) $(DESTDIR)$(man5dir)
|
||||||
$(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
|
$(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
|
||||||
|
|
||||||
|
|
||||||
@ -99,7 +106,7 @@ cmd-list.made: cmd-list.perl $(MAN1_TXT)
|
|||||||
git.7 git.html: git.txt core-intro.txt
|
git.7 git.html: git.txt core-intro.txt
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.xml *.xml+ *.html *.html+ *.1 *.7 howto-index.txt howto/*.html doc.dep
|
rm -f *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 howto-index.txt howto/*.html doc.dep
|
||||||
rm -f $(cmds_txt) *.made
|
rm -f $(cmds_txt) *.made
|
||||||
|
|
||||||
%.html : %.txt
|
%.html : %.txt
|
||||||
@ -109,7 +116,7 @@ clean:
|
|||||||
sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+
|
sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+
|
||||||
mv $@+ $@
|
mv $@+ $@
|
||||||
|
|
||||||
%.1 %.7 : %.xml
|
%.1 %.5 %.7 : %.xml
|
||||||
xmlto -m callouts.xsl man $<
|
xmlto -m callouts.xsl man $<
|
||||||
|
|
||||||
%.xml : %.txt
|
%.xml : %.txt
|
||||||
|
@ -84,6 +84,7 @@ git-bundle mainporcelain
|
|||||||
git-cat-file plumbinginterrogators
|
git-cat-file plumbinginterrogators
|
||||||
git-checkout-index plumbingmanipulators
|
git-checkout-index plumbingmanipulators
|
||||||
git-checkout mainporcelain
|
git-checkout mainporcelain
|
||||||
|
git-check-attr purehelpers
|
||||||
git-check-ref-format purehelpers
|
git-check-ref-format purehelpers
|
||||||
git-cherry ancillaryinterrogators
|
git-cherry ancillaryinterrogators
|
||||||
git-cherry-pick mainporcelain
|
git-cherry-pick mainporcelain
|
||||||
|
@ -525,6 +525,19 @@ merge.verbosity::
|
|||||||
conflicts, 2 outputs conflicts and file changes. Level 5 and
|
conflicts, 2 outputs conflicts and file changes. Level 5 and
|
||||||
above outputs debugging information. The default is level 2.
|
above outputs debugging information. The default is level 2.
|
||||||
|
|
||||||
|
merge.<driver>.name::
|
||||||
|
Defines a human readable name for a custom low-level
|
||||||
|
merge driver. See gitlink:gitattributes[5] for details.
|
||||||
|
|
||||||
|
merge.<driver>.driver::
|
||||||
|
Defines the command that implements a custom low-level
|
||||||
|
merge driver. See gitlink:gitattributes[5] for details.
|
||||||
|
|
||||||
|
merge.<driver>.recursive::
|
||||||
|
Names a low-level merge driver to be used when
|
||||||
|
performing an internal merge between common ancestors.
|
||||||
|
See gitlink:gitattributes[5] for details.
|
||||||
|
|
||||||
pack.window::
|
pack.window::
|
||||||
The size of the window used by gitlink:git-pack-objects[1] when no
|
The size of the window used by gitlink:git-pack-objects[1] when no
|
||||||
window size is given on the command line. Defaults to 10.
|
window size is given on the command line. Defaults to 10.
|
||||||
|
37
Documentation/git-check-attr.txt
Normal file
37
Documentation/git-check-attr.txt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
git-check-attr(1)
|
||||||
|
=================
|
||||||
|
|
||||||
|
NAME
|
||||||
|
----
|
||||||
|
git-check-attr - Display gitattributes information.
|
||||||
|
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
--------
|
||||||
|
'git-check-attr' attr... [--] pathname...
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
-----------
|
||||||
|
For every pathname, this command will list if each attr is 'unspecified',
|
||||||
|
'set', or 'unset' as a gitattribute on that pathname.
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
-------
|
||||||
|
\--::
|
||||||
|
Interpret all preceding arguments as attributes, and all following
|
||||||
|
arguments as path names. If not supplied, only the first argument will
|
||||||
|
be treated as an attribute.
|
||||||
|
|
||||||
|
|
||||||
|
Author
|
||||||
|
------
|
||||||
|
Written by Junio C Hamano <junkio@cox.net>
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
--------------
|
||||||
|
Documentation by James Bowes.
|
||||||
|
|
||||||
|
GIT
|
||||||
|
---
|
||||||
|
Part of the gitlink:git[7] suite
|
||||||
|
|
285
Documentation/gitattributes.txt
Normal file
285
Documentation/gitattributes.txt
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
gitattributes(5)
|
||||||
|
================
|
||||||
|
|
||||||
|
NAME
|
||||||
|
----
|
||||||
|
gitattributes - defining attributes per path
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
--------
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
-----------
|
||||||
|
|
||||||
|
A `gitattributes` file is a simple text file that gives
|
||||||
|
`attributes` to pathnames.
|
||||||
|
|
||||||
|
Each line in `gitattributes` file is of form:
|
||||||
|
|
||||||
|
glob attr1 attr2 ...
|
||||||
|
|
||||||
|
That is, a glob pattern followed by an attributes list,
|
||||||
|
separated by whitespaces. When the glob pattern matches the
|
||||||
|
path in question, the attributes listed on the line are given to
|
||||||
|
the path.
|
||||||
|
|
||||||
|
Each attribute can be in one of these states for a given path:
|
||||||
|
|
||||||
|
Set::
|
||||||
|
|
||||||
|
The path has the attribute with special value "true";
|
||||||
|
this is specified by listing only the name of the
|
||||||
|
attribute in the attribute list.
|
||||||
|
|
||||||
|
Unset::
|
||||||
|
|
||||||
|
The path has the attribute with special value "false";
|
||||||
|
this is specified by listing the name of the attribute
|
||||||
|
prefixed with a dash `-` in the attribute list.
|
||||||
|
|
||||||
|
Set to a value::
|
||||||
|
|
||||||
|
The path has the attribute with specified string value;
|
||||||
|
this is specified by listing the name of the attribute
|
||||||
|
followed by an equal sign `=` and its value in the
|
||||||
|
attribute list.
|
||||||
|
|
||||||
|
Unspecified::
|
||||||
|
|
||||||
|
No glob pattern matches the path, and nothing says if
|
||||||
|
the path has or does not have the attribute.
|
||||||
|
|
||||||
|
When more than one glob pattern matches the path, a later line
|
||||||
|
overrides an earlier line.
|
||||||
|
|
||||||
|
When deciding what attributes are assigned to a path, git
|
||||||
|
consults `$GIT_DIR/info/attributes` file (which has the highest
|
||||||
|
precedence), `.gitattributes` file in the same directory as the
|
||||||
|
path in question, and its parent directories (the further the
|
||||||
|
directory that contains `.gitattributes` is from the path in
|
||||||
|
question, the lower its precedence).
|
||||||
|
|
||||||
|
Sometimes you would need to override an setting of an attribute
|
||||||
|
for a path to `unspecified` state. This can be done by listing
|
||||||
|
the name of the attribute prefixed with an exclamation point `!`.
|
||||||
|
|
||||||
|
|
||||||
|
EFFECTS
|
||||||
|
-------
|
||||||
|
|
||||||
|
Certain operations by git can be influenced by assigning
|
||||||
|
particular attributes to a path. Currently, three operations
|
||||||
|
are attributes-aware.
|
||||||
|
|
||||||
|
Checking-out and checking-in
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The attribute `crlf` affects how the contents stored in the
|
||||||
|
repository are copied to the working tree files when commands
|
||||||
|
such as `git checkout` and `git merge` run. It also affects how
|
||||||
|
git stores the contents you prepare in the working tree in the
|
||||||
|
repository upon `git add` and `git commit`.
|
||||||
|
|
||||||
|
Set::
|
||||||
|
|
||||||
|
Setting the `crlf` attribute on a path is meant to mark
|
||||||
|
the path as a "text" file. 'core.autocrlf' conversion
|
||||||
|
takes place without guessing the content type by
|
||||||
|
inspection.
|
||||||
|
|
||||||
|
Unset::
|
||||||
|
|
||||||
|
Unsetting the `crlf` attribute on a path is meant to
|
||||||
|
mark the path as a "binary" file. The path never goes
|
||||||
|
through line endings conversion upon checkin/checkout.
|
||||||
|
|
||||||
|
Unspecified::
|
||||||
|
|
||||||
|
Unspecified `crlf` attribute tells git to apply the
|
||||||
|
`core.autocrlf` conversion when the file content looks
|
||||||
|
like text.
|
||||||
|
|
||||||
|
Set to string value "input"::
|
||||||
|
|
||||||
|
This is similar to setting the attribute to `true`, but
|
||||||
|
also forces git to act as if `core.autocrlf` is set to
|
||||||
|
`input` for the path.
|
||||||
|
|
||||||
|
Any other value set to `crlf` attribute is ignored and git acts
|
||||||
|
as if the attribute is left unspecified.
|
||||||
|
|
||||||
|
|
||||||
|
The `core.autocrlf` conversion
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If the configuration variable `core.autocrlf` is false, no
|
||||||
|
conversion is done.
|
||||||
|
|
||||||
|
When `core.autocrlf` is true, it means that the platform wants
|
||||||
|
CRLF line endings for files in the working tree, and you want to
|
||||||
|
convert them back to the normal LF line endings when checking
|
||||||
|
in to the repository.
|
||||||
|
|
||||||
|
When `core.autocrlf` is set to "input", line endings are
|
||||||
|
converted to LF upon checkin, but there is no conversion done
|
||||||
|
upon checkout.
|
||||||
|
|
||||||
|
|
||||||
|
Generating diff text
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The attribute `diff` affects if `git diff` generates textual
|
||||||
|
patch for the path or just says `Binary files differ`.
|
||||||
|
|
||||||
|
Set::
|
||||||
|
|
||||||
|
A path to which the `diff` attribute is set is treated
|
||||||
|
as text, even when they contain byte values that
|
||||||
|
normally never appear in text files, such as NUL.
|
||||||
|
|
||||||
|
Unset::
|
||||||
|
|
||||||
|
A path to which the `diff` attribute is unset will
|
||||||
|
generate `Binary files differ`.
|
||||||
|
|
||||||
|
Unspecified::
|
||||||
|
|
||||||
|
A path to which the `diff` attribute is unspecified
|
||||||
|
first gets its contents inspected, and if it looks like
|
||||||
|
text, it is treated as text. Otherwise it would
|
||||||
|
generate `Binary files differ`.
|
||||||
|
|
||||||
|
Any other value set to `diff` attribute is ignored and git acts
|
||||||
|
as if the attribute is left unspecified.
|
||||||
|
|
||||||
|
|
||||||
|
Performing a three-way merge
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The attribute `merge` affects how three versions of a file is
|
||||||
|
merged when a file-level merge is necessary during `git merge`,
|
||||||
|
and other programs such as `git revert` and `git cherry-pick`.
|
||||||
|
|
||||||
|
Set::
|
||||||
|
|
||||||
|
Built-in 3-way merge driver is used to merge the
|
||||||
|
contents in a way similar to `merge` command of `RCS`
|
||||||
|
suite. This is suitable for ordinary text files.
|
||||||
|
|
||||||
|
Unset::
|
||||||
|
|
||||||
|
Take the version from the current branch as the
|
||||||
|
tentative merge result, and declare that the merge has
|
||||||
|
conflicts. This is suitable for binary files that does
|
||||||
|
not have a well-defined merge semantics.
|
||||||
|
|
||||||
|
Unspecified::
|
||||||
|
|
||||||
|
By default, this uses the same built-in 3-way merge
|
||||||
|
driver as is the case the `merge` attribute is set.
|
||||||
|
However, `merge.default` configuration variable can name
|
||||||
|
different merge driver to be used for paths to which the
|
||||||
|
`merge` attribute is unspecified.
|
||||||
|
|
||||||
|
Any other string value::
|
||||||
|
|
||||||
|
3-way merge is performed using the specified custom
|
||||||
|
merge driver. The built-in 3-way merge driver can be
|
||||||
|
explicitly specified by asking for "text" driver; the
|
||||||
|
built-in "take the current branch" driver can be
|
||||||
|
requested by "binary".
|
||||||
|
|
||||||
|
|
||||||
|
Defining a custom merge driver
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The definition of a merge driver is done in `gitconfig` not
|
||||||
|
`gitattributes` file, so strictly speaking this manual page is a
|
||||||
|
wrong place to talk about it. However...
|
||||||
|
|
||||||
|
To define a custom merge driver `filfre`, add a section to your
|
||||||
|
`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
[merge "filfre"]
|
||||||
|
name = feel-free merge driver
|
||||||
|
driver = filfre %O %A %B
|
||||||
|
recursive = binary
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
The `merge.*.name` variable gives the driver a human-readable
|
||||||
|
name.
|
||||||
|
|
||||||
|
The `merge.*.driver` variable's value is used to construct a
|
||||||
|
command to run to merge ancestor's version (`%O`), current
|
||||||
|
version (`%A`) and the other branches' version (`%B`). These
|
||||||
|
three tokens are replaced with the names of temporary files that
|
||||||
|
hold the contents of these versions when the command line is
|
||||||
|
built.
|
||||||
|
|
||||||
|
The merge driver is expected to leave the result of the merge in
|
||||||
|
the file named with `%A` by overwriting it, and exit with zero
|
||||||
|
status if it managed to merge them cleanly, or non-zero if there
|
||||||
|
were conflicts.
|
||||||
|
|
||||||
|
The `merge.*.recursive` variable specifies what other merge
|
||||||
|
driver to use when the merge driver is called for an internal
|
||||||
|
merge between common ancestors, when there are more than one.
|
||||||
|
When left unspecified, the driver itself is used for both
|
||||||
|
internal merge and the final merge.
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLE
|
||||||
|
-------
|
||||||
|
|
||||||
|
If you have these three `gitattributes` file:
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
(in $GIT_DIR/info/attributes)
|
||||||
|
|
||||||
|
a* foo !bar -baz
|
||||||
|
|
||||||
|
(in .gitattributes)
|
||||||
|
abc foo bar baz
|
||||||
|
|
||||||
|
(in t/.gitattributes)
|
||||||
|
ab* merge=filfre
|
||||||
|
abc -foo -bar
|
||||||
|
*.c frotz
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
the attributes given to path `t/abc` are computed as follows:
|
||||||
|
|
||||||
|
1. By examining `t/.gitattributes` (which is in the same
|
||||||
|
diretory as the path in question), git finds that the first
|
||||||
|
line matches. `merge` attribute is set. It also finds that
|
||||||
|
the second line matches, and attributes `foo` and `bar`
|
||||||
|
are unset.
|
||||||
|
|
||||||
|
2. Then it examines `.gitattributes` (which is in the parent
|
||||||
|
directory), and finds that the first line matches, but
|
||||||
|
`t/.gitattributes` file already decided how `merge`, `foo`
|
||||||
|
and `bar` attributes should be given to this path, so it
|
||||||
|
leaves `foo` and `bar` unset. Attribute `baz` is set.
|
||||||
|
|
||||||
|
3. Finally it examines `$GIT_DIR/info/gitattributes`. This file
|
||||||
|
is used to override the in-tree settings. The first line is
|
||||||
|
a match, and `foo` is set, `bar` is reverted to unspecified
|
||||||
|
state, and `baz` is unset.
|
||||||
|
|
||||||
|
As the result, the attributes assignement to `t/abc` becomes:
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
foo set to true
|
||||||
|
bar unspecified
|
||||||
|
baz set to false
|
||||||
|
merge set to string value "filfre"
|
||||||
|
frotz unspecified
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
GIT
|
||||||
|
---
|
||||||
|
Part of the gitlink:git[7] suite
|
8
Makefile
8
Makefile
@ -283,7 +283,7 @@ LIB_H = \
|
|||||||
diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
|
diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
|
||||||
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
|
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
|
||||||
tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
|
tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
|
||||||
utf8.h reflog-walk.h patch-ids.h decorate.h
|
utf8.h reflog-walk.h patch-ids.h attr.h decorate.h
|
||||||
|
|
||||||
DIFF_OBJS = \
|
DIFF_OBJS = \
|
||||||
diff.o diff-lib.o diffcore-break.o diffcore-order.o \
|
diff.o diff-lib.o diffcore-break.o diffcore-order.o \
|
||||||
@ -305,7 +305,7 @@ LIB_OBJS = \
|
|||||||
write_or_die.o trace.o list-objects.o grep.o match-trees.o \
|
write_or_die.o trace.o list-objects.o grep.o match-trees.o \
|
||||||
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
|
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
|
||||||
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
|
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
|
||||||
convert.o decorate.o
|
convert.o attr.o decorate.o
|
||||||
|
|
||||||
BUILTIN_OBJS = \
|
BUILTIN_OBJS = \
|
||||||
builtin-add.o \
|
builtin-add.o \
|
||||||
@ -316,6 +316,7 @@ BUILTIN_OBJS = \
|
|||||||
builtin-branch.o \
|
builtin-branch.o \
|
||||||
builtin-bundle.o \
|
builtin-bundle.o \
|
||||||
builtin-cat-file.o \
|
builtin-cat-file.o \
|
||||||
|
builtin-check-attr.o \
|
||||||
builtin-checkout-index.o \
|
builtin-checkout-index.o \
|
||||||
builtin-check-ref-format.o \
|
builtin-check-ref-format.o \
|
||||||
builtin-commit-tree.o \
|
builtin-commit-tree.o \
|
||||||
@ -1032,9 +1033,10 @@ dist-doc:
|
|||||||
gzip -n -9 -f $(htmldocs).tar
|
gzip -n -9 -f $(htmldocs).tar
|
||||||
:
|
:
|
||||||
rm -fr .doc-tmp-dir
|
rm -fr .doc-tmp-dir
|
||||||
mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7
|
mkdir -p .doc-tmp-dir/man1 .doc-tmp-dir/man5 .doc-tmp-dir/man7
|
||||||
$(MAKE) -C Documentation DESTDIR=./ \
|
$(MAKE) -C Documentation DESTDIR=./ \
|
||||||
man1dir=../.doc-tmp-dir/man1 \
|
man1dir=../.doc-tmp-dir/man1 \
|
||||||
|
man5dir=../.doc-tmp-dir/man5 \
|
||||||
man7dir=../.doc-tmp-dir/man7 \
|
man7dir=../.doc-tmp-dir/man7 \
|
||||||
install
|
install
|
||||||
cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
|
cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
|
||||||
|
563
attr.c
Normal file
563
attr.c
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
#include "cache.h"
|
||||||
|
#include "attr.h"
|
||||||
|
|
||||||
|
const char git_attr__true[] = "(builtin)true";
|
||||||
|
const char git_attr__false[] = "\0(builtin)false";
|
||||||
|
static const char git_attr__unknown[] = "(builtin)unknown";
|
||||||
|
#define ATTR__TRUE git_attr__true
|
||||||
|
#define ATTR__FALSE git_attr__false
|
||||||
|
#define ATTR__UNSET NULL
|
||||||
|
#define ATTR__UNKNOWN git_attr__unknown
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The basic design decision here is that we are not going to have
|
||||||
|
* insanely large number of attributes.
|
||||||
|
*
|
||||||
|
* This is a randomly chosen prime.
|
||||||
|
*/
|
||||||
|
#define HASHSIZE 257
|
||||||
|
|
||||||
|
#ifndef DEBUG_ATTR
|
||||||
|
#define DEBUG_ATTR 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct git_attr {
|
||||||
|
struct git_attr *next;
|
||||||
|
unsigned h;
|
||||||
|
int attr_nr;
|
||||||
|
char name[FLEX_ARRAY];
|
||||||
|
};
|
||||||
|
static int attr_nr;
|
||||||
|
|
||||||
|
static struct git_attr_check *check_all_attr;
|
||||||
|
static struct git_attr *(git_attr_hash[HASHSIZE]);
|
||||||
|
|
||||||
|
static unsigned hash_name(const char *name, int namelen)
|
||||||
|
{
|
||||||
|
unsigned val = 0;
|
||||||
|
unsigned char c;
|
||||||
|
|
||||||
|
while (namelen--) {
|
||||||
|
c = *name++;
|
||||||
|
val = ((val << 7) | (val >> 22)) ^ c;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int invalid_attr_name(const char *name, int namelen)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Attribute name cannot begin with '-' and from
|
||||||
|
* [-A-Za-z0-9_.]. We'd specifically exclude '=' for now,
|
||||||
|
* as we might later want to allow non-binary value for
|
||||||
|
* attributes, e.g. "*.svg merge=special-merge-program-for-svg"
|
||||||
|
*/
|
||||||
|
if (*name == '-')
|
||||||
|
return -1;
|
||||||
|
while (namelen--) {
|
||||||
|
char ch = *name++;
|
||||||
|
if (! (ch == '-' || ch == '.' || ch == '_' ||
|
||||||
|
('0' <= ch && ch <= '9') ||
|
||||||
|
('a' <= ch && ch <= 'z') ||
|
||||||
|
('A' <= ch && ch <= 'Z')) )
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct git_attr *git_attr(const char *name, int len)
|
||||||
|
{
|
||||||
|
unsigned hval = hash_name(name, len);
|
||||||
|
unsigned pos = hval % HASHSIZE;
|
||||||
|
struct git_attr *a;
|
||||||
|
|
||||||
|
for (a = git_attr_hash[pos]; a; a = a->next) {
|
||||||
|
if (a->h == hval &&
|
||||||
|
!memcmp(a->name, name, len) && !a->name[len])
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalid_attr_name(name, len))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
a = xmalloc(sizeof(*a) + len + 1);
|
||||||
|
memcpy(a->name, name, len);
|
||||||
|
a->name[len] = 0;
|
||||||
|
a->h = hval;
|
||||||
|
a->next = git_attr_hash[pos];
|
||||||
|
a->attr_nr = attr_nr++;
|
||||||
|
git_attr_hash[pos] = a;
|
||||||
|
|
||||||
|
check_all_attr = xrealloc(check_all_attr,
|
||||||
|
sizeof(*check_all_attr) * attr_nr);
|
||||||
|
check_all_attr[a->attr_nr].attr = a;
|
||||||
|
check_all_attr[a->attr_nr].value = ATTR__UNKNOWN;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* .gitattributes file is one line per record, each of which is
|
||||||
|
*
|
||||||
|
* (1) glob pattern.
|
||||||
|
* (2) whitespace
|
||||||
|
* (3) whitespace separated list of attribute names, each of which
|
||||||
|
* could be prefixed with '-' to mean "set to false", '!' to mean
|
||||||
|
* "unset".
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* What does a matched pattern decide? */
|
||||||
|
struct attr_state {
|
||||||
|
struct git_attr *attr;
|
||||||
|
const char *setto;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct match_attr {
|
||||||
|
union {
|
||||||
|
char *pattern;
|
||||||
|
struct git_attr *attr;
|
||||||
|
} u;
|
||||||
|
char is_macro;
|
||||||
|
unsigned num_attr;
|
||||||
|
struct attr_state state[FLEX_ARRAY];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char blank[] = " \t\r\n";
|
||||||
|
|
||||||
|
static const char *parse_attr(const char *src, int lineno, const char *cp,
|
||||||
|
int *num_attr, struct match_attr *res)
|
||||||
|
{
|
||||||
|
const char *ep, *equals;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
ep = cp + strcspn(cp, blank);
|
||||||
|
equals = strchr(cp, '=');
|
||||||
|
if (equals && ep < equals)
|
||||||
|
equals = NULL;
|
||||||
|
if (equals)
|
||||||
|
len = equals - cp;
|
||||||
|
else
|
||||||
|
len = ep - cp;
|
||||||
|
if (!res) {
|
||||||
|
if (*cp == '-' || *cp == '!') {
|
||||||
|
cp++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
if (invalid_attr_name(cp, len)) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%.*s is not a valid attribute name: %s:%d\n",
|
||||||
|
len, cp, src, lineno);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct attr_state *e;
|
||||||
|
|
||||||
|
e = &(res->state[*num_attr]);
|
||||||
|
if (*cp == '-' || *cp == '!') {
|
||||||
|
e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
|
||||||
|
cp++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
else if (!equals)
|
||||||
|
e->setto = ATTR__TRUE;
|
||||||
|
else {
|
||||||
|
char *value;
|
||||||
|
int vallen = ep - equals;
|
||||||
|
value = xmalloc(vallen);
|
||||||
|
memcpy(value, equals+1, vallen-1);
|
||||||
|
value[vallen-1] = 0;
|
||||||
|
e->setto = value;
|
||||||
|
}
|
||||||
|
e->attr = git_attr(cp, len);
|
||||||
|
}
|
||||||
|
(*num_attr)++;
|
||||||
|
return ep + strspn(ep, blank);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct match_attr *parse_attr_line(const char *line, const char *src,
|
||||||
|
int lineno, int macro_ok)
|
||||||
|
{
|
||||||
|
int namelen;
|
||||||
|
int num_attr;
|
||||||
|
const char *cp, *name;
|
||||||
|
struct match_attr *res = NULL;
|
||||||
|
int pass;
|
||||||
|
int is_macro;
|
||||||
|
|
||||||
|
cp = line + strspn(line, blank);
|
||||||
|
if (!*cp || *cp == '#')
|
||||||
|
return NULL;
|
||||||
|
name = cp;
|
||||||
|
namelen = strcspn(name, blank);
|
||||||
|
if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen &&
|
||||||
|
!prefixcmp(name, ATTRIBUTE_MACRO_PREFIX)) {
|
||||||
|
if (!macro_ok) {
|
||||||
|
fprintf(stderr, "%s not allowed: %s:%d\n",
|
||||||
|
name, src, lineno);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
is_macro = 1;
|
||||||
|
name += strlen(ATTRIBUTE_MACRO_PREFIX);
|
||||||
|
name += strspn(name, blank);
|
||||||
|
namelen = strcspn(name, blank);
|
||||||
|
if (invalid_attr_name(name, namelen)) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%.*s is not a valid attribute name: %s:%d\n",
|
||||||
|
namelen, name, src, lineno);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
is_macro = 0;
|
||||||
|
|
||||||
|
for (pass = 0; pass < 2; pass++) {
|
||||||
|
/* pass 0 counts and allocates, pass 1 fills */
|
||||||
|
num_attr = 0;
|
||||||
|
cp = name + namelen;
|
||||||
|
cp = cp + strspn(cp, blank);
|
||||||
|
while (*cp)
|
||||||
|
cp = parse_attr(src, lineno, cp, &num_attr, res);
|
||||||
|
if (pass)
|
||||||
|
break;
|
||||||
|
res = xcalloc(1,
|
||||||
|
sizeof(*res) +
|
||||||
|
sizeof(struct attr_state) * num_attr +
|
||||||
|
(is_macro ? 0 : namelen + 1));
|
||||||
|
if (is_macro)
|
||||||
|
res->u.attr = git_attr(name, namelen);
|
||||||
|
else {
|
||||||
|
res->u.pattern = (char*)&(res->state[num_attr]);
|
||||||
|
memcpy(res->u.pattern, name, namelen);
|
||||||
|
res->u.pattern[namelen] = 0;
|
||||||
|
}
|
||||||
|
res->is_macro = is_macro;
|
||||||
|
res->num_attr = num_attr;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like info/exclude and .gitignore, the attribute information can
|
||||||
|
* come from many places.
|
||||||
|
*
|
||||||
|
* (1) .gitattribute file of the same directory;
|
||||||
|
* (2) .gitattribute file of the parent directory if (1) does not have
|
||||||
|
* any match; this goes recursively upwards, just like .gitignore.
|
||||||
|
* (3) $GIT_DIR/info/attributes, which overrides both of the above.
|
||||||
|
*
|
||||||
|
* In the same file, later entries override the earlier match, so in the
|
||||||
|
* global list, we would have entries from info/attributes the earliest
|
||||||
|
* (reading the file from top to bottom), .gitattribute of the root
|
||||||
|
* directory (again, reading the file from top to bottom) down to the
|
||||||
|
* current directory, and then scan the list backwards to find the first match.
|
||||||
|
* This is exactly the same as what excluded() does in dir.c to deal with
|
||||||
|
* .gitignore
|
||||||
|
*/
|
||||||
|
|
||||||
|
static struct attr_stack {
|
||||||
|
struct attr_stack *prev;
|
||||||
|
char *origin;
|
||||||
|
unsigned num_matches;
|
||||||
|
struct match_attr **attrs;
|
||||||
|
} *attr_stack;
|
||||||
|
|
||||||
|
static void free_attr_elem(struct attr_stack *e)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
free(e->origin);
|
||||||
|
for (i = 0; i < e->num_matches; i++) {
|
||||||
|
struct match_attr *a = e->attrs[i];
|
||||||
|
int j;
|
||||||
|
for (j = 0; j < a->num_attr; j++) {
|
||||||
|
const char *setto = a->state[j].setto;
|
||||||
|
if (setto == ATTR__TRUE ||
|
||||||
|
setto == ATTR__FALSE ||
|
||||||
|
setto == ATTR__UNSET ||
|
||||||
|
setto == ATTR__UNKNOWN)
|
||||||
|
;
|
||||||
|
else
|
||||||
|
free((char*) setto);
|
||||||
|
}
|
||||||
|
free(a);
|
||||||
|
}
|
||||||
|
free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *builtin_attr[] = {
|
||||||
|
"[attr]binary -diff -crlf",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attr_stack *read_attr_from_array(const char **list)
|
||||||
|
{
|
||||||
|
struct attr_stack *res;
|
||||||
|
const char *line;
|
||||||
|
int lineno = 0;
|
||||||
|
|
||||||
|
res = xcalloc(1, sizeof(*res));
|
||||||
|
while ((line = *(list++)) != NULL) {
|
||||||
|
struct match_attr *a;
|
||||||
|
|
||||||
|
a = parse_attr_line(line, "[builtin]", ++lineno, 1);
|
||||||
|
if (!a)
|
||||||
|
continue;
|
||||||
|
res->attrs = xrealloc(res->attrs, res->num_matches + 1);
|
||||||
|
res->attrs[res->num_matches++] = a;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
struct attr_stack *res;
|
||||||
|
char buf[2048];
|
||||||
|
int lineno = 0;
|
||||||
|
|
||||||
|
res = xcalloc(1, sizeof(*res));
|
||||||
|
fp = fopen(path, "r");
|
||||||
|
if (!fp)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
while (fgets(buf, sizeof(buf), fp)) {
|
||||||
|
struct match_attr *a;
|
||||||
|
|
||||||
|
a = parse_attr_line(buf, path, ++lineno, macro_ok);
|
||||||
|
if (!a)
|
||||||
|
continue;
|
||||||
|
res->attrs = xrealloc(res->attrs, res->num_matches + 1);
|
||||||
|
res->attrs[res->num_matches++] = a;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG_ATTR
|
||||||
|
static void debug_info(const char *what, struct attr_stack *elem)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: %s\n", what, elem->origin ? elem->origin : "()");
|
||||||
|
}
|
||||||
|
static void debug_set(const char *what, const char *match, struct git_attr *attr, void *v)
|
||||||
|
{
|
||||||
|
const char *value = v;
|
||||||
|
|
||||||
|
if (ATTR_TRUE(value))
|
||||||
|
value = "set";
|
||||||
|
else if (ATTR_FALSE(value))
|
||||||
|
value = "unset";
|
||||||
|
else if (ATTR_UNSET(value))
|
||||||
|
value = "unspecified";
|
||||||
|
|
||||||
|
fprintf(stderr, "%s: %s => %s (%s)\n",
|
||||||
|
what, attr->name, (char *) value, match);
|
||||||
|
}
|
||||||
|
#define debug_push(a) debug_info("push", (a))
|
||||||
|
#define debug_pop(a) debug_info("pop", (a))
|
||||||
|
#else
|
||||||
|
#define debug_push(a) do { ; } while (0)
|
||||||
|
#define debug_pop(a) do { ; } while (0)
|
||||||
|
#define debug_set(a,b,c,d) do { ; } while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void bootstrap_attr_stack(void)
|
||||||
|
{
|
||||||
|
if (!attr_stack) {
|
||||||
|
struct attr_stack *elem;
|
||||||
|
|
||||||
|
elem = read_attr_from_array(builtin_attr);
|
||||||
|
elem->origin = NULL;
|
||||||
|
elem->prev = attr_stack;
|
||||||
|
attr_stack = elem;
|
||||||
|
|
||||||
|
elem = read_attr_from_file(GITATTRIBUTES_FILE, 1);
|
||||||
|
elem->origin = strdup("");
|
||||||
|
elem->prev = attr_stack;
|
||||||
|
attr_stack = elem;
|
||||||
|
debug_push(elem);
|
||||||
|
|
||||||
|
elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
|
||||||
|
elem->origin = NULL;
|
||||||
|
elem->prev = attr_stack;
|
||||||
|
attr_stack = elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepare_attr_stack(const char *path, int dirlen)
|
||||||
|
{
|
||||||
|
struct attr_stack *elem, *info;
|
||||||
|
int len;
|
||||||
|
char pathbuf[PATH_MAX];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At the bottom of the attribute stack is the built-in
|
||||||
|
* set of attribute definitions. Then, contents from
|
||||||
|
* .gitattribute files from directories closer to the
|
||||||
|
* root to the ones in deeper directories are pushed
|
||||||
|
* to the stack. Finally, at the very top of the stack
|
||||||
|
* we always keep the contents of $GIT_DIR/info/attributes.
|
||||||
|
*
|
||||||
|
* When checking, we use entries from near the top of the
|
||||||
|
* stack, preferring $GIT_DIR/info/attributes, then
|
||||||
|
* .gitattributes in deeper directories to shallower ones,
|
||||||
|
* and finally use the built-in set as the default.
|
||||||
|
*/
|
||||||
|
if (!attr_stack)
|
||||||
|
bootstrap_attr_stack();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pop the "info" one that is always at the top of the stack.
|
||||||
|
*/
|
||||||
|
info = attr_stack;
|
||||||
|
attr_stack = info->prev;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pop the ones from directories that are not the prefix of
|
||||||
|
* the path we are checking.
|
||||||
|
*/
|
||||||
|
while (attr_stack && attr_stack->origin) {
|
||||||
|
int namelen = strlen(attr_stack->origin);
|
||||||
|
|
||||||
|
elem = attr_stack;
|
||||||
|
if (namelen <= dirlen &&
|
||||||
|
!strncmp(elem->origin, path, namelen))
|
||||||
|
break;
|
||||||
|
|
||||||
|
debug_pop(elem);
|
||||||
|
attr_stack = elem->prev;
|
||||||
|
free_attr_elem(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read from parent directories and push them down
|
||||||
|
*/
|
||||||
|
while (1) {
|
||||||
|
char *cp;
|
||||||
|
|
||||||
|
len = strlen(attr_stack->origin);
|
||||||
|
if (dirlen <= len)
|
||||||
|
break;
|
||||||
|
memcpy(pathbuf, path, dirlen);
|
||||||
|
memcpy(pathbuf + dirlen, "/", 2);
|
||||||
|
cp = strchr(pathbuf + len + 1, '/');
|
||||||
|
strcpy(cp + 1, GITATTRIBUTES_FILE);
|
||||||
|
elem = read_attr_from_file(pathbuf, 0);
|
||||||
|
*cp = '\0';
|
||||||
|
elem->origin = strdup(pathbuf);
|
||||||
|
elem->prev = attr_stack;
|
||||||
|
attr_stack = elem;
|
||||||
|
debug_push(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finally push the "info" one at the top of the stack.
|
||||||
|
*/
|
||||||
|
info->prev = attr_stack;
|
||||||
|
attr_stack = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int path_matches(const char *pathname, int pathlen,
|
||||||
|
const char *pattern,
|
||||||
|
const char *base, int baselen)
|
||||||
|
{
|
||||||
|
if (!strchr(pattern, '/')) {
|
||||||
|
/* match basename */
|
||||||
|
const char *basename = strrchr(pathname, '/');
|
||||||
|
basename = basename ? basename + 1 : pathname;
|
||||||
|
return (fnmatch(pattern, basename, 0) == 0);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* match with FNM_PATHNAME; the pattern has base implicitly
|
||||||
|
* in front of it.
|
||||||
|
*/
|
||||||
|
if (*pattern == '/')
|
||||||
|
pattern++;
|
||||||
|
if (pathlen < baselen ||
|
||||||
|
(baselen && pathname[baselen - 1] != '/') ||
|
||||||
|
strncmp(pathname, base, baselen))
|
||||||
|
return 0;
|
||||||
|
return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fill_one(const char *what, struct match_attr *a, int rem)
|
||||||
|
{
|
||||||
|
struct git_attr_check *check = check_all_attr;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; 0 < rem && i < a->num_attr; i++) {
|
||||||
|
struct git_attr *attr = a->state[i].attr;
|
||||||
|
const char **n = &(check[attr->attr_nr].value);
|
||||||
|
const char *v = a->state[i].setto;
|
||||||
|
|
||||||
|
if (*n == ATTR__UNKNOWN) {
|
||||||
|
debug_set(what, a->u.pattern, attr, v);
|
||||||
|
*n = v;
|
||||||
|
rem--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
const char *base = stk->origin ? stk->origin : "";
|
||||||
|
|
||||||
|
for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
|
||||||
|
struct match_attr *a = stk->attrs[i];
|
||||||
|
if (a->is_macro)
|
||||||
|
continue;
|
||||||
|
if (path_matches(path, pathlen,
|
||||||
|
a->u.pattern, base, strlen(base)))
|
||||||
|
rem = fill_one("fill", a, rem);
|
||||||
|
}
|
||||||
|
return rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int macroexpand(struct attr_stack *stk, int rem)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct git_attr_check *check = check_all_attr;
|
||||||
|
|
||||||
|
for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
|
||||||
|
struct match_attr *a = stk->attrs[i];
|
||||||
|
if (!a->is_macro)
|
||||||
|
continue;
|
||||||
|
if (check[a->u.attr->attr_nr].value != ATTR__TRUE)
|
||||||
|
continue;
|
||||||
|
rem = fill_one("expand", a, rem);
|
||||||
|
}
|
||||||
|
return rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
int git_checkattr(const char *path, int num, struct git_attr_check *check)
|
||||||
|
{
|
||||||
|
struct attr_stack *stk;
|
||||||
|
const char *cp;
|
||||||
|
int dirlen, pathlen, i, rem;
|
||||||
|
|
||||||
|
bootstrap_attr_stack();
|
||||||
|
for (i = 0; i < attr_nr; i++)
|
||||||
|
check_all_attr[i].value = ATTR__UNKNOWN;
|
||||||
|
|
||||||
|
pathlen = strlen(path);
|
||||||
|
cp = strrchr(path, '/');
|
||||||
|
if (!cp)
|
||||||
|
dirlen = 0;
|
||||||
|
else
|
||||||
|
dirlen = cp - path;
|
||||||
|
prepare_attr_stack(path, dirlen);
|
||||||
|
rem = attr_nr;
|
||||||
|
for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
|
||||||
|
rem = fill(path, pathlen, stk, rem);
|
||||||
|
|
||||||
|
for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
|
||||||
|
rem = macroexpand(stk, rem);
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
const char *value = check_all_attr[check[i].attr->attr_nr].value;
|
||||||
|
if (value == ATTR__UNKNOWN)
|
||||||
|
value = ATTR__UNSET;
|
||||||
|
check[i].value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
34
attr.h
Normal file
34
attr.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef ATTR_H
|
||||||
|
#define ATTR_H
|
||||||
|
|
||||||
|
/* An attribute is a pointer to this opaque structure */
|
||||||
|
struct git_attr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a string, return the gitattribute object that
|
||||||
|
* corresponds to it.
|
||||||
|
*/
|
||||||
|
struct git_attr *git_attr(const char *, int);
|
||||||
|
|
||||||
|
/* Internal use */
|
||||||
|
extern const char git_attr__true[];
|
||||||
|
extern const char git_attr__false[];
|
||||||
|
|
||||||
|
/* For public to check git_attr_check results */
|
||||||
|
#define ATTR_TRUE(v) ((v) == git_attr__true)
|
||||||
|
#define ATTR_FALSE(v) ((v) == git_attr__false)
|
||||||
|
#define ATTR_UNSET(v) ((v) == NULL)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send one or more git_attr_check to git_checkattr(), and
|
||||||
|
* each 'value' member tells what its value is.
|
||||||
|
* Unset one is returned as NULL.
|
||||||
|
*/
|
||||||
|
struct git_attr_check {
|
||||||
|
struct git_attr *attr;
|
||||||
|
const char *value;
|
||||||
|
};
|
||||||
|
|
||||||
|
int git_checkattr(const char *path, int, struct git_attr_check *);
|
||||||
|
|
||||||
|
#endif /* ATTR_H */
|
@ -1475,8 +1475,8 @@ static int read_old_data(struct stat *st, const char *path, char **buf_p, unsign
|
|||||||
}
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
nsize = got;
|
nsize = got;
|
||||||
nbuf = buf;
|
nbuf = convert_to_git(path, buf, &nsize);
|
||||||
if (convert_to_git(path, &nbuf, &nsize)) {
|
if (nbuf) {
|
||||||
free(buf);
|
free(buf);
|
||||||
*buf_p = nbuf;
|
*buf_p = nbuf;
|
||||||
*alloc_p = nsize;
|
*alloc_p = nsize;
|
||||||
@ -2355,9 +2355,8 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
|
|||||||
|
|
||||||
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
|
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
|
||||||
{
|
{
|
||||||
int fd, converted;
|
int fd;
|
||||||
char *nbuf;
|
char *nbuf;
|
||||||
unsigned long nsize;
|
|
||||||
|
|
||||||
if (has_symlinks && S_ISLNK(mode))
|
if (has_symlinks && S_ISLNK(mode))
|
||||||
/* Although buf:size is counted string, it also is NUL
|
/* Although buf:size is counted string, it also is NUL
|
||||||
@ -2369,13 +2368,10 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
|
|||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
nsize = size;
|
nbuf = convert_to_working_tree(path, buf, &size);
|
||||||
nbuf = (char *) buf;
|
if (nbuf)
|
||||||
converted = convert_to_working_tree(path, &nbuf, &nsize);
|
|
||||||
if (converted) {
|
|
||||||
buf = nbuf;
|
buf = nbuf;
|
||||||
size = nsize;
|
|
||||||
}
|
|
||||||
while (size) {
|
while (size) {
|
||||||
int written = xwrite(fd, buf, size);
|
int written = xwrite(fd, buf, size);
|
||||||
if (written < 0)
|
if (written < 0)
|
||||||
@ -2387,7 +2383,7 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
|
|||||||
}
|
}
|
||||||
if (close(fd) < 0)
|
if (close(fd) < 0)
|
||||||
die("closing file %s: %s", path, strerror(errno));
|
die("closing file %s: %s", path, strerror(errno));
|
||||||
if (converted)
|
if (nbuf)
|
||||||
free(nbuf);
|
free(nbuf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
59
builtin-check-attr.c
Normal file
59
builtin-check-attr.c
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include "builtin.h"
|
||||||
|
#include "attr.h"
|
||||||
|
#include "quote.h"
|
||||||
|
|
||||||
|
static const char check_attr_usage[] =
|
||||||
|
"git-check-attr attr... [--] pathname...";
|
||||||
|
|
||||||
|
int cmd_check_attr(int argc, const char **argv, const char *prefix)
|
||||||
|
{
|
||||||
|
struct git_attr_check *check;
|
||||||
|
int cnt, i, doubledash;
|
||||||
|
|
||||||
|
doubledash = -1;
|
||||||
|
for (i = 1; doubledash < 0 && i < argc; i++) {
|
||||||
|
if (!strcmp(argv[i], "--"))
|
||||||
|
doubledash = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there is no double dash, we handle only one attribute */
|
||||||
|
if (doubledash < 0) {
|
||||||
|
cnt = 1;
|
||||||
|
doubledash = 1;
|
||||||
|
} else
|
||||||
|
cnt = doubledash - 1;
|
||||||
|
doubledash++;
|
||||||
|
|
||||||
|
if (cnt <= 0 || argc < doubledash)
|
||||||
|
usage(check_attr_usage);
|
||||||
|
check = xcalloc(cnt, sizeof(*check));
|
||||||
|
for (i = 0; i < cnt; i++) {
|
||||||
|
const char *name;
|
||||||
|
struct git_attr *a;
|
||||||
|
name = argv[i + 1];
|
||||||
|
a = git_attr(name, strlen(name));
|
||||||
|
if (!a)
|
||||||
|
return error("%s: not a valid attribute name", name);
|
||||||
|
check[i].attr = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = doubledash; i < argc; i++) {
|
||||||
|
int j;
|
||||||
|
if (git_checkattr(argv[i], cnt, check))
|
||||||
|
die("git_checkattr died");
|
||||||
|
for (j = 0; j < cnt; j++) {
|
||||||
|
const char *value = check[j].value;
|
||||||
|
|
||||||
|
if (ATTR_TRUE(value))
|
||||||
|
value = "set";
|
||||||
|
else if (ATTR_FALSE(value))
|
||||||
|
value = "unset";
|
||||||
|
else if (ATTR_UNSET(value))
|
||||||
|
value = "unspecified";
|
||||||
|
|
||||||
|
write_name_quoted("", 0, argv[i], 1, stdout);
|
||||||
|
printf(": %s: %s\n", argv[j+1], value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
@ -22,6 +22,7 @@ extern int cmd_branch(int argc, const char **argv, const char *prefix);
|
|||||||
extern int cmd_bundle(int argc, const char **argv, const char *prefix);
|
extern int cmd_bundle(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
|
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
|
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
|
||||||
|
extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
|
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_cherry(int argc, const char **argv, const char *prefix);
|
extern int cmd_cherry(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
|
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
|
||||||
|
8
cache.h
8
cache.h
@ -169,6 +169,9 @@ enum object_type {
|
|||||||
#define CONFIG_ENVIRONMENT "GIT_CONFIG"
|
#define CONFIG_ENVIRONMENT "GIT_CONFIG"
|
||||||
#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
|
#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
|
||||||
#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
|
#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
|
||||||
|
#define GITATTRIBUTES_FILE ".gitattributes"
|
||||||
|
#define INFOATTRIBUTES_FILE "info/attributes"
|
||||||
|
#define ATTRIBUTE_MACRO_PREFIX "[attr]"
|
||||||
|
|
||||||
extern int is_bare_repository_cfg;
|
extern int is_bare_repository_cfg;
|
||||||
extern int is_bare_repository(void);
|
extern int is_bare_repository(void);
|
||||||
@ -224,6 +227,7 @@ extern int refresh_cache(unsigned int flags);
|
|||||||
|
|
||||||
struct lock_file {
|
struct lock_file {
|
||||||
struct lock_file *next;
|
struct lock_file *next;
|
||||||
|
pid_t owner;
|
||||||
char on_list;
|
char on_list;
|
||||||
char filename[PATH_MAX];
|
char filename[PATH_MAX];
|
||||||
};
|
};
|
||||||
@ -512,8 +516,8 @@ extern void trace_printf(const char *format, ...);
|
|||||||
extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
|
extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
|
||||||
|
|
||||||
/* convert.c */
|
/* convert.c */
|
||||||
extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep);
|
extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep);
|
||||||
extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep);
|
extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep);
|
||||||
|
|
||||||
/* match-trees.c */
|
/* match-trees.c */
|
||||||
void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
|
void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
|
||||||
|
179
convert.c
179
convert.c
@ -1,4 +1,6 @@
|
|||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
|
#include "attr.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* convert.c - convert a file when checking it out and checking it in.
|
* convert.c - convert a file when checking it out and checking it in.
|
||||||
*
|
*
|
||||||
@ -8,6 +10,11 @@
|
|||||||
* translation when the "auto_crlf" option is set.
|
* translation when the "auto_crlf" option is set.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define CRLF_GUESS (-1)
|
||||||
|
#define CRLF_BINARY 0
|
||||||
|
#define CRLF_TEXT 1
|
||||||
|
#define CRLF_INPUT 2
|
||||||
|
|
||||||
struct text_stat {
|
struct text_stat {
|
||||||
/* CR, LF and CRLF counts */
|
/* CR, LF and CRLF counts */
|
||||||
unsigned cr, lf, crlf;
|
unsigned cr, lf, crlf;
|
||||||
@ -72,115 +79,171 @@ static int is_binary(unsigned long size, struct text_stat *stats)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int convert_to_git(const char *path, char **bufp, unsigned long *sizep)
|
static char *crlf_to_git(const char *path, const char *src, unsigned long *sizep, int action)
|
||||||
{
|
{
|
||||||
char *buffer, *nbuf;
|
char *buffer, *dst;
|
||||||
unsigned long size, nsize;
|
unsigned long size, nsize;
|
||||||
struct text_stat stats;
|
struct text_stat stats;
|
||||||
|
|
||||||
/*
|
if ((action == CRLF_BINARY) || (action == CRLF_GUESS && !auto_crlf))
|
||||||
* FIXME! Other pluggable conversions should go here,
|
return NULL;
|
||||||
* based on filename patterns. Right now we just do the
|
|
||||||
* stupid auto-CRLF one.
|
|
||||||
*/
|
|
||||||
if (!auto_crlf)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
size = *sizep;
|
size = *sizep;
|
||||||
if (!size)
|
if (!size)
|
||||||
return 0;
|
return NULL;
|
||||||
buffer = *bufp;
|
|
||||||
|
|
||||||
gather_stats(buffer, size, &stats);
|
gather_stats(src, size, &stats);
|
||||||
|
|
||||||
/* No CR? Nothing to convert, regardless. */
|
/* No CR? Nothing to convert, regardless. */
|
||||||
if (!stats.cr)
|
if (!stats.cr)
|
||||||
return 0;
|
return NULL;
|
||||||
|
|
||||||
/*
|
if (action == CRLF_GUESS) {
|
||||||
* We're currently not going to even try to convert stuff
|
/*
|
||||||
* that has bare CR characters. Does anybody do that crazy
|
* We're currently not going to even try to convert stuff
|
||||||
* stuff?
|
* that has bare CR characters. Does anybody do that crazy
|
||||||
*/
|
* stuff?
|
||||||
if (stats.cr != stats.crlf)
|
*/
|
||||||
return 0;
|
if (stats.cr != stats.crlf)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* And add some heuristics for binary vs text, of course...
|
* And add some heuristics for binary vs text, of course...
|
||||||
*/
|
*/
|
||||||
if (is_binary(size, &stats))
|
if (is_binary(size, &stats))
|
||||||
return 0;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ok, allocate a new buffer, fill it in, and return true
|
* Ok, allocate a new buffer, fill it in, and return true
|
||||||
* to let the caller know that we switched buffers on it.
|
* to let the caller know that we switched buffers on it.
|
||||||
*/
|
*/
|
||||||
nsize = size - stats.crlf;
|
nsize = size - stats.crlf;
|
||||||
nbuf = xmalloc(nsize);
|
buffer = xmalloc(nsize);
|
||||||
*bufp = nbuf;
|
|
||||||
*sizep = nsize;
|
*sizep = nsize;
|
||||||
do {
|
|
||||||
unsigned char c = *buffer++;
|
|
||||||
if (c != '\r')
|
|
||||||
*nbuf++ = c;
|
|
||||||
} while (--size);
|
|
||||||
|
|
||||||
return 1;
|
dst = buffer;
|
||||||
|
if (action == CRLF_GUESS) {
|
||||||
|
/*
|
||||||
|
* If we guessed, we already know we rejected a file with
|
||||||
|
* lone CR, and we can strip a CR without looking at what
|
||||||
|
* follow it.
|
||||||
|
*/
|
||||||
|
do {
|
||||||
|
unsigned char c = *src++;
|
||||||
|
if (c != '\r')
|
||||||
|
*dst++ = c;
|
||||||
|
} while (--size);
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
unsigned char c = *src++;
|
||||||
|
if (! (c == '\r' && (1 < size && *buffer == '\n')))
|
||||||
|
*dst++ = c;
|
||||||
|
} while (--size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
|
static char *crlf_to_worktree(const char *path, const char *src, unsigned long *sizep, int action)
|
||||||
{
|
{
|
||||||
char *buffer, *nbuf;
|
char *buffer, *dst;
|
||||||
unsigned long size, nsize;
|
unsigned long size, nsize;
|
||||||
struct text_stat stats;
|
struct text_stat stats;
|
||||||
unsigned char last;
|
unsigned char last;
|
||||||
|
|
||||||
/*
|
if ((action == CRLF_BINARY) || (action == CRLF_INPUT) ||
|
||||||
* FIXME! Other pluggable conversions should go here,
|
(action == CRLF_GUESS && auto_crlf <= 0))
|
||||||
* based on filename patterns. Right now we just do the
|
return NULL;
|
||||||
* stupid auto-CRLF one.
|
|
||||||
*/
|
|
||||||
if (auto_crlf <= 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
size = *sizep;
|
size = *sizep;
|
||||||
if (!size)
|
if (!size)
|
||||||
return 0;
|
return NULL;
|
||||||
buffer = *bufp;
|
|
||||||
|
|
||||||
gather_stats(buffer, size, &stats);
|
gather_stats(src, size, &stats);
|
||||||
|
|
||||||
/* No LF? Nothing to convert, regardless. */
|
/* No LF? Nothing to convert, regardless. */
|
||||||
if (!stats.lf)
|
if (!stats.lf)
|
||||||
return 0;
|
return NULL;
|
||||||
|
|
||||||
/* Was it already in CRLF format? */
|
/* Was it already in CRLF format? */
|
||||||
if (stats.lf == stats.crlf)
|
if (stats.lf == stats.crlf)
|
||||||
return 0;
|
return NULL;
|
||||||
|
|
||||||
/* If we have any bare CR characters, we're not going to touch it */
|
if (action == CRLF_GUESS) {
|
||||||
if (stats.cr != stats.crlf)
|
/* If we have any bare CR characters, we're not going to touch it */
|
||||||
return 0;
|
if (stats.cr != stats.crlf)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
if (is_binary(size, &stats))
|
if (is_binary(size, &stats))
|
||||||
return 0;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ok, allocate a new buffer, fill it in, and return true
|
* Ok, allocate a new buffer, fill it in, and return true
|
||||||
* to let the caller know that we switched buffers on it.
|
* to let the caller know that we switched buffers on it.
|
||||||
*/
|
*/
|
||||||
nsize = size + stats.lf - stats.crlf;
|
nsize = size + stats.lf - stats.crlf;
|
||||||
nbuf = xmalloc(nsize);
|
buffer = xmalloc(nsize);
|
||||||
*bufp = nbuf;
|
|
||||||
*sizep = nsize;
|
*sizep = nsize;
|
||||||
last = 0;
|
last = 0;
|
||||||
|
|
||||||
|
dst = buffer;
|
||||||
do {
|
do {
|
||||||
unsigned char c = *buffer++;
|
unsigned char c = *src++;
|
||||||
if (c == '\n' && last != '\r')
|
if (c == '\n' && last != '\r')
|
||||||
*nbuf++ = '\r';
|
*dst++ = '\r';
|
||||||
*nbuf++ = c;
|
*dst++ = c;
|
||||||
last = c;
|
last = c;
|
||||||
} while (--size);
|
} while (--size);
|
||||||
|
|
||||||
return 1;
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup_convert_check(struct git_attr_check *check)
|
||||||
|
{
|
||||||
|
static struct git_attr *attr_crlf;
|
||||||
|
|
||||||
|
if (!attr_crlf)
|
||||||
|
attr_crlf = git_attr("crlf", 4);
|
||||||
|
check->attr = attr_crlf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int git_path_check_crlf(const char *path, struct git_attr_check *check)
|
||||||
|
{
|
||||||
|
const char *value = check->value;
|
||||||
|
|
||||||
|
if (ATTR_TRUE(value))
|
||||||
|
return CRLF_TEXT;
|
||||||
|
else if (ATTR_FALSE(value))
|
||||||
|
return CRLF_BINARY;
|
||||||
|
else if (ATTR_UNSET(value))
|
||||||
|
;
|
||||||
|
else if (!strcmp(value, "input"))
|
||||||
|
return CRLF_INPUT;
|
||||||
|
return CRLF_GUESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *convert_to_git(const char *path, const char *src, unsigned long *sizep)
|
||||||
|
{
|
||||||
|
struct git_attr_check check[1];
|
||||||
|
int crlf = CRLF_GUESS;
|
||||||
|
|
||||||
|
setup_convert_check(check);
|
||||||
|
if (!git_checkattr(path, 1, check)) {
|
||||||
|
crlf = git_path_check_crlf(path, check);
|
||||||
|
}
|
||||||
|
return crlf_to_git(path, src, sizep, crlf);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep)
|
||||||
|
{
|
||||||
|
struct git_attr_check check[1];
|
||||||
|
int crlf = CRLF_GUESS;
|
||||||
|
|
||||||
|
setup_convert_check(check);
|
||||||
|
if (!git_checkattr(path, 1, check)) {
|
||||||
|
crlf = git_path_check_crlf(path, check);
|
||||||
|
}
|
||||||
|
return crlf_to_worktree(path, src, sizep, crlf);
|
||||||
}
|
}
|
||||||
|
56
diff.c
56
diff.c
@ -8,6 +8,7 @@
|
|||||||
#include "delta.h"
|
#include "delta.h"
|
||||||
#include "xdiff-interface.h"
|
#include "xdiff-interface.h"
|
||||||
#include "color.h"
|
#include "color.h"
|
||||||
|
#include "attr.h"
|
||||||
|
|
||||||
#ifdef NO_FAST_WORKING_DIRECTORY
|
#ifdef NO_FAST_WORKING_DIRECTORY
|
||||||
#define FAST_WORKING_DIRECTORY 0
|
#define FAST_WORKING_DIRECTORY 0
|
||||||
@ -1051,13 +1052,44 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
|
|||||||
emit_binary_diff_body(two, one);
|
emit_binary_diff_body(two, one);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define FIRST_FEW_BYTES 8000
|
static void setup_diff_attr_check(struct git_attr_check *check)
|
||||||
static int mmfile_is_binary(mmfile_t *mf)
|
|
||||||
{
|
{
|
||||||
long sz = mf->size;
|
static struct git_attr *attr_diff;
|
||||||
|
|
||||||
|
if (!attr_diff)
|
||||||
|
attr_diff = git_attr("diff", 4);
|
||||||
|
check->attr = attr_diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FIRST_FEW_BYTES 8000
|
||||||
|
static int file_is_binary(struct diff_filespec *one)
|
||||||
|
{
|
||||||
|
unsigned long sz;
|
||||||
|
struct git_attr_check attr_diff_check;
|
||||||
|
|
||||||
|
setup_diff_attr_check(&attr_diff_check);
|
||||||
|
if (!git_checkattr(one->path, 1, &attr_diff_check)) {
|
||||||
|
const char *value = attr_diff_check.value;
|
||||||
|
if (ATTR_TRUE(value))
|
||||||
|
return 0;
|
||||||
|
else if (ATTR_FALSE(value))
|
||||||
|
return 1;
|
||||||
|
else if (ATTR_UNSET(value))
|
||||||
|
;
|
||||||
|
else
|
||||||
|
die("unknown value %s given to 'diff' attribute",
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!one->data) {
|
||||||
|
if (!DIFF_FILE_VALID(one))
|
||||||
|
return 0;
|
||||||
|
diff_populate_filespec(one, 0);
|
||||||
|
}
|
||||||
|
sz = one->size;
|
||||||
if (FIRST_FEW_BYTES < sz)
|
if (FIRST_FEW_BYTES < sz)
|
||||||
sz = FIRST_FEW_BYTES;
|
sz = FIRST_FEW_BYTES;
|
||||||
return !!memchr(mf->ptr, 0, sz);
|
return !!memchr(one->data, 0, sz);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void builtin_diff(const char *name_a,
|
static void builtin_diff(const char *name_a,
|
||||||
@ -1114,7 +1146,7 @@ static void builtin_diff(const char *name_a,
|
|||||||
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
||||||
die("unable to read files to diff");
|
die("unable to read files to diff");
|
||||||
|
|
||||||
if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
|
if (!o->text && (file_is_binary(one) || file_is_binary(two))) {
|
||||||
/* Quite common confusing case */
|
/* Quite common confusing case */
|
||||||
if (mf1.size == mf2.size &&
|
if (mf1.size == mf2.size &&
|
||||||
!memcmp(mf1.ptr, mf2.ptr, mf1.size))
|
!memcmp(mf1.ptr, mf2.ptr, mf1.size))
|
||||||
@ -1190,7 +1222,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
|
|||||||
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
||||||
die("unable to read files to diff");
|
die("unable to read files to diff");
|
||||||
|
|
||||||
if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) {
|
if (file_is_binary(one) || file_is_binary(two)) {
|
||||||
data->is_binary = 1;
|
data->is_binary = 1;
|
||||||
data->added = mf2.size;
|
data->added = mf2.size;
|
||||||
data->deleted = mf1.size;
|
data->deleted = mf1.size;
|
||||||
@ -1228,7 +1260,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
|
|||||||
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
||||||
die("unable to read files to diff");
|
die("unable to read files to diff");
|
||||||
|
|
||||||
if (mmfile_is_binary(&mf2))
|
if (file_is_binary(two))
|
||||||
return;
|
return;
|
||||||
else {
|
else {
|
||||||
/* Crazy xdl interfaces.. */
|
/* Crazy xdl interfaces.. */
|
||||||
@ -1481,9 +1513,9 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
|
|||||||
/*
|
/*
|
||||||
* Convert from working tree format to canonical git format
|
* Convert from working tree format to canonical git format
|
||||||
*/
|
*/
|
||||||
buf = s->data;
|
|
||||||
size = s->size;
|
size = s->size;
|
||||||
if (convert_to_git(s->path, &buf, &size)) {
|
buf = convert_to_git(s->path, s->data, &size);
|
||||||
|
if (buf) {
|
||||||
munmap(s->data, s->size);
|
munmap(s->data, s->size);
|
||||||
s->should_munmap = 0;
|
s->should_munmap = 0;
|
||||||
s->data = buf;
|
s->data = buf;
|
||||||
@ -1825,8 +1857,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
|
|||||||
|
|
||||||
if (o->binary) {
|
if (o->binary) {
|
||||||
mmfile_t mf;
|
mmfile_t mf;
|
||||||
if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
|
if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
|
||||||
(!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
|
(!fill_mmfile(&mf, two) && file_is_binary(two)))
|
||||||
abbrev = 40;
|
abbrev = 40;
|
||||||
}
|
}
|
||||||
len += snprintf(msg + len, sizeof(msg) - len,
|
len += snprintf(msg + len, sizeof(msg) - len,
|
||||||
@ -2721,7 +2753,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
|
|||||||
return error("unable to read files to diff");
|
return error("unable to read files to diff");
|
||||||
|
|
||||||
/* Maybe hash p->two? into the patch id? */
|
/* Maybe hash p->two? into the patch id? */
|
||||||
if (mmfile_is_binary(&mf2))
|
if (file_is_binary(p->two))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
len1 = remove_space(p->one->path, strlen(p->one->path));
|
len1 = remove_space(p->one->path, strlen(p->one->path));
|
||||||
|
8
entry.c
8
entry.c
@ -82,7 +82,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
|
|||||||
|
|
||||||
switch (ntohl(ce->ce_mode) & S_IFMT) {
|
switch (ntohl(ce->ce_mode) & S_IFMT) {
|
||||||
char *buf, *new;
|
char *buf, *new;
|
||||||
unsigned long size, nsize;
|
unsigned long size;
|
||||||
|
|
||||||
case S_IFREG:
|
case S_IFREG:
|
||||||
new = read_blob_entry(ce, path, &size);
|
new = read_blob_entry(ce, path, &size);
|
||||||
@ -103,12 +103,10 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
|
|||||||
/*
|
/*
|
||||||
* Convert from git internal format to working tree format
|
* Convert from git internal format to working tree format
|
||||||
*/
|
*/
|
||||||
buf = new;
|
buf = convert_to_working_tree(ce->name, new, &size);
|
||||||
nsize = size;
|
if (buf) {
|
||||||
if (convert_to_working_tree(ce->name, &buf, &nsize)) {
|
|
||||||
free(new);
|
free(new);
|
||||||
new = buf;
|
new = buf;
|
||||||
size = nsize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wrote = write_in_full(fd, new, size);
|
wrote = write_in_full(fd, new, size);
|
||||||
|
1
git.c
1
git.c
@ -234,6 +234,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
|
|||||||
{ "cat-file", cmd_cat_file, RUN_SETUP },
|
{ "cat-file", cmd_cat_file, RUN_SETUP },
|
||||||
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
|
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
|
||||||
{ "check-ref-format", cmd_check_ref_format },
|
{ "check-ref-format", cmd_check_ref_format },
|
||||||
|
{ "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
|
||||||
{ "cherry", cmd_cherry, RUN_SETUP },
|
{ "cherry", cmd_cherry, RUN_SETUP },
|
||||||
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
|
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
|
||||||
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
|
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
|
||||||
|
@ -8,8 +8,11 @@ static const char *alternate_index_output;
|
|||||||
|
|
||||||
static void remove_lock_file(void)
|
static void remove_lock_file(void)
|
||||||
{
|
{
|
||||||
|
pid_t me = getpid();
|
||||||
|
|
||||||
while (lock_file_list) {
|
while (lock_file_list) {
|
||||||
if (lock_file_list->filename[0])
|
if (lock_file_list->owner == me &&
|
||||||
|
lock_file_list->filename[0])
|
||||||
unlink(lock_file_list->filename);
|
unlink(lock_file_list->filename);
|
||||||
lock_file_list = lock_file_list->next;
|
lock_file_list = lock_file_list->next;
|
||||||
}
|
}
|
||||||
@ -28,6 +31,7 @@ static int lock_file(struct lock_file *lk, const char *path)
|
|||||||
sprintf(lk->filename, "%s.lock", path);
|
sprintf(lk->filename, "%s.lock", path);
|
||||||
fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
|
fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
|
||||||
if (0 <= fd) {
|
if (0 <= fd) {
|
||||||
|
lk->owner = getpid();
|
||||||
if (!lk->on_list) {
|
if (!lk->on_list) {
|
||||||
lk->next = lock_file_list;
|
lk->next = lock_file_list;
|
||||||
lock_file_list = lk;
|
lock_file_list = lk;
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
#include "unpack-trees.h"
|
#include "unpack-trees.h"
|
||||||
#include "path-list.h"
|
#include "path-list.h"
|
||||||
#include "xdiff-interface.h"
|
#include "xdiff-interface.h"
|
||||||
|
#include "interpolate.h"
|
||||||
|
#include "attr.h"
|
||||||
|
|
||||||
static int subtree_merge;
|
static int subtree_merge;
|
||||||
|
|
||||||
@ -645,6 +647,384 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
|
|||||||
mm->size = size;
|
mm->size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Customizable low-level merge drivers support.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct ll_merge_driver;
|
||||||
|
typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
|
||||||
|
const char *path,
|
||||||
|
mmfile_t *orig,
|
||||||
|
mmfile_t *src1, const char *name1,
|
||||||
|
mmfile_t *src2, const char *name2,
|
||||||
|
mmbuffer_t *result);
|
||||||
|
|
||||||
|
struct ll_merge_driver {
|
||||||
|
const char *name;
|
||||||
|
const char *description;
|
||||||
|
ll_merge_fn fn;
|
||||||
|
const char *recursive;
|
||||||
|
struct ll_merge_driver *next;
|
||||||
|
char *cmdline;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Built-in low-levels
|
||||||
|
*/
|
||||||
|
static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
||||||
|
const char *path_unused,
|
||||||
|
mmfile_t *orig,
|
||||||
|
mmfile_t *src1, const char *name1,
|
||||||
|
mmfile_t *src2, const char *name2,
|
||||||
|
mmbuffer_t *result)
|
||||||
|
{
|
||||||
|
xpparam_t xpp;
|
||||||
|
|
||||||
|
memset(&xpp, 0, sizeof(xpp));
|
||||||
|
return xdl_merge(orig,
|
||||||
|
src1, name1,
|
||||||
|
src2, name2,
|
||||||
|
&xpp, XDL_MERGE_ZEALOUS,
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ll_union_merge(const struct ll_merge_driver *drv_unused,
|
||||||
|
const char *path_unused,
|
||||||
|
mmfile_t *orig,
|
||||||
|
mmfile_t *src1, const char *name1,
|
||||||
|
mmfile_t *src2, const char *name2,
|
||||||
|
mmbuffer_t *result)
|
||||||
|
{
|
||||||
|
char *src, *dst;
|
||||||
|
long size;
|
||||||
|
const int marker_size = 7;
|
||||||
|
|
||||||
|
int status = ll_xdl_merge(drv_unused, path_unused,
|
||||||
|
orig, src1, NULL, src2, NULL, result);
|
||||||
|
if (status <= 0)
|
||||||
|
return status;
|
||||||
|
size = result->size;
|
||||||
|
src = dst = result->ptr;
|
||||||
|
while (size) {
|
||||||
|
char ch;
|
||||||
|
if ((marker_size < size) &&
|
||||||
|
(*src == '<' || *src == '=' || *src == '>')) {
|
||||||
|
int i;
|
||||||
|
ch = *src;
|
||||||
|
for (i = 0; i < marker_size; i++)
|
||||||
|
if (src[i] != ch)
|
||||||
|
goto not_a_marker;
|
||||||
|
if (src[marker_size] != '\n')
|
||||||
|
goto not_a_marker;
|
||||||
|
src += marker_size + 1;
|
||||||
|
size -= marker_size + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
not_a_marker:
|
||||||
|
do {
|
||||||
|
ch = *src++;
|
||||||
|
*dst++ = ch;
|
||||||
|
size--;
|
||||||
|
} while (ch != '\n' && size);
|
||||||
|
}
|
||||||
|
result->size = dst - result->ptr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
|
||||||
|
const char *path_unused,
|
||||||
|
mmfile_t *orig,
|
||||||
|
mmfile_t *src1, const char *name1,
|
||||||
|
mmfile_t *src2, const char *name2,
|
||||||
|
mmbuffer_t *result)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The tentative merge result is "ours" for the final round,
|
||||||
|
* or common ancestor for an internal merge. Still return
|
||||||
|
* "conflicted merge" status.
|
||||||
|
*/
|
||||||
|
mmfile_t *stolen = index_only ? orig : src1;
|
||||||
|
|
||||||
|
result->ptr = stolen->ptr;
|
||||||
|
result->size = stolen->size;
|
||||||
|
stolen->ptr = NULL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LL_BINARY_MERGE 0
|
||||||
|
#define LL_TEXT_MERGE 1
|
||||||
|
#define LL_UNION_MERGE 2
|
||||||
|
static struct ll_merge_driver ll_merge_drv[] = {
|
||||||
|
{ "binary", "built-in binary merge", ll_binary_merge },
|
||||||
|
{ "text", "built-in 3-way text merge", ll_xdl_merge },
|
||||||
|
{ "union", "built-in union merge", ll_union_merge },
|
||||||
|
};
|
||||||
|
|
||||||
|
static void create_temp(mmfile_t *src, char *path)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
strcpy(path, ".merge_file_XXXXXX");
|
||||||
|
fd = mkstemp(path);
|
||||||
|
if (fd < 0)
|
||||||
|
die("unable to create temp-file");
|
||||||
|
if (write_in_full(fd, src->ptr, src->size) != src->size)
|
||||||
|
die("unable to write temp-file");
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* User defined low-level merge driver support.
|
||||||
|
*/
|
||||||
|
static int ll_ext_merge(const struct ll_merge_driver *fn,
|
||||||
|
const char *path,
|
||||||
|
mmfile_t *orig,
|
||||||
|
mmfile_t *src1, const char *name1,
|
||||||
|
mmfile_t *src2, const char *name2,
|
||||||
|
mmbuffer_t *result)
|
||||||
|
{
|
||||||
|
char temp[3][50];
|
||||||
|
char cmdbuf[2048];
|
||||||
|
struct interp table[] = {
|
||||||
|
{ "%O" },
|
||||||
|
{ "%A" },
|
||||||
|
{ "%B" },
|
||||||
|
};
|
||||||
|
struct child_process child;
|
||||||
|
const char *args[20];
|
||||||
|
int status, fd, i;
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
if (fn->cmdline == NULL)
|
||||||
|
die("custom merge driver %s lacks command line.", fn->name);
|
||||||
|
|
||||||
|
result->ptr = NULL;
|
||||||
|
result->size = 0;
|
||||||
|
create_temp(orig, temp[0]);
|
||||||
|
create_temp(src1, temp[1]);
|
||||||
|
create_temp(src2, temp[2]);
|
||||||
|
|
||||||
|
interp_set_entry(table, 0, temp[0]);
|
||||||
|
interp_set_entry(table, 1, temp[1]);
|
||||||
|
interp_set_entry(table, 2, temp[2]);
|
||||||
|
|
||||||
|
output(1, "merging %s using %s", path,
|
||||||
|
fn->description ? fn->description : fn->name);
|
||||||
|
|
||||||
|
interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
|
||||||
|
|
||||||
|
memset(&child, 0, sizeof(child));
|
||||||
|
child.argv = args;
|
||||||
|
args[0] = "sh";
|
||||||
|
args[1] = "-c";
|
||||||
|
args[2] = cmdbuf;
|
||||||
|
args[3] = NULL;
|
||||||
|
|
||||||
|
status = run_command(&child);
|
||||||
|
if (status < -ERR_RUN_COMMAND_FORK)
|
||||||
|
; /* failure in run-command */
|
||||||
|
else
|
||||||
|
status = -status;
|
||||||
|
fd = open(temp[1], O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
goto bad;
|
||||||
|
if (fstat(fd, &st))
|
||||||
|
goto close_bad;
|
||||||
|
result->size = st.st_size;
|
||||||
|
result->ptr = xmalloc(result->size + 1);
|
||||||
|
if (read_in_full(fd, result->ptr, result->size) != result->size) {
|
||||||
|
free(result->ptr);
|
||||||
|
result->ptr = NULL;
|
||||||
|
result->size = 0;
|
||||||
|
}
|
||||||
|
close_bad:
|
||||||
|
close(fd);
|
||||||
|
bad:
|
||||||
|
for (i = 0; i < 3; i++)
|
||||||
|
unlink(temp[i]);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* merge.default and merge.driver configuration items
|
||||||
|
*/
|
||||||
|
static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
|
||||||
|
static const char *default_ll_merge;
|
||||||
|
|
||||||
|
static int read_merge_config(const char *var, const char *value)
|
||||||
|
{
|
||||||
|
struct ll_merge_driver *fn;
|
||||||
|
const char *ep, *name;
|
||||||
|
int namelen;
|
||||||
|
|
||||||
|
if (!strcmp(var, "merge.default")) {
|
||||||
|
if (value)
|
||||||
|
default_ll_merge = strdup(value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We are not interested in anything but "merge.<name>.variable";
|
||||||
|
* especially, we do not want to look at variables such as
|
||||||
|
* "merge.summary", "merge.tool", and "merge.verbosity".
|
||||||
|
*/
|
||||||
|
if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find existing one as we might be processing merge.<name>.var2
|
||||||
|
* after seeing merge.<name>.var1.
|
||||||
|
*/
|
||||||
|
name = var + 6;
|
||||||
|
namelen = ep - name;
|
||||||
|
for (fn = ll_user_merge; fn; fn = fn->next)
|
||||||
|
if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
|
||||||
|
break;
|
||||||
|
if (!fn) {
|
||||||
|
char *namebuf;
|
||||||
|
fn = xcalloc(1, sizeof(struct ll_merge_driver));
|
||||||
|
namebuf = xmalloc(namelen + 1);
|
||||||
|
memcpy(namebuf, name, namelen);
|
||||||
|
namebuf[namelen] = 0;
|
||||||
|
fn->name = namebuf;
|
||||||
|
fn->fn = ll_ext_merge;
|
||||||
|
fn->next = NULL;
|
||||||
|
*ll_user_merge_tail = fn;
|
||||||
|
ll_user_merge_tail = &(fn->next);
|
||||||
|
}
|
||||||
|
|
||||||
|
ep++;
|
||||||
|
|
||||||
|
if (!strcmp("name", ep)) {
|
||||||
|
if (!value)
|
||||||
|
return error("%s: lacks value", var);
|
||||||
|
fn->description = strdup(value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp("driver", ep)) {
|
||||||
|
if (!value)
|
||||||
|
return error("%s: lacks value", var);
|
||||||
|
/*
|
||||||
|
* merge.<name>.driver specifies the command line:
|
||||||
|
*
|
||||||
|
* command-line
|
||||||
|
*
|
||||||
|
* The command-line will be interpolated with the following
|
||||||
|
* tokens and is given to the shell:
|
||||||
|
*
|
||||||
|
* %O - temporary file name for the merge base.
|
||||||
|
* %A - temporary file name for our version.
|
||||||
|
* %B - temporary file name for the other branches' version.
|
||||||
|
*
|
||||||
|
* The external merge driver should write the results in the
|
||||||
|
* file named by %A, and signal that it has done with zero exit
|
||||||
|
* status.
|
||||||
|
*/
|
||||||
|
fn->cmdline = strdup(value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp("recursive", ep)) {
|
||||||
|
if (!value)
|
||||||
|
return error("%s: lacks value", var);
|
||||||
|
fn->recursive = strdup(value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialize_ll_merge(void)
|
||||||
|
{
|
||||||
|
if (ll_user_merge_tail)
|
||||||
|
return;
|
||||||
|
ll_user_merge_tail = &ll_user_merge;
|
||||||
|
git_config(read_merge_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
|
||||||
|
{
|
||||||
|
struct ll_merge_driver *fn;
|
||||||
|
const char *name;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
initialize_ll_merge();
|
||||||
|
|
||||||
|
if (ATTR_TRUE(merge_attr))
|
||||||
|
return &ll_merge_drv[LL_TEXT_MERGE];
|
||||||
|
else if (ATTR_FALSE(merge_attr))
|
||||||
|
return &ll_merge_drv[LL_BINARY_MERGE];
|
||||||
|
else if (ATTR_UNSET(merge_attr)) {
|
||||||
|
if (!default_ll_merge)
|
||||||
|
return &ll_merge_drv[LL_TEXT_MERGE];
|
||||||
|
else
|
||||||
|
name = default_ll_merge;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
name = merge_attr;
|
||||||
|
|
||||||
|
for (fn = ll_user_merge; fn; fn = fn->next)
|
||||||
|
if (!strcmp(fn->name, name))
|
||||||
|
return fn;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
|
||||||
|
if (!strcmp(ll_merge_drv[i].name, name))
|
||||||
|
return &ll_merge_drv[i];
|
||||||
|
|
||||||
|
/* default to the 3-way */
|
||||||
|
return &ll_merge_drv[LL_TEXT_MERGE];
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *git_path_check_merge(const char *path)
|
||||||
|
{
|
||||||
|
static struct git_attr_check attr_merge_check;
|
||||||
|
|
||||||
|
if (!attr_merge_check.attr)
|
||||||
|
attr_merge_check.attr = git_attr("merge", 5);
|
||||||
|
|
||||||
|
if (git_checkattr(path, 1, &attr_merge_check))
|
||||||
|
return NULL;
|
||||||
|
return attr_merge_check.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ll_merge(mmbuffer_t *result_buf,
|
||||||
|
struct diff_filespec *o,
|
||||||
|
struct diff_filespec *a,
|
||||||
|
struct diff_filespec *b,
|
||||||
|
const char *branch1,
|
||||||
|
const char *branch2)
|
||||||
|
{
|
||||||
|
mmfile_t orig, src1, src2;
|
||||||
|
char *name1, *name2;
|
||||||
|
int merge_status;
|
||||||
|
const char *ll_driver_name;
|
||||||
|
const struct ll_merge_driver *driver;
|
||||||
|
|
||||||
|
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
|
||||||
|
name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
|
||||||
|
|
||||||
|
fill_mm(o->sha1, &orig);
|
||||||
|
fill_mm(a->sha1, &src1);
|
||||||
|
fill_mm(b->sha1, &src2);
|
||||||
|
|
||||||
|
ll_driver_name = git_path_check_merge(a->path);
|
||||||
|
driver = find_ll_merge_driver(ll_driver_name);
|
||||||
|
|
||||||
|
if (index_only && driver->recursive)
|
||||||
|
driver = find_ll_merge_driver(driver->recursive);
|
||||||
|
merge_status = driver->fn(driver, a->path,
|
||||||
|
&orig, &src1, name1, &src2, name2,
|
||||||
|
result_buf);
|
||||||
|
|
||||||
|
free(name1);
|
||||||
|
free(name2);
|
||||||
|
free(orig.ptr);
|
||||||
|
free(src1.ptr);
|
||||||
|
free(src2.ptr);
|
||||||
|
return merge_status;
|
||||||
|
}
|
||||||
|
|
||||||
static struct merge_file_info merge_file(struct diff_filespec *o,
|
static struct merge_file_info merge_file(struct diff_filespec *o,
|
||||||
struct diff_filespec *a, struct diff_filespec *b,
|
struct diff_filespec *a, struct diff_filespec *b,
|
||||||
const char *branch1, const char *branch2)
|
const char *branch1, const char *branch2)
|
||||||
@ -673,30 +1053,11 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
|
|||||||
else if (sha_eq(b->sha1, o->sha1))
|
else if (sha_eq(b->sha1, o->sha1))
|
||||||
hashcpy(result.sha, a->sha1);
|
hashcpy(result.sha, a->sha1);
|
||||||
else if (S_ISREG(a->mode)) {
|
else if (S_ISREG(a->mode)) {
|
||||||
mmfile_t orig, src1, src2;
|
|
||||||
mmbuffer_t result_buf;
|
mmbuffer_t result_buf;
|
||||||
xpparam_t xpp;
|
|
||||||
char *name1, *name2;
|
|
||||||
int merge_status;
|
int merge_status;
|
||||||
|
|
||||||
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
|
merge_status = ll_merge(&result_buf, o, a, b,
|
||||||
name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
|
branch1, branch2);
|
||||||
|
|
||||||
fill_mm(o->sha1, &orig);
|
|
||||||
fill_mm(a->sha1, &src1);
|
|
||||||
fill_mm(b->sha1, &src2);
|
|
||||||
|
|
||||||
memset(&xpp, 0, sizeof(xpp));
|
|
||||||
merge_status = xdl_merge(&orig,
|
|
||||||
&src1, name1,
|
|
||||||
&src2, name2,
|
|
||||||
&xpp, XDL_MERGE_ZEALOUS,
|
|
||||||
&result_buf);
|
|
||||||
free(name1);
|
|
||||||
free(name2);
|
|
||||||
free(orig.ptr);
|
|
||||||
free(src1.ptr);
|
|
||||||
free(src2.ptr);
|
|
||||||
|
|
||||||
if ((merge_status < 0) || !result_buf.ptr)
|
if ((merge_status < 0) || !result_buf.ptr)
|
||||||
die("Failed to execute internal merge");
|
die("Failed to execute internal merge");
|
||||||
|
@ -2338,10 +2338,9 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
|
|||||||
*/
|
*/
|
||||||
if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
|
if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
|
||||||
unsigned long nsize = size;
|
unsigned long nsize = size;
|
||||||
char *nbuf = buf;
|
char *nbuf = convert_to_git(path, buf, &nsize);
|
||||||
if (convert_to_git(path, &nbuf, &nsize)) {
|
if (nbuf) {
|
||||||
if (size)
|
munmap(buf, size);
|
||||||
munmap(buf, size);
|
|
||||||
size = nsize;
|
size = nsize;
|
||||||
buf = nbuf;
|
buf = nbuf;
|
||||||
re_allocated = 1;
|
re_allocated = 1;
|
||||||
|
@ -4,6 +4,10 @@ test_description='CRLF conversion'
|
|||||||
|
|
||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
q_to_nul () {
|
||||||
|
tr Q '\0'
|
||||||
|
}
|
||||||
|
|
||||||
append_cr () {
|
append_cr () {
|
||||||
sed -e 's/$/Q/' | tr Q '\015'
|
sed -e 's/$/Q/' | tr Q '\015'
|
||||||
}
|
}
|
||||||
@ -20,6 +24,7 @@ test_expect_success setup '
|
|||||||
for w in Hello world how are you; do echo $w; done >one &&
|
for w in Hello world how are you; do echo $w; done >one &&
|
||||||
mkdir dir &&
|
mkdir dir &&
|
||||||
for w in I am very very fine thank you; do echo $w; done >dir/two &&
|
for w in I am very very fine thank you; do echo $w; done >dir/two &&
|
||||||
|
for w in Oh here is NULQin text here; do echo $w; done | q_to_nul >three &&
|
||||||
git add . &&
|
git add . &&
|
||||||
|
|
||||||
git commit -m initial &&
|
git commit -m initial &&
|
||||||
@ -27,6 +32,7 @@ test_expect_success setup '
|
|||||||
one=`git rev-parse HEAD:one` &&
|
one=`git rev-parse HEAD:one` &&
|
||||||
dir=`git rev-parse HEAD:dir` &&
|
dir=`git rev-parse HEAD:dir` &&
|
||||||
two=`git rev-parse HEAD:dir/two` &&
|
two=`git rev-parse HEAD:dir/two` &&
|
||||||
|
three=`git rev-parse HEAD:three` &&
|
||||||
|
|
||||||
for w in Some extra lines here; do echo $w; done >>one &&
|
for w in Some extra lines here; do echo $w; done >>one &&
|
||||||
git diff >patch.file &&
|
git diff >patch.file &&
|
||||||
@ -38,7 +44,7 @@ test_expect_success setup '
|
|||||||
|
|
||||||
test_expect_success 'update with autocrlf=input' '
|
test_expect_success 'update with autocrlf=input' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
git repo-config core.autocrlf input &&
|
git repo-config core.autocrlf input &&
|
||||||
|
|
||||||
@ -62,7 +68,7 @@ test_expect_success 'update with autocrlf=input' '
|
|||||||
|
|
||||||
test_expect_success 'update with autocrlf=true' '
|
test_expect_success 'update with autocrlf=true' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
git repo-config core.autocrlf true &&
|
git repo-config core.autocrlf true &&
|
||||||
|
|
||||||
@ -86,7 +92,7 @@ test_expect_success 'update with autocrlf=true' '
|
|||||||
|
|
||||||
test_expect_success 'checkout with autocrlf=true' '
|
test_expect_success 'checkout with autocrlf=true' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf true &&
|
git repo-config core.autocrlf true &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -110,7 +116,7 @@ test_expect_success 'checkout with autocrlf=true' '
|
|||||||
|
|
||||||
test_expect_success 'checkout with autocrlf=input' '
|
test_expect_success 'checkout with autocrlf=input' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf input &&
|
git repo-config core.autocrlf input &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -136,7 +142,7 @@ test_expect_success 'checkout with autocrlf=input' '
|
|||||||
|
|
||||||
test_expect_success 'apply patch (autocrlf=input)' '
|
test_expect_success 'apply patch (autocrlf=input)' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf input &&
|
git repo-config core.autocrlf input &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -149,7 +155,7 @@ test_expect_success 'apply patch (autocrlf=input)' '
|
|||||||
|
|
||||||
test_expect_success 'apply patch --cached (autocrlf=input)' '
|
test_expect_success 'apply patch --cached (autocrlf=input)' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf input &&
|
git repo-config core.autocrlf input &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -162,7 +168,7 @@ test_expect_success 'apply patch --cached (autocrlf=input)' '
|
|||||||
|
|
||||||
test_expect_success 'apply patch --index (autocrlf=input)' '
|
test_expect_success 'apply patch --index (autocrlf=input)' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf input &&
|
git repo-config core.autocrlf input &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -176,7 +182,7 @@ test_expect_success 'apply patch --index (autocrlf=input)' '
|
|||||||
|
|
||||||
test_expect_success 'apply patch (autocrlf=true)' '
|
test_expect_success 'apply patch (autocrlf=true)' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf true &&
|
git repo-config core.autocrlf true &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -189,7 +195,7 @@ test_expect_success 'apply patch (autocrlf=true)' '
|
|||||||
|
|
||||||
test_expect_success 'apply patch --cached (autocrlf=true)' '
|
test_expect_success 'apply patch --cached (autocrlf=true)' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf true &&
|
git repo-config core.autocrlf true &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -202,7 +208,7 @@ test_expect_success 'apply patch --cached (autocrlf=true)' '
|
|||||||
|
|
||||||
test_expect_success 'apply patch --index (autocrlf=true)' '
|
test_expect_success 'apply patch --index (autocrlf=true)' '
|
||||||
|
|
||||||
rm -f tmp one dir/two &&
|
rm -f tmp one dir/two three &&
|
||||||
git repo-config core.autocrlf true &&
|
git repo-config core.autocrlf true &&
|
||||||
git read-tree --reset -u HEAD &&
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
@ -214,4 +220,74 @@ test_expect_success 'apply patch --index (autocrlf=true)' '
|
|||||||
}
|
}
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '.gitattributes says two is binary' '
|
||||||
|
|
||||||
|
rm -f tmp one dir/two three &&
|
||||||
|
echo "two -crlf" >.gitattributes &&
|
||||||
|
git repo-config core.autocrlf true &&
|
||||||
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
|
if remove_cr dir/two >/dev/null
|
||||||
|
then
|
||||||
|
echo "Huh?"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
: happy
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
if remove_cr one >/dev/null
|
||||||
|
then
|
||||||
|
: happy
|
||||||
|
else
|
||||||
|
echo "Huh?"
|
||||||
|
false
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
if remove_cr three >/dev/null
|
||||||
|
then
|
||||||
|
echo "Huh?"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
: happy
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '.gitattributes says two is input' '
|
||||||
|
|
||||||
|
rm -f tmp one dir/two three &&
|
||||||
|
echo "two crlf=input" >.gitattributes &&
|
||||||
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
|
if remove_cr dir/two >/dev/null
|
||||||
|
then
|
||||||
|
echo "Huh?"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
: happy
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '.gitattributes says two and three are text' '
|
||||||
|
|
||||||
|
rm -f tmp one dir/two three &&
|
||||||
|
echo "t* crlf" >.gitattributes &&
|
||||||
|
git read-tree --reset -u HEAD &&
|
||||||
|
|
||||||
|
if remove_cr dir/two >/dev/null
|
||||||
|
then
|
||||||
|
: happy
|
||||||
|
else
|
||||||
|
echo "Huh?"
|
||||||
|
false
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
if remove_cr three >/dev/null
|
||||||
|
then
|
||||||
|
: happy
|
||||||
|
else
|
||||||
|
echo "Huh?"
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
145
t/t6026-merge-attr.sh
Executable file
145
t/t6026-merge-attr.sh
Executable file
@ -0,0 +1,145 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 Junio C Hamano
|
||||||
|
#
|
||||||
|
|
||||||
|
test_description='per path merge controlled by merge attribute'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success setup '
|
||||||
|
|
||||||
|
for f in text binary union
|
||||||
|
do
|
||||||
|
echo Initial >$f && git add $f || break
|
||||||
|
done &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m Initial &&
|
||||||
|
|
||||||
|
git branch side &&
|
||||||
|
for f in text binary union
|
||||||
|
do
|
||||||
|
echo Master >>$f && git add $f || break
|
||||||
|
done &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m Master &&
|
||||||
|
|
||||||
|
git checkout side &&
|
||||||
|
for f in text binary union
|
||||||
|
do
|
||||||
|
echo Side >>$f && git add $f || break
|
||||||
|
done &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m Side &&
|
||||||
|
|
||||||
|
git tag anchor
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success merge '
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "binary -merge"
|
||||||
|
echo "union merge=union"
|
||||||
|
} >.gitattributes &&
|
||||||
|
|
||||||
|
if git merge master
|
||||||
|
then
|
||||||
|
echo Gaah, should have conflicted
|
||||||
|
false
|
||||||
|
else
|
||||||
|
echo Ok, conflicted.
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check merge result in index' '
|
||||||
|
|
||||||
|
git ls-files -u | grep binary &&
|
||||||
|
git ls-files -u | grep text &&
|
||||||
|
! (git ls-files -u | grep union)
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check merge result in working tree' '
|
||||||
|
|
||||||
|
git cat-file -p HEAD:binary >binary-orig &&
|
||||||
|
grep "<<<<<<<" text &&
|
||||||
|
cmp binary-orig binary &&
|
||||||
|
! grep "<<<<<<<" union &&
|
||||||
|
grep Master union &&
|
||||||
|
grep Side union
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >./custom-merge <<\EOF
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
orig="$1" ours="$2" theirs="$3" exit="$4"
|
||||||
|
(
|
||||||
|
echo "orig is $orig"
|
||||||
|
echo "ours is $ours"
|
||||||
|
echo "theirs is $theirs"
|
||||||
|
echo "=== orig ==="
|
||||||
|
cat "$orig"
|
||||||
|
echo "=== ours ==="
|
||||||
|
cat "$ours"
|
||||||
|
echo "=== theirs ==="
|
||||||
|
cat "$theirs"
|
||||||
|
) >"$ours+"
|
||||||
|
cat "$ours+" >"$ours"
|
||||||
|
rm -f "$ours+"
|
||||||
|
exit "$exit"
|
||||||
|
EOF
|
||||||
|
chmod +x ./custom-merge
|
||||||
|
|
||||||
|
test_expect_success 'custom merge backend' '
|
||||||
|
|
||||||
|
echo "* merge=union" >.gitattributes &&
|
||||||
|
echo "text merge=custom" >>.gitattributes &&
|
||||||
|
|
||||||
|
git reset --hard anchor &&
|
||||||
|
git config --replace-all \
|
||||||
|
merge.custom.driver "./custom-merge %O %A %B 0" &&
|
||||||
|
git config --replace-all \
|
||||||
|
merge.custom.name "custom merge driver for testing" &&
|
||||||
|
|
||||||
|
git merge master &&
|
||||||
|
|
||||||
|
cmp binary union &&
|
||||||
|
sed -e 1,3d text >check-1 &&
|
||||||
|
o=$(git-unpack-file master^:text) &&
|
||||||
|
a=$(git-unpack-file side^:text) &&
|
||||||
|
b=$(git-unpack-file master:text) &&
|
||||||
|
sh -c "./custom-merge $o $a $b 0" &&
|
||||||
|
sed -e 1,3d $a >check-2 &&
|
||||||
|
cmp check-1 check-2 &&
|
||||||
|
rm -f $o $a $b
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'custom merge backend' '
|
||||||
|
|
||||||
|
git reset --hard anchor &&
|
||||||
|
git config --replace-all \
|
||||||
|
merge.custom.driver "./custom-merge %O %A %B 1" &&
|
||||||
|
git config --replace-all \
|
||||||
|
merge.custom.name "custom merge driver for testing" &&
|
||||||
|
|
||||||
|
if git merge master
|
||||||
|
then
|
||||||
|
echo "Eh? should have conflicted"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
echo "Ok, conflicted"
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
cmp binary union &&
|
||||||
|
sed -e 1,3d text >check-1 &&
|
||||||
|
o=$(git-unpack-file master^:text) &&
|
||||||
|
a=$(git-unpack-file anchor:text) &&
|
||||||
|
b=$(git-unpack-file master:text) &&
|
||||||
|
sh -c "./custom-merge $o $a $b 0" &&
|
||||||
|
sed -e 1,3d $a >check-2 &&
|
||||||
|
cmp check-1 check-2 &&
|
||||||
|
rm -f $o $a $b
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user