Subject: [PATCH nd/sparse] Support directory exclusion from index

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Sat, 31 Oct 2009 21:09:23 +0700

The function excluded_from_list (or its public API excluded) is
currently used to mark what entry is included in sparse checkout.
Because index does not have directories, the pattern "foo", while
would match directory "foo" on working directory, would not match
against index.

To overcome this, if a pattern does not match, check if it matches
parent directories too before moving on to the next pattern. This
behavior only applies to index matching.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 There is subtle difference, which might break things. When working
 with file system, it checks top-down, parent directories first.
 I do bottom-up.

 If/you/have/deep/directories sparse checkout may become slow.

 And my test broke :(

 dir.c                                |   24 ++++++++++++++++++++++--
 t/t1009-read-tree-sparse-checkout.sh |    4 ++--
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/dir.c b/dir.c
index 3a8d3e6..82227e5 100644
--- a/dir.c
+++ b/dir.c
@@ -334,13 +334,15 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 }
 
 /* Scan the list and let the last match determine the fate.
+ * dtype == NULL means matching against index, not working directory.
  * Return 1 for exclude, 0 for include and -1 for undecided.
  */
-int excluded_from_list(const char *pathname,
-		       int pathlen, const char *basename, int *dtype,
+int excluded_from_list(const char *pathname_,
+		       int pathlen_, const char *basename_, int *dtype,
 		       struct exclude_list *el)
 {
 	int i;
+	char buf[PATH_MAX];
 
 	if (el->nr) {
 		for (i = el->nr - 1; 0 <= i; i--) {
@@ -348,6 +350,11 @@ int excluded_from_list(const char *pathname,
 			const char *exclude = x->pattern;
 			int to_exclude = x->to_exclude;
 
+			const char *pathname = pathname_;
+			const char *basename = basename_;
+			int pathlen = pathlen_;
+
+recheck:
 			if (x->flags & EXC_FLAG_MUSTBEDIR) {
 				if (!dtype) {
 					if (!prefixcmp(pathname, exclude))
@@ -398,6 +405,19 @@ int excluded_from_list(const char *pathname,
 					    return to_exclude;
 				}
 			}
+
+			if (!dtype) { /* matching against index */
+				basename = strrchr(pathname, '/');
+				if (basename) {
+					pathlen = basename-pathname;
+					memcpy(buf, pathname, pathlen);
+					buf[pathlen] = '\0';
+					pathname = buf;
+					basename = strrchr(pathname, '/');
+					basename = (basename) ? basename+1 : pathname;
+					goto recheck;
+				}
+			}
 		}
 	}
 	return -1; /* undecided */
diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh
index 62246db..b57d237 100755
--- a/t/t1009-read-tree-sparse-checkout.sh
+++ b/t/t1009-read-tree-sparse-checkout.sh
@@ -84,13 +84,13 @@ cat >expected.swt <<EOF
 H init.t
 H sub/added
 EOF
-test_expect_failure 'match directories without trailing slash' '
+test_expect_success 'match directories without trailing slash' '
 	echo init.t > .git/info/sparse-checkout &&
 	echo sub >> .git/info/sparse-checkout &&
 	git read-tree -m -u HEAD &&
 	git ls-files -t > result &&
 	test_cmp expected.swt result &&
-	test ! -f init.t &&
+	test -f init.t &&
 	test -f sub/added
 '
 
-- 
1.6.5.2.216.g9c1ec