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-bundle
|
||||
git-cat-file
|
||||
git-check-attr
|
||||
git-check-ref-format
|
||||
git-checkout
|
||||
git-checkout-index
|
||||
|
@ -2,9 +2,10 @@ MAN1_TXT= \
|
||||
$(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
|
||||
$(wildcard git-*.txt)) \
|
||||
gitk.txt
|
||||
MAN5_TXT=gitattributes.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-2
|
||||
@ -23,12 +24,14 @@ SP_ARTICLES = howto/revert-branch-rebase user-manual
|
||||
DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
|
||||
|
||||
DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
|
||||
DOC_MAN5=$(patsubst %.txt,%.5,$(MAN1_TXT))
|
||||
DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
|
||||
|
||||
prefix?=$(HOME)
|
||||
bindir?=$(prefix)/bin
|
||||
mandir?=$(prefix)/man
|
||||
man1dir=$(mandir)/man1
|
||||
man5dir=$(mandir)/man5
|
||||
man7dir=$(mandir)/man7
|
||||
# DESTDIR=
|
||||
|
||||
@ -53,15 +56,19 @@ all: html man
|
||||
|
||||
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)
|
||||
man5: $(DOC_MAN5)
|
||||
man7: $(DOC_MAN7)
|
||||
|
||||
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_MAN5) $(DESTDIR)$(man5dir)
|
||||
$(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
|
||||
|
||||
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
|
||||
|
||||
%.html : %.txt
|
||||
@ -109,7 +116,7 @@ clean:
|
||||
sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+
|
||||
mv $@+ $@
|
||||
|
||||
%.1 %.7 : %.xml
|
||||
%.1 %.5 %.7 : %.xml
|
||||
xmlto -m callouts.xsl man $<
|
||||
|
||||
%.xml : %.txt
|
||||
|
@ -84,6 +84,7 @@ git-bundle mainporcelain
|
||||
git-cat-file plumbinginterrogators
|
||||
git-checkout-index plumbingmanipulators
|
||||
git-checkout mainporcelain
|
||||
git-check-attr purehelpers
|
||||
git-check-ref-format purehelpers
|
||||
git-cherry ancillaryinterrogators
|
||||
git-cherry-pick mainporcelain
|
||||
|
@ -525,6 +525,19 @@ merge.verbosity::
|
||||
conflicts, 2 outputs conflicts and file changes. Level 5 and
|
||||
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::
|
||||
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.
|
||||
|
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 \
|
||||
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 \
|
||||
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.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 \
|
||||
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 \
|
||||
convert.o decorate.o
|
||||
convert.o attr.o decorate.o
|
||||
|
||||
BUILTIN_OBJS = \
|
||||
builtin-add.o \
|
||||
@ -316,6 +316,7 @@ BUILTIN_OBJS = \
|
||||
builtin-branch.o \
|
||||
builtin-bundle.o \
|
||||
builtin-cat-file.o \
|
||||
builtin-check-attr.o \
|
||||
builtin-checkout-index.o \
|
||||
builtin-check-ref-format.o \
|
||||
builtin-commit-tree.o \
|
||||
@ -1032,9 +1033,10 @@ dist-doc:
|
||||
gzip -n -9 -f $(htmldocs).tar
|
||||
:
|
||||
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=./ \
|
||||
man1dir=../.doc-tmp-dir/man1 \
|
||||
man5dir=../.doc-tmp-dir/man5 \
|
||||
man7dir=../.doc-tmp-dir/man7 \
|
||||
install
|
||||
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);
|
||||
nsize = got;
|
||||
nbuf = buf;
|
||||
if (convert_to_git(path, &nbuf, &nsize)) {
|
||||
nbuf = convert_to_git(path, buf, &nsize);
|
||||
if (nbuf) {
|
||||
free(buf);
|
||||
*buf_p = nbuf;
|
||||
*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)
|
||||
{
|
||||
int fd, converted;
|
||||
int fd;
|
||||
char *nbuf;
|
||||
unsigned long nsize;
|
||||
|
||||
if (has_symlinks && S_ISLNK(mode))
|
||||
/* 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)
|
||||
return -1;
|
||||
|
||||
nsize = size;
|
||||
nbuf = (char *) buf;
|
||||
converted = convert_to_working_tree(path, &nbuf, &nsize);
|
||||
if (converted) {
|
||||
nbuf = convert_to_working_tree(path, buf, &size);
|
||||
if (nbuf)
|
||||
buf = nbuf;
|
||||
size = nsize;
|
||||
}
|
||||
|
||||
while (size) {
|
||||
int written = xwrite(fd, buf, size);
|
||||
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)
|
||||
die("closing file %s: %s", path, strerror(errno));
|
||||
if (converted)
|
||||
if (nbuf)
|
||||
free(nbuf);
|
||||
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_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_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_cherry(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_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
|
||||
#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(void);
|
||||
@ -224,6 +227,7 @@ extern int refresh_cache(unsigned int flags);
|
||||
|
||||
struct lock_file {
|
||||
struct lock_file *next;
|
||||
pid_t owner;
|
||||
char on_list;
|
||||
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, ...);
|
||||
|
||||
/* convert.c */
|
||||
extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep);
|
||||
extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep);
|
||||
extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep);
|
||||
extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep);
|
||||
|
||||
/* match-trees.c */
|
||||
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 "attr.h"
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define CRLF_GUESS (-1)
|
||||
#define CRLF_BINARY 0
|
||||
#define CRLF_TEXT 1
|
||||
#define CRLF_INPUT 2
|
||||
|
||||
struct text_stat {
|
||||
/* CR, LF and CRLF counts */
|
||||
unsigned cr, lf, crlf;
|
||||
@ -72,115 +79,171 @@ static int is_binary(unsigned long size, struct text_stat *stats)
|
||||
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;
|
||||
struct text_stat stats;
|
||||
|
||||
/*
|
||||
* FIXME! Other pluggable conversions should go here,
|
||||
* based on filename patterns. Right now we just do the
|
||||
* stupid auto-CRLF one.
|
||||
*/
|
||||
if (!auto_crlf)
|
||||
return 0;
|
||||
if ((action == CRLF_BINARY) || (action == CRLF_GUESS && !auto_crlf))
|
||||
return NULL;
|
||||
|
||||
size = *sizep;
|
||||
if (!size)
|
||||
return 0;
|
||||
buffer = *bufp;
|
||||
return NULL;
|
||||
|
||||
gather_stats(buffer, size, &stats);
|
||||
gather_stats(src, size, &stats);
|
||||
|
||||
/* No CR? Nothing to convert, regardless. */
|
||||
if (!stats.cr)
|
||||
return 0;
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* We're currently not going to even try to convert stuff
|
||||
* that has bare CR characters. Does anybody do that crazy
|
||||
* stuff?
|
||||
*/
|
||||
if (stats.cr != stats.crlf)
|
||||
return 0;
|
||||
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
|
||||
* stuff?
|
||||
*/
|
||||
if (stats.cr != stats.crlf)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* And add some heuristics for binary vs text, of course...
|
||||
*/
|
||||
if (is_binary(size, &stats))
|
||||
return 0;
|
||||
/*
|
||||
* And add some heuristics for binary vs text, of course...
|
||||
*/
|
||||
if (is_binary(size, &stats))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ok, allocate a new buffer, fill it in, and return true
|
||||
* to let the caller know that we switched buffers on it.
|
||||
*/
|
||||
nsize = size - stats.crlf;
|
||||
nbuf = xmalloc(nsize);
|
||||
*bufp = nbuf;
|
||||
buffer = xmalloc(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;
|
||||
struct text_stat stats;
|
||||
unsigned char last;
|
||||
|
||||
/*
|
||||
* FIXME! Other pluggable conversions should go here,
|
||||
* based on filename patterns. Right now we just do the
|
||||
* stupid auto-CRLF one.
|
||||
*/
|
||||
if (auto_crlf <= 0)
|
||||
return 0;
|
||||
if ((action == CRLF_BINARY) || (action == CRLF_INPUT) ||
|
||||
(action == CRLF_GUESS && auto_crlf <= 0))
|
||||
return NULL;
|
||||
|
||||
size = *sizep;
|
||||
if (!size)
|
||||
return 0;
|
||||
buffer = *bufp;
|
||||
return NULL;
|
||||
|
||||
gather_stats(buffer, size, &stats);
|
||||
gather_stats(src, size, &stats);
|
||||
|
||||
/* No LF? Nothing to convert, regardless. */
|
||||
if (!stats.lf)
|
||||
return 0;
|
||||
return NULL;
|
||||
|
||||
/* Was it already in CRLF format? */
|
||||
if (stats.lf == stats.crlf)
|
||||
return 0;
|
||||
return NULL;
|
||||
|
||||
/* If we have any bare CR characters, we're not going to touch it */
|
||||
if (stats.cr != stats.crlf)
|
||||
return 0;
|
||||
if (action == CRLF_GUESS) {
|
||||
/* If we have any bare CR characters, we're not going to touch it */
|
||||
if (stats.cr != stats.crlf)
|
||||
return NULL;
|
||||
|
||||
if (is_binary(size, &stats))
|
||||
return 0;
|
||||
if (is_binary(size, &stats))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ok, allocate a new buffer, fill it in, and return true
|
||||
* to let the caller know that we switched buffers on it.
|
||||
*/
|
||||
nsize = size + stats.lf - stats.crlf;
|
||||
nbuf = xmalloc(nsize);
|
||||
*bufp = nbuf;
|
||||
buffer = xmalloc(nsize);
|
||||
*sizep = nsize;
|
||||
last = 0;
|
||||
|
||||
dst = buffer;
|
||||
do {
|
||||
unsigned char c = *buffer++;
|
||||
unsigned char c = *src++;
|
||||
if (c == '\n' && last != '\r')
|
||||
*nbuf++ = '\r';
|
||||
*nbuf++ = c;
|
||||
*dst++ = '\r';
|
||||
*dst++ = c;
|
||||
last = c;
|
||||
} 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 "xdiff-interface.h"
|
||||
#include "color.h"
|
||||
#include "attr.h"
|
||||
|
||||
#ifdef NO_FAST_WORKING_DIRECTORY
|
||||
#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);
|
||||
}
|
||||
|
||||
#define FIRST_FEW_BYTES 8000
|
||||
static int mmfile_is_binary(mmfile_t *mf)
|
||||
static void setup_diff_attr_check(struct git_attr_check *check)
|
||||
{
|
||||
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)
|
||||
sz = FIRST_FEW_BYTES;
|
||||
return !!memchr(mf->ptr, 0, sz);
|
||||
return !!memchr(one->data, 0, sz);
|
||||
}
|
||||
|
||||
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)
|
||||
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 */
|
||||
if (mf1.size == mf2.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)
|
||||
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->added = mf2.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)
|
||||
die("unable to read files to diff");
|
||||
|
||||
if (mmfile_is_binary(&mf2))
|
||||
if (file_is_binary(two))
|
||||
return;
|
||||
else {
|
||||
/* 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
|
||||
*/
|
||||
buf = s->data;
|
||||
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);
|
||||
s->should_munmap = 0;
|
||||
s->data = buf;
|
||||
@ -1825,8 +1857,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
|
||||
|
||||
if (o->binary) {
|
||||
mmfile_t mf;
|
||||
if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
|
||||
(!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
|
||||
if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
|
||||
(!fill_mmfile(&mf, two) && file_is_binary(two)))
|
||||
abbrev = 40;
|
||||
}
|
||||
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");
|
||||
|
||||
/* Maybe hash p->two? into the patch id? */
|
||||
if (mmfile_is_binary(&mf2))
|
||||
if (file_is_binary(p->two))
|
||||
continue;
|
||||
|
||||
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) {
|
||||
char *buf, *new;
|
||||
unsigned long size, nsize;
|
||||
unsigned long size;
|
||||
|
||||
case S_IFREG:
|
||||
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
|
||||
*/
|
||||
buf = new;
|
||||
nsize = size;
|
||||
if (convert_to_working_tree(ce->name, &buf, &nsize)) {
|
||||
buf = convert_to_working_tree(ce->name, new, &size);
|
||||
if (buf) {
|
||||
free(new);
|
||||
new = buf;
|
||||
size = nsize;
|
||||
}
|
||||
|
||||
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 },
|
||||
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
|
||||
{ "check-ref-format", cmd_check_ref_format },
|
||||
{ "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
|
||||
{ "cherry", cmd_cherry, RUN_SETUP },
|
||||
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
|
||||
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
|
||||
|
@ -8,8 +8,11 @@ static const char *alternate_index_output;
|
||||
|
||||
static void remove_lock_file(void)
|
||||
{
|
||||
pid_t me = getpid();
|
||||
|
||||
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);
|
||||
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);
|
||||
fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
|
||||
if (0 <= fd) {
|
||||
lk->owner = getpid();
|
||||
if (!lk->on_list) {
|
||||
lk->next = lock_file_list;
|
||||
lock_file_list = lk;
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "unpack-trees.h"
|
||||
#include "path-list.h"
|
||||
#include "xdiff-interface.h"
|
||||
#include "interpolate.h"
|
||||
#include "attr.h"
|
||||
|
||||
static int subtree_merge;
|
||||
|
||||
@ -645,6 +647,384 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
|
||||
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,
|
||||
struct diff_filespec *a, struct diff_filespec *b,
|
||||
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))
|
||||
hashcpy(result.sha, a->sha1);
|
||||
else if (S_ISREG(a->mode)) {
|
||||
mmfile_t orig, src1, src2;
|
||||
mmbuffer_t result_buf;
|
||||
xpparam_t xpp;
|
||||
char *name1, *name2;
|
||||
int merge_status;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
merge_status = ll_merge(&result_buf, o, a, b,
|
||||
branch1, branch2);
|
||||
|
||||
if ((merge_status < 0) || !result_buf.ptr)
|
||||
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)) {
|
||||
unsigned long nsize = size;
|
||||
char *nbuf = buf;
|
||||
if (convert_to_git(path, &nbuf, &nsize)) {
|
||||
if (size)
|
||||
munmap(buf, size);
|
||||
char *nbuf = convert_to_git(path, buf, &nsize);
|
||||
if (nbuf) {
|
||||
munmap(buf, size);
|
||||
size = nsize;
|
||||
buf = nbuf;
|
||||
re_allocated = 1;
|
||||
|
@ -4,6 +4,10 @@ test_description='CRLF conversion'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
q_to_nul () {
|
||||
tr Q '\0'
|
||||
}
|
||||
|
||||
append_cr () {
|
||||
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 &&
|
||||
mkdir dir &&
|
||||
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 commit -m initial &&
|
||||
@ -27,6 +32,7 @@ test_expect_success setup '
|
||||
one=`git rev-parse HEAD:one` &&
|
||||
dir=`git rev-parse HEAD:dir` &&
|
||||
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 &&
|
||||
git diff >patch.file &&
|
||||
@ -38,7 +44,7 @@ test_expect_success setup '
|
||||
|
||||
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 repo-config core.autocrlf input &&
|
||||
|
||||
@ -62,7 +68,7 @@ test_expect_success 'update with autocrlf=input' '
|
||||
|
||||
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 repo-config core.autocrlf true &&
|
||||
|
||||
@ -86,7 +92,7 @@ test_expect_success 'update 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 read-tree --reset -u HEAD &&
|
||||
|
||||
@ -110,7 +116,7 @@ test_expect_success 'checkout with autocrlf=true' '
|
||||
|
||||
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 read-tree --reset -u HEAD &&
|
||||
|
||||
@ -136,7 +142,7 @@ test_expect_success 'checkout with 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 read-tree --reset -u HEAD &&
|
||||
|
||||
@ -149,7 +155,7 @@ test_expect_success 'apply patch (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 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)' '
|
||||
|
||||
rm -f tmp one dir/two &&
|
||||
rm -f tmp one dir/two three &&
|
||||
git repo-config core.autocrlf input &&
|
||||
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)' '
|
||||
|
||||
rm -f tmp one dir/two &&
|
||||
rm -f tmp one dir/two three &&
|
||||
git repo-config core.autocrlf true &&
|
||||
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)' '
|
||||
|
||||
rm -f tmp one dir/two &&
|
||||
rm -f tmp one dir/two three &&
|
||||
git repo-config core.autocrlf true &&
|
||||
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)' '
|
||||
|
||||
rm -f tmp one dir/two &&
|
||||
rm -f tmp one dir/two three &&
|
||||
git repo-config core.autocrlf true &&
|
||||
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
|
||||
|
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