git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] git-ls-files: --exclude mechanism updates.

From
Junio C Hamano <junkio@cox.net>
Date
Jul 24, 2005, 22:50 UTC
Message-ID
<7v64uz3jji.fsf_-_@assigned-by-dhcp.cox.net>
In-Reply-To
<7vd5p73jlu.fsf@assigned-by-dhcp.cox.net>

Add --exclude-per-directory=<name> option that specifies a file to contain exclude patterns local to that directory and its subdirectories. Update the exclusion logic to be able to say "include files that match this more specific pattern, even though later exclude patterns may match them". Also enhances that a pattern can contain '/' in which case fnmatch is called with FNM_PATHNAME flag to match the entire path.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 ls-files.c                         |  123 ++++++++++++++++++++++++++++++------
 t/t3001-ls-files-others-exclude.sh |   55 ++++++++++++++++
 2 files changed, 157 insertions(+), 21 deletions(-)
 create mode 100755 t/t3001-ls-files-others-exclude.sh
d1466fd8701ca79a91b41c6225c115a0a9866d6e
diff --git a/ls-files.c b/ls-files.c
--- a/ls-files.c
+++ b/ls-files.c
@@ -25,20 +25,31 @@ static const char *tag_removed = "";
 static const char *tag_other = "";
 static const char *tag_killed = "";
 
+static char *exclude_per_dir = NULL;
 static int nr_excludes;
-static const char **excludes;
 static int excludes_alloc;
+static struct exclude {
+	const char *pattern;
+	const char *base;
+	int baselen;
+} **excludes;
 
-static void add_exclude(const char *string)
+static void add_exclude(const char *string, const char *base, int baselen)
 {
+	struct exclude *x = xmalloc(sizeof (*x));
+
+	x->pattern = string;
+	x->base = base;
+	x->baselen = baselen;
 	if (nr_excludes == excludes_alloc) {
 		excludes_alloc = alloc_nr(excludes_alloc);
 		excludes = realloc(excludes, excludes_alloc*sizeof(char *));
 	}
-	excludes[nr_excludes++] = string;
+	excludes[nr_excludes++] = x;
 }
 
