The fix is twofold: First, we force .gitattributes files to always be the first ones checked out. This is the part in check_updates(). Second, we make sure that the checked out attributes get honored by popping off elements on the attribute stack, until we reach the level where a new .gitattributes was checked out. The next time someone calls git_checkattr(), it will reconstruct the attributes from that point. Note that in unlink_entry() there is code to support the case where .gitattributes is removed (test case #3 in t2013-checkout-crlf.sh), but this doesn't work properly because Git tries to read new attributes from the index, but the old index (where .gitattributes still exists) is still active. --- attr.c | 86 +++++++++++++++++++++++++++++++++++++++------ attr.h | 1 + entry.c | 19 ++++++++++ t/t2013-checkout-crlf.sh | 2 +- unpack-trees.c | 77 +++++++++++++++++++++++++++++++---------- 5 files changed, 154 insertions(+), 31 deletions(-) diff --git a/attr.c b/attr.c index 17f6a4d..2bc0847 100644 --- a/attr.c +++ b/attr.c @@ -455,6 +455,23 @@ static void bootstrap_attr_stack(void) } } +static void pop_attr_stack(const char *path, int dirlen) +{ + struct attr_stack *elem; + 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); + } +} + static void prepare_attr_stack(const char *path, int dirlen) { struct attr_stack *elem, *info; @@ -489,18 +506,7 @@ static void prepare_attr_stack(const char *path, int dirlen) * 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); - } + pop_attr_stack(path, dirlen); /* * Read from parent directories and push them down @@ -642,3 +648,59 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) return 0; } + +void git_attr_invalidate_path(const char *path) +{ + int dirlen; + const char *cp; + struct attr_stack *info, *elem; + + bootstrap_attr_stack(); + + /* + * Pop the "info" one that is always at the top of the stack. + */ + info = attr_stack; + attr_stack = info->prev; + + cp = strrchr(path, '/'); + dirlen = cp ? cp - path : 0; + /* Pop everything up to, and including, path. */ + pop_attr_stack(path, dirlen); + + if (!strcmp(path, "") && attr_stack->origin && !strcmp(attr_stack->origin, "")) { + /* + * Special handling when the root attributes must be invalidated. + * Note that we cannot reread the attributes here. The reason is that + * when unlinking the root .gitattributes file, the index entry is + * removed after unlinking. If we reread the attributes here, we + * would get the same attributes as before because .gitattributes + * is still in the index. Thus we must wait until the next call to + * read_attr, when the index has been updated. + */ + /* $GIT_DIR/info/attributes */ + elem = info; + debug_pop(elem); + free_attr_elem(elem); + + /* $GIT_WORK_TREE/.gitattributes */ + elem = attr_stack; + debug_pop(elem); + attr_stack = elem->prev; + free_attr_elem(elem); + + /* builtins */ + elem = attr_stack; + debug_pop(elem); + attr_stack = elem->prev; + free_attr_elem(elem); + + assert(!attr_stack); + } else { + /* + * Finally push the "info" one at the top of the stack. + */ + info->prev = attr_stack; + attr_stack = info; + } +} diff --git a/attr.h b/attr.h index f1c2038..8f4135b 100644 --- a/attr.h +++ b/attr.h @@ -30,5 +30,6 @@ struct git_attr_check { }; int git_checkattr(const char *path, int, struct git_attr_check *); +void git_attr_invalidate_path(const char *path); #endif /* ATTR_H */ diff --git a/entry.c b/entry.c index 05aa58d..7fb27fd 100644 --- a/entry.c +++ b/entry.c @@ -1,6 +1,7 @@ #include "cache.h" #include "blob.h" #include "dir.h" +#include "attr.h" static void create_directories(const char *path, const struct checkout *state) { @@ -91,6 +92,9 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout { int fd; long wrote; + int gitattrlen; + int pathlen; + char *cp; switch (ce->ce_mode & S_IFMT) { char *new; @@ -171,6 +175,21 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout return error("git checkout-index: unknown file mode for %s", path); } + gitattrlen = strlen(GITATTRIBUTES_FILE); + pathlen = strlen(path); + if (pathlen >= gitattrlen && !strncmp(path + pathlen - gitattrlen, + GITATTRIBUTES_FILE, gitattrlen)) { + /* Invalidate attributes if a new .gitattributes file was checked out. */ + cp = strrchr(path, '/'); + if (!cp) { + git_attr_invalidate_path(""); + } else { + *cp = '\0'; + git_attr_invalidate_path(path); + *cp = '/'; + } + } + if (state->refresh_cache) { struct stat st; lstat(ce->name, &st); diff --git a/t/t2013-checkout-crlf.sh b/t/t2013-checkout-crlf.sh index d9d6465..e704c93 100755 --- a/t/t2013-checkout-crlf.sh +++ b/t/t2013-checkout-crlf.sh @@ -21,7 +21,7 @@ test_expect_success 'setup' ' ' -test_expect_failure 'checkout with existing .gitattributes' ' +test_expect_success 'checkout with existing .gitattributes' ' git checkout master~2 && git checkout master~1 && diff --git a/unpack-trees.c b/unpack-trees.c index e547282..63a41f8 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -7,6 +7,7 @@ #include "unpack-trees.h" #include "progress.h" #include "refs.h" +#include "attr.h" /* * Error messages expected by scripts out of plumbing commands such as @@ -60,6 +61,7 @@ static void unlink_entry(struct cache_entry *ce) { char *cp, *prev; char *name = ce->name; + int namelen, gitattrlen; if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name)) return; @@ -82,6 +84,21 @@ static void unlink_entry(struct cache_entry *ce) } prev = cp; } + + if (!prev) { + gitattrlen = strlen(GITATTRIBUTES_FILE); + namelen = strlen(name); + if (namelen >= gitattrlen && !strncmp(name + namelen - gitattrlen, + GITATTRIBUTES_FILE, gitattrlen)) { + if (cp) { + *cp = '\0'; + git_attr_invalidate_path(name); + *cp = '/'; + } else { + git_attr_invalidate_path(""); + } + } + } } static struct checkout state; @@ -92,6 +109,9 @@ static int check_updates(struct unpack_trees_options *o) struct index_state *index = &o->result; int i; int errs = 0; + int attr; + int gitattrlen; + int namelen; if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { @@ -105,27 +125,48 @@ static int check_updates(struct unpack_trees_options *o) cnt = 0; } - for (i = 0; i < index->cache_nr; i++) { - struct cache_entry *ce = index->cache[i]; + gitattrlen = strlen(GITATTRIBUTES_FILE); - if (ce->ce_flags & CE_REMOVE) { - display_progress(progress, ++cnt); - if (o->update) - unlink_entry(ce); - remove_index_entry_at(&o->result, i); - i--; - continue; + /* + * We want to checkout .gitattributes before everything else. So: + * attr == 0 -> .gitattributes + * attr == 1 -> everything else + */ + for (attr = 0; attr < 2; attr++) { + for (i = 0; i < index->cache_nr; i++) { + struct cache_entry *ce = index->cache[i]; + + namelen = strlen(ce->name); + if ((attr == 0) != (namelen >= gitattrlen && strncmp( + ce->name + namelen - gitattrlen, + GITATTRIBUTES_FILE, gitattrlen) == 0)) + continue; + if (ce->ce_flags & CE_REMOVE) { + display_progress(progress, ++cnt); + if (o->update) + unlink_entry(ce); + remove_index_entry_at(&o->result, i); + i--; + continue; + } } } - for (i = 0; i < index->cache_nr; i++) { - struct cache_entry *ce = index->cache[i]; + for (attr = 0; attr < 2; attr++) { + for (i = 0; i < index->cache_nr; i++) { + struct cache_entry *ce = index->cache[i]; - if (ce->ce_flags & CE_UPDATE) { - display_progress(progress, ++cnt); - ce->ce_flags &= ~CE_UPDATE; - if (o->update) { - errs |= checkout_entry(ce, &state, NULL); + namelen = strlen(ce->name); + if ((attr == 0) != (namelen >= gitattrlen && strncmp( + ce->name + namelen - gitattrlen, + GITATTRIBUTES_FILE, gitattrlen) == 0)) + continue; + if (ce->ce_flags & CE_UPDATE) { + display_progress(progress, ++cnt); + ce->ce_flags &= ~CE_UPDATE; + if (o->update) { + errs |= checkout_entry(ce, &state, NULL); + } } } } -- 1.6.1.1 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html