[PATCH 2/2] Make Git respect changes to .gitattributes during checkout.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux