Subject: [PATCH 6/6] Implement negative pathspec

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Wed, 12 Oct 2011 09:44:43 +1100

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 I really like the mnemonic ^ but it's regex. ":^Documentation" looks
 nicer than ":~Documentation". Do we plan on supporting regex in
 pathspec too?

 We should mention these magic in a less obscure document. Glossary is
 mostly for developer discussions. git-diff may be a good place
 because it's one of the two frequently used commands (the other one
 is grep) that benefit magic the most (with a short reference from
 git.txt)

 Documentation/glossary-content.txt |    8 +++---
 cache.h                            |    1 +
 dir.c                              |    2 +
 setup.c                            |    1 +
 tree-walk.c                        |   37 +++++++++++++++++++++++++++++++++--
 5 files changed, 42 insertions(+), 7 deletions(-)

diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 3595b58..9a2765d 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -319,12 +319,12 @@ top `/`;;
 	The magic word `top` (mnemonic: `/`) makes the pattern match
 	from the root of the working tree, even when you are running
 	the command from inside a subdirectory.
+
+exclude `~`;;
+	The magic word `exclude` (mnemonic: `~`) excludes paths that
+	match the pattern.
 --
 +
-Currently only the slash `/` is recognized as the "magic signature",
-but it is envisioned that we will support more types of magic in later
-versions of git.
-+
 A pathspec with only a colon means "there is no pathspec". This form
 should not be combined with other pathspec.
 
diff --git a/cache.h b/cache.h
index 719d4a3..75fe589 100644
--- a/cache.h
+++ b/cache.h
@@ -541,6 +541,7 @@ extern int ie_modified(const struct index_state *, struct cache_entry *, struct
  */
 #define PATHSPEC_FROMTOP    (1<<0)
 #define PATHSPEC_NOGLOB     (1<<1)
+#define PATHSPEC_NEGATE     (1<<2)
 
 struct pathspec {
 	const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
diff --git a/dir.c b/dir.c
index d38af0f..46dd35f 100644
--- a/dir.c
+++ b/dir.c
@@ -1305,6 +1305,8 @@ int parse_pathspec(struct pathspec *pathspec, const char *prefix,
 				pitem->magic |= PATHSPEC_NOGLOB;
 			else
 				pathspec->magic &= ~PATHSPEC_NOGLOB;
+			if (pitem->magic & PATHSPEC_NEGATE)
+				pathspec->magic |= PATHSPEC_NEGATE;
 			pitem++;
 			dst++;
 		}
diff --git a/setup.c b/setup.c
index b074210..42beb9b 100644
--- a/setup.c
+++ b/setup.c
@@ -111,6 +111,7 @@ static struct pathspec_magic {
 	const char *name;
 } pathspec_magic[] = {
 	{ PATHSPEC_FROMTOP, '/', "top" },
+	{ PATHSPEC_NEGATE, '~', "exclude" },
 };
 
 /*
diff --git a/tree-walk.c b/tree-walk.c
index db07fd6..936b5da 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -580,15 +580,17 @@ static int match_dir_prefix(const char *base, int baselen,
  *  - zero for no
  *  - negative for "no, and no subsequent entries will be either"
  */
-int tree_entry_interesting(const struct name_entry *entry,
-			   struct strbuf *base, int base_offset,
-			   const struct pathspec *ps)
+static int tree_entry_interesting_1(const struct name_entry *entry,
+				    struct strbuf *base, int base_offset,
+				    const struct pathspec *ps, int negative_magic)
 {
 	int i;
 	int pathlen, baselen = base->len - base_offset;
 	int never_interesting = ps->magic & PATHSPEC_NOGLOB ? -1 : 0;
+	int has_effective_pathspec = 0;
 
 	if (!ps->nr) {
+no_pathspec:
 		if (!ps->recursive || ps->max_depth == -1)
 			return 2;
 		return !!within_depth(base->buf + base_offset, baselen,
@@ -604,6 +606,12 @@ int tree_entry_interesting(const struct name_entry *entry,
 		const char *base_str = base->buf + base_offset;
 		int matchlen = item->len;
 
+		if ((!negative_magic && !(item->magic & PATHSPEC_NEGATE)) ||
+		    ( negative_magic &&  (item->magic & PATHSPEC_NEGATE)))
+			has_effective_pathspec = 1;
+		else
+			continue;
+
 		if (baselen >= matchlen) {
 			/* If it doesn't match, move along... */
 			if (!match_dir_prefix(base_str, baselen, match, matchlen))
@@ -663,5 +671,28 @@ match_wildcards:
 		if (ps->recursive && S_ISDIR(entry->mode))
 			return 1;
 	}
+
+	/* the same effect with ps->nr == 0 */
+	if (!has_effective_pathspec)
+		goto no_pathspec;
+
 	return never_interesting; /* No matches */
 }
+
+int tree_entry_interesting(const struct name_entry *entry,
+			   struct strbuf *base, int base_offset,
+			   const struct pathspec *ps)
+{
+	int next_ret, ret;
+
+	ret = tree_entry_interesting_1(entry, base, base_offset, ps, 0);
+	if (ps->magic & PATHSPEC_NEGATE) {
+		next_ret = tree_entry_interesting_1(entry, base, base_offset, ps, 1);
+		switch (next_ret) {
+		case 2: ret = -1; break;
+		case 1: ret = 0; break;
+		default: break;
+		}
+	}
+	return ret;
+}
-- 
1.7.3.1.256.g2539c.dirty