-static void add_excludes_from_file(const char *fname)
+static int add_excludes_from_file_1(const char *fname,
+				    const char *base, int baselen)
 {
 	int fd, i;
 	long size;
@@ -53,7 +64,7 @@ static void add_excludes_from_file(const
 	lseek(fd, 0, SEEK_SET);
 	if (size == 0) {
 		close(fd);
-		return;
+		return 0;
 	}
 	buf = xmalloc(size);
 	if (read(fd, buf, size) != size)
@@ -63,28 +74,89 @@ static void add_excludes_from_file(const
 	entry = buf;
 	for (i = 0; i < size; i++) {
 		if (buf[i] == '\n') {
-			if (entry != buf + i) {
+			if (entry != buf + i && entry[0] != '#') {
 				buf[i] = 0;
-				add_exclude(entry);
+				add_exclude(entry, base, baselen);
 			}
 			entry = buf + i + 1;
 		}
 	}
-	return;
+	return 0;
 
-err:	perror(fname);
-	exit(1);
+ err:
+	if (0 <= fd)
+		close(fd);
+	return -1;
+}
+
+static void add_excludes_from_file(const char *fname)
+{
+	if (add_excludes_from_file_1(fname, "", 0) < 0)
+		die("cannot use %s as an exclude file", fname);
+}
+
+static int push_exclude_per_directory(const char *base, int baselen)
+{
+	char exclude_file[PATH_MAX];
+	int current_nr = nr_excludes;
+
+	if (exclude_per_dir) {
+		memcpy(exclude_file, base, baselen);
+		strcpy(exclude_file + baselen, exclude_per_dir);
+		add_excludes_from_file_1(exclude_file, base, baselen);
+	}
+	return current_nr;
+}
+
+static void pop_exclude_per_directory(int stk)
+{
+	while (stk < nr_excludes)
+		free(excludes[--nr_excludes]);
 }
 
 static int excluded(const char *pathname)
 {
 	int i;
+
 	if (nr_excludes) {
-		const char *basename = strrchr(pathname, '/');
-		basename = (basename) ? basename+1 : pathname;
-		for (i = 0; i < nr_excludes; i++)
-			if (fnmatch(excludes[i], basename, 0) == 0)
-				return 1;
+		int pathlen = strlen(pathname);
+
+		for (i = 0; i < nr_excludes; i++) {
+			struct exclude *x = excludes[i];
+			const char *exclude = x->pattern;
+			int to_exclude = 1;
+
+			if (*exclude == '!') {
+				to_exclude = 0;
+				exclude++;
+			}
+
+			if (!strchr(exclude, '/')) {
+				/* match basename */
+				const char *basename = strrchr(pathname, '/');
+				basename = (basename) ? basename+1 : pathname;
+				if (fnmatch(exclude, basename, 0) == 0)
+					return to_exclude;
+			}
+			else {
+				/* match with FNM_PATHNAME:
+				 * exclude has base (baselen long) inplicitly
+				 * in front of it.
+				 */
+				int baselen = x->baselen;
+				if (*exclude == '/')
+					exclude++;
+
+				if (pathlen < baselen ||
+				    (baselen && pathname[baselen-1] != '/') ||
+				    strncmp(pathname, x->base, baselen))
+				    continue;
+
+				if (fnmatch(exclude, pathname+baselen,
+					    FNM_PATHNAME) == 0)
+					return to_exclude;
+			}
+		}
 	}
 	return 0;
 }
@@ -121,7 +193,7 @@ static void add_name(const char *pathnam
  * doesn't handle them at all yet. Maybe that will change some
  * day.
  *
- * Also, we currently ignore all names starting with a dot.
+ * Also, we ignore the name ".git" (even if it is not a directory).
  * That likely will not change.
  */
 static void read_directory(const char *path, const char *base, int baselen)
@@ -129,10 +201,13 @@ static void read_directory(const char *p
 	DIR *dir = opendir(path);
 
 	if (dir) {
+		int exclude_stk;
 		struct dirent *de;
 		char fullname[MAXPATHLEN + 1];
 		memcpy(fullname, base, baselen);
 
+		exclude_stk = push_exclude_per_directory(base, baselen);
+
 		while ((de = readdir(dir)) != NULL) {
 			int len;
 
@@ -141,10 +216,10 @@ static void read_directory(const char *p
 			     !strcmp(de->d_name + 1, ".") ||
 			     !strcmp(de->d_name + 1, "git")))
 				continue;
-			if (excluded(de->d_name) != show_ignored)
-				continue;
 			len = strlen(de->d_name);
 			memcpy(fullname + baselen, de->d_name, len+1);
+			if (excluded(fullname) != show_ignored)
+				continue;
 
 			switch (DTYPE(de)) {
 			struct stat st;
@@ -170,6 +245,8 @@ static void read_directory(const char *p
 			add_name(fullname, baselen + len);
 		}
 		closedir(dir);
+
+		pop_exclude_per_directory(exclude_stk);
 	}
 }
 
@@ -287,7 +364,9 @@ static void show_files(void)
 
 static const char *ls_files_usage =
 	"git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* "
-	"[ --ignored [--exclude=<pattern>] [--exclude-from=<file>) ]";
+	"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
+	"[ --exclude-per-directory=<filename> ]";
+;
 
 int main(int argc, char **argv)
 {
@@ -323,13 +402,15 @@ int main(int argc, char **argv)
 			show_stage = 1;
 			show_unmerged = 1;
 		} else if (!strcmp(arg, "-x") && i+1 < argc) {
-			add_exclude(argv[++i]);
+			add_exclude(argv[++i], "", 0);
 		} else if (!strncmp(arg, "--exclude=", 10)) {
-			add_exclude(arg+10);
+			add_exclude(arg+10, "", 0);
 		} else if (!strcmp(arg, "-X") && i+1 < argc) {
 			add_excludes_from_file(argv[++i]);
 		} else if (!strncmp(arg, "--exclude-from=", 15)) {
 			add_excludes_from_file(arg+15);
+		} else if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+			exclude_per_dir = arg + 24;
 		} else
 			usage(ls_files_usage);
 	}
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
new file mode 100755
--- /dev/null
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files --others --exclude
+
+This test runs git-ls-files --others and tests --exclude patterns.
+'
+
+. ./test-lib.sh
+
+rm -fr one three
+for dir in . one one/two three
+do
+  mkdir -p $dir &&
+  for i in 1 2 3 4 5
+  do
+    >$dir/a.$i
+  done
+done
+
+cat >expect <<EOF
+a.2
+a.4
+a.5
+one/a.3
+one/a.4
+one/a.5
+one/two/a.3
+one/two/a.5
+three/a.2
+three/a.3
+three/a.4
+three/a.5
+EOF
+
+echo '.gitignore
+output
+expect
+.gitignore
+' >.git/ignore
+
+echo '*.1
+/*.3' >.gitignore
+echo '*.2
+two/*.4' >one/.gitignore
+
+test_expect_success \
+    'git-ls-files --others --exclude.' \
+    'git-ls-files --others \
+       --exclude-per-directory=.gitignore \
+       --exclude-from=.git/ignore \
+       >output &&
+     diff -u expect output'
Previous: Junio C HamanoNext: Junio C Hamano
Message 8 of 55 in “Tell vim the textwidth is 75.”
  1. 1/1 Tell vim the textwidth is 75.Bryan larsen, Jul 21, 2005
  2. Junio C HamanoJul 22, 2005
  3. Catalin MarinasJul 22, 2005
  4. Sam RavnborgJul 22, 2005
  5. Junio C HamanoJul 22, 2005
  6. Petr BaudisJul 22, 2005
  7. [RFC] extending git-ls-files --exclude.Junio C Hamano, Jul 24, 2005
  8. git-ls-files: --exclude mechanism updates.Junio C Hamano, Jul 24, 2005
  9. Documentation: describe git-ls-files --exclude patterns.Junio C Hamano, Jul 24, 2005
  10. Catalin MarinasJul 25, 2005
  11. Junio C HamanoJul 25, 2005
  12. Linus TorvaldsJul 25, 2005
  13. Junio C HamanoJul 25, 2005
  14. Catalin MarinasJul 25, 2005
  15. Petr BaudisJul 28, 2005
  16. Catalin MarinasJul 25, 2005
  17. Petr BaudisJul 28, 2005
  18. A Large Angry SCMJul 28, 2005
  19. Matthias UrlichsJul 28, 2005
  20. Petr BaudisJul 29, 2005
  21. Matthias UrlichsJul 29, 2005
  22. A Large Angry SCMJul 29, 2005
  23. Junio C HamanoJul 29, 2005
  24. Petr BaudisJul 29, 2005
  25. Junio C HamanoJul 29, 2005
  26. Petr BaudisJul 29, 2005
  27. Wayne ScottAug 1, 2005
  28. ls-files: rework exclude patterns.Junio C Hamano, Jul 29, 2005
  29. Documentation and tests: ls-files exclude pattern.Junio C Hamano, Jul 29, 2005
  30. Catalin MarinasJul 22, 2005
  31. Junio C HamanoJul 22, 2005
  32. Catalin MarinasJul 23, 2005
  33. Petr BaudisJul 23, 2005
  34. Catalin MarinasJul 23, 2005
  35. Bryan LarsenJul 23, 2005
  36. Catalin MarinasJul 23, 2005
  37. Petr BaudisJul 28, 2005
  38. Junio C HamanoJul 29, 2005
  39. Linus TorvaldsJul 29, 2005
  40. Catalin MarinasJul 29, 2005
  41. Petr BaudisJul 29, 2005
  42. Catalin MarinasJul 29, 2005
  43. Junio C HamanoJul 30, 2005
  44. Petr BaudisJul 23, 2005
  45. Junio C HamanoJul 24, 2005
  46. Catalin MarinasJul 22, 2005
  47. Petr BaudisJul 22, 2005
  48. Junio C HamanoJul 22, 2005
  49. Petr BaudisJul 22, 2005
  50. Junio C HamanoJul 22, 2005
  51. Petr BaudisJul 22, 2005
  52. Catalin MarinasJul 23, 2005
  53. Updating diff-raw status letter to 'A' for added files.Junio C Hamano, Jul 26, 2005
  54. 1/2 Use symbolic constants for diff-raw status indicators.Junio C Hamano, Jul 26, 2005
  55. 2/2 diff-raw: Use 'A' instead of 'N' for added files.Junio C Hamano, Jul 26, 2005

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.