git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH v3 00/19] new git check-ignore sub-command
@ 2012-12-27  2:32 Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 01/19] api-directory-listing.txt: update to match code Adam Spiers
                   ` (21 more replies)
  0 siblings, 22 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

This v3 re-roll of my check-ignore series is a reasonably substantial
revamp over v2, and applies on top of Junio's current
nd/attr-match-optim-more branch (82dce998c202).

All feedback and patches from the v2 series has been incorporated, and
several other improvements too, including:

  - composite exclude_lists have been split up into
    exclude_list_groups each containing one exclude_list per source

  - smaller commits for easier review

  - minor memory leaks have been fixed and verified via valgrind

  - t0007-ignores.sh has been renumbered to t0008-ignores.sh to avoid
    a conflict with t0007-git-var.sh

  - improvements to documentation and comments

For reference, the v2 series was announced here:

    http://thread.gmane.org/gmane.comp.version-control.git/204661/focus=206074

All tests pass except for t91*, since there seems to be some
pre-existing breakage in 82dce998c202 relating to git-svn.

Adam Spiers (19):
  api-directory-listing.txt: update to match code
  Improve documentation and comments regarding directory traversal API
  dir.c: rename cryptic 'which' variable to more consistent name
  dir.c: rename path_excluded() to is_path_excluded()
  dir.c: rename excluded_from_list() to is_excluded_from_list()
  dir.c: rename excluded() to is_excluded()
  dir.c: refactor is_excluded_from_list()
  dir.c: refactor is_excluded()
  dir.c: refactor is_path_excluded()
  dir.c: rename free_excludes() to clear_exclude_list()
  dir.c: use a single struct exclude_list per source of excludes
  dir.c: keep track of where patterns came from
  dir.c: provide clear_directory() for reclaiming dir_struct memory
  add.c: refactor treat_gitlinks()
  add.c: remove unused argument from validate_pathspec()
  pathspec.c: move reusable code from builtin/add.c
  pathspec.c: extract new validate_path() for reuse
  setup.c: document get_pathspec()
  Add git-check-ignore sub-command

 .gitignore                                        |   1 +
 Documentation/git-check-ignore.txt                |  89 ++++
 Documentation/gitignore.txt                       |   6 +-
 Documentation/technical/api-directory-listing.txt |  35 +-
 Makefile                                          |   3 +
 attr.c                                            |   2 +-
 builtin.h                                         |   1 +
 builtin/add.c                                     |  84 +--
 builtin/check-ignore.c                            | 170 +++++++
 builtin/clean.c                                   |   3 +-
 builtin/ls-files.c                                |  11 +-
 command-list.txt                                  |   1 +
 contrib/completion/git-completion.bash            |   1 +
 dir.c                                             | 243 +++++++--
 dir.h                                             |  87 +++-
 git.c                                             |   1 +
 pathspec.c                                        | 107 ++++
 pathspec.h                                        |   6 +
 setup.c                                           |  15 +
 t/t0008-ignores.sh                                | 595 ++++++++++++++++++++++
 unpack-trees.c                                    |  14 +-
 21 files changed, 1305 insertions(+), 170 deletions(-)
 create mode 100644 Documentation/git-check-ignore.txt
 create mode 100644 builtin/check-ignore.c
 create mode 100644 pathspec.c
 create mode 100644 pathspec.h
 create mode 100755 t/t0008-ignores.sh

-- 
1.7.11.2.249.g31c7954

^ permalink raw reply	[flat|nested] 55+ messages in thread

* [PATCH v3 01/19] api-directory-listing.txt: update to match code
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API Adam Spiers
                   ` (20 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

7c4c97c0ac turned the flags in struct dir_struct into a single bitfield
variable, but forgot to update this document.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
For some reason this patch was dropped from the v2 series when it was
applied to the 'pu' branch.

 Documentation/technical/api-directory-listing.txt | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
index add6f43..0356d25 100644
--- a/Documentation/technical/api-directory-listing.txt
+++ b/Documentation/technical/api-directory-listing.txt
@@ -17,24 +17,24 @@ options are:
 	The name of the file to be read in each directory for excluded
 	files (typically `.gitignore`).
 
-`collect_ignored`::
+`flags`::
 
-	Include paths that are to be excluded in the result.
+	A bit-field of options:
 
-`show_ignored`::
+`DIR_SHOW_IGNORED`:::
 
 	The traversal is for finding just ignored files, not unignored
 	files.
 
-`show_other_directories`::
+`DIR_SHOW_OTHER_DIRECTORIES`:::
 
 	Include a directory that is not tracked.
 
-`hide_empty_directories`::
+`DIR_HIDE_EMPTY_DIRECTORIES`:::
 
 	Do not include a directory that is not tracked and is empty.
 
-`no_gitlinks`::
+`DIR_NO_GITLINKS`:::
 
 	If set, recurse into a directory that looks like a git
 	directory.  Otherwise it is shown as a directory.
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 01/19] api-directory-listing.txt: update to match code Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2013-01-01 20:52   ` Junio C Hamano
  2012-12-27  2:32 ` [PATCH v3 03/19] dir.c: rename cryptic 'which' variable to more consistent name Adam Spiers
                   ` (19 subsequent siblings)
  21 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

>From the perspective of a newcomer to the codebase, the directory
traversal API has a few potentially confusing properties.  These
comments clarify a few key aspects and will hopefully make it easier
to understand for other newcomers in the future.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
For some reason this patch was dropped from the v2 series when it was
applied to the 'pu' branch.

 Documentation/technical/api-directory-listing.txt |  9 +++++---
 dir.c                                             |  8 ++++++-
 dir.h                                             | 26 +++++++++++++++++++++--
 3 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
index 0356d25..944fc39 100644
--- a/Documentation/technical/api-directory-listing.txt
+++ b/Documentation/technical/api-directory-listing.txt
@@ -9,8 +9,11 @@ Data structure
 --------------
 
 `struct dir_struct` structure is used to pass directory traversal
-options to the library and to record the paths discovered.  The notable
-options are:
+options to the library and to record the paths discovered.  A single
+`struct dir_struct` is used regardless of whether or not the traversal
+recursively descends into subdirectories.
+
+The notable options are:
 
 `exclude_per_dir`::
 
@@ -39,7 +42,7 @@ options are:
 	If set, recurse into a directory that looks like a git
 	directory.  Otherwise it is shown as a directory.
 
-The result of the enumeration is left in these fields::
+The result of the enumeration is left in these fields:
 
 `entries[]`::
 
diff --git a/dir.c b/dir.c
index ee8e711..89e27a6 100644
--- a/dir.c
+++ b/dir.c
@@ -2,6 +2,8 @@
  * This handles recursive filename detection with exclude
  * files, index knowledge etc..
  *
+ * See Documentation/technical/api-directory-listing.txt
+ *
  * Copyright (C) Linus Torvalds, 2005-2006
  *		 Junio Hamano, 2005-2006
  */
@@ -476,6 +478,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 		die("cannot use %s as an exclude file", fname);
 }
 
+/*
+ * Loads the per-directory exclude list for the substring of base
+ * which has a char length of baselen.
+ */
 static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 {
 	struct exclude_list *el;
@@ -486,7 +492,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 	    (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
 		return; /* too long a path -- ignore */
 
-	/* Pop the ones that are not the prefix of the path being checked. */
+	/* Pop the directories that are not the prefix of the path being checked. */
 	el = &dir->exclude_list[EXC_DIRS];
 	while ((stk = dir->exclude_stack) != NULL) {
 		if (stk->baselen <= baselen &&
diff --git a/dir.h b/dir.h
index f5c89e3..e0869bc 100644
--- a/dir.h
+++ b/dir.h
@@ -1,6 +1,8 @@
 #ifndef DIR_H
 #define DIR_H
 
+/* See Documentation/technical/api-directory-listing.txt */
+
 #include "strbuf.h"
 
 struct dir_entry {
@@ -13,6 +15,12 @@ struct dir_entry {
 #define EXC_FLAG_MUSTBEDIR 8
 #define EXC_FLAG_NEGATIVE 16
 
+/*
+ * Each .gitignore file will be parsed into patterns which are then
+ * appended to the relevant exclude_list (either EXC_DIRS or
+ * EXC_FILE).  exclude_lists are also used to represent the list of
+ * --exclude values passed via CLI args (EXC_CMDL).
+ */
 struct exclude_list {
 	int nr;
 	int alloc;
@@ -26,9 +34,15 @@ struct exclude_list {
 	} **excludes;
 };
 
+/*
+ * The contents of the per-directory exclude files are lazily read on
+ * demand and then cached in memory, one per exclude_stack struct, in
+ * order to avoid opening and parsing each one every time that
+ * directory is traversed.
+ */
 struct exclude_stack {
-	struct exclude_stack *prev;
-	char *filebuf;
+	struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
+	char *filebuf; /* remember pointer to per-directory exclude file contents so we can free() */
 	int baselen;
 	int exclude_ix;
 };
@@ -59,6 +73,14 @@ struct dir_struct {
 #define EXC_DIRS 1
 #define EXC_FILE 2
 
+	/*
+	 * Temporary variables which are used during loading of the
+	 * per-directory exclude lists.
+	 *
+	 * exclude_stack points to the top of the exclude_stack, and
+	 * basebuf contains the full path to the current
+	 * (sub)directory in the traversal.
+	 */
 	struct exclude_stack *exclude_stack;
 	char basebuf[PATH_MAX];
 };
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 03/19] dir.c: rename cryptic 'which' variable to more consistent name
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 01/19] api-directory-listing.txt: update to match code Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 04/19] dir.c: rename path_excluded() to is_path_excluded() Adam Spiers
                   ` (18 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

'el' is only *slightly* less cryptic, but is already used as the
variable name for a struct exclude_list pointer in numerous other
places, so this reduces the number of cryptic variable names in use by
one :-)

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 dir.c | 10 +++++-----
 dir.h |  4 ++--
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/dir.c b/dir.c
index 89e27a6..f31aa59 100644
--- a/dir.c
+++ b/dir.c
@@ -349,7 +349,7 @@ void parse_exclude_pattern(const char **pattern,
 }
 
 void add_exclude(const char *string, const char *base,
-		 int baselen, struct exclude_list *which)
+		 int baselen, struct exclude_list *el)
 {
 	struct exclude *x;
 	int patternlen;
@@ -373,8 +373,8 @@ void add_exclude(const char *string, const char *base,
 	x->base = base;
 	x->baselen = baselen;
 	x->flags = flags;
-	ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
-	which->excludes[which->nr++] = x;
+	ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
+	el->excludes[el->nr++] = x;
 }
 
 static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
@@ -416,7 +416,7 @@ int add_excludes_from_file_to_list(const char *fname,
 				   const char *base,
 				   int baselen,
 				   char **buf_p,
-				   struct exclude_list *which,
+				   struct exclude_list *el,
 				   int check_index)
 {
 	struct stat st;
@@ -463,7 +463,7 @@ int add_excludes_from_file_to_list(const char *fname,
 		if (buf[i] == '\n') {
 			if (entry != buf + i && entry[0] != '#') {
 				buf[i - (i && buf[i-1] == '\r')] = 0;
-				add_exclude(entry, base, baselen, which);
+				add_exclude(entry, base, baselen, el);
 			}
 			entry = buf + i + 1;
 		}
diff --git a/dir.h b/dir.h
index e0869bc..680c1eb 100644
--- a/dir.h
+++ b/dir.h
@@ -127,11 +127,11 @@ extern int path_excluded(struct path_exclude_check *, const char *, int namelen,
 
 
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
-					  char **buf_p, struct exclude_list *which, int check_index);
+					  char **buf_p, struct exclude_list *el, int check_index);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
-			int baselen, struct exclude_list *which);
+			int baselen, struct exclude_list *el);
 extern void free_excludes(struct exclude_list *el);
 extern int file_exists(const char *);
 
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 04/19] dir.c: rename path_excluded() to is_path_excluded()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (2 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 03/19] dir.c: rename cryptic 'which' variable to more consistent name Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 05/19] dir.c: rename excluded_from_list() to is_excluded_from_list() Adam Spiers
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

Start adopting clearer names for exclude functions.  This 'is_*'
naming pattern for functions returning booleans was agreed here:

http://thread.gmane.org/gmane.comp.version-control.git/204661/focus=204924

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 builtin/add.c      | 2 +-
 builtin/ls-files.c | 2 +-
 dir.c              | 4 ++--
 dir.h              | 2 +-
 unpack-trees.c     | 2 +-
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/builtin/add.c b/builtin/add.c
index 89dce56..c689f37 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -453,7 +453,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 			    && !file_exists(pathspec[i])) {
 				if (ignore_missing) {
 					int dtype = DT_UNKNOWN;
-					if (path_excluded(&check, pathspec[i], -1, &dtype))
+					if (is_path_excluded(&check, pathspec[i], -1, &dtype))
 						dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
 				} else
 					die(_("pathspec '%s' did not match any files"),
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 31b3f2d..ef7f99a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -203,7 +203,7 @@ static void show_ru_info(void)
 static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce)
 {
 	int dtype = ce_to_dtype(ce);
-	return path_excluded(check, ce->name, ce_namelen(ce), &dtype);
+	return is_path_excluded(check, ce->name, ce_namelen(ce), &dtype);
 }
 
 static void show_files(struct dir_struct *dir)
diff --git a/dir.c b/dir.c
index f31aa59..f1c0abd 100644
--- a/dir.c
+++ b/dir.c
@@ -685,8 +685,8 @@ void path_exclude_check_clear(struct path_exclude_check *check)
  * A path to a directory known to be excluded is left in check->path to
  * optimize for repeated checks for files in the same excluded directory.
  */
-int path_excluded(struct path_exclude_check *check,
-		  const char *name, int namelen, int *dtype)
+int is_path_excluded(struct path_exclude_check *check,
+		     const char *name, int namelen, int *dtype)
 {
 	int i;
 	struct strbuf *path = &check->path;
diff --git a/dir.h b/dir.h
index 680c1eb..c59bad8 100644
--- a/dir.h
+++ b/dir.h
@@ -123,7 +123,7 @@ struct path_exclude_check {
 };
 extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
 extern void path_exclude_check_clear(struct path_exclude_check *);
-extern int path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
+extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
 
 
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
diff --git a/unpack-trees.c b/unpack-trees.c
index 33a5819..3ac6370 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1372,7 +1372,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
 		return 0;
 
 	if (o->dir &&
-	    path_excluded(o->path_exclude_check, name, -1, &dtype))
+	    is_path_excluded(o->path_exclude_check, name, -1, &dtype))
 		/*
 		 * ce->name is explicitly excluded, so it is Ok to
 		 * overwrite it.
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 05/19] dir.c: rename excluded_from_list() to is_excluded_from_list()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (3 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 04/19] dir.c: rename path_excluded() to is_path_excluded() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 06/19] dir.c: rename excluded() to is_excluded() Adam Spiers
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

Continue adopting clearer names for exclude functions.  This 'is_*'
naming pattern for functions returning booleans was discussed here:

http://thread.gmane.org/gmane.comp.version-control.git/204661/focus=204924

Also adjust their callers as necessary.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 dir.c          | 11 ++++++-----
 dir.h          |  4 ++--
 unpack-trees.c |  8 +++++---
 3 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/dir.c b/dir.c
index f1c0abd..0800491 100644
--- a/dir.c
+++ b/dir.c
@@ -605,9 +605,9 @@ int match_pathname(const char *pathname, int pathlen,
 /* Scan the list and let the last match determine the fate.
  * 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,
-		       struct exclude_list *el)
+int is_excluded_from_list(const char *pathname,
+			  int pathlen, const char *basename, int *dtype,
+			  struct exclude_list *el)
 {
 	int i;
 
@@ -654,8 +654,9 @@ static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 
 	prep_exclude(dir, pathname, basename-pathname);
 	for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-		switch (excluded_from_list(pathname, pathlen, basename,
-					   dtype_p, &dir->exclude_list[st])) {
+		switch (is_excluded_from_list(pathname, pathlen,
+					      basename, dtype_p,
+					      &dir->exclude_list[st])) {
 		case 0:
 			return 0;
 		case 1:
diff --git a/dir.h b/dir.h
index c59bad8..554f87a 100644
--- a/dir.h
+++ b/dir.h
@@ -98,8 +98,8 @@ extern int within_depth(const char *name, int namelen, int depth, int max_depth)
 extern int fill_directory(struct dir_struct *dir, const char **pathspec);
 extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
 
-extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
-			      int *dtype, struct exclude_list *el);
+extern int is_excluded_from_list(const char *pathname, int pathlen, const char *basename,
+				 int *dtype, struct exclude_list *el);
 struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
 
 /*
diff --git a/unpack-trees.c b/unpack-trees.c
index 3ac6370..c0da094 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -836,7 +836,8 @@ static int clear_ce_flags_dir(struct cache_entry **cache, int nr,
 {
 	struct cache_entry **cache_end;
 	int dtype = DT_DIR;
-	int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el);
+	int ret = is_excluded_from_list(prefix, prefix_len,
+					basename, &dtype, el);
 
 	prefix[prefix_len++] = '/';
 
@@ -855,7 +856,7 @@ static int clear_ce_flags_dir(struct cache_entry **cache, int nr,
 	 * with ret (iow, we know in advance the incl/excl
 	 * decision for the entire directory), clear flag here without
 	 * calling clear_ce_flags_1(). That function will call
-	 * the expensive excluded_from_list() on every entry.
+	 * the expensive is_excluded_from_list() on every entry.
 	 */
 	return clear_ce_flags_1(cache, cache_end - cache,
 				prefix, prefix_len,
@@ -938,7 +939,8 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr,
 
 		/* Non-directory */
 		dtype = ce_to_dtype(ce);
-		ret = excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el);
+		ret = is_excluded_from_list(ce->name, ce_namelen(ce),
+					    name, &dtype, el);
 		if (ret < 0)
 			ret = defval;
 		if (ret > 0)
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 06/19] dir.c: rename excluded() to is_excluded()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (4 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 05/19] dir.c: rename excluded_from_list() to is_excluded_from_list() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 07/19] dir.c: refactor is_excluded_from_list() Adam Spiers
                   ` (15 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

Continue adopting clearer names for exclude functions.  This is_*
naming pattern for functions returning booleans was discussed here:

http://thread.gmane.org/gmane.comp.version-control.git/204661/focus=204924

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 attr.c |  2 +-
 dir.c  | 10 +++++-----
 dir.h  |  4 ++--
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/attr.c b/attr.c
index 2fc6353..5362563 100644
--- a/attr.c
+++ b/attr.c
@@ -284,7 +284,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
  * (reading the file from top to bottom), .gitattribute of the root
  * directory (again, reading the file from top to bottom) down to the
  * current directory, and then scan the list backwards to find the first match.
- * This is exactly the same as what excluded() does in dir.c to deal with
+ * This is exactly the same as what is_excluded() does in dir.c to deal with
  * .gitignore
  */
 
diff --git a/dir.c b/dir.c
index 0800491..8c99dc4 100644
--- a/dir.c
+++ b/dir.c
@@ -645,7 +645,7 @@ int is_excluded_from_list(const char *pathname,
 	return -1; /* undecided */
 }
 
-static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 {
 	int pathlen = strlen(pathname);
 	int st;
@@ -695,7 +695,7 @@ int is_path_excluded(struct path_exclude_check *check,
 	/*
 	 * we allow the caller to pass namelen as an optimization; it
 	 * must match the length of the name, as we eventually call
-	 * excluded() on the whole name string.
+	 * is_excluded() on the whole name string.
 	 */
 	if (namelen < 0)
 		namelen = strlen(name);
@@ -712,7 +712,7 @@ int is_path_excluded(struct path_exclude_check *check,
 
 		if (ch == '/') {
 			int dt = DT_DIR;
-			if (excluded(check->dir, path->buf, &dt))
+			if (is_excluded(check->dir, path->buf, &dt))
 				return 1;
 		}
 		strbuf_addch(path, ch);
@@ -721,7 +721,7 @@ int is_path_excluded(struct path_exclude_check *check,
 	/* An entry in the index; cannot be a directory with subentries */
 	strbuf_setlen(path, 0);
 
-	return excluded(check->dir, name, dtype);
+	return is_excluded(check->dir, name, dtype);
 }
 
 static struct dir_entry *dir_entry_new(const char *pathname, int len)
@@ -1021,7 +1021,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
 					  const struct path_simplify *simplify,
 					  int dtype, struct dirent *de)
 {
-	int exclude = excluded(dir, path->buf, &dtype);
+	int exclude = is_excluded(dir, path->buf, &dtype);
 	if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
 	    && exclude_matches_pathspec(path->buf, path->len, simplify))
 		dir_add_ignored(dir, path->buf, path->len);
diff --git a/dir.h b/dir.h
index 554f87a..d68a997 100644
--- a/dir.h
+++ b/dir.h
@@ -113,8 +113,8 @@ extern int match_pathname(const char *, int,
 			  const char *, int, int, int);
 
 /*
- * The excluded() API is meant for callers that check each level of leading
- * directory hierarchies with excluded() to avoid recursing into excluded
+ * The is_excluded() API is meant for callers that check each level of leading
+ * directory hierarchies with is_excluded() to avoid recursing into excluded
  * directories.  Callers that do not do so should use this API instead.
  */
 struct path_exclude_check {
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 07/19] dir.c: refactor is_excluded_from_list()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (5 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 06/19] dir.c: rename excluded() to is_excluded() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 08/19] dir.c: refactor is_excluded() Adam Spiers
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

The excluded function uses a new helper function called
last_exclude_matching_from_list() to perform the inner loop over all of
the exclude patterns.  The helper just tells us whether the path is
included, excluded, or undecided.

However, it may be useful to know _which_ pattern was triggered.  So
let's pass out the entire exclude match, which contains the status
information we were already passing out.

Further patches can make use of this.

This is a modified forward port of a patch from 2009 by Jeff King:
http://article.gmane.org/gmane.comp.version-control.git/108815

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 dir.c | 37 ++++++++++++++++++++++++++++---------
 1 file changed, 28 insertions(+), 9 deletions(-)

diff --git a/dir.c b/dir.c
index 8c99dc4..d1a0413 100644
--- a/dir.c
+++ b/dir.c
@@ -602,22 +602,26 @@ int match_pathname(const char *pathname, int pathlen,
 	return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;
 }
 
-/* Scan the list and let the last match determine the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
+/*
+ * Scan the given exclude list in reverse to see whether pathname
+ * should be ignored.  The first match (i.e. the last on the list), if
+ * any, determines the fate.  Returns the exclude_list element which
+ * matched, or NULL for undecided.
  */
-int is_excluded_from_list(const char *pathname,
-			  int pathlen, const char *basename, int *dtype,
-			  struct exclude_list *el)
+static struct exclude *last_exclude_matching_from_list(const char *pathname,
+						       int pathlen,
+						       const char *basename,
+						       int *dtype,
+						       struct exclude_list *el)
 {
 	int i;
 
 	if (!el->nr)
-		return -1;	/* undefined */
+		return NULL;	/* undefined */
 
 	for (i = el->nr - 1; 0 <= i; i--) {
 		struct exclude *x = el->excludes[i];
 		const char *exclude = x->pattern;
-		int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 		int prefix = x->nowildcardlen;
 
 		if (x->flags & EXC_FLAG_MUSTBEDIR) {
@@ -632,7 +636,7 @@ int is_excluded_from_list(const char *pathname,
 					   pathlen - (basename - pathname),
 					   exclude, prefix, x->patternlen,
 					   x->flags))
-				return to_exclude;
+				return x;
 			continue;
 		}
 
@@ -640,8 +644,23 @@ int is_excluded_from_list(const char *pathname,
 		if (match_pathname(pathname, pathlen,
 				   x->base, x->baselen ? x->baselen - 1 : 0,
 				   exclude, prefix, x->patternlen, x->flags))
-			return to_exclude;
+			return x;
 	}
+	return NULL; /* undecided */
+}
+
+/*
+ * Scan the list and let the last match determine the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+int is_excluded_from_list(const char *pathname,
+			  int pathlen, const char *basename, int *dtype,
+			  struct exclude_list *el)
+{
+	struct exclude *exclude;
+	exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
+	if (exclude)
+		return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 	return -1; /* undecided */
 }
 
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 08/19] dir.c: refactor is_excluded()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (6 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 07/19] dir.c: refactor is_excluded_from_list() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 09/19] dir.c: refactor is_path_excluded() Adam Spiers
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

In a similar way to the previous commit, this extracts a new helper
function last_exclude_matching() which returns the last exclude_list
element which matched, or NULL if no match was found.  is_excluded()
becomes a wrapper around this, and just returns 0 or 1 depending on
whether any matching exclude_list element was found.

This allows callers to find out _why_ a given path was excluded,
rather than just whether it was or not, paving the way for a new git
sub-command which allows users to test their exclude lists from the
command line.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 dir.c | 38 +++++++++++++++++++++++++++++---------
 1 file changed, 29 insertions(+), 9 deletions(-)

diff --git a/dir.c b/dir.c
index d1a0413..b9d4234 100644
--- a/dir.c
+++ b/dir.c
@@ -664,24 +664,44 @@ int is_excluded_from_list(const char *pathname,
 	return -1; /* undecided */
 }
 
-static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns the exclude_list element which matched, or NULL for
+ * undecided.
+ */
+static struct exclude *last_exclude_matching(struct dir_struct *dir,
+					     const char *pathname,
+					     int *dtype_p)
 {
 	int pathlen = strlen(pathname);
 	int st;
+	struct exclude *exclude;
 	const char *basename = strrchr(pathname, '/');
 	basename = (basename) ? basename+1 : pathname;
 
 	prep_exclude(dir, pathname, basename-pathname);
 	for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-		switch (is_excluded_from_list(pathname, pathlen,
-					      basename, dtype_p,
-					      &dir->exclude_list[st])) {
-		case 0:
-			return 0;
-		case 1:
-			return 1;
-		}
+		exclude = last_exclude_matching_from_list(
+			pathname, pathlen, basename, dtype_p,
+			&dir->exclude_list[st]);
+		if (exclude)
+			return exclude;
 	}
+	return NULL;
+}
+
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns 1 if true, otherwise 0.
+ */
+static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+{
+	struct exclude *exclude =
+		last_exclude_matching(dir, pathname, dtype_p);
+	if (exclude)
+		return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 	return 0;
 }
 
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 09/19] dir.c: refactor is_path_excluded()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (7 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 08/19] dir.c: refactor is_excluded() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 10/19] dir.c: rename free_excludes() to clear_exclude_list() Adam Spiers
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

In a similar way to the previous commit, this extracts a new helper
function last_exclude_matching_path() which return the last
exclude_list element which matched, or NULL if no match was found.
is_path_excluded() becomes a wrapper around this, and just returns 0
or 1 depending on whether any matching exclude_list element was found.

This allows callers to find out _why_ a given path was excluded,
rather than just whether it was or not, paving the way for a new git
sub-command which allows users to test their exclude lists from the
command line.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 dir.c | 47 ++++++++++++++++++++++++++++++++++++++---------
 dir.h |  3 +++
 2 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/dir.c b/dir.c
index b9d4234..16e10b0 100644
--- a/dir.c
+++ b/dir.c
@@ -709,6 +709,7 @@ void path_exclude_check_init(struct path_exclude_check *check,
 			     struct dir_struct *dir)
 {
 	check->dir = dir;
+	check->exclude = NULL;
 	strbuf_init(&check->path, 256);
 }
 
@@ -718,18 +719,21 @@ void path_exclude_check_clear(struct path_exclude_check *check)
 }
 
 /*
- * Is this name excluded?  This is for a caller like show_files() that
- * do not honor directory hierarchy and iterate through paths that are
- * possibly in an ignored directory.
+ * For each subdirectory in name, starting with the top-most, checks
+ * to see if that subdirectory is excluded, and if so, returns the
+ * corresponding exclude structure.  Otherwise, checks whether name
+ * itself (which is presumably a file) is excluded.
  *
  * A path to a directory known to be excluded is left in check->path to
  * optimize for repeated checks for files in the same excluded directory.
  */
-int is_path_excluded(struct path_exclude_check *check,
-		     const char *name, int namelen, int *dtype)
+struct exclude *last_exclude_matching_path(struct path_exclude_check *check,
+					   const char *name, int namelen,
+					   int *dtype)
 {
 	int i;
 	struct strbuf *path = &check->path;
+	struct exclude *exclude;
 
 	/*
 	 * we allow the caller to pass namelen as an optimization; it
@@ -739,11 +743,17 @@ int is_path_excluded(struct path_exclude_check *check,
 	if (namelen < 0)
 		namelen = strlen(name);
 
+	/*
+	 * If path is non-empty, and name is equal to path or a
+	 * subdirectory of path, name should be excluded, because
+	 * it's inside a directory which is already known to be
+	 * excluded and was previously left in check->path.
+	 */
 	if (path->len &&
 	    path->len <= namelen &&
 	    !memcmp(name, path->buf, path->len) &&
 	    (!name[path->len] || name[path->len] == '/'))
-		return 1;
+		return check->exclude;
 
 	strbuf_setlen(path, 0);
 	for (i = 0; name[i]; i++) {
@@ -751,8 +761,12 @@ int is_path_excluded(struct path_exclude_check *check,
 
 		if (ch == '/') {
 			int dt = DT_DIR;
-			if (is_excluded(check->dir, path->buf, &dt))
-				return 1;
+			exclude = last_exclude_matching(check->dir,
+							path->buf, &dt);
+			if (exclude) {
+				check->exclude = exclude;
+				return exclude;
+			}
 		}
 		strbuf_addch(path, ch);
 	}
@@ -760,7 +774,22 @@ int is_path_excluded(struct path_exclude_check *check,
 	/* An entry in the index; cannot be a directory with subentries */
 	strbuf_setlen(path, 0);
 
-	return is_excluded(check->dir, name, dtype);
+	return last_exclude_matching(check->dir, name, dtype);
+}
+
+/*
+ * Is this name excluded?  This is for a caller like show_files() that
+ * do not honor directory hierarchy and iterate through paths that are
+ * possibly in an ignored directory.
+ */
+int is_path_excluded(struct path_exclude_check *check,
+		  const char *name, int namelen, int *dtype)
+{
+	struct exclude *exclude =
+		last_exclude_matching_path(check, name, namelen, dtype);
+	if (exclude)
+		return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
+	return 0;
 }
 
 static struct dir_entry *dir_entry_new(const char *pathname, int len)
diff --git a/dir.h b/dir.h
index d68a997..dcb1ad3 100644
--- a/dir.h
+++ b/dir.h
@@ -119,10 +119,13 @@ extern int match_pathname(const char *, int,
  */
 struct path_exclude_check {
 	struct dir_struct *dir;
+	struct exclude *exclude;
 	struct strbuf path;
 };
 extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
 extern void path_exclude_check_clear(struct path_exclude_check *);
+extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *,
+						  int namelen, int *dtype);
 extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
 
 
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 10/19] dir.c: rename free_excludes() to clear_exclude_list()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (8 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 09/19] dir.c: refactor is_path_excluded() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

It is clearer to use a 'clear_' prefix for functions which empty
and deallocate the contents of a data structure without freeing
the structure itself, and a 'free_' prefix for functions which
also free the structure itself.

http://article.gmane.org/gmane.comp.version-control.git/206128

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 dir.c          | 6 +++++-
 dir.h          | 2 +-
 unpack-trees.c | 2 +-
 3 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/dir.c b/dir.c
index 16e10b0..41f141c 100644
--- a/dir.c
+++ b/dir.c
@@ -400,7 +400,11 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
 	return data;
 }
 
-void free_excludes(struct exclude_list *el)
+/*
+ * Frees memory within el which was allocated for exclude patterns and
+ * the file buffer.  Does not free el itself.
+ */
+void clear_exclude_list(struct exclude_list *el)
 {
 	int i;
 
diff --git a/dir.h b/dir.h
index dcb1ad3..5664ba8 100644
--- a/dir.h
+++ b/dir.h
@@ -135,7 +135,7 @@ extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
 			int baselen, struct exclude_list *el);
-extern void free_excludes(struct exclude_list *el);
+extern void clear_exclude_list(struct exclude_list *el);
 extern int file_exists(const char *);
 
 extern int is_inside_dir(const char *dir);
diff --git a/unpack-trees.c b/unpack-trees.c
index c0da094..ad621d9 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1153,7 +1153,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 		*o->dst_index = o->result;
 
 done:
-	free_excludes(&el);
+	clear_exclude_list(&el);
 	if (o->path_exclude_check) {
 		path_exclude_check_clear(o->path_exclude_check);
 		free(o->path_exclude_check);
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (9 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 10/19] dir.c: rename free_excludes() to clear_exclude_list() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2013-01-04 21:03   ` Junio C Hamano
  2012-12-27  2:32 ` [PATCH v3 12/19] dir.c: keep track of where patterns came from Adam Spiers
                   ` (10 subsequent siblings)
  21 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

Previously each exclude_list could potentially contain patterns
from multiple sources.  For example dir->exclude_list[EXC_FILE]
would typically contain patterns from .git/info/exclude and
core.excludesfile, and dir->exclude_list[EXC_DIRS] could contain
patterns from multiple per-directory .gitignore files during
directory traversal (i.e. when dir->exclude_stack was more than
one item deep).

We split these composite exclude_lists up into three groups of
exclude_lists (EXC_CMDL / EXC_DIRS / EXC_FILE as before), so that each
exclude_list now contains patterns from a single source.  This will
allow us to cleanly track the origin of each pattern simply by adding
a src field to struct exclude_list, rather than to struct exclude,
which would make memory management of the source string tricky in the
EXC_DIRS case where its contents are dynamically generated.

Similarly, by moving the filebuf member from struct exclude_stack to
struct exclude_list, it allows us to track and subsequently free
memory buffers allocated during the parsing of all exclude files,
rather than only tracking buffers allocated for files in the EXC_DIRS
group.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 Documentation/technical/api-directory-listing.txt | 12 +++--
 builtin/clean.c                                   |  3 +-
 builtin/ls-files.c                                |  8 +--
 dir.c                                             | 60 ++++++++++++++++-------
 dir.h                                             | 36 ++++++++++----
 unpack-trees.c                                    |  2 +-
 6 files changed, 83 insertions(+), 38 deletions(-)

diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
index 944fc39..fa9c8ae 100644
--- a/Documentation/technical/api-directory-listing.txt
+++ b/Documentation/technical/api-directory-listing.txt
@@ -67,11 +67,13 @@ marked. If you to exclude files, make sure you have loaded index first.
 * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
   sizeof(dir))`.
 
-* Call `add_exclude()` to add single exclude pattern,
-  `add_excludes_from_file()` to add patterns from a file
-  (e.g. `.git/info/exclude`), and/or set `dir.exclude_per_dir`.  A
-  short-hand function `setup_standard_excludes()` can be used to set up
-  the standard set of exclude settings.
+* To add single exclude pattern, call `add_exclude_list()` and then
+  `add_exclude()`.
+
+* To add patterns from a file (e.g. `.git/info/exclude`), call
+  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  A
+  short-hand function `setup_standard_excludes()` can be used to set
+  up the standard set of exclude settings.
 
 * Set options described in the Data Structure section above.
 
diff --git a/builtin/clean.c b/builtin/clean.c
index 0c7b3d0..bd18b88 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 	if (!ignored)
 		setup_standard_excludes(&dir);
 
+	add_exclude_list(&dir, EXC_CMDL);
 	for (i = 0; i < exclude_list.nr; i++)
 		add_exclude(exclude_list.items[i].string, "", 0,
-			    &dir.exclude_list[EXC_CMDL]);
+			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);
 
 	pathspec = get_pathspec(prefix, argv);
 
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index ef7f99a..c448e06 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -420,10 +420,10 @@ static int option_parse_z(const struct option *opt,
 static int option_parse_exclude(const struct option *opt,
 				const char *arg, int unset)
 {
-	struct exclude_list *list = opt->value;
+	struct exclude_list_group *group = opt->value;
 
 	exc_given = 1;
-	add_exclude(arg, "", 0, list);
+	add_exclude(arg, "", 0, &group->ary[0]);
 
 	return 0;
 }
@@ -488,7 +488,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 			"show unmerged files in the output"),
 		OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
 			    "show resolve-undo information"),
-		{ OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
+		{ OPTION_CALLBACK, 'x', "exclude",
+			&dir.exclude_list_groups[EXC_CMDL], "pattern",
 			"skip files matching pattern",
 			0, option_parse_exclude },
 		{ OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
@@ -523,6 +524,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 	if (read_cache() < 0)
 		die("index file corrupt");
 
+	add_exclude_list(&dir, EXC_CMDL);
 	argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
 			ls_files_usage, 0);
 	if (show_tag || show_valid_bit) {
diff --git a/dir.c b/dir.c
index 41f141c..3a9d89b 100644
--- a/dir.c
+++ b/dir.c
@@ -411,15 +411,16 @@ void clear_exclude_list(struct exclude_list *el)
 	for (i = 0; i < el->nr; i++)
 		free(el->excludes[i]);
 	free(el->excludes);
+	free(el->filebuf);
 
 	el->nr = 0;
 	el->excludes = NULL;
+	el->filebuf = NULL;
 }
 
 int add_excludes_from_file_to_list(const char *fname,
 				   const char *base,
 				   int baselen,
-				   char **buf_p,
 				   struct exclude_list *el,
 				   int check_index)
 {
@@ -460,8 +461,7 @@ int add_excludes_from_file_to_list(const char *fname,
 		close(fd);
 	}
 
-	if (buf_p)
-		*buf_p = buf;
+	el->filebuf = buf;
 	entry = buf;
 	for (i = 0; i < size; i++) {
 		if (buf[i] == '\n') {
@@ -475,10 +475,26 @@ int add_excludes_from_file_to_list(const char *fname,
 	return 0;
 }
 
+struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type)
+{
+	struct exclude_list *el;
+	struct exclude_list_group *group;
+
+	group = &dir->exclude_list_groups[group_type];
+	ALLOC_GROW(group->ary, group->nr + 1, group->alloc);
+	el = &group->ary[group->nr++];
+	memset(el, 0, sizeof(*el));
+	return el;
+}
+
+/*
+ * Used to set up core.excludesfile and .git/info/exclude lists.
+ */
 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 {
-	if (add_excludes_from_file_to_list(fname, "", 0, NULL,
-					   &dir->exclude_list[EXC_FILE], 0) < 0)
+	struct exclude_list *el;
+	el = add_exclude_list(dir, EXC_FILE);
+	if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
 		die("cannot use %s as an exclude file", fname);
 }
 
@@ -488,6 +504,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
  */
 static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 {
+	struct exclude_list_group *group;
 	struct exclude_list *el;
 	struct exclude_stack *stk = NULL;
 	int current;
@@ -496,17 +513,18 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 	    (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
 		return; /* too long a path -- ignore */
 
+	group = &dir->exclude_list_groups[EXC_DIRS];
+
 	/* Pop the directories that are not the prefix of the path being checked. */
-	el = &dir->exclude_list[EXC_DIRS];
 	while ((stk = dir->exclude_stack) != NULL) {
 		if (stk->baselen <= baselen &&
 		    !strncmp(dir->basebuf, base, stk->baselen))
 			break;
+		el = &group->ary[dir->exclude_stack->exclude_ix];
 		dir->exclude_stack = stk->prev;
-		while (stk->exclude_ix < el->nr)
-			free(el->excludes[--el->nr]);
-		free(stk->filebuf);
+		clear_exclude_list(el);
 		free(stk);
+		group->nr--;
 	}
 
 	/* Read from the parent directories and push them down. */
@@ -527,13 +545,14 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 		}
 		stk->prev = dir->exclude_stack;
 		stk->baselen = cp - base;
-		stk->exclude_ix = el->nr;
 		memcpy(dir->basebuf + current, base + current,
 		       stk->baselen - current);
 		strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
+		el = add_exclude_list(dir, EXC_DIRS);
+		stk->exclude_ix = group->nr - 1;
 		add_excludes_from_file_to_list(dir->basebuf,
 					       dir->basebuf, stk->baselen,
-					       &stk->filebuf, el, 1);
+					       el, 1);
 		dir->exclude_stack = stk;
 		current = stk->baselen;
 	}
@@ -679,18 +698,23 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,
 					     int *dtype_p)
 {
 	int pathlen = strlen(pathname);
-	int st;
+	int i, j;
+	struct exclude_list_group *group;
 	struct exclude *exclude;
 	const char *basename = strrchr(pathname, '/');
 	basename = (basename) ? basename+1 : pathname;
 
 	prep_exclude(dir, pathname, basename-pathname);
-	for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-		exclude = last_exclude_matching_from_list(
-			pathname, pathlen, basename, dtype_p,
-			&dir->exclude_list[st]);
-		if (exclude)
-			return exclude;
+
+	for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+		group = &dir->exclude_list_groups[i];
+		for (j = group->nr - 1; j >= 0; j--) {
+			exclude = last_exclude_matching_from_list(
+				pathname, pathlen, basename, dtype_p,
+				&group->ary[j]);
+			if (exclude)
+				return exclude;
+		}
 	}
 	return NULL;
 }
diff --git a/dir.h b/dir.h
index 5664ba8..9408fbe 100644
--- a/dir.h
+++ b/dir.h
@@ -16,14 +16,18 @@ struct dir_entry {
 #define EXC_FLAG_NEGATIVE 16
 
 /*
- * Each .gitignore file will be parsed into patterns which are then
- * appended to the relevant exclude_list (either EXC_DIRS or
- * EXC_FILE).  exclude_lists are also used to represent the list of
- * --exclude values passed via CLI args (EXC_CMDL).
+ * Each excludes file will be parsed into a fresh exclude_list which
+ * is appended to the relevant exclude_list_group (either EXC_DIRS or
+ * EXC_FILE).  An exclude_list within the EXC_CMDL exclude_list_group
+ * can also be used to represent the list of --exclude values passed
+ * via CLI args.
  */
 struct exclude_list {
 	int nr;
 	int alloc;
+	/* remember pointer to exclude file contents so we can free() */
+	char *filebuf;
+
 	struct exclude {
 		const char *pattern;
 		int patternlen;
@@ -42,9 +46,13 @@ struct exclude_list {
  */
 struct exclude_stack {
 	struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
-	char *filebuf; /* remember pointer to per-directory exclude file contents so we can free() */
 	int baselen;
-	int exclude_ix;
+	int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
+};
+
+struct exclude_list_group {
+	int nr, alloc;
+	struct exclude_list *ary;
 };
 
 struct dir_struct {
@@ -62,16 +70,23 @@ struct dir_struct {
 
 	/* Exclude info */
 	const char *exclude_per_dir;
-	struct exclude_list exclude_list[3];
+
 	/*
-	 * We maintain three exclude pattern lists:
+	 * We maintain three groups of exclude pattern lists:
+	 *
 	 * EXC_CMDL lists patterns explicitly given on the command line.
 	 * EXC_DIRS lists patterns obtained from per-directory ignore files.
-	 * EXC_FILE lists patterns from fallback ignore files.
+	 * EXC_FILE lists patterns from fallback ignore files, e.g.
+	 *   - .git/info/exclude
+	 *   - core.excludesfile
+	 *
+	 * Each group contains multiple exclude lists, a single list
+	 * per source.
 	 */
 #define EXC_CMDL 0
 #define EXC_DIRS 1
 #define EXC_FILE 2
+	struct exclude_list_group exclude_list_groups[3];
 
 	/*
 	 * Temporary variables which are used during loading of the
@@ -129,8 +144,9 @@ extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, c
 extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
 
 
+extern struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type);
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
-					  char **buf_p, struct exclude_list *el, int check_index);
+					  struct exclude_list *el, int check_index);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
diff --git a/unpack-trees.c b/unpack-trees.c
index ad621d9..de8da46 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1019,7 +1019,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 	if (!core_apply_sparse_checkout || !o->update)
 		o->skip_sparse_checkout = 1;
 	if (!o->skip_sparse_checkout) {
-		if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0)
+		if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, &el, 0) < 0)
 			o->skip_sparse_checkout = 1;
 		else
 			o->el = &el;
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 12/19] dir.c: keep track of where patterns came from
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (10 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 13/19] dir.c: provide clear_directory() for reclaiming dir_struct memory Adam Spiers
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

For exclude patterns read in from files, the filename is stored in the
exclude list, and the originating line number is stored in the
individual exclude (counting starting at 1).

For exclude patterns provided on the command line, a string describing
the source of the patterns is stored in the exclude list, and the
sequence number assigned to each exclude pattern is negative, with
counting starting at -1.  So for example the 2nd pattern provided via
--exclude would be numbered -2.  This allows any future consumers of
that data to easily distinguish between exclude patterns from files
vs. from the CLI.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 builtin/clean.c    |  4 ++--
 builtin/ls-files.c |  5 +++--
 dir.c              | 26 ++++++++++++++++++++------
 dir.h              | 21 +++++++++++++++++++--
 4 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/builtin/clean.c b/builtin/clean.c
index bd18b88..72d2876 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -97,10 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 	if (!ignored)
 		setup_standard_excludes(&dir);
 
-	add_exclude_list(&dir, EXC_CMDL);
+	add_exclude_list(&dir, EXC_CMDL, "--exclude option");
 	for (i = 0; i < exclude_list.nr; i++)
 		add_exclude(exclude_list.items[i].string, "", 0,
-			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);
+			    &dir.exclude_list_groups[EXC_CMDL].ary[0], -(i+1));
 
 	pathspec = get_pathspec(prefix, argv);
 
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index c448e06..d4e55c2 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -35,6 +35,7 @@ static int error_unmatch;
 static char *ps_matched;
 static const char *with_tree;
 static int exc_given;
+static int exclude_args;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -423,7 +424,7 @@ static int option_parse_exclude(const struct option *opt,
 	struct exclude_list_group *group = opt->value;
 
 	exc_given = 1;
-	add_exclude(arg, "", 0, &group->ary[0]);
+	add_exclude(arg, "", 0, &group->ary[0], --exclude_args);
 
 	return 0;
 }
@@ -524,7 +525,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 	if (read_cache() < 0)
 		die("index file corrupt");
 
-	add_exclude_list(&dir, EXC_CMDL);
+	add_exclude_list(&dir, EXC_CMDL, "--exclude option");
 	argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
 			ls_files_usage, 0);
 	if (show_tag || show_valid_bit) {
diff --git a/dir.c b/dir.c
index 3a9d89b..aefe2bb 100644
--- a/dir.c
+++ b/dir.c
@@ -349,7 +349,7 @@ void parse_exclude_pattern(const char **pattern,
 }
 
 void add_exclude(const char *string, const char *base,
-		 int baselen, struct exclude_list *el)
+		 int baselen, struct exclude_list *el, int srcpos)
 {
 	struct exclude *x;
 	int patternlen;
@@ -373,8 +373,10 @@ void add_exclude(const char *string, const char *base,
 	x->base = base;
 	x->baselen = baselen;
 	x->flags = flags;
+	x->srcpos = srcpos;
 	ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
 	el->excludes[el->nr++] = x;
+	x->el = el;
 }
 
 static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
@@ -425,7 +427,7 @@ int add_excludes_from_file_to_list(const char *fname,
 				   int check_index)
 {
 	struct stat st;
-	int fd, i;
+	int fd, i, lineno = 1;
 	size_t size = 0;
 	char *buf, *entry;
 
@@ -467,15 +469,17 @@ int add_excludes_from_file_to_list(const char *fname,
 		if (buf[i] == '\n') {
 			if (entry != buf + i && entry[0] != '#') {
 				buf[i - (i && buf[i-1] == '\r')] = 0;
-				add_exclude(entry, base, baselen, el);
+				add_exclude(entry, base, baselen, el, lineno);
 			}
+			lineno++;
 			entry = buf + i + 1;
 		}
 	}
 	return 0;
 }
 
-struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type)
+struct exclude_list *add_exclude_list(struct dir_struct *dir,
+				      int group_type, const char *src)
 {
 	struct exclude_list *el;
 	struct exclude_list_group *group;
@@ -484,6 +488,7 @@ struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type)
 	ALLOC_GROW(group->ary, group->nr + 1, group->alloc);
 	el = &group->ary[group->nr++];
 	memset(el, 0, sizeof(*el));
+	el->src = src;
 	return el;
 }
 
@@ -493,7 +498,7 @@ struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type)
 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 {
 	struct exclude_list *el;
-	el = add_exclude_list(dir, EXC_FILE);
+	el = add_exclude_list(dir, EXC_FILE, fname);
 	if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
 		die("cannot use %s as an exclude file", fname);
 }
@@ -522,6 +527,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 			break;
 		el = &group->ary[dir->exclude_stack->exclude_ix];
 		dir->exclude_stack = stk->prev;
+		free((char *)el->src); /* see strdup() below */
 		clear_exclude_list(el);
 		free(stk);
 		group->nr--;
@@ -548,7 +554,15 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 		memcpy(dir->basebuf + current, base + current,
 		       stk->baselen - current);
 		strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
-		el = add_exclude_list(dir, EXC_DIRS);
+		/*
+		 * dir->basebuf gets reused by the traversal, but we
+		 * need fname to remain unchanged to ensure the src
+		 * member of each struct exclude correctly
+		 * back-references its source file.  Other invocations
+		 * of add_exclude_list provide stable strings, so we
+		 * strdup() and free() here in the caller.
+		 */
+		el = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));
 		stk->exclude_ix = group->nr - 1;
 		add_excludes_from_file_to_list(dir->basebuf,
 					       dir->basebuf, stk->baselen,
diff --git a/dir.h b/dir.h
index 9408fbe..f91770a 100644
--- a/dir.h
+++ b/dir.h
@@ -25,16 +25,32 @@ struct dir_entry {
 struct exclude_list {
 	int nr;
 	int alloc;
+
 	/* remember pointer to exclude file contents so we can free() */
 	char *filebuf;
 
+	/* origin of list, e.g. path to filename, or descriptive string */
+	const char *src;
+
 	struct exclude {
+		/*
+		 * This allows callers of last_exclude_matching() etc.
+		 * to determine the origin of the matching pattern.
+		 */
+		struct exclude_list *el;
+
 		const char *pattern;
 		int patternlen;
 		int nowildcardlen;
 		const char *base;
 		int baselen;
 		int flags;
+
+		/*
+		 * Counting starts from 1 for line numbers in ignore files,
+		 * and from -1 decrementing for patterns from CLI args.
+		 */
+		int srcpos;
 	} **excludes;
 };
 
@@ -144,13 +160,14 @@ extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, c
 extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
 
 
-extern struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type);
+extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
+					     int group_type, const char *src);
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
 					  struct exclude_list *el, int check_index);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
-			int baselen, struct exclude_list *el);
+			int baselen, struct exclude_list *el, int srcpos);
 extern void clear_exclude_list(struct exclude_list *el);
 extern int file_exists(const char *);
 
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 13/19] dir.c: provide clear_directory() for reclaiming dir_struct memory
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (11 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 12/19] dir.c: keep track of where patterns came from Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 14/19] add.c: refactor treat_gitlinks() Adam Spiers
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

By the end of a directory traversal, a dir_struct instance will
typically contains pointers to various data structures on the heap.
clear_directory() provides a convenient way to reclaim that memory.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 Documentation/technical/api-directory-listing.txt |  2 ++
 dir.c                                             | 30 +++++++++++++++++++++++
 dir.h                                             |  1 +
 3 files changed, 33 insertions(+)

diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
index fa9c8ae..fbceb62 100644
--- a/Documentation/technical/api-directory-listing.txt
+++ b/Documentation/technical/api-directory-listing.txt
@@ -81,4 +81,6 @@ marked. If you to exclude files, make sure you have loaded index first.
 
 * Use `dir.entries[]`.
 
+* Call `free_directory()` when none of the contained elements are no longer in use.
+
 (JC)
diff --git a/dir.c b/dir.c
index aefe2bb..0d1b7e9 100644
--- a/dir.c
+++ b/dir.c
@@ -1555,3 +1555,33 @@ void free_pathspec(struct pathspec *pathspec)
 	free(pathspec->items);
 	pathspec->items = NULL;
 }
+
+/*
+ * Frees memory within dir which was allocated for exclude lists and
+ * the exclude_stack.  Does not free dir itself.
+ */
+void clear_directory(struct dir_struct *dir)
+{
+	int i, j;
+	struct exclude_list_group *group;
+	struct exclude_list *el;
+	struct exclude_stack *stk;
+
+	for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+		group = &dir->exclude_list_groups[i];
+		for (j = 0; j < group->nr; j++) {
+			el = &group->ary[j];
+			if (i == EXC_DIRS)
+				free((char *)el->src);
+			clear_exclude_list(el);
+		}
+		free(group->ary);
+	}
+
+	stk = dir->exclude_stack;
+	while (stk) {
+		struct exclude_stack *prev = stk->prev;
+		free(stk);
+		stk = prev;
+	}
+}
diff --git a/dir.h b/dir.h
index f91770a..286de4e 100644
--- a/dir.h
+++ b/dir.h
@@ -169,6 +169,7 @@ extern void parse_exclude_pattern(const char **string, int *patternlen, int *fla
 extern void add_exclude(const char *string, const char *base,
 			int baselen, struct exclude_list *el, int srcpos);
 extern void clear_exclude_list(struct exclude_list *el);
+extern void clear_directory(struct dir_struct *dir);
 extern int file_exists(const char *);
 
 extern int is_inside_dir(const char *dir);
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 14/19] add.c: refactor treat_gitlinks()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (12 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 13/19] dir.c: provide clear_directory() for reclaiming dir_struct memory Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 15/19] add.c: remove unused argument from validate_pathspec() Adam Spiers
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

Extract the body of the for loop in treat_gitlinks() into a separate
treat_gitlink() function so that it can be reused elsewhere.  This
paves the way for a new check-ignore sub-command.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 builtin/add.c | 50 ++++++++++++++++++++++++++++++++------------------
 1 file changed, 32 insertions(+), 18 deletions(-)

diff --git a/builtin/add.c b/builtin/add.c
index c689f37..856d232 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -153,31 +153,45 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, int
 	return seen;
 }
 
-static void treat_gitlinks(const char **pathspec)
+/*
+ * Check whether path refers to a submodule, or something inside a
+ * submodule.  If the former, returns the path with any trailing slash
+ * stripped.  If the latter, dies with an error message.
+ */
+const char *treat_gitlink(const char *path)
 {
-	int i;
-
-	if (!pathspec || !*pathspec)
-		return;
-
+	int i, path_len = strlen(path);
 	for (i = 0; i < active_nr; i++) {
 		struct cache_entry *ce = active_cache[i];
 		if (S_ISGITLINK(ce->ce_mode)) {
-			int len = ce_namelen(ce), j;
-			for (j = 0; pathspec[j]; j++) {
-				int len2 = strlen(pathspec[j]);
-				if (len2 <= len || pathspec[j][len] != '/' ||
-				    memcmp(ce->name, pathspec[j], len))
-					continue;
-				if (len2 == len + 1)
-					/* strip trailing slash */
-					pathspec[j] = xstrndup(ce->name, len);
-				else
-					die (_("Path '%s' is in submodule '%.*s'"),
-						pathspec[j], len, ce->name);
+			int ce_len = ce_namelen(ce);
+			if (path_len <= ce_len || path[ce_len] != '/' ||
+			    memcmp(ce->name, path, ce_len))
+				/* path does not refer to this
+				 * submodule or anything inside it */
+				continue;
+			if (path_len == ce_len + 1) {
+				/* path refers to submodule;
+				 * strip trailing slash */
+				return xstrndup(ce->name, ce_len);
+			} else {
+				die (_("Path '%s' is in submodule '%.*s'"),
+				     path, ce_len, ce->name);
 			}
 		}
 	}
+	return path;
+}
+
+void treat_gitlinks(const char **pathspec)
+{
+	int i;
+
+	if (!pathspec || !*pathspec)
+		return;
+
+	for (i = 0; pathspec[i]; i++)
+		pathspec[i] = treat_gitlink(pathspec[i]);
 }
 
 static void refresh(int verbose, const char **pathspec)
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 15/19] add.c: remove unused argument from validate_pathspec()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (13 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 14/19] add.c: refactor treat_gitlinks() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-27  2:32 ` [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c Adam Spiers
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

The 'argc' argument passed to validate_pathspec() was never used.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 builtin/add.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/builtin/add.c b/builtin/add.c
index 856d232..1ba2a86 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -211,7 +211,7 @@ static void refresh(int verbose, const char **pathspec)
         free(seen);
 }
 
-static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
+static const char **validate_pathspec(const char **argv, const char *prefix)
 {
 	const char **pathspec = get_pathspec(prefix, argv);
 
@@ -262,7 +262,7 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch)
 	const char **pathspec = NULL;
 
 	if (argc) {
-		pathspec = validate_pathspec(argc, argv, prefix);
+		pathspec = validate_pathspec(argv, prefix);
 		if (!pathspec)
 			return -1;
 	}
@@ -428,7 +428,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
 		return 0;
 	}
-	pathspec = validate_pathspec(argc, argv, prefix);
+	pathspec = validate_pathspec(argv, prefix);
 
 	if (read_cache() < 0)
 		die(_("index file corrupt"));
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (14 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 15/19] add.c: remove unused argument from validate_pathspec() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-28 20:32   ` Junio C Hamano
  2012-12-28 20:48   ` Junio C Hamano
  2012-12-27  2:32 ` [PATCH v3 17/19] pathspec.c: extract new validate_path() for reuse Adam Spiers
                   ` (5 subsequent siblings)
  21 siblings, 2 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

Extract the following functions from builtin/add.c to pathspec.c, in
preparation for reuse by a new git check-ignore command:

  - fill_pathspec_matches()
  - find_used_pathspec()
  - treat_gitlink()
  - treat_gitlinks()
  - validate_pathspec()

The functions being extracted are not changed in any way, except
removal of the 'static' qualifier.

Also add a comment documenting validate_pathspec().

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 Makefile      |  2 ++
 builtin/add.c | 92 +-----------------------------------------------------
 pathspec.c    | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 pathspec.h    |  5 +++
 4 files changed, 107 insertions(+), 91 deletions(-)
 create mode 100644 pathspec.c
 create mode 100644 pathspec.h

diff --git a/Makefile b/Makefile
index 13293d3..48facad 100644
--- a/Makefile
+++ b/Makefile
@@ -645,6 +645,7 @@ LIB_H += pack-refs.h
 LIB_H += pack-revindex.h
 LIB_H += parse-options.h
 LIB_H += patch-ids.h
+LIB_H += pathspec.h
 LIB_H += pkt-line.h
 LIB_H += progress.h
 LIB_H += prompt.h
@@ -758,6 +759,7 @@ LIB_OBJS += parse-options-cb.o
 LIB_OBJS += patch-delta.o
 LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
+LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
diff --git a/builtin/add.c b/builtin/add.c
index 1ba2a86..d3bae78 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
+#include "pathspec.h"
 #include "exec_cmd.h"
 #include "cache-tree.h"
 #include "run-command.h"
@@ -97,39 +98,6 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
 	return !!data.add_errors;
 }
 
-static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
-{
-	int num_unmatched = 0, i;
-
-	/*
-	 * Since we are walking the index as if we were walking the directory,
-	 * we have to mark the matched pathspec as seen; otherwise we will
-	 * mistakenly think that the user gave a pathspec that did not match
-	 * anything.
-	 */
-	for (i = 0; i < specs; i++)
-		if (!seen[i])
-			num_unmatched++;
-	if (!num_unmatched)
-		return;
-	for (i = 0; i < active_nr; i++) {
-		struct cache_entry *ce = active_cache[i];
-		match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
-	}
-}
-
-static char *find_used_pathspec(const char **pathspec)
-{
-	char *seen;
-	int i;
-
-	for (i = 0; pathspec[i];  i++)
-		; /* just counting */
-	seen = xcalloc(i, 1);
-	fill_pathspec_matches(pathspec, seen, i);
-	return seen;
-}
-
 static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
 {
 	char *seen;
@@ -153,47 +121,6 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, int
 	return seen;
 }
 
-/*
- * Check whether path refers to a submodule, or something inside a
- * submodule.  If the former, returns the path with any trailing slash
- * stripped.  If the latter, dies with an error message.
- */
-const char *treat_gitlink(const char *path)
-{
-	int i, path_len = strlen(path);
-	for (i = 0; i < active_nr; i++) {
-		struct cache_entry *ce = active_cache[i];
-		if (S_ISGITLINK(ce->ce_mode)) {
-			int ce_len = ce_namelen(ce);
-			if (path_len <= ce_len || path[ce_len] != '/' ||
-			    memcmp(ce->name, path, ce_len))
-				/* path does not refer to this
-				 * submodule or anything inside it */
-				continue;
-			if (path_len == ce_len + 1) {
-				/* path refers to submodule;
-				 * strip trailing slash */
-				return xstrndup(ce->name, ce_len);
-			} else {
-				die (_("Path '%s' is in submodule '%.*s'"),
-				     path, ce_len, ce->name);
-			}
-		}
-	}
-	return path;
-}
-
-void treat_gitlinks(const char **pathspec)
-{
-	int i;
-
-	if (!pathspec || !*pathspec)
-		return;
-
-	for (i = 0; pathspec[i]; i++)
-		pathspec[i] = treat_gitlink(pathspec[i]);
-}
-
 static void refresh(int verbose, const char **pathspec)
 {
 	char *seen;
@@ -211,23 +138,6 @@ static void refresh(int verbose, const char **pathspec)
         free(seen);
 }
 
-static const char **validate_pathspec(const char **argv, const char *prefix)
-{
-	const char **pathspec = get_pathspec(prefix, argv);
-
-	if (pathspec) {
-		const char **p;
-		for (p = pathspec; *p; p++) {
-			if (has_symlink_leading_path(*p, strlen(*p))) {
-				int len = prefix ? strlen(prefix) : 0;
-				die(_("'%s' is beyond a symbolic link"), *p + len);
-			}
-		}
-	}
-
-	return pathspec;
-}
-
 int run_add_interactive(const char *revision, const char *patch_mode,
 			const char **pathspec)
 {
diff --git a/pathspec.c b/pathspec.c
new file mode 100644
index 0000000..8aea0d2
--- /dev/null
+++ b/pathspec.c
@@ -0,0 +1,99 @@
+#include "cache.h"
+#include "dir.h"
+#include "pathspec.h"
+
+void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
+{
+	int num_unmatched = 0, i;
+
+	/*
+	 * Since we are walking the index as if we were walking the directory,
+	 * we have to mark the matched pathspec as seen; otherwise we will
+	 * mistakenly think that the user gave a pathspec that did not match
+	 * anything.
+	 */
+	for (i = 0; i < specs; i++)
+		if (!seen[i])
+			num_unmatched++;
+	if (!num_unmatched)
+		return;
+	for (i = 0; i < active_nr; i++) {
+		struct cache_entry *ce = active_cache[i];
+		match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+	}
+}
+
+char *find_used_pathspec(const char **pathspec)
+{
+	char *seen;
+	int i;
+
+	for (i = 0; pathspec[i];  i++)
+		; /* just counting */
+	seen = xcalloc(i, 1);
+	fill_pathspec_matches(pathspec, seen, i);
+	return seen;
+}
+
+/*
+ * Check whether path refers to a submodule, or something inside a
+ * submodule.  If the former, returns the path with any trailing slash
+ * stripped.  If the latter, dies with an error message.
+ */
+const char *treat_gitlink(const char *path)
+{
+	int i, path_len = strlen(path);
+	for (i = 0; i < active_nr; i++) {
+		struct cache_entry *ce = active_cache[i];
+		if (S_ISGITLINK(ce->ce_mode)) {
+			int ce_len = ce_namelen(ce);
+			if (path_len <= ce_len || path[ce_len] != '/' ||
+			    memcmp(ce->name, path, ce_len))
+				/* path does not refer to this
+				 * submodule or anything inside it */
+				continue;
+			if (path_len == ce_len + 1) {
+				/* path refers to submodule;
+				 * strip trailing slash */
+				return xstrndup(ce->name, ce_len);
+			} else {
+				die (_("Path '%s' is in submodule '%.*s'"),
+				     path, ce_len, ce->name);
+			}
+		}
+	}
+	return path;
+}
+
+void treat_gitlinks(const char **pathspec)
+{
+	int i;
+
+	if (!pathspec || !*pathspec)
+		return;
+
+	for (i = 0; pathspec[i]; i++)
+		pathspec[i] = treat_gitlink(pathspec[i]);
+}
+
+/*
+ * Normalizes argv relative to prefix, via get_pathspec(), and then
+ * dies if any path in the normalized list refers to a file inside a
+ * symlinked directory.
+ */
+const char **validate_pathspec(const char **argv, const char *prefix)
+{
+	const char **pathspec = get_pathspec(prefix, argv);
+
+	if (pathspec) {
+		const char **p;
+		for (p = pathspec; *p; p++) {
+			if (has_symlink_leading_path(*p, strlen(*p))) {
+				int len = prefix ? strlen(prefix) : 0;
+				die(_("'%s' is beyond a symbolic link"), *p + len);
+			}
+		}
+	}
+
+	return pathspec;
+}
diff --git a/pathspec.h b/pathspec.h
new file mode 100644
index 0000000..8bb670b
--- /dev/null
+++ b/pathspec.h
@@ -0,0 +1,5 @@
+extern char *find_used_pathspec(const char **pathspec);
+extern void fill_pathspec_matches(const char **pathspec, char *seen, int specs);
+extern const char *treat_gitlink(const char *path);
+extern void treat_gitlinks(const char **pathspec);
+extern const char **validate_pathspec(const char **argv, const char *prefix);
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 17/19] pathspec.c: extract new validate_path() for reuse
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (15 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-28 20:44   ` Junio C Hamano
  2012-12-27  2:32 ` [PATCH v3 18/19] setup.c: document get_pathspec() Adam Spiers
                   ` (4 subsequent siblings)
  21 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

This will be reused by a new git check-ignore command.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 pathspec.c | 20 ++++++++++++++------
 pathspec.h |  1 +
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/pathspec.c b/pathspec.c
index 8aea0d2..6724121 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -77,9 +77,20 @@ void treat_gitlinks(const char **pathspec)
 }
 
 /*
+ * Dies if the given path refers to a file inside a symlinked
+ * directory.
+ */
+void validate_path(const char *path, const char *prefix)
+{
+	if (has_symlink_leading_path(path, strlen(path))) {
+		int len = prefix ? strlen(prefix) : 0;
+		die(_("'%s' is beyond a symbolic link"), path + len);
+	}
+}
+
+/*
  * Normalizes argv relative to prefix, via get_pathspec(), and then
- * dies if any path in the normalized list refers to a file inside a
- * symlinked directory.
+ * runs validate_path() on each path in the normalized list.
  */
 const char **validate_pathspec(const char **argv, const char *prefix)
 {
@@ -88,10 +99,7 @@ const char **validate_pathspec(const char **argv, const char *prefix)
 	if (pathspec) {
 		const char **p;
 		for (p = pathspec; *p; p++) {
-			if (has_symlink_leading_path(*p, strlen(*p))) {
-				int len = prefix ? strlen(prefix) : 0;
-				die(_("'%s' is beyond a symbolic link"), *p + len);
-			}
+			validate_path(*p, prefix);
 		}
 	}
 
diff --git a/pathspec.h b/pathspec.h
index 8bb670b..c251441 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -2,4 +2,5 @@ extern char *find_used_pathspec(const char **pathspec);
 extern void fill_pathspec_matches(const char **pathspec, char *seen, int specs);
 extern const char *treat_gitlink(const char *path);
 extern void treat_gitlinks(const char **pathspec);
+extern void validate_path(const char *path, const char *prefix);
 extern const char **validate_pathspec(const char **argv, const char *prefix);
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 18/19] setup.c: document get_pathspec()
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (16 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 17/19] pathspec.c: extract new validate_path() for reuse Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-28 20:36   ` Junio C Hamano
  2012-12-27  2:32 ` [PATCH v3 19/19] Add git-check-ignore sub-command Adam Spiers
                   ` (3 subsequent siblings)
  21 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

Since we have just created a new pathspec-handling library, now is a
good time to add some comments explaining get_pathspec().

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 setup.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/setup.c b/setup.c
index 7663a4c..03d6d5c 100644
--- a/setup.c
+++ b/setup.c
@@ -249,6 +249,21 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char
 		return prefix_path(prefix, prefixlen, copyfrom);
 }
 
+/*
+ * prefix - a path relative to the root of the working tree
+ * pathspec - a list of paths underneath the prefix path
+ *
+ * Iterates over pathspec, prepending each path with prefix,
+ * and return the resulting list.
+ *
+ * If pathspec is empty, return a singleton list containing prefix.
+ *
+ * If pathspec and prefix are both empty, return an empty list.
+ *
+ * This is typically used by built-in commands such as add.c, in order
+ * to normalize argv arguments provided to the built-in into a list of
+ * paths to process, all relative to the root of the working tree.
+ */
 const char **get_pathspec(const char *prefix, const char **pathspec)
 {
 	const char *entry = *pathspec;
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* [PATCH v3 19/19] Add git-check-ignore sub-command
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (17 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 18/19] setup.c: document get_pathspec() Adam Spiers
@ 2012-12-27  2:32 ` Adam Spiers
  2012-12-28 21:21   ` Junio C Hamano
  2012-12-27  5:15 ` [PATCH v3 00/19] new git check-ignore sub-command Michael Leal
                   ` (2 subsequent siblings)
  21 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-27  2:32 UTC (permalink / raw)
  To: git list

This works in a similar manner to git-check-attr.

Thanks to Jeff King and Junio C Hamano for the idea:
http://thread.gmane.org/gmane.comp.version-control.git/108671/focus=108815

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
Since v2, the missing documentation has been fixed, and the erroneous
tweak to t/t9902-completion.sh has been removed.

 .gitignore                                        |   1 +
 Documentation/git-check-ignore.txt                |  89 ++++
 Documentation/gitignore.txt                       |   6 +-
 Documentation/technical/api-directory-listing.txt |   2 +-
 Makefile                                          |   1 +
 builtin.h                                         |   1 +
 builtin/check-ignore.c                            | 170 +++++++
 command-list.txt                                  |   1 +
 contrib/completion/git-completion.bash            |   1 +
 git.c                                             |   1 +
 t/t0008-ignores.sh                                | 595 ++++++++++++++++++++++
 11 files changed, 865 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/git-check-ignore.txt
 create mode 100644 builtin/check-ignore.c
 create mode 100755 t/t0008-ignores.sh

diff --git a/.gitignore b/.gitignore
index f1acd3e..20ef4e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@
 /git-bundle
 /git-cat-file
 /git-check-attr
+/git-check-ignore
 /git-check-ref-format
 /git-checkout
 /git-checkout-index
diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt
new file mode 100644
index 0000000..854e4d0
--- /dev/null
+++ b/Documentation/git-check-ignore.txt
@@ -0,0 +1,89 @@
+git-check-ignore(1)
+===================
+
+NAME
+----
+git-check-ignore - Debug gitignore / exclude files
+
+
+SYNOPSIS
+--------
+[verse]
+'git check-ignore' [options] pathname...
+'git check-ignore' [options] --stdin < <list-of-paths>
+
+DESCRIPTION
+-----------
+
+For each pathname given via the command-line or from a file via
+`--stdin`, show the pattern from .gitignore (or other input files to
+the exclude mechanism) that decides if the pathname is excluded or
+included.  Later patterns within a file take precedence over earlier
+ones.
+
+OPTIONS
+-------
+-q, --quiet::
+	Don't output anything, just set exit status.  This is only
+	valid with a single pathname.
+
+-v, --verbose::
+	Also output details about the matching pattern (if any)
+	for each given pathname.
+
+--stdin::
+	Read file names from stdin instead of from the command-line.
+
+-z::
+	The output format is modified to be machine-parseable (see
+	below).  If `--stdin` is also given, input paths are separated
+	with a NUL character instead of a linefeed character.
+
+OUTPUT
+------
+
+By default, any of the given pathnames which match an ignore pattern
+will be output, one per line.  If no pattern matches a given path,
+nothing will be output for that path; this means that path will not be
+ignored.
+
+If `--verbose` is specified, the output is a series of lines of the form:
+
+<source> <COLON> <linenum> <COLON> <pattern> <HT> <pathname>
+
+<pathname> is the path of a file being queried, <pattern> is the
+matching pattern, <source> is the pattern's source file, and <linenum>
+is the line number of the pattern within that source.  If the pattern
+contained a `!` prefix or `/` suffix, it will be preserved in the
+output.  <source> will be an absolute path when referring to the file
+configured by `core.excludesfile`, or relative to the repository root
+when referring to `.git/info/exclude` or a per-directory exclude file.
+
+If `-z` is specified, the pathnames in the output are delimited by the
+null character; if `--verbose` is also specified then null characters
+are also used instead of colons and hard tabs:
+
+<source> <NULL> <linenum> <NULL> <pattern> <NULL> <pathname> <NULL>
+
+
+EXIT STATUS
+-----------
+
+0::
+	One or more of the provided paths is ignored.
+
+1::
+	None of the provided paths are ignored.
+
+128::
+	A fatal error was encountered.
+
+SEE ALSO
+--------
+linkgit:gitignore[5]
+linkgit:gitconfig[5]
+linkgit:git-ls-files[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index 2e7328b..f401b8c 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -153,8 +153,10 @@ The second .gitignore prevents git from ignoring
 
 SEE ALSO
 --------
-linkgit:git-rm[1], linkgit:git-update-index[1],
-linkgit:gitrepository-layout[5]
+linkgit:git-rm[1],
+linkgit:git-update-index[1],
+linkgit:gitrepository-layout[5],
+linkgit:git-check-ignore[1]
 
 GIT
 ---
diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
index fbceb62..9d3e352 100644
--- a/Documentation/technical/api-directory-listing.txt
+++ b/Documentation/technical/api-directory-listing.txt
@@ -81,6 +81,6 @@ marked. If you to exclude files, make sure you have loaded index first.
 
 * Use `dir.entries[]`.
 
-* Call `free_directory()` when none of the contained elements are no longer in use.
+* Call `clear_directory()` when none of the contained elements are no longer in use.
 
 (JC)
diff --git a/Makefile b/Makefile
index 48facad..8476fc8 100644
--- a/Makefile
+++ b/Makefile
@@ -822,6 +822,7 @@ BUILTIN_OBJS += builtin/branch.o
 BUILTIN_OBJS += builtin/bundle.o
 BUILTIN_OBJS += builtin/cat-file.o
 BUILTIN_OBJS += builtin/check-attr.o
+BUILTIN_OBJS += builtin/check-ignore.o
 BUILTIN_OBJS += builtin/check-ref-format.o
 BUILTIN_OBJS += builtin/checkout-index.o
 BUILTIN_OBJS += builtin/checkout.o
diff --git a/builtin.h b/builtin.h
index dffb34e..d57faf4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -58,6 +58,7 @@ extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
+extern int cmd_check_ignore(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
new file mode 100644
index 0000000..c825736
--- /dev/null
+++ b/builtin/check-ignore.c
@@ -0,0 +1,170 @@
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "parse-options.h"
+
+static int quiet, verbose, stdin_paths;
+static const char * const check_ignore_usage[] = {
+"git check-ignore [options] pathname...",
+"git check-ignore [options] --stdin < <list-of-paths>",
+NULL
+};
+
+static int null_term_line;
+
+static const struct option check_ignore_options[] = {
+	OPT__QUIET(&quiet, N_("suppress progress reporting")),
+	OPT__VERBOSE(&verbose, N_("be verbose")),
+	OPT_GROUP(""),
+	OPT_BOOLEAN(0, "stdin", &stdin_paths,
+		    N_("read file names from stdin")),
+	OPT_BOOLEAN('z', NULL, &null_term_line,
+		    N_("input paths are terminated by a null character")),
+	OPT_END()
+};
+
+static void output_exclude(const char *path, struct exclude *exclude)
+{
+	char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
+	char *dir  = (exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
+	if (!null_term_line) {
+		if (!verbose) {
+			write_name_quoted(path, stdout, '\n');
+		} else {
+			quote_c_style(exclude->el->src, NULL, stdout, 0);
+			printf(":%d:%s%s%s\t",
+			       exclude->srcpos,
+			       bang, exclude->pattern, dir);
+			quote_c_style(path, NULL, stdout, 0);
+			fputc('\n', stdout);
+		}
+	} else {
+		if (!verbose) {
+			printf("%s%c", path, '\0');
+		} else {
+			printf("%s%c%d%c%s%s%s%c%s%c",
+			       exclude->el->src, '\0',
+			       exclude->srcpos, '\0',
+			       bang, exclude->pattern, dir, '\0',
+			       path, '\0');
+		}
+	}
+}
+
+static int check_ignore(const char *prefix, const char **pathspec)
+{
+	struct dir_struct dir;
+	const char *path;
+	char *seen = NULL;
+	int num_ignored = 0;
+
+	/* read_cache() is only necessary so we can watch out for submodules. */
+	if (read_cache() < 0)
+		die(_("index file corrupt"));
+
+	memset(&dir, 0, sizeof(dir));
+	dir.flags |= DIR_COLLECT_IGNORED;
+	setup_standard_excludes(&dir);
+
+	if (pathspec) {
+		int i;
+		struct path_exclude_check check;
+		struct exclude *exclude;
+
+		path_exclude_check_init(&check, &dir);
+		if (!seen)
+			seen = find_used_pathspec(pathspec);
+		for (i = 0; pathspec[i]; i++) {
+			const char *full_path;
+			path = pathspec[i];
+			full_path = prefix_path(prefix, prefix
+						? strlen(prefix) : 0, path);
+			full_path = treat_gitlink(full_path);
+			validate_path(full_path, prefix);
+			if (!seen[i] && path[0]) {
+				int dtype = DT_UNKNOWN;
+				exclude = last_exclude_matching_path(&check, full_path,
+								     -1, &dtype);
+				if (exclude) {
+					if (!quiet)
+						output_exclude(path, exclude);
+					num_ignored++;
+				}
+			}
+		}
+		free(seen);
+		clear_directory(&dir);
+		path_exclude_check_clear(&check);
+	} else {
+		printf("no pathspec\n");
+	}
+	return num_ignored;
+}
+
+static int check_ignore_stdin_paths(const char *prefix)
+{
+	struct strbuf buf, nbuf;
+	char **pathspec = NULL;
+	size_t nr = 0, alloc = 0;
+	int line_termination = null_term_line ? 0 : '\n';
+	int num_ignored;
+
+	strbuf_init(&buf, 0);
+	strbuf_init(&nbuf, 0);
+	while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+		if (line_termination && buf.buf[0] == '"') {
+			strbuf_reset(&nbuf);
+			if (unquote_c_style(&nbuf, buf.buf, NULL))
+				die("line is badly quoted");
+			strbuf_swap(&buf, &nbuf);
+		}
+		ALLOC_GROW(pathspec, nr + 1, alloc);
+		pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf));
+		strcpy(pathspec[nr++], buf.buf);
+	}
+	ALLOC_GROW(pathspec, nr + 1, alloc);
+	pathspec[nr] = NULL;
+	num_ignored = check_ignore(prefix, (const char **)pathspec);
+	maybe_flush_or_die(stdout, "attribute to stdout");
+	strbuf_release(&buf);
+	strbuf_release(&nbuf);
+	free(pathspec);
+	return num_ignored;
+}
+
+int cmd_check_ignore(int argc, const char **argv, const char *prefix)
+{
+	int num_ignored = 0;
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix, check_ignore_options,
+			     check_ignore_usage, 0);
+
+	if (stdin_paths) {
+		if (0 < argc)
+			die(_("cannot specify pathnames with --stdin"));
+	} else {
+		if (null_term_line)
+			die(_("-z only makes sense with --stdin"));
+		if (argc == 0)
+			die(_("no path specified"));
+	}
+	if (quiet) {
+		if (argc > 1)
+			die(_("--quiet is only valid with a single pathname"));
+		if (verbose)
+			die(_("cannot have both --quiet and --verbose"));
+	}
+
+	if (stdin_paths) {
+		num_ignored = check_ignore_stdin_paths(prefix);
+	} else {
+		num_ignored = check_ignore(prefix, argv);
+		maybe_flush_or_die(stdout, "ignore to stdout");
+	}
+
+	return num_ignored > 0 ? 0 : 1;
+}
diff --git a/command-list.txt b/command-list.txt
index 14ea67a..ef7f39c 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -12,6 +12,7 @@ git-branch                              mainporcelain common
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
+git-check-ignore                        purehelpers
 git-checkout                            mainporcelain common
 git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 2e1b5e1..1fb896b 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -842,6 +842,7 @@ __git_list_porcelain_commands ()
 		archimport)       : import;;
 		cat-file)         : plumbing;;
 		check-attr)       : plumbing;;
+		check-ignore)     : plumbing;;
 		check-ref-format) : plumbing;;
 		checkout-index)   : plumbing;;
 		commit-tree)      : plumbing;;
diff --git a/git.c b/git.c
index d232de9..0b31e66 100644
--- a/git.c
+++ b/git.c
@@ -340,6 +340,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "bundle", cmd_bundle, RUN_SETUP_GENTLY },
 		{ "cat-file", cmd_cat_file, RUN_SETUP },
 		{ "check-attr", cmd_check_attr, RUN_SETUP },
+		{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
 		{ "check-ref-format", cmd_check_ref_format },
 		{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
 		{ "checkout-index", cmd_checkout_index,
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
new file mode 100755
index 0000000..3937ca4
--- /dev/null
+++ b/t/t0008-ignores.sh
@@ -0,0 +1,595 @@
+#!/bin/sh
+
+test_description=check-ignore
+
+. ./test-lib.sh
+
+init_vars () {
+	global_excludes="$(pwd)/global-excludes"
+}
+
+enable_global_excludes () {
+	init_vars
+	git config core.excludesfile "$global_excludes"
+}
+
+expect_in () {
+	dest="$HOME/expected-$1" text="$2"
+	if test -z "$text"
+	then
+		>"$dest" # avoid newline
+	else
+		echo "$text" >"$dest"
+	fi
+}
+
+expect () {
+	expect_in stdout "$1"
+}
+
+expect_from_stdin () {
+	cat >"$HOME/expected-stdout"
+}
+
+test_stderr () {
+	expected="$1"
+	expect_in stderr "$1" &&
+	test_cmp "$HOME/expected-stderr" "$HOME/stderr"
+}
+
+stderr_contains () {
+	regexp="$1"
+	if grep -q "$regexp" "$HOME/stderr"
+	then
+		return 0
+	else
+		echo "didn't find /$regexp/ in $HOME/stderr"
+		cat "$HOME/stderr"
+		return 1
+	fi
+}
+
+stderr_empty_on_success () {
+	expect_code="$1"
+	if test $expect_code = 0
+	then
+		test_stderr ""
+	else
+		# If we expect failure then stderr might or might not be empty
+		# due to --quiet - the caller can check its contents
+		return 0
+	fi
+}
+
+test_check_ignore () {
+	args="$1" expect_code="${2:-0}" global_args="$3"
+
+	init_vars &&
+	rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" &&
+	echo git $global_args check-ignore $quiet_opt $verbose_opt $args \
+		>"$HOME/cmd" &&
+	test_expect_code "$expect_code" \
+		git $global_args check-ignore $quiet_opt $verbose_opt $args \
+		>"$HOME/stdout" 2>"$HOME/stderr" &&
+	test_cmp "$HOME/expected-stdout" "$HOME/stdout" &&
+	stderr_empty_on_success "$expect_code"
+}
+
+test_expect_success_multi () {
+	prereq=
+	if test $# -eq 4
+	then
+		prereq=$1
+		shift
+	fi
+	testname="$1" expect_verbose="$2" code="$3"
+
+	expect=$( echo "$expect_verbose" | sed -e 's/.*	//' )
+
+	test_expect_success $prereq "$testname" "
+		expect '$expect' &&
+		$code
+	"
+
+	for quiet_opt in '-q' '--quiet'
+	do
+		test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" "
+			expect '' &&
+			$code
+		"
+	done
+	quiet_opt=
+
+	for verbose_opt in '-v' '--verbose'
+	do
+		test_expect_success $prereq "$testname${verbose_opt:+ with $verbose_opt}" "
+			expect '$expect_verbose' &&
+			$code
+		"
+	done
+	verbose_opt=
+}
+
+test_expect_success 'setup' '
+	init_vars
+	mkdir -p a/b/ignored-dir a/submodule b &&
+	if test_have_prereq SYMLINKS
+	then
+		ln -s b a/symlink
+	fi &&
+	(
+		cd a/submodule &&
+		git init &&
+		echo a > a &&
+		git add a &&
+		git commit -m"commit in submodule"
+	) &&
+	git add a/submodule &&
+	cat <<-\EOF >.gitignore &&
+		one
+	EOF
+	cat <<-\EOF >a/.gitignore &&
+		two*
+		*three
+	EOF
+	cat <<-\EOF >a/b/.gitignore &&
+		four
+		five
+		# this comment should affect the line numbers
+		six
+		ignored-dir/
+		# and so should this blank line:
+
+		!on*
+		!two
+	EOF
+	echo "seven" >a/b/ignored-dir/.gitignore &&
+	test -n "$HOME" &&
+	cat <<-\EOF >"$global_excludes" &&
+		globalone
+		!globaltwo
+		globalthree
+	EOF
+	cat <<-\EOF >>.git/info/exclude
+		per-repo
+	EOF
+'
+
+############################################################################
+#
+# test invalid inputs
+
+test_expect_success_multi 'empty command line' '' '
+	test_check_ignore "" 128 &&
+	stderr_contains "fatal: no path specified"
+'
+
+test_expect_success '-q with multiple args' '
+	expect "" &&
+	test_check_ignore "-q one two" 128 &&
+	stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+test_expect_success '--quiet with multiple args' '
+	expect "" &&
+	test_check_ignore "--quiet one two" 128 &&
+	stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+for verbose_opt in '-v' '--verbose'
+do
+	for quiet_opt in '-q' '--quiet'
+	do
+		test_expect_success "$quiet_opt $verbose_opt" "
+			expect '' &&
+			test_check_ignore '$quiet_opt $verbose_opt foo' 128 &&
+			stderr_contains 'fatal: cannot have both --quiet and --verbose'
+		"
+	done
+done
+
+test_expect_success '--quiet with multiple args' '
+	expect "" &&
+	test_check_ignore "--quiet one two" 128 &&
+	stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+test_expect_success_multi 'erroneous use of --' '' '
+	test_check_ignore "--" 128 &&
+	stderr_contains "fatal: no path specified"
+'
+
+test_expect_success_multi '--stdin with superfluous arg' '' '
+	test_check_ignore "--stdin foo" 128 &&
+	stderr_contains "fatal: cannot specify pathnames with --stdin"
+'
+
+test_expect_success_multi '--stdin -z with superfluous arg' '' '
+	test_check_ignore "--stdin -z foo" 128 &&
+	stderr_contains "fatal: cannot specify pathnames with --stdin"
+'
+
+test_expect_success_multi '-z without --stdin' '' '
+	test_check_ignore "-z" 128 &&
+	stderr_contains "fatal: -z only makes sense with --stdin"
+'
+
+test_expect_success_multi '-z without --stdin and superfluous arg' '' '
+	test_check_ignore "-z foo" 128 &&
+	stderr_contains "fatal: -z only makes sense with --stdin"
+'
+
+test_expect_success_multi 'needs work tree' '' '
+	(
+		cd .git &&
+		test_check_ignore "foo" 128
+	) &&
+	stderr_contains "fatal: This operation must be run in a work tree"
+'
+
+############################################################################
+#
+# test standard ignores
+
+test_expect_success_multi "top-level not ignored" '' '
+	test_check_ignore "foo" 1
+'
+
+test_expect_success_multi "top-level ignored" \
+	'.gitignore:1:one	one' '
+	test_check_ignore "one"
+'
+
+test_expect_success_multi 'sub-directory ignore from top' \
+	'.gitignore:1:one	a/one' '
+	test_check_ignore "a/one"
+'
+
+test_expect_success 'sub-directory local ignore' '
+	expect "a/3-three" &&
+	test_check_ignore "a/3-three a/three-not-this-one"
+'
+
+test_expect_success 'sub-directory local ignore with --verbose'  '
+	expect "a/.gitignore:2:*three	a/3-three" &&
+	test_check_ignore "--verbose a/3-three a/three-not-this-one"
+'
+
+test_expect_success 'local ignore inside a sub-directory' '
+	expect "3-three" &&
+	(
+		cd a &&
+		test_check_ignore "3-three three-not-this-one"
+	)
+'
+test_expect_success 'local ignore inside a sub-directory with --verbose' '
+	expect "a/.gitignore:2:*three	3-three" &&
+	(
+		cd a &&
+		test_check_ignore "--verbose 3-three three-not-this-one"
+	)
+'
+
+test_expect_success_multi 'nested include' \
+	'a/b/.gitignore:8:!on*	a/b/one' '
+	test_check_ignore "a/b/one"
+'
+
+############################################################################
+#
+# test ignored sub-directories
+
+test_expect_success_multi 'ignored sub-directory' \
+	'a/b/.gitignore:5:ignored-dir/	a/b/ignored-dir' '
+	test_check_ignore "a/b/ignored-dir"
+'
+
+test_expect_success 'multiple files inside ignored sub-directory' '
+	expect_from_stdin <<-\EOF &&
+		a/b/ignored-dir/foo
+		a/b/ignored-dir/twoooo
+		a/b/ignored-dir/seven
+	EOF
+	test_check_ignore "a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"
+'
+
+test_expect_success 'multiple files inside ignored sub-directory with -v' '
+	expect_from_stdin <<-\EOF &&
+		a/b/.gitignore:5:ignored-dir/	a/b/ignored-dir/foo
+		a/b/.gitignore:5:ignored-dir/	a/b/ignored-dir/twoooo
+		a/b/.gitignore:5:ignored-dir/	a/b/ignored-dir/seven
+	EOF
+	test_check_ignore "-v a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"
+'
+
+test_expect_success 'cd to ignored sub-directory' '
+	expect_from_stdin <<-\EOF &&
+		foo
+		twoooo
+		../one
+		seven
+		../../one
+	EOF
+	(
+		cd a/b/ignored-dir &&
+		test_check_ignore "foo twoooo ../one seven ../../one"
+	)
+'
+
+test_expect_success 'cd to ignored sub-directory with -v' '
+	expect_from_stdin <<-\EOF &&
+		a/b/.gitignore:5:ignored-dir/	foo
+		a/b/.gitignore:5:ignored-dir/	twoooo
+		a/b/.gitignore:8:!on*	../one
+		a/b/.gitignore:5:ignored-dir/	seven
+		.gitignore:1:one	../../one
+	EOF
+	(
+		cd a/b/ignored-dir &&
+		test_check_ignore "-v foo twoooo ../one seven ../../one"
+	)
+'
+
+############################################################################
+#
+# test handling of symlinks
+
+test_expect_success_multi SYMLINKS 'symlink' '' '
+	test_check_ignore "a/symlink" 1
+'
+
+test_expect_success_multi SYMLINKS 'beyond a symlink' '' '
+	test_check_ignore "a/symlink/foo" 128 &&
+	test_stderr "fatal: '\''a/symlink/foo'\'' is beyond a symbolic link"
+'
+
+test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' '
+	(
+		cd a &&
+		test_check_ignore "symlink/foo" 128
+	) &&
+	test_stderr "fatal: '\''symlink/foo'\'' is beyond a symbolic link"
+'
+
+############################################################################
+#
+# test handling of submodules
+
+test_expect_success_multi 'submodule' '' '
+	test_check_ignore "a/submodule/one" 128 &&
+	test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+'
+
+test_expect_success_multi 'submodule from subdirectory' '' '
+	(
+		cd a &&
+		test_check_ignore "submodule/one" 128
+	) &&
+	test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+'
+
+############################################################################
+#
+# test handling of global ignore files
+
+test_expect_success 'global ignore not yet enabled' '
+	expect_from_stdin <<-\EOF &&
+		.git/info/exclude:7:per-repo	per-repo
+		a/.gitignore:2:*three	a/globalthree
+		.git/info/exclude:7:per-repo	a/per-repo
+	EOF
+	test_check_ignore "-v globalone per-repo a/globalthree a/per-repo not-ignored a/globaltwo"
+'
+
+test_expect_success 'global ignore' '
+	enable_global_excludes &&
+	expect_from_stdin <<-\EOF &&
+		globalone
+		per-repo
+		globalthree
+		a/globalthree
+		a/per-repo
+		globaltwo
+	EOF
+	test_check_ignore "globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
+'
+
+test_expect_success 'global ignore with -v' '
+	enable_global_excludes &&
+	expect_from_stdin <<-EOF &&
+		$global_excludes:1:globalone	globalone
+		.git/info/exclude:7:per-repo	per-repo
+		$global_excludes:3:globalthree	globalthree
+		a/.gitignore:2:*three	a/globalthree
+		.git/info/exclude:7:per-repo	a/per-repo
+		$global_excludes:2:!globaltwo	globaltwo
+	EOF
+	test_check_ignore "-v globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
+'
+
+############################################################################
+#
+# test --stdin
+
+cat <<-\EOF >stdin
+	one
+	not-ignored
+	a/one
+	a/not-ignored
+	a/b/on
+	a/b/one
+	a/b/one one
+	"a/b/one two"
+	"a/b/one\"three"
+	a/b/not-ignored
+	a/b/two
+	a/b/twooo
+	globaltwo
+	a/globaltwo
+	a/b/globaltwo
+	b/globaltwo
+EOF
+cat <<-\EOF >expected-default
+	one
+	a/one
+	a/b/on
+	a/b/one
+	a/b/one one
+	a/b/one two
+	"a/b/one\"three"
+	a/b/two
+	a/b/twooo
+	globaltwo
+	a/globaltwo
+	a/b/globaltwo
+	b/globaltwo
+EOF
+cat <<-EOF >expected-verbose
+	.gitignore:1:one	one
+	.gitignore:1:one	a/one
+	a/b/.gitignore:8:!on*	a/b/on
+	a/b/.gitignore:8:!on*	a/b/one
+	a/b/.gitignore:8:!on*	a/b/one one
+	a/b/.gitignore:8:!on*	a/b/one two
+	a/b/.gitignore:8:!on*	"a/b/one\"three"
+	a/b/.gitignore:9:!two	a/b/two
+	a/.gitignore:1:two*	a/b/twooo
+	$global_excludes:2:!globaltwo	globaltwo
+	$global_excludes:2:!globaltwo	a/globaltwo
+	$global_excludes:2:!globaltwo	a/b/globaltwo
+	$global_excludes:2:!globaltwo	b/globaltwo
+EOF
+
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
+	tr "\n" "\0" >stdin0
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' expected-default | \
+	tr "\n" "\0" >expected-default0
+sed -e 's/	"/	/' -e 's/\\//' -e 's/"$//' expected-verbose | \
+	tr ":\t\n" "\0" >expected-verbose0
+
+test_expect_success '--stdin' '
+	expect_from_stdin <expected-default &&
+	test_check_ignore "--stdin" <stdin
+'
+
+test_expect_success '--stdin -q' '
+	expect "" &&
+	test_check_ignore "-q --stdin" <stdin
+'
+
+test_expect_success '--stdin -v' '
+	expect_from_stdin <expected-verbose &&
+	test_check_ignore "-v --stdin" <stdin
+'
+
+for opts in '--stdin -z' '-z --stdin'
+do
+	test_expect_success "$opts" "
+		expect_from_stdin <expected-default0 &&
+		test_check_ignore '$opts' <stdin0
+	"
+
+	test_expect_success "$opts -q" "
+		expect "" &&
+		test_check_ignore '-q $opts' <stdin0
+	"
+
+	test_expect_success "$opts -v" "
+		expect_from_stdin <expected-verbose0 &&
+		test_check_ignore '-v $opts' <stdin0
+	"
+done
+
+cat <<-\EOF >stdin
+	../one
+	../not-ignored
+	one
+	not-ignored
+	b/on
+	b/one
+	b/one one
+	"b/one two"
+	"b/one\"three"
+	b/two
+	b/not-ignored
+	b/twooo
+	../globaltwo
+	globaltwo
+	b/globaltwo
+	../b/globaltwo
+EOF
+cat <<-\EOF >expected-default
+	../one
+	one
+	b/on
+	b/one
+	b/one one
+	b/one two
+	"b/one\"three"
+	b/two
+	b/twooo
+	../globaltwo
+	globaltwo
+	b/globaltwo
+	../b/globaltwo
+EOF
+cat <<-EOF >expected-verbose
+	.gitignore:1:one	../one
+	.gitignore:1:one	one
+	a/b/.gitignore:8:!on*	b/on
+	a/b/.gitignore:8:!on*	b/one
+	a/b/.gitignore:8:!on*	b/one one
+	a/b/.gitignore:8:!on*	b/one two
+	a/b/.gitignore:8:!on*	"b/one\"three"
+	a/b/.gitignore:9:!two	b/two
+	a/.gitignore:1:two*	b/twooo
+	$global_excludes:2:!globaltwo	../globaltwo
+	$global_excludes:2:!globaltwo	globaltwo
+	$global_excludes:2:!globaltwo	b/globaltwo
+	$global_excludes:2:!globaltwo	../b/globaltwo
+EOF
+
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
+	tr "\n" "\0" >stdin0
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' expected-default | \
+	tr "\n" "\0" >expected-default0
+sed -e 's/	"/	/' -e 's/\\//' -e 's/"$//' expected-verbose | \
+	tr ":\t\n" "\0" >expected-verbose0
+
+test_expect_success '--stdin from subdirectory' '
+	expect_from_stdin <expected-default &&
+	(
+		cd a &&
+		test_check_ignore "--stdin" <../stdin
+	)
+'
+
+test_expect_success '--stdin from subdirectory with -v' '
+	expect_from_stdin <expected-verbose &&
+	(
+		cd a &&
+		test_check_ignore "--stdin -v" <../stdin
+	)
+'
+
+for opts in '--stdin -z' '-z --stdin'
+do
+	test_expect_success "$opts from subdirectory" '
+		expect_from_stdin <expected-default0 &&
+		(
+			cd a &&
+			test_check_ignore "'"$opts"'" <../stdin0
+		)
+	'
+
+	test_expect_success "$opts from subdirectory with -v" '
+		expect_from_stdin <expected-verbose0 &&
+		(
+			cd a &&
+			test_check_ignore "'"$opts"' -v" <../stdin0
+		)
+	'
+done
+
+
+test_done
-- 
1.7.11.2.249.g31c7954

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 00/19] new git check-ignore sub-command
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (18 preceding siblings ...)
  2012-12-27  2:32 ` [PATCH v3 19/19] Add git-check-ignore sub-command Adam Spiers
@ 2012-12-27  5:15 ` Michael Leal
  2012-12-28 18:50 ` Junio C Hamano
  2012-12-28 21:23 ` Junio C Hamano
  21 siblings, 0 replies; 55+ messages in thread
From: Michael Leal @ 2012-12-27  5:15 UTC (permalink / raw)
  To: git

Adam Spiers <git <at> 
adamspiers.org> writes:

> 
> This v3 re-roll of my check-
ignore series is a reasonably 
substantial
> revamp over v2, and applies on 
top of Junio's current
> nd/attr-match-optim-more 
branch (82dce998c202).
> 
> All feedback and patches from 
the v2 series has been 
incorporated, and
> several other improvements 
too, including:
> 
>   - composite exclude_lists 
have been split up into
>     exclude_list_groups each 
containing one exclude_list per 
source
> 
>   - smaller commits for easier 
review
> 
>   - minor memory leaks have 
been fixed and verified via 
valgrind
> 
>   - t0007-ignores.sh has been 
renumbered to t0008-ignores.sh 
to avoid
>     a conflict with t0007-git-
var.sh
> 
>   - improvements to 
documentation and comments
> 
> For reference, the v2 series 
was announced here:
> 
>     http://thread.gmane.org/
gmane.comp.version-
control.git/204661/
focus=206074
> 
> All tests pass except for t91*, 
since there seems to be some
> pre-existing breakage in 
82dce998c202 relating to git-
svn.
> 
> Adam Spiers (19):
>   api-directory-listing.txt: 
update to match code
>   Improve documentation and 
comments regarding directory 
traversal API
>   dir.c: rename cryptic 'which' 
variable to more consistent 
name
>   dir.c: rename path_excluded() 
to is_path_excluded()
>   dir.c: rename 
excluded_from_list() to 
is_excluded_from_list()
>   dir.c: rename excluded() to 
is_excluded()
>   dir.c: refactor 
is_excluded_from_list()
>   dir.c: refactor is_excluded()
>   dir.c: refactor 
is_path_excluded()
>   dir.c: rename free_excludes() 
to clear_exclude_list()
>   dir.c: use a single struct 
exclude_list per source of 
excludes
>   dir.c: keep track of where 
patterns came from
>   dir.c: provide clear_directory() 
for reclaiming dir_struct memory
>   add.c: refactor treat_gitlinks()
>   add.c: remove unused 
argument from 
validate_pathspec()
>   pathspec.c: move reusable 
code from builtin/add.c
>   pathspec.c: extract new 
validate_path() for reuse
>   setup.c: document 
get_pathspec()
>   Add git-check-ignore sub-
command
> 
>  .gitignore                                        
|   1 +
>  Documentation/git-check-
ignore.txt                |  89 ++++
>  Documentation/gitignore.txt                       
|   6 +-
>  Documentation/technical/api-
directory-listing.txt |  35 +-
>  Makefile                                          
|   3 +
>  attr.c                                            |   
2 +-
>  builtin.h                                         
|   1 +
>  builtin/add.c                                     
|  84 +--
>  builtin/check-ignore.c                            
| 170 +++++++
>  builtin/clean.c                                   
|   3 +-
>  builtin/ls-files.c                                
|  11 +-
>  command-list.txt                                  
|   1 +
>  contrib/completion/git-
completion.bash            |   1 +
>  dir.c                                             | 
243 +++++++--
>  dir.h                                             |  
87 +++-
>  git.c                                             |   
1 +
>  pathspec.c                                        
| 107 ++++
>  pathspec.h                                        
|   6 +
>  setup.c                                           
|  15 +
>  t/t0008-ignores.sh                                
| 595 
++++++++++++++++++++++
>  unpack-trees.c                                    
|  14 +-
>  21 files changed, 1305 
insertions(+), 170 deletions(-)
>  create mode 100644 
Documentation/git-check-
ignore.txt
>  create mode 100644 builtin/
check-ignore.c
>  create mode 100644 
pathspec.c
>  create mode 100644 
pathspec.h
>  create mode 100755 t/t0008-
ignores.sh
> 

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 00/19] new git check-ignore sub-command
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (19 preceding siblings ...)
  2012-12-27  5:15 ` [PATCH v3 00/19] new git check-ignore sub-command Michael Leal
@ 2012-12-28 18:50 ` Junio C Hamano
  2012-12-28 19:39   ` Adam Spiers
  2012-12-28 21:23 ` Junio C Hamano
  21 siblings, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 18:50 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list, Antoine Pelisse

Adam Spiers <git@adamspiers.org> writes:

> This v3 re-roll of my check-ignore series is a reasonably substantial
> revamp over v2, and applies on top of Junio's current
> nd/attr-match-optim-more branch (82dce998c202).

Thanks.

Does this (and should this, if it doesn't) interact with the more
recent discussion around "git status --untracked/--ignored" [*1*],
which also wants to touch the recursive directory traversal logic in
"dir.c"?



[Reference]

*1* http://thread.gmane.org/gmane.comp.version-control.git/212127/focus=212136

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 00/19] new git check-ignore sub-command
  2012-12-28 18:50 ` Junio C Hamano
@ 2012-12-28 19:39   ` Adam Spiers
  2012-12-28 20:15     ` Antoine Pelisse
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-28 19:39 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list, Antoine Pelisse

On Fri, Dec 28, 2012 at 6:50 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Adam Spiers <git@adamspiers.org> writes:
>
>> This v3 re-roll of my check-ignore series is a reasonably substantial
>> revamp over v2, and applies on top of Junio's current
>> nd/attr-match-optim-more branch (82dce998c202).
>
> Thanks.
>
> Does this (and should this, if it doesn't) interact with the more
> recent discussion around "git status --untracked/--ignored" [*1*],
> which also wants to touch the recursive directory traversal logic in
> "dir.c"?

I cannot think of a reason why they would or should interact.  If I'm
wrong, I expect that either set of unit tests would show me up :-)

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 00/19] new git check-ignore sub-command
  2012-12-28 19:39   ` Adam Spiers
@ 2012-12-28 20:15     ` Antoine Pelisse
  2012-12-28 21:31       ` Junio C Hamano
  0 siblings, 1 reply; 55+ messages in thread
From: Antoine Pelisse @ 2012-12-28 20:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Adam Spiers, git list

I think they will interact, but I need to have a deeper look to Adam's series.
If it does, do you want me to base my work on the top of his branch ?

On Fri, Dec 28, 2012 at 8:39 PM, Adam Spiers <git@adamspiers.org> wrote:
> On Fri, Dec 28, 2012 at 6:50 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> Adam Spiers <git@adamspiers.org> writes:
>>
>>> This v3 re-roll of my check-ignore series is a reasonably substantial
>>> revamp over v2, and applies on top of Junio's current
>>> nd/attr-match-optim-more branch (82dce998c202).
>>
>> Thanks.
>>
>> Does this (and should this, if it doesn't) interact with the more
>> recent discussion around "git status --untracked/--ignored" [*1*],
>> which also wants to touch the recursive directory traversal logic in
>> "dir.c"?
>
> I cannot think of a reason why they would or should interact.  If I'm
> wrong, I expect that either set of unit tests would show me up :-)

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c
  2012-12-27  2:32 ` [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c Adam Spiers
@ 2012-12-28 20:32   ` Junio C Hamano
  2012-12-28 20:45     ` Adam Spiers
  2012-12-28 20:48   ` Junio C Hamano
  1 sibling, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 20:32 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> diff --git a/pathspec.h b/pathspec.h
> new file mode 100644
> index 0000000..8bb670b
> --- /dev/null
> +++ b/pathspec.h
> @@ -0,0 +1,5 @@
> +extern char *find_used_pathspec(const char **pathspec);
> +extern void fill_pathspec_matches(const char **pathspec, char *seen, int specs);
> +extern const char *treat_gitlink(const char *path);
> +extern void treat_gitlinks(const char **pathspec);
> +extern const char **validate_pathspec(const char **argv, const char *prefix);

Protect this against multiple inclusion with "#ifndef PATHSPEC_H".

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 18/19] setup.c: document get_pathspec()
  2012-12-27  2:32 ` [PATCH v3 18/19] setup.c: document get_pathspec() Adam Spiers
@ 2012-12-28 20:36   ` Junio C Hamano
  2012-12-28 20:40     ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 20:36 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> Since we have just created a new pathspec-handling library, now is a
> good time to add some comments explaining get_pathspec().
>
> Signed-off-by: Adam Spiers <git@adamspiers.org>
> ---

Yes, but we would rather not to see new users of this function added
to our codebase in its current form, as explained in the nearby
comment.  We would want to migrate everybody to "struct pathspec"
based interface to support magic pathspecs in the longer term.

>  setup.c | 15 +++++++++++++++
>  1 file changed, 15 insertions(+)
>
> diff --git a/setup.c b/setup.c
> index 7663a4c..03d6d5c 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -249,6 +249,21 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char
>  		return prefix_path(prefix, prefixlen, copyfrom);
>  }
>  
> +/*
> + * prefix - a path relative to the root of the working tree
> + * pathspec - a list of paths underneath the prefix path
> + *
> + * Iterates over pathspec, prepending each path with prefix,
> + * and return the resulting list.
> + *
> + * If pathspec is empty, return a singleton list containing prefix.
> + *
> + * If pathspec and prefix are both empty, return an empty list.
> + *
> + * This is typically used by built-in commands such as add.c, in order
> + * to normalize argv arguments provided to the built-in into a list of
> + * paths to process, all relative to the root of the working tree.
> + */
>  const char **get_pathspec(const char *prefix, const char **pathspec)
>  {
>  	const char *entry = *pathspec;

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 18/19] setup.c: document get_pathspec()
  2012-12-28 20:36   ` Junio C Hamano
@ 2012-12-28 20:40     ` Adam Spiers
  2012-12-29  0:52       ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-28 20:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list

On Fri, Dec 28, 2012 at 8:36 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Adam Spiers <git@adamspiers.org> writes:
>
>> Since we have just created a new pathspec-handling library, now is a
>> good time to add some comments explaining get_pathspec().
>>
>> Signed-off-by: Adam Spiers <git@adamspiers.org>
>> ---
>
> Yes, but we would rather not to see new users of this function added
> to our codebase in its current form, as explained in the nearby
> comment.  We would want to migrate everybody to "struct pathspec"
> based interface to support magic pathspecs in the longer term.

I see.  Please feel free to drop that patch from the series or amend
as you see fit.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 17/19] pathspec.c: extract new validate_path() for reuse
  2012-12-27  2:32 ` [PATCH v3 17/19] pathspec.c: extract new validate_path() for reuse Adam Spiers
@ 2012-12-28 20:44   ` Junio C Hamano
  2012-12-28 21:08     ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 20:44 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> This will be reused by a new git check-ignore command.
>
> Signed-off-by: Adam Spiers <git@adamspiers.org>
> ---
>  pathspec.c | 20 ++++++++++++++------
>  pathspec.h |  1 +
>  2 files changed, 15 insertions(+), 6 deletions(-)
>
> diff --git a/pathspec.c b/pathspec.c
> index 8aea0d2..6724121 100644
> --- a/pathspec.c
> +++ b/pathspec.c
> @@ -77,9 +77,20 @@ void treat_gitlinks(const char **pathspec)
>  }
>  
>  /*
> + * Dies if the given path refers to a file inside a symlinked
> + * directory.
> + */
> +void validate_path(const char *path, const char *prefix)

The name needs to be a lot more specific.

There may be 47 different kinds of "validations" various callers may
want to do on a path, but this function only caters to one kind of
callers that want to make sure that the path refers to something
that we would directly add to our index.

> +{
> +	if (has_symlink_leading_path(path, strlen(path))) {
> +		int len = prefix ? strlen(prefix) : 0;
> +		die(_("'%s' is beyond a symbolic link"), path + len);
> +	}
> +}

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c
  2012-12-28 20:32   ` Junio C Hamano
@ 2012-12-28 20:45     ` Adam Spiers
  2012-12-29  0:40       ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-28 20:45 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list

On Fri, Dec 28, 2012 at 8:32 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Adam Spiers <git@adamspiers.org> writes:
>
>> diff --git a/pathspec.h b/pathspec.h
>> new file mode 100644
>> index 0000000..8bb670b
>> --- /dev/null
>> +++ b/pathspec.h
>> @@ -0,0 +1,5 @@
>> +extern char *find_used_pathspec(const char **pathspec);
>> +extern void fill_pathspec_matches(const char **pathspec, char *seen, int specs);
>> +extern const char *treat_gitlink(const char *path);
>> +extern void treat_gitlinks(const char **pathspec);
>> +extern const char **validate_pathspec(const char **argv, const char *prefix);
>
> Protect this against multiple inclusion with "#ifndef PATHSPEC_H".

Yep good idea, how should I submit this?  It will cause a trivially
resolvable conflict with the next patch in the series (17/19):

  pathspec.c: extract new validate_path() for reuse

but I'd prefer not to re-roll 16--19 when just 16 and 17 are sufficient.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c
  2012-12-27  2:32 ` [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c Adam Spiers
  2012-12-28 20:32   ` Junio C Hamano
@ 2012-12-28 20:48   ` Junio C Hamano
  2012-12-28 21:15     ` Adam Spiers
  1 sibling, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 20:48 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> diff --git a/pathspec.c b/pathspec.c
> new file mode 100644
> index 0000000..8aea0d2
> --- /dev/null
> +++ b/pathspec.c
> @@ -0,0 +1,99 @@
> +#include "cache.h"
> +#include "dir.h"
> +#include "pathspec.h"
> +
> +void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
> +{

It did not matter while it was an implementation detail of "git
add", but as a public function, something needs to clarify that this
"fill"s matches *against the index*, not against a tree or the files
in the current directory.  The same comment applies to all the
internal functions this patch exposes to the outside world.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 17/19] pathspec.c: extract new validate_path() for reuse
  2012-12-28 20:44   ` Junio C Hamano
@ 2012-12-28 21:08     ` Adam Spiers
  0 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-28 21:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list

On Fri, Dec 28, 2012 at 8:44 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Adam Spiers <git@adamspiers.org> writes:
>
>> This will be reused by a new git check-ignore command.
>>
>> Signed-off-by: Adam Spiers <git@adamspiers.org>
>> ---
>>  pathspec.c | 20 ++++++++++++++------
>>  pathspec.h |  1 +
>>  2 files changed, 15 insertions(+), 6 deletions(-)
>>
>> diff --git a/pathspec.c b/pathspec.c
>> index 8aea0d2..6724121 100644
>> --- a/pathspec.c
>> +++ b/pathspec.c
>> @@ -77,9 +77,20 @@ void treat_gitlinks(const char **pathspec)
>>  }
>>
>>  /*
>> + * Dies if the given path refers to a file inside a symlinked
>> + * directory.
>> + */
>> +void validate_path(const char *path, const char *prefix)
>
> The name needs to be a lot more specific.
>
> There may be 47 different kinds of "validations" various callers may
> want to do on a path, but this function only caters to one kind of
> callers that want to make sure that the path refers to something
> that we would directly add to our index.
>
>> +{
>> +     if (has_symlink_leading_path(path, strlen(path))) {
>> +             int len = prefix ? strlen(prefix) : 0;
>> +             die(_("'%s' is beyond a symbolic link"), path + len);
>> +     }
>> +}

Good point.  Which do you prefer of these suggested names?

- die_if_path_beyond_symlink()
- validate_path_not_beyond_symlink()
- die_if_symlink_leading_path()
- validate_no_symlink_leading_path()
- validate_path_addable_to_index()

Or something else?

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c
  2012-12-28 20:48   ` Junio C Hamano
@ 2012-12-28 21:15     ` Adam Spiers
  0 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-28 21:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list

On Fri, Dec 28, 2012 at 8:48 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Adam Spiers <git@adamspiers.org> writes:
>
>> diff --git a/pathspec.c b/pathspec.c
>> new file mode 100644
>> index 0000000..8aea0d2
>> --- /dev/null
>> +++ b/pathspec.c
>> @@ -0,0 +1,99 @@
>> +#include "cache.h"
>> +#include "dir.h"
>> +#include "pathspec.h"
>> +
>> +void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
>> +{
>
> It did not matter while it was an implementation detail of "git
> add", but as a public function, something needs to clarify that this
> "fill"s matches *against the index*, not against a tree or the files
> in the current directory.  The same comment applies to all the
> internal functions this patch exposes to the outside world.

Prior to submitting the v3 series, I attempted to understand
fill_pathspec_matches() and find_used_pathspec() well enough to
document them all, but I failed.  Perhaps some kind soul could explain
what is the exact purpose of these functions, and in particular the
role of char *seen in both?

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 19/19] Add git-check-ignore sub-command
  2012-12-27  2:32 ` [PATCH v3 19/19] Add git-check-ignore sub-command Adam Spiers
@ 2012-12-28 21:21   ` Junio C Hamano
  2012-12-29  1:23     ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 21:21 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
> new file mode 100644
> index 0000000..c825736
> --- /dev/null
> +++ b/builtin/check-ignore.c
> @@ -0,0 +1,170 @@
> +#include "builtin.h"
> +#include "cache.h"
> +#include "dir.h"
> +#include "quote.h"
> +#include "pathspec.h"
> +#include "parse-options.h"
> +
> +static int quiet, verbose, stdin_paths;
> +static const char * const check_ignore_usage[] = {
> +"git check-ignore [options] pathname...",
> +"git check-ignore [options] --stdin < <list-of-paths>",
> +NULL
> +};
> +
> +static int null_term_line;
> +
> +static const struct option check_ignore_options[] = {
> +	OPT__QUIET(&quiet, N_("suppress progress reporting")),
> +	OPT__VERBOSE(&verbose, N_("be verbose")),
> +	OPT_GROUP(""),
> +	OPT_BOOLEAN(0, "stdin", &stdin_paths,
> +		    N_("read file names from stdin")),
> +	OPT_BOOLEAN('z', NULL, &null_term_line,
> +		    N_("input paths are terminated by a null character")),
> +	OPT_END()
> +};
> +
> +static void output_exclude(const char *path, struct exclude *exclude)
> +{
> +	char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
> +	char *dir  = (exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";

That's inconsistent.  Either s/bang/negated/ or s/dir/slash/ (but
obviously not both).

> +static int check_ignore(const char *prefix, const char **pathspec)
> +{
> +	struct dir_struct dir;
> +...
> +	if (pathspec) {
> +...
> +	} else {
> +		printf("no pathspec\n");
> +	}

Is this an error message, an informative warning, or what?  The
command is presumably for script consumption downstream of a pipe.
Does it make sense to emit this message to their standard input?
Does it even have to be output?  Especially what should happen when
the user says "--quiet"?

Perhaps

	if (!quiet)
		fprintf(stderr, "no pathspec given.\n");

or something?

> +int cmd_check_ignore(int argc, const char **argv, const char *prefix)
> +{
> +	int num_ignored = 0;

I do not think you need to initialize this one.

> +	if (stdin_paths) {
> +		num_ignored = check_ignore_stdin_paths(prefix);
> +	} else {
> +		num_ignored = check_ignore(prefix, argv);
> +		maybe_flush_or_die(stdout, "ignore to stdout");
> +	}
> +
> +	return num_ignored > 0 ? 0 : 1;

Given that num_ignored will always be >=0, that is a funny way to
say

	return !num_ignored;

or if you plan to report a non-fatal error as negative return from
the two check_ignore* functions, perhaps:

	return num_ignored <= 0;

> diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
> new file mode 100755
> index 0000000..3937ca4
> --- /dev/null
> +++ b/t/t0008-ignores.sh
> @@ -0,0 +1,595 @@
> +#!/bin/sh
> +
> +test_description=check-ignore
> +
> +. ./test-lib.sh
> +
> +init_vars () {
> +	global_excludes="$(pwd)/global-excludes"
> +}
> +
> +enable_global_excludes () {
> +	init_vars
> +	git config core.excludesfile "$global_excludes"
> +}
> +
> +expect_in () {
> +	dest="$HOME/expected-$1" text="$2"
> +	if test -z "$text"
> +	then
> +		>"$dest" # avoid newline
> +	else
> +		echo "$text" >"$dest"
> +	fi
> +}
> +
> +expect () {
> +	expect_in stdout "$1"
> +}
> +
> +expect_from_stdin () {
> +	cat >"$HOME/expected-stdout"
> +}
> +
> +test_stderr () {
> +	expected="$1"
> +	expect_in stderr "$1" &&
> +	test_cmp "$HOME/expected-stderr" "$HOME/stderr"
> +}
> +
> +stderr_contains () {
> +	regexp="$1"
> +	if grep -q "$regexp" "$HOME/stderr"

Please do not use "grep -q"; the output from commands in test
harness is normally hidden already.  This only makes script more
cluttered and robs debuggers of potentially useful output when the
test script is run with "-v".

> +test_check_ignore () {
> +	args="$1" expect_code="${2:-0}" global_args="$3"
> +
> +	init_vars &&
> +	rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" &&
> +	echo git $global_args check-ignore $quiet_opt $verbose_opt $args \
> +		>"$HOME/cmd" &&

Does a debugger needs this "cmd" file when we already have "-v" option?

> +test_expect_success_multi () {
> +	prereq=
> +	if test $# -eq 4
> +	then
> +		prereq=$1
> +		shift
> +	fi
> +	testname="$1" expect_verbose="$2" code="$3"
> +
> +	expect=$( echo "$expect_verbose" | sed -e 's/.*	//' )
> +
> +	test_expect_success $prereq "$testname" "
> +		expect '$expect' &&
> +		$code
> +	"

This is brittle when $expect may have single quotes, isn't it?
Perhaps

	test_expect_success $prereq "$testname" '
		expect "$expect" && eval "$code"
        '

may fix it, but in general I hate to see each test script trying to
invent their own mini-harness like this (and getting them wrong).

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 00/19] new git check-ignore sub-command
  2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
                   ` (20 preceding siblings ...)
  2012-12-28 18:50 ` Junio C Hamano
@ 2012-12-28 21:23 ` Junio C Hamano
  21 siblings, 0 replies; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 21:23 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

After skimming the series twice quickly, I found that the early part
of refactorings are excellently done.  Making existing private
functions into public needs a lot more careful thought on namings, I
think, though.

The end result looks promising.  Thanks for a pleasant read.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 00/19] new git check-ignore sub-command
  2012-12-28 20:15     ` Antoine Pelisse
@ 2012-12-28 21:31       ` Junio C Hamano
  0 siblings, 0 replies; 55+ messages in thread
From: Junio C Hamano @ 2012-12-28 21:31 UTC (permalink / raw)
  To: Antoine Pelisse; +Cc: Adam Spiers, git list

Antoine Pelisse <apelisse@gmail.com> writes:

> I think they will interact, but I need to have a deeper look to Adam's series.
> If it does, do you want me to base my work on the top of his branch ?

Not necessarily.  If it becomes absolutely necessary to introduce
patch dependencies, I would rather see an addition of new command
rebased on a fix.

I just wanted to make sure that parties touching the same area of
the codebase (and me who will be merging their efforts) are aware of
what is going on.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c
  2012-12-28 20:45     ` Adam Spiers
@ 2012-12-29  0:40       ` Adam Spiers
  0 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-29  0:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list

On Fri, Dec 28, 2012 at 8:45 PM, Adam Spiers <git@adamspiers.org> wrote:
> On Fri, Dec 28, 2012 at 8:32 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> Adam Spiers <git@adamspiers.org> writes:
>>
>>> diff --git a/pathspec.h b/pathspec.h
>>> new file mode 100644
>>> index 0000000..8bb670b
>>> --- /dev/null
>>> +++ b/pathspec.h
>>> @@ -0,0 +1,5 @@
>>> +extern char *find_used_pathspec(const char **pathspec);
>>> +extern void fill_pathspec_matches(const char **pathspec, char *seen, int specs);
>>> +extern const char *treat_gitlink(const char *path);
>>> +extern void treat_gitlinks(const char **pathspec);
>>> +extern const char **validate_pathspec(const char **argv, const char *prefix);
>>
>> Protect this against multiple inclusion with "#ifndef PATHSPEC_H".
>
> Yep good idea, how should I submit this?  It will cause a trivially
> resolvable conflict with the next patch in the series (17/19):
>
>   pathspec.c: extract new validate_path() for reuse

I was wrong about that - it didn't cause a conflict, although it does
marginally change the context at the end of the pathspec.h hunk in the
above patch.

> but I'd prefer not to re-roll 16--19 when just 16 and 17 are sufficient.

Based on your other feedback, all of 16--19 require changes, and as
things stand, conveniently nothing earlier in the series does, so I'll
re-roll those four once the outstanding issues are all resolved.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 18/19] setup.c: document get_pathspec()
  2012-12-28 20:40     ` Adam Spiers
@ 2012-12-29  0:52       ` Adam Spiers
  2012-12-29  1:36         ` Junio C Hamano
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-29  0:52 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list

On Fri, Dec 28, 2012 at 8:40 PM, Adam Spiers <git@adamspiers.org> wrote:
> On Fri, Dec 28, 2012 at 8:36 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> Adam Spiers <git@adamspiers.org> writes:
>>
>>> Since we have just created a new pathspec-handling library, now is a
>>> good time to add some comments explaining get_pathspec().
>>>
>>> Signed-off-by: Adam Spiers <git@adamspiers.org>
>>> ---
>>
>> Yes, but we would rather not to see new users of this function added
>> to our codebase in its current form, as explained in the nearby
>> comment.  We would want to migrate everybody to "struct pathspec"
>> based interface to support magic pathspecs in the longer term.
>
> I see.  Please feel free to drop that patch from the series or amend
> as you see fit.

I've added this sentence to the top of the comments above
get_pathspec():

    /*
     * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
     * based interface - see pathspec_magic above.
     *
    [...]

That should be sufficient to discourage people from adding new users
of get_pathspec().

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 19/19] Add git-check-ignore sub-command
  2012-12-28 21:21   ` Junio C Hamano
@ 2012-12-29  1:23     ` Adam Spiers
  2012-12-29  3:32       ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2012-12-29  1:23 UTC (permalink / raw)
  To: git list

On Fri, Dec 28, 2012 at 01:21:02PM -0800, Junio C Hamano wrote:
> Adam Spiers <git@adamspiers.org> writes:
> > +static void output_exclude(const char *path, struct exclude *exclude)
> > +{
> > +	char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
> > +	char *dir  = (exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
> 
> That's inconsistent.  Either s/bang/negated/ or s/dir/slash/ (but
> obviously not both).

OK.  I'll make the use of parentheses there consistent too.

> > +static int check_ignore(const char *prefix, const char **pathspec)
> > +{
> > +	struct dir_struct dir;
> > +...
> > +	if (pathspec) {
> > +...
> > +	} else {
> > +		printf("no pathspec\n");
> > +	}
> 
> Is this an error message, an informative warning, or what?  The
> command is presumably for script consumption downstream of a pipe.
> Does it make sense to emit this message to their standard input?
> Does it even have to be output?  Especially what should happen when
> the user says "--quiet"?
>
> Perhaps
> 
> 	if (!quiet)
> 		fprintf(stderr, "no pathspec given.\n");
> 
> or something?

Hmm.  I suspect this was an unfinished remnant of one of my early
prototypes, because this code path never gets hit.  There's even a
test which checks that a fatal error is generated when no pathspecs
are given via argv.  

However, the test for behaviour when no pathspecs are given via
--stdin *is* missing.  In this case, I think the code you suggest
above makes sense for generating a warning, and the existing behaviour
of returning an exit code of 1 also seems correct.  I have added your
code and a test to cover it.

> > +int cmd_check_ignore(int argc, const char **argv, const char *prefix)
> > +{
> > +	int num_ignored = 0;
> 
> I do not think you need to initialize this one.

Fixed.

> > +	if (stdin_paths) {
> > +		num_ignored = check_ignore_stdin_paths(prefix);
> > +	} else {
> > +		num_ignored = check_ignore(prefix, argv);
> > +		maybe_flush_or_die(stdout, "ignore to stdout");
> > +	}
> > +
> > +	return num_ignored > 0 ? 0 : 1;
> 
> Given that num_ignored will always be >=0, that is a funny way to
> say
> 
> 	return !num_ignored;

Personally I find that harder to read, but I'm not a regular C
programmer so I'll defer to your sense of style.

> or if you plan to report a non-fatal error as negative return from
> the two check_ignore* functions,

I don't think that's necessary here, but I'll bear it in mind.

> > +stderr_contains () {
> > +	regexp="$1"
> > +	if grep -q "$regexp" "$HOME/stderr"
> 
> Please do not use "grep -q"; the output from commands in test
> harness is normally hidden already.  This only makes script more
> cluttered and robs debuggers of potentially useful output when the
> test script is run with "-v".

Fixed.

> > +test_check_ignore () {
> > +	args="$1" expect_code="${2:-0}" global_args="$3"
> > +
> > +	init_vars &&
> > +	rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" &&
> > +	echo git $global_args check-ignore $quiet_opt $verbose_opt $args \
> > +		>"$HOME/cmd" &&
> 
> Does a debugger needs this "cmd" file when we already have "-v" option?

Yes, I think it's useful; for example:

    $ ./t0008-ignores.sh -i -v

    [... test fails ...]

    $ cd trash\ directory.t0008-ignores
    $ gdb --args ../../$(<cmd)

> > +test_expect_success_multi () {
> > +	prereq=
> > +	if test $# -eq 4
> > +	then
> > +		prereq=$1
> > +		shift
> > +	fi
> > +	testname="$1" expect_verbose="$2" code="$3"
> > +
> > +	expect=$( echo "$expect_verbose" | sed -e 's/.*	//' )
> > +
> > +	test_expect_success $prereq "$testname" "
> > +		expect '$expect' &&
> > +		$code
> > +	"
> 
> This is brittle when $expect may have single quotes, isn't it?

Currently $expect will never have single quotes, but I grant this may
change in the future.

> Perhaps
> 
> 	test_expect_success $prereq "$testname" '
> 		expect "$expect" && eval "$code"
>         '
> 
> may fix it,

Yes that works, thanks.

> but in general I hate to see each test script trying to
> invent their own mini-harness like this (and getting them wrong).

test_expect_success_multi is specific to check-ignore and avoids a
huge amount of code duplication.  I can't think of a better approach
but if you can then I'm all ears.

I believe this only leaves two items from your review of v3 which are
yet to be resolved:

1. What should validate_path() should be renamed to in order to avoid
   ambiguity with other path validation functions?  Currently my
   preferred choice is die_if_path_beyond_symlink() but I don't really
   mind - I'll go with that unless I hear someone prefers another
   name.

2. The now-public functions fill_pathspec_matches() and
   find_used_pathspec(), need to be documented, and in particular need
   to mention that they traverse the index not a tree or the working
   directory.  I'll work on improving my understanding of these
   functions to the point where I can document them accurately (but
   hints are still welcome).

Once these are both resolved, I'll re-roll patches 16--19 only of the
v3 series, labelled as v4.

FYI, attached is the diff between check-ignore-v3 and my current
check-ignore, which is available at github:

    https://github.com/aspiers/git/commits/check-ignore

diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index c825736..06e250e 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -27,8 +27,8 @@ static const struct option check_ignore_options[] = {
 
 static void output_exclude(const char *path, struct exclude *exclude)
 {
-	char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
-	char *dir  = (exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
+	char *bang  = exclude->flags & EXC_FLAG_NEGATIVE  ? "!" : "";
+	char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : "";
 	if (!null_term_line) {
 		if (!verbose) {
 			write_name_quoted(path, stdout, '\n');
@@ -36,7 +36,7 @@ static void output_exclude(const char *path, struct exclude *exclude)
 			quote_c_style(exclude->el->src, NULL, stdout, 0);
 			printf(":%d:%s%s%s\t",
 			       exclude->srcpos,
-			       bang, exclude->pattern, dir);
+			       bang, exclude->pattern, slash);
 			quote_c_style(path, NULL, stdout, 0);
 			fputc('\n', stdout);
 		}
@@ -47,7 +47,7 @@ static void output_exclude(const char *path, struct exclude *exclude)
 			printf("%s%c%d%c%s%s%s%c%s%c",
 			       exclude->el->src, '\0',
 			       exclude->srcpos, '\0',
-			       bang, exclude->pattern, dir, '\0',
+			       bang, exclude->pattern, slash, '\0',
 			       path, '\0');
 		}
 	}
@@ -57,8 +57,10 @@ static int check_ignore(const char *prefix, const char **pathspec)
 {
 	struct dir_struct dir;
 	const char *path;
-	char *seen = NULL;
-	int num_ignored = 0;
+	char *seen;
+	int num_ignored = 0, i;
+	struct path_exclude_check check;
+	struct exclude *exclude;
 
 	/* read_cache() is only necessary so we can watch out for submodules. */
 	if (read_cache() < 0)
@@ -68,38 +70,36 @@ static int check_ignore(const char *prefix, const char **pathspec)
 	dir.flags |= DIR_COLLECT_IGNORED;
 	setup_standard_excludes(&dir);
 
-	if (pathspec) {
-		int i;
-		struct path_exclude_check check;
-		struct exclude *exclude;
-
-		path_exclude_check_init(&check, &dir);
-		if (!seen)
-			seen = find_used_pathspec(pathspec);
-		for (i = 0; pathspec[i]; i++) {
-			const char *full_path;
-			path = pathspec[i];
-			full_path = prefix_path(prefix, prefix
-						? strlen(prefix) : 0, path);
-			full_path = treat_gitlink(full_path);
-			validate_path(full_path, prefix);
-			if (!seen[i] && path[0]) {
-				int dtype = DT_UNKNOWN;
-				exclude = last_exclude_matching_path(&check, full_path,
-								     -1, &dtype);
-				if (exclude) {
-					if (!quiet)
-						output_exclude(path, exclude);
-					num_ignored++;
-				}
+	if (!pathspec || !*pathspec) {
+		if (!quiet)
+			fprintf(stderr, "no pathspec given.\n");
+		return 0;
+	}
+
+	path_exclude_check_init(&check, &dir);
+	seen = find_used_pathspec(pathspec);
+	for (i = 0; pathspec[i]; i++) {
+		const char *full_path;
+		path = pathspec[i];
+		full_path = prefix_path(prefix, prefix
+					? strlen(prefix) : 0, path);
+		full_path = treat_gitlink(full_path);
+		die_if_path_beyond_symlink(full_path, prefix);
+		if (!seen[i] && path[0]) {
+			int dtype = DT_UNKNOWN;
+			exclude = last_exclude_matching_path(&check, full_path,
+							     -1, &dtype);
+			if (exclude) {
+				if (!quiet)
+					output_exclude(path, exclude);
+				num_ignored++;
 			}
 		}
-		free(seen);
-		clear_directory(&dir);
-		path_exclude_check_clear(&check);
-	} else {
-		printf("no pathspec\n");
 	}
+	free(seen);
+	clear_directory(&dir);
+	path_exclude_check_clear(&check);
+
 	return num_ignored;
 }
 
@@ -136,7 +136,7 @@ static int check_ignore_stdin_paths(const char *prefix)
 
 int cmd_check_ignore(int argc, const char **argv, const char *prefix)
 {
-	int num_ignored = 0;
+	int num_ignored;
 
 	git_config(git_default_config, NULL);
 
@@ -144,7 +144,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
 			     check_ignore_usage, 0);
 
 	if (stdin_paths) {
-		if (0 < argc)
+		if (argc > 0)
 			die(_("cannot specify pathnames with --stdin"));
 	} else {
 		if (null_term_line)
@@ -166,5 +166,5 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
 		maybe_flush_or_die(stdout, "ignore to stdout");
 	}
 
-	return num_ignored > 0 ? 0 : 1;
+	return !num_ignored;
 }
diff --git a/pathspec.c b/pathspec.c
index 6724121..3789b14 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -36,9 +36,10 @@ char *find_used_pathspec(const char **pathspec)
 }
 
 /*
- * Check whether path refers to a submodule, or something inside a
- * submodule.  If the former, returns the path with any trailing slash
- * stripped.  If the latter, dies with an error message.
+ * Check the index to see whether path refers to a submodule, or
+ * something inside a submodule.  If the former, returns the path with
+ * any trailing slash stripped.  If the latter, dies with an error
+ * message.
  */
 const char *treat_gitlink(const char *path)
 {
@@ -78,9 +79,9 @@ void treat_gitlinks(const char **pathspec)
 
 /*
  * Dies if the given path refers to a file inside a symlinked
- * directory.
+ * directory in the index.
  */
-void validate_path(const char *path, const char *prefix)
+void die_if_path_beyond_symlink(const char *path, const char *prefix)
 {
 	if (has_symlink_leading_path(path, strlen(path))) {
 		int len = prefix ? strlen(prefix) : 0;
@@ -90,7 +91,8 @@ void validate_path(const char *path, const char *prefix)
 
 /*
  * Normalizes argv relative to prefix, via get_pathspec(), and then
- * runs validate_path() on each path in the normalized list.
+ * runs die_if_path_beyond_symlink() on each path in the normalized
+ * list.
  */
 const char **validate_pathspec(const char **argv, const char *prefix)
 {
@@ -99,7 +101,7 @@ const char **validate_pathspec(const char **argv, const char *prefix)
 	if (pathspec) {
 		const char **p;
 		for (p = pathspec; *p; p++) {
-			validate_path(*p, prefix);
+			die_if_path_beyond_symlink(*p, prefix);
 		}
 	}
 
diff --git a/pathspec.h b/pathspec.h
index c251441..1961b19 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -1,6 +1,11 @@
+#ifndef PATHSPEC_H
+#define PATHSPEC_H
+
 extern char *find_used_pathspec(const char **pathspec);
 extern void fill_pathspec_matches(const char **pathspec, char *seen, int specs);
 extern const char *treat_gitlink(const char *path);
 extern void treat_gitlinks(const char **pathspec);
 extern void validate_path(const char *path, const char *prefix);
 extern const char **validate_pathspec(const char **argv, const char *prefix);
+
+#endif /* PATHSPEC_H */
diff --git a/setup.c b/setup.c
index 03d6d5c..9570147 100644
--- a/setup.c
+++ b/setup.c
@@ -250,8 +250,12 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char
 }
 
 /*
- * prefix - a path relative to the root of the working tree
- * pathspec - a list of paths underneath the prefix path
+ * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
+ * based interface - see pathspec_magic above.
+ *
+ * Arguments:
+ *  - prefix - a path relative to the root of the working tree
+ *  - pathspec - a list of paths underneath the prefix path
  *
  * Iterates over pathspec, prepending each path with prefix,
  * and return the resulting list.
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
index 3937ca4..8780b1e 100755
--- a/t/t0008-ignores.sh
+++ b/t/t0008-ignores.sh
@@ -39,7 +39,7 @@ test_stderr () {
 
 stderr_contains () {
 	regexp="$1"
-	if grep -q "$regexp" "$HOME/stderr"
+	if grep "$regexp" "$HOME/stderr"
 	then
 		return 0
 	else
@@ -86,10 +86,10 @@ test_expect_success_multi () {
 
 	expect=$( echo "$expect_verbose" | sed -e 's/.*	//' )
 
-	test_expect_success $prereq "$testname" "
-		expect '$expect' &&
-		$code
-	"
+	test_expect_success $prereq "$testname" '
+		expect "$expect" &&
+		eval "$code"
+	'
 
 	for quiet_opt in '-q' '--quiet'
 	do
@@ -164,6 +164,15 @@ test_expect_success_multi 'empty command line' '' '
 	stderr_contains "fatal: no path specified"
 '
 
+test_expect_success_multi '--stdin with empty STDIN' '' '
+	test_check_ignore "--stdin" 1 </dev/null &&
+	if test -n "$quiet_opt"; then
+		test_stderr ""
+	else
+		test_stderr "no pathspec given."
+	fi
+'
+
 test_expect_success '-q with multiple args' '
 	expect "" &&
 	test_check_ignore "-q one two" 128 &&

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 18/19] setup.c: document get_pathspec()
  2012-12-29  0:52       ` Adam Spiers
@ 2012-12-29  1:36         ` Junio C Hamano
  0 siblings, 0 replies; 55+ messages in thread
From: Junio C Hamano @ 2012-12-29  1:36 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> I've added this sentence to the top of the comments above
> get_pathspec():
>
>     /*
>      * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
>      * based interface - see pathspec_magic above.
>      *
>     [...]
>
> That should be sufficient to discourage people from adding new users
> of get_pathspec().

Yeah, that sounds sensible.

Thanks, and happy new year to you ;-)

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 19/19] Add git-check-ignore sub-command
  2012-12-29  1:23     ` Adam Spiers
@ 2012-12-29  3:32       ` Adam Spiers
  0 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2012-12-29  3:32 UTC (permalink / raw)
  To: git list

On Sat, Dec 29, 2012 at 01:23:52AM +0000, Adam Spiers wrote:
> FYI, attached is the diff between check-ignore-v3 and my current
> check-ignore, which is available at github:
> 
>     https://github.com/aspiers/git/commits/check-ignore

[snipped]

> diff --git a/pathspec.c b/pathspec.c
> index 6724121..3789b14 100644
> --- a/pathspec.c
> +++ b/pathspec.c

[snipped]

> -void validate_path(const char *path, const char *prefix)
> +void die_if_path_beyond_symlink(const char *path, const char *prefix)

[snipped]

> diff --git a/pathspec.h b/pathspec.h
> index c251441..1961b19 100644
> --- a/pathspec.h
> +++ b/pathspec.h
> @@ -1,6 +1,11 @@
> +#ifndef PATHSPEC_H
> +#define PATHSPEC_H
> +
>  extern char *find_used_pathspec(const char **pathspec);
>  extern void fill_pathspec_matches(const char **pathspec, char *seen, int specs);
>  extern const char *treat_gitlink(const char *path);
>  extern void treat_gitlinks(const char **pathspec);
>  extern void validate_path(const char *path, const char *prefix);
               ^^^^^^^^^^^^^
I forgot to rename this one.  Will be fixed in v4.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API
  2012-12-27  2:32 ` [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API Adam Spiers
@ 2013-01-01 20:52   ` Junio C Hamano
  2013-01-02 12:54     ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2013-01-01 20:52 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
> index 0356d25..944fc39 100644
> --- a/Documentation/technical/api-directory-listing.txt
> +++ b/Documentation/technical/api-directory-listing.txt
> @@ -9,8 +9,11 @@ Data structure
>  --------------
>  
>  `struct dir_struct` structure is used to pass directory traversal
> -options to the library and to record the paths discovered.  The notable
> -options are:
> +options to the library and to record the paths discovered.  A single
> +`struct dir_struct` is used regardless of whether or not the traversal
> +recursively descends into subdirectories.

I am somewhat lukewarm on this part of the change.

The added "regardless of..." does not seem to add as much value as
the two extra lines the patch spends.  If we say something like:

	A `struct dir_struct` structure is used to pass options to
        traverse directories recursively, and to record all the
        paths discovered by the traversal.

it might be much more direct and informative, I suspect, though.

After all, the word "traversal" pretty much implies that the library
goes in and out of the directories recursively.

> @@ -39,7 +42,7 @@ options are:
>  	If set, recurse into a directory that looks like a git
>  	directory.  Otherwise it is shown as a directory.
>  
> -The result of the enumeration is left in these fields::
> +The result of the enumeration is left in these fields:

Good eyes.

> diff --git a/dir.c b/dir.c
> index ee8e711..89e27a6 100644
> --- a/dir.c
> +++ b/dir.c
> @@ -2,6 +2,8 @@
>   * This handles recursive filename detection with exclude
>   * files, index knowledge etc..
>   *
> + * See Documentation/technical/api-directory-listing.txt
> + *
>   * Copyright (C) Linus Torvalds, 2005-2006
>   *		 Junio Hamano, 2005-2006
>   */
> @@ -476,6 +478,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
>  		die("cannot use %s as an exclude file", fname);
>  }
>  
> +/*
> + * Loads the per-directory exclude list for the substring of base
> + * which has a char length of baselen.
> + */
>  static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
>  {
>  	struct exclude_list *el;
> @@ -486,7 +492,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
>  	    (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
>  		return; /* too long a path -- ignore */
>  
> -	/* Pop the ones that are not the prefix of the path being checked. */
> +	/* Pop the directories that are not the prefix of the path being checked. */

The "one" does not refer to a "directory", but to an "exclude-list".

	Pop the ones that are not for parent directories of the path
	being checked

perhaps?

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API
  2013-01-01 20:52   ` Junio C Hamano
@ 2013-01-02 12:54     ` Adam Spiers
  2013-01-06 12:02       ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2013-01-02 12:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git list

On Tue, Jan 1, 2013 at 8:52 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Adam Spiers <git@adamspiers.org> writes:
>
>> diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
>> index 0356d25..944fc39 100644
>> --- a/Documentation/technical/api-directory-listing.txt
>> +++ b/Documentation/technical/api-directory-listing.txt
>> @@ -9,8 +9,11 @@ Data structure
>>  --------------
>>
>>  `struct dir_struct` structure is used to pass directory traversal
>> -options to the library and to record the paths discovered.  The notable
>> -options are:
>> +options to the library and to record the paths discovered.  A single
>> +`struct dir_struct` is used regardless of whether or not the traversal
>> +recursively descends into subdirectories.
>
> I am somewhat lukewarm on this part of the change.
>
> The added "regardless of..." does not seem to add as much value as
> the two extra lines the patch spends.  If we say something like:
>
>         A `struct dir_struct` structure is used to pass options to
>         traverse directories recursively, and to record all the
>         paths discovered by the traversal.
>
> it might be much more direct and informative, I suspect, though.

I somewhat disagree ;) When I first encountered this code, I naturally
assumed that one struct would be created per sub-directory traversed.
This is after all a natural and very common design pattern.  The point
of this hunk was to make it explicitly clear that this is *not* how it
works in dir.c.  IMHO your rewording still contains a certain amount of
ambiguity in this regard.  For example, it could mean that each
dir_struct records all the paths discovered underneath the sub-directory
it represents, and that these recursively bubble up to a top-level
dir_struct.

>> diff --git a/dir.c b/dir.c
>> index ee8e711..89e27a6 100644
>> --- a/dir.c
>> +++ b/dir.c
>> @@ -2,6 +2,8 @@
>>   * This handles recursive filename detection with exclude
>>   * files, index knowledge etc..
>>   *
>> + * See Documentation/technical/api-directory-listing.txt
>> + *
>>   * Copyright (C) Linus Torvalds, 2005-2006
>>   *            Junio Hamano, 2005-2006
>>   */
>> @@ -476,6 +478,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
>>               die("cannot use %s as an exclude file", fname);
>>  }
>>
>> +/*
>> + * Loads the per-directory exclude list for the substring of base
>> + * which has a char length of baselen.
>> + */
>>  static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
>>  {
>>       struct exclude_list *el;
>> @@ -486,7 +492,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
>>           (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
>>               return; /* too long a path -- ignore */
>>
>> -     /* Pop the ones that are not the prefix of the path being checked. */
>> +     /* Pop the directories that are not the prefix of the path being checked. */
>
> The "one" does not refer to a "directory", but to an "exclude-list".

No, if that was the case, it would mean that multiple exclude lists
would be popped, but that is not the case here (prior to v4).

>         Pop the ones that are not for parent directories of the path
>         being checked

Better would be:

    Pop the entries within the EXCL_DIRS exclude list which originate
    from directories not in the prefix of the path being checked.

although as previously stated, the v4 series I have been holding off
from submitting (in order not to distract you from a maint release)
actually changes this behaviour so EXCL_DIRS becomes an exclude_group of
multiple exclude_lists, one per directory.  So in v4, multiple
exclude_lists *will* be popped.  I'll tweak the comment in v4 to make
this clear.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2012-12-27  2:32 ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
@ 2013-01-04 21:03   ` Junio C Hamano
  2013-01-05  7:54     ` Junio C Hamano
  2013-01-06 15:20     ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
  0 siblings, 2 replies; 55+ messages in thread
From: Junio C Hamano @ 2013-01-04 21:03 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> diff --git a/builtin/clean.c b/builtin/clean.c
> index 0c7b3d0..bd18b88 100644
> --- a/builtin/clean.c
> +++ b/builtin/clean.c
> @@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>  	if (!ignored)
>  		setup_standard_excludes(&dir);
>  
> +	add_exclude_list(&dir, EXC_CMDL);
>  	for (i = 0; i < exclude_list.nr; i++)
>  		add_exclude(exclude_list.items[i].string, "", 0,
> -			    &dir.exclude_list[EXC_CMDL]);
> +			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);

This looks somewhat ugly for two reasons.

 * The abstraction add_exclude() offers to its callers is just to
   let them add one pattern to the list of patterns for the kind
   (here, EXC_CMDL); why should they care about .ary[0] part?  Are
   there cases any sane caller (not the implementation of the
   exclude_list_group machinery e.g. add_excludes_from_... function)
   may want to call it with .ary[1]?  I do not think of any.
   Shouldn't the public API function add_exclude() take a pointer to
   the list group itself?

 * When naming an array of things, we tend to prefer naming it

     type thing[count]

   so that the second element can be called "thing[2]" and not
   "things[2]".  dir.exclude_list_group[EXC_CMDL] reads beter.

> diff --git a/builtin/ls-files.c b/builtin/ls-files.c
> index ef7f99a..c448e06 100644
> --- a/builtin/ls-files.c
> +++ b/builtin/ls-files.c
> @@ -420,10 +420,10 @@ static int option_parse_z(const struct option *opt,
>  static int option_parse_exclude(const struct option *opt,
>  				const char *arg, int unset)
>  {
> -	struct exclude_list *list = opt->value;
> +	struct exclude_list_group *group = opt->value;
>  
>  	exc_given = 1;
> -	add_exclude(arg, "", 0, list);
> +	add_exclude(arg, "", 0, &group->ary[0]);

This is another example where the caller would wish to be able to say

	add_exclude(arg, "", 0, group);

instead.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2013-01-04 21:03   ` Junio C Hamano
@ 2013-01-05  7:54     ` Junio C Hamano
  2013-01-06 15:27       ` Adam Spiers
  2013-01-06 15:20     ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
  1 sibling, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2013-01-05  7:54 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Junio C Hamano <gitster@pobox.com> writes:

> Adam Spiers <git@adamspiers.org> writes:
>
>> diff --git a/builtin/clean.c b/builtin/clean.c
>> index 0c7b3d0..bd18b88 100644
>> --- a/builtin/clean.c
>> +++ b/builtin/clean.c
>> @@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>>  	if (!ignored)
>>  		setup_standard_excludes(&dir);
>>  
>> +	add_exclude_list(&dir, EXC_CMDL);
>>  	for (i = 0; i < exclude_list.nr; i++)
>>  		add_exclude(exclude_list.items[i].string, "", 0,
>> -			    &dir.exclude_list[EXC_CMDL]);
>> +			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);
>
> This looks somewhat ugly for two reasons.
>
>  * The abstraction add_exclude() offers to its callers is just to
>    let them add one pattern to the list of patterns for the kind
>    (here, EXC_CMDL); why should they care about .ary[0] part?  Are
>    there cases any sane caller (not the implementation of the
>    exclude_list_group machinery e.g. add_excludes_from_... function)
>    may want to call it with .ary[1]?  I do not think of any.
>    Shouldn't the public API function add_exclude() take a pointer to
>    the list group itself?
>
>  * When naming an array of things, we tend to prefer naming it
>
>      type thing[count]
>
>    so that the second element can be called "thing[2]" and not
>    "things[2]".  dir.exclude_list_group[EXC_CMDL] reads better.

Also, "ary[]" is a bad name, even as an implementation detail, for
two reasons: it is naming it after its type (being an "array") not
after what it is (if it holds the patterns from the same information
source, e.g. file, togeter, "src" might be a better name), and it
uses rather unusual abbreviation (I haven't seen "array" shortened
to "ary" anywhere else).

>
>> diff --git a/builtin/ls-files.c b/builtin/ls-files.c
>> index ef7f99a..c448e06 100644
>> --- a/builtin/ls-files.c
>> +++ b/builtin/ls-files.c
>> @@ -420,10 +420,10 @@ static int option_parse_z(const struct option *opt,
>>  static int option_parse_exclude(const struct option *opt,
>>  				const char *arg, int unset)
>>  {
>> -	struct exclude_list *list = opt->value;
>> +	struct exclude_list_group *group = opt->value;
>>  
>>  	exc_given = 1;
>> -	add_exclude(arg, "", 0, list);
>> +	add_exclude(arg, "", 0, &group->ary[0]);
>
> This is another example where the caller would wish to be able to say
>
> 	add_exclude(arg, "", 0, group);
>
> instead.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API
  2013-01-02 12:54     ` Adam Spiers
@ 2013-01-06 12:02       ` Adam Spiers
  0 siblings, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2013-01-06 12:02 UTC (permalink / raw)
  To: git list

On Wed, Jan 02, 2013 at 12:54:19PM +0000, Adam Spiers wrote:
> On Tue, Jan 1, 2013 at 8:52 PM, Junio C Hamano <gitster@pobox.com> wrote:
> > Adam Spiers <git@adamspiers.org> writes:
> >> diff --git a/dir.c b/dir.c
> >> index ee8e711..89e27a6 100644
> >> --- a/dir.c
> >> +++ b/dir.c
> >> @@ -2,6 +2,8 @@
> >>   * This handles recursive filename detection with exclude
> >>   * files, index knowledge etc..
> >>   *
> >> + * See Documentation/technical/api-directory-listing.txt
> >> + *
> >>   * Copyright (C) Linus Torvalds, 2005-2006
> >>   *            Junio Hamano, 2005-2006
> >>   */
> >> @@ -476,6 +478,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
> >>               die("cannot use %s as an exclude file", fname);
> >>  }
> >>
> >> +/*
> >> + * Loads the per-directory exclude list for the substring of base
> >> + * which has a char length of baselen.
> >> + */
> >>  static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
> >>  {
> >>       struct exclude_list *el;
> >> @@ -486,7 +492,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
> >>           (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
> >>               return; /* too long a path -- ignore */
> >>
> >> -     /* Pop the ones that are not the prefix of the path being checked. */
> >> +     /* Pop the directories that are not the prefix of the path being checked. */
> >
> > The "one" does not refer to a "directory", but to an "exclude-list".
> 
> No, if that was the case, it would mean that multiple exclude lists
> would be popped, but that is not the case here (prior to v4).

Sorry, I meant prior to v3 11/19.

> >         Pop the ones that are not for parent directories of the path
> >         being checked
> 
> Better would be:
> 
>     Pop the entries within the EXCL_DIRS exclude list which originate
>     from directories not in the prefix of the path being checked.
> 
> although as previously stated, the v4 series I have been holding off
> from submitting (in order not to distract you from a maint release)
> actually changes this behaviour so EXCL_DIRS becomes an exclude_group of
> multiple exclude_lists, one per directory.  So in v4, multiple
> exclude_lists *will* be popped.  I'll tweak the comment in v4 to make
> this clear.

Again, I got confused and forgot that I already included the switch to
exclude_list_groups as v3 11/19.  But since the patch being discussed
here is v3 02/19 which precedes it, everything I said still applies.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2013-01-04 21:03   ` Junio C Hamano
  2013-01-05  7:54     ` Junio C Hamano
@ 2013-01-06 15:20     ` Adam Spiers
  2013-01-06 20:25       ` Junio C Hamano
  1 sibling, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2013-01-06 15:20 UTC (permalink / raw)
  To: git list

On Fri, Jan 04, 2013 at 01:03:59PM -0800, Junio C Hamano wrote:
> Adam Spiers <git@adamspiers.org> writes:
> 
> > diff --git a/builtin/clean.c b/builtin/clean.c
> > index 0c7b3d0..bd18b88 100644
> > --- a/builtin/clean.c
> > +++ b/builtin/clean.c
> > @@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
> >  	if (!ignored)
> >  		setup_standard_excludes(&dir);
> >  
> > +	add_exclude_list(&dir, EXC_CMDL);
> >  	for (i = 0; i < exclude_list.nr; i++)
> >  		add_exclude(exclude_list.items[i].string, "", 0,
> > -			    &dir.exclude_list[EXC_CMDL]);
> > +			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);
> 
> This looks somewhat ugly for two reasons.
> 
>  * The abstraction add_exclude() offers to its callers is just to
>    let them add one pattern to the list of patterns for the kind
>    (here, EXC_CMDL); why should they care about .ary[0] part?

Because the caller has to decide which exclude list the new exclude
should be added to; that is how it has been for a long time, and I am
not proposing we change that.

There are currently three callers:

  builtin/clean.c:    cmd_clean()
  builtin/ls-files.c: option_parse_exclude()
  dir.c:              add_excludes_from_file_to_list()

The first two add to EXC_CMDL, but the latter could be adding to
numerous different possible lists, e.g.

    - .git/info/exclude (EXC_FILE)
    - core.excludesfile (EXC_FILE)
    - any of the per-directory .gitignore lists (EXC_DIRS)

>    Are
>    there cases any sane caller (not the implementation of the
>    exclude_list_group machinery e.g. add_excludes_from_... function)
>    may want to call it with .ary[1]?

Effectively yes, although it is not written like ".ary[1]".  For
example prep_exclude() calls add_excludes_from_file_to_list() for each
new .gitignore file

>    Shouldn't the public API function add_exclude() take a pointer to
>    the list group itself?

Typically EXC_DIRS and EXC_FILE will contain excludes from multiple
sources, whereas EXC_CMDL will contain excludes from a single source.

Therefore when transitioning to exclude_list_groups, I had to make a
choice whether to keep EXC_CMDL as a singleton list, or split it out
into a separate field in struct dir_struct, e.g.:

	struct exclude_list exclude_list_cmdl;
	struct exclude_list exclude_list[2];
#define EXC_DIRS 0
#define EXC_FILE 1

I decided it was cleaner to use a singleton list, because this
preserved the design that excludes in earlier members of
exclude_list[3] take priority over excludes in later members of
exclude_list[3].  That way, this loop in last_exclude_matching():

	for (i = EXC_CMDL; i <= EXC_FILE; i++) {

still makes sense.

>  * When naming an array of things, we tend to prefer naming it
> 
>      type thing[count]
> 
>    so that the second element can be called "thing[2]" and not
>    "things[2]".  dir.exclude_list_group[EXC_CMDL] reads beter.

OK, I will change that.

> > diff --git a/builtin/ls-files.c b/builtin/ls-files.c
> > index ef7f99a..c448e06 100644
> > --- a/builtin/ls-files.c
> > +++ b/builtin/ls-files.c
> > @@ -420,10 +420,10 @@ static int option_parse_z(const struct option *opt,
> >  static int option_parse_exclude(const struct option *opt,
> >  				const char *arg, int unset)
> >  {
> > -	struct exclude_list *list = opt->value;
> > +	struct exclude_list_group *group = opt->value;
> >  
> >  	exc_given = 1;
> > -	add_exclude(arg, "", 0, list);
> > +	add_exclude(arg, "", 0, &group->ary[0]);
> 
> This is another example where the caller would wish to be able to say
> 
> 	add_exclude(arg, "", 0, group);
> 
> instead.

Why?  The caller needs to decide which exclude list the exclude should
go on, because that determines matching priority.  Specifying an
exclude_list_group is insufficient information to determine that.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2013-01-05  7:54     ` Junio C Hamano
@ 2013-01-06 15:27       ` Adam Spiers
  2013-01-06 15:35         ` [PATCH] api-allocation-growing.txt: encourage better variable naming Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2013-01-06 15:27 UTC (permalink / raw)
  To: git list

On Fri, Jan 04, 2013 at 11:54:34PM -0800, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
> > Adam Spiers <git@adamspiers.org> writes:
> >
> >> diff --git a/builtin/clean.c b/builtin/clean.c
> >> index 0c7b3d0..bd18b88 100644
> >> --- a/builtin/clean.c
> >> +++ b/builtin/clean.c
> >> @@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
> >>  	if (!ignored)
> >>  		setup_standard_excludes(&dir);
> >>  
> >> +	add_exclude_list(&dir, EXC_CMDL);
> >>  	for (i = 0; i < exclude_list.nr; i++)
> >>  		add_exclude(exclude_list.items[i].string, "", 0,
> >> -			    &dir.exclude_list[EXC_CMDL]);
> >> +			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);
> >
> > This looks somewhat ugly for two reasons.
> >
> >  * The abstraction add_exclude() offers to its callers is just to
> >    let them add one pattern to the list of patterns for the kind
> >    (here, EXC_CMDL); why should they care about .ary[0] part?  Are
> >    there cases any sane caller (not the implementation of the
> >    exclude_list_group machinery e.g. add_excludes_from_... function)
> >    may want to call it with .ary[1]?  I do not think of any.
> >    Shouldn't the public API function add_exclude() take a pointer to
> >    the list group itself?
> >
> >  * When naming an array of things, we tend to prefer naming it
> >
> >      type thing[count]
> >
> >    so that the second element can be called "thing[2]" and not
> >    "things[2]".  dir.exclude_list_group[EXC_CMDL] reads better.
> 
> Also, "ary[]" is a bad name, even as an implementation detail, for
> two reasons: it is naming it after its type (being an "array") not
> after what it is (if it holds the patterns from the same information
> source, e.g. file, togeter, "src" might be a better name), and it
> uses rather unusual abbreviation (I haven't seen "array" shortened
> to "ary" anywhere else).

OK, well in that case Documentation/technical/api-allocation-growing.txt
needs to be fixed, because I copied it from that.  I would never normally
shorten "array" to "ary" either, but I did it in an attempt to conform
to the stated guidelines.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* [PATCH] api-allocation-growing.txt: encourage better variable naming
  2013-01-06 15:27       ` Adam Spiers
@ 2013-01-06 15:35         ` Adam Spiers
  2013-01-06 20:29           ` Junio C Hamano
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2013-01-06 15:35 UTC (permalink / raw)
  To: git list

The documentation for the ALLOC_GROW API implicitly encouraged
developers to use "ary" as the variable name for the array which is
dynamically grown.  However "ary" is an unusual abbreviation hardly
used anywhere else in the source tree, and it is also better to name
variables based on their contents not on their type.

Signed-off-by: Adam Spiers <git@adamspiers.org>
---
 Documentation/technical/api-allocation-growing.txt | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/Documentation/technical/api-allocation-growing.txt b/Documentation/technical/api-allocation-growing.txt
index 43dbe09..3894815 100644
--- a/Documentation/technical/api-allocation-growing.txt
+++ b/Documentation/technical/api-allocation-growing.txt
@@ -5,7 +5,9 @@ Dynamically growing an array using realloc() is error prone and boring.
 
 Define your array with:
 
-* a pointer (`ary`) that points at the array, initialized to `NULL`;
+* a pointer (`array`) that points at the array, initialized to `NULL`
+  (although please name the variable based on its contents, not on its
+  type);
 
 * an integer variable (`alloc`) that keeps track of how big the current
   allocation is, initialized to `0`;
@@ -13,22 +15,22 @@ Define your array with:
 * another integer variable (`nr`) to keep track of how many elements the
   array currently has, initialized to `0`.
 
-Then before adding `n`th element to the array, call `ALLOC_GROW(ary, n,
+Then before adding `n`th element to the array, call `ALLOC_GROW(array, n,
 alloc)`.  This ensures that the array can hold at least `n` elements by
 calling `realloc(3)` and adjusting `alloc` variable.
 
 ------------
-sometype *ary;
+sometype *array;
 size_t nr;
 size_t alloc
 
 for (i = 0; i < nr; i++)
-	if (we like ary[i] already)
+	if (we like array[i] already)
 		return;
 
 /* we did not like any existing one, so add one */
-ALLOC_GROW(ary, nr + 1, alloc);
-ary[nr++] = value you like;
+ALLOC_GROW(array, nr + 1, alloc);
+array[nr++] = value you like;
 ------------
 
 You are responsible for updating the `nr` variable.
-- 
1.7.11.7.33.gb8feba5

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2013-01-06 15:20     ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
@ 2013-01-06 20:25       ` Junio C Hamano
  2013-01-06 22:53         ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2013-01-06 20:25 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> On Fri, Jan 04, 2013 at 01:03:59PM -0800, Junio C Hamano wrote:
>> Adam Spiers <git@adamspiers.org> writes:
>> 
>> > diff --git a/builtin/clean.c b/builtin/clean.c
>> > index 0c7b3d0..bd18b88 100644
>> > --- a/builtin/clean.c
>> > +++ b/builtin/clean.c
>> > @@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>> >  	if (!ignored)
>> >  		setup_standard_excludes(&dir);
>> >  
>> > +	add_exclude_list(&dir, EXC_CMDL);
>> >  	for (i = 0; i < exclude_list.nr; i++)
>> >  		add_exclude(exclude_list.items[i].string, "", 0,
>> > -			    &dir.exclude_list[EXC_CMDL]);
>> > +			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);
>> 
>> This looks somewhat ugly for two reasons.
>> 
>>  * The abstraction add_exclude() offers to its callers is just to
>>    let them add one pattern to the list of patterns for the kind
>>    (here, EXC_CMDL); why should they care about .ary[0] part?
>
> Because the caller has to decide which exclude list the new exclude
> should be added to; that is how it has been for a long time, and I am
> not proposing we change that.

Unless I was mistaken, I never objected to the EXC_CMDL, etc
appearing in the text of the calling site of add_exclude().

The objection was about the .ary[0] bit.  From the point of view of
a caller of the API, it:

    - calls add_exclude_list() to declare "I now start adding new
      patterns that come from a new source of patterns"; then

    - calls add_exclude() repeatedly to add the patterns that come
      from that source.

no?  Why does the latter has to keep repeating "Here is the new
pattern for the EXC_CMDL group; it comes from the latest source I
earlier declared, by the way", instead of just "Here is the new
pattern for the EXC_CMDL group"?  The ary[0] part always using "0"
(not "4" or "ix") is what repeats that "by the way".

>>    Are
>>    there cases any sane caller (not the implementation of the
>>    exclude_list_group machinery e.g. add_excludes_from_... function)
>>    may want to call it with .ary[1]?
>
> Effectively yes, although it is not written like ".ary[1]".  For
> example prep_exclude() calls add_excludes_from_file_to_list() for each
> new .gitignore file

That is part of the "implementation of the machinery".  If the API
for the outside callers are to call add_exclude_list() to declare
that patterns added by subsequent calls to add_exclude() are from
one new source of the patterns (e.g. .gitignore file in a new
directory level), and then call add_exclude() to add each pattern,
then the callers to add_exclude() shouldn't have to care about the
implementation detail that individual sources in exclude_list_group
is implemented as an array in that sructure, and the latest ones
should go to its ->array[0].

The implementation of the machinery may find it more convenient if
they can add one or more "sources" to an exclude_list_group before
starting to add patterns to ->array[0] or ->array[1] or ->array[2],
and a finer grained internal API that lets the caller pass an
instance of "struct exclude_list" regardless of where in an
exclude_list_group's ary[] that instance sits may be necessary to do
so.

But that does not mean other existing callers has to be aware of
such inner detail.  If the implementation of the machinery needs a
helper function that adds an element to any struct exclude_list, not
necessarily the one at the tip of an exclude_list_group, we can
still do that by having the bulk of the logic in the internal, finer
grained helper, say, add_pattern_to_exclude_list(), and keep the
external API simpler by making it a thin wrapper around it, perhaps
like:

   static void add_pattern_to_exclude_list(const char *pattern,
   		    const char *base, int baselen,
                    struct exclude_list *el);

   void add_exclude(const char *pattern,
   		    const char *base, int baselen,
                    struct exclude_list_group *group) {
	add_pattern_to_exclude_list(pattern, base, baselen, &group->ary[0]);
   }    

no?

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH] api-allocation-growing.txt: encourage better variable naming
  2013-01-06 15:35         ` [PATCH] api-allocation-growing.txt: encourage better variable naming Adam Spiers
@ 2013-01-06 20:29           ` Junio C Hamano
  2013-01-06 20:52             ` Adam Spiers
  0 siblings, 1 reply; 55+ messages in thread
From: Junio C Hamano @ 2013-01-06 20:29 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> The documentation for the ALLOC_GROW API implicitly encouraged
> developers to use "ary" as the variable name for the array which is
> dynamically grown.  However "ary" is an unusual abbreviation hardly
> used anywhere else in the source tree, and it is also better to name
> variables based on their contents not on their type.

Sounds good.  To follow "not type but contents", a further rewrite
with s/array/item/ is even better, no?

I can obviously squash it in without resending, if you agree, or you
can point out why item[] is not a good idea and array[] is better.

>
> Signed-off-by: Adam Spiers <git@adamspiers.org>
> ---
>  Documentation/technical/api-allocation-growing.txt | 14 ++++++++------
>  1 file changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/technical/api-allocation-growing.txt b/Documentation/technical/api-allocation-growing.txt
> index 43dbe09..3894815 100644
> --- a/Documentation/technical/api-allocation-growing.txt
> +++ b/Documentation/technical/api-allocation-growing.txt
> @@ -5,7 +5,9 @@ Dynamically growing an array using realloc() is error prone and boring.
>  
>  Define your array with:
>  
> -* a pointer (`ary`) that points at the array, initialized to `NULL`;
> +* a pointer (`array`) that points at the array, initialized to `NULL`
> +  (although please name the variable based on its contents, not on its
> +  type);
>  
>  * an integer variable (`alloc`) that keeps track of how big the current
>    allocation is, initialized to `0`;
> @@ -13,22 +15,22 @@ Define your array with:
>  * another integer variable (`nr`) to keep track of how many elements the
>    array currently has, initialized to `0`.
>  
> -Then before adding `n`th element to the array, call `ALLOC_GROW(ary, n,
> +Then before adding `n`th element to the array, call `ALLOC_GROW(array, n,
>  alloc)`.  This ensures that the array can hold at least `n` elements by
>  calling `realloc(3)` and adjusting `alloc` variable.
>  
>  ------------
> -sometype *ary;
> +sometype *array;
>  size_t nr;
>  size_t alloc
>  
>  for (i = 0; i < nr; i++)
> -	if (we like ary[i] already)
> +	if (we like array[i] already)
>  		return;
>  
>  /* we did not like any existing one, so add one */
> -ALLOC_GROW(ary, nr + 1, alloc);
> -ary[nr++] = value you like;
> +ALLOC_GROW(array, nr + 1, alloc);
> +array[nr++] = value you like;
>  ------------
>  
>  You are responsible for updating the `nr` variable.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH] api-allocation-growing.txt: encourage better variable naming
  2013-01-06 20:29           ` Junio C Hamano
@ 2013-01-06 20:52             ` Adam Spiers
  2013-01-06 20:58               ` Junio C Hamano
  0 siblings, 1 reply; 55+ messages in thread
From: Adam Spiers @ 2013-01-06 20:52 UTC (permalink / raw)
  To: git list

On Sun, Jan 06, 2013 at 12:29:33PM -0800, Junio C Hamano wrote:
> Adam Spiers <git@adamspiers.org> writes:
> 
> > The documentation for the ALLOC_GROW API implicitly encouraged
> > developers to use "ary" as the variable name for the array which is
> > dynamically grown.  However "ary" is an unusual abbreviation hardly
> > used anywhere else in the source tree, and it is also better to name
> > variables based on their contents not on their type.
> 
> Sounds good.  To follow "not type but contents", a further rewrite
> with s/array/item/ is even better, no?
> 
> I can obviously squash it in without resending, if you agree, or you
> can point out why item[] is not a good idea and array[] is better.

I agree.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH] api-allocation-growing.txt: encourage better variable naming
  2013-01-06 20:52             ` Adam Spiers
@ 2013-01-06 20:58               ` Junio C Hamano
  0 siblings, 0 replies; 55+ messages in thread
From: Junio C Hamano @ 2013-01-06 20:58 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

>> Sounds good.  To follow "not type but contents", a further rewrite
>> with s/array/item/ is even better, no?
>
> I agree.

Thanks for a quick response; let's do this then.

-- >8 --
From: Adam Spiers <git@adamspiers.org>

The documentation for the ALLOC_GROW API implicitly encouraged
developers to use "ary" as the variable name for the array which is
dynamically grown.  However "ary" is an unusual abbreviation hardly
used anywhere else in the source tree, and it is also better to name
variables based on their contents not on their type.

Signed-off-by: Adam Spiers <git@adamspiers.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/technical/api-allocation-growing.txt | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/Documentation/technical/api-allocation-growing.txt b/Documentation/technical/api-allocation-growing.txt
index 43dbe09..542946b 100644
--- a/Documentation/technical/api-allocation-growing.txt
+++ b/Documentation/technical/api-allocation-growing.txt
@@ -5,7 +5,9 @@ Dynamically growing an array using realloc() is error prone and boring.
 
 Define your array with:
 
-* a pointer (`ary`) that points at the array, initialized to `NULL`;
+* a pointer (`item`) that points at the array, initialized to `NULL`
+  (although please name the variable based on its contents, not on its
+  type);
 
 * an integer variable (`alloc`) that keeps track of how big the current
   allocation is, initialized to `0`;
@@ -13,22 +15,22 @@ Define your array with:
 * another integer variable (`nr`) to keep track of how many elements the
   array currently has, initialized to `0`.
 
-Then before adding `n`th element to the array, call `ALLOC_GROW(ary, n,
+Then before adding `n`th element to the item, call `ALLOC_GROW(item, n,
 alloc)`.  This ensures that the array can hold at least `n` elements by
 calling `realloc(3)` and adjusting `alloc` variable.
 
 ------------
-sometype *ary;
+sometype *item;
 size_t nr;
 size_t alloc
 
 for (i = 0; i < nr; i++)
-	if (we like ary[i] already)
+	if (we like item[i] already)
 		return;
 
 /* we did not like any existing one, so add one */
-ALLOC_GROW(ary, nr + 1, alloc);
-ary[nr++] = value you like;
+ALLOC_GROW(item, nr + 1, alloc);
+item[nr++] = value you like;
 ------------
 
 You are responsible for updating the `nr` variable.
-- 
1.8.1.302.g0f4eaa7

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2013-01-06 20:25       ` Junio C Hamano
@ 2013-01-06 22:53         ` Adam Spiers
  2013-01-06 23:17           ` Adam Spiers
  2013-01-06 23:19           ` Junio C Hamano
  0 siblings, 2 replies; 55+ messages in thread
From: Adam Spiers @ 2013-01-06 22:53 UTC (permalink / raw)
  To: git list

On Sun, Jan 06, 2013 at 12:25:48PM -0800, Junio C Hamano wrote:
> Adam Spiers <git@adamspiers.org> writes:
> 
> > On Fri, Jan 04, 2013 at 01:03:59PM -0800, Junio C Hamano wrote:
> >> Adam Spiers <git@adamspiers.org> writes:
> >> 
> >> > diff --git a/builtin/clean.c b/builtin/clean.c
> >> > index 0c7b3d0..bd18b88 100644
> >> > --- a/builtin/clean.c
> >> > +++ b/builtin/clean.c
> >> > @@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
> >> >  	if (!ignored)
> >> >  		setup_standard_excludes(&dir);
> >> >  
> >> > +	add_exclude_list(&dir, EXC_CMDL);
> >> >  	for (i = 0; i < exclude_list.nr; i++)
> >> >  		add_exclude(exclude_list.items[i].string, "", 0,
> >> > -			    &dir.exclude_list[EXC_CMDL]);
> >> > +			    &dir.exclude_list_groups[EXC_CMDL].ary[0]);
> >> 
> >> This looks somewhat ugly for two reasons.
> >> 
> >>  * The abstraction add_exclude() offers to its callers is just to
> >>    let them add one pattern to the list of patterns for the kind
> >>    (here, EXC_CMDL); why should they care about .ary[0] part?
> >
> > Because the caller has to decide which exclude list the new exclude
> > should be added to; that is how it has been for a long time, and I am
> > not proposing we change that.
> 
> Unless I was mistaken, I never objected to the EXC_CMDL, etc
> appearing in the text of the calling site of add_exclude().
> 
> The objection was about the .ary[0] bit.  From the point of view of
> a caller of the API, it:
> 
>     - calls add_exclude_list() to declare "I now start adding new
>       patterns that come from a new source of patterns"; then
> 
>     - calls add_exclude() repeatedly to add the patterns that come
>       from that source.
> 
> no?

Correct.

> Why does the latter has to keep repeating "Here is the new
> pattern for the EXC_CMDL group; it comes from the latest source I
> earlier declared, by the way", instead of just "Here is the new
> pattern for the EXC_CMDL group"?

Mainly because there is no guarantee that such a group exists.

unpack_trees() has:

	if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, &el, 0) < 0)

so if you change the signature of add_exclude() to require an
exclude_list_group, then there is no way to implement
add_excludes_from_file_to_list().

Even if you could, you still haven't reduced the number of parameters
add_exclude() requires, so I'm dubious of the benefits of this
"simplification".

> >>    Are
> >>    there cases any sane caller (not the implementation of the
> >>    exclude_list_group machinery e.g. add_excludes_from_... function)
> >>    may want to call it with .ary[1]?
> >
> > Effectively yes, although it is not written like ".ary[1]".  For
> > example prep_exclude() calls add_excludes_from_file_to_list() for each
> > new .gitignore file
> 
> That is part of the "implementation of the machinery".  If the API
> for the outside callers are to call add_exclude_list() to declare
> that patterns added by subsequent calls to add_exclude() are from
> one new source of the patterns (e.g. .gitignore file in a new
> directory level), and then call add_exclude() to add each pattern,
> then the callers to add_exclude() shouldn't have to care about the
> implementation detail that individual sources in exclude_list_group
> is implemented as an array in that sructure, and the latest ones
> should go to its ->array[0].

That's a valid point.  However, the ary[0] part which assumes external
knowledge of the internal implementation can trivially be avoided by
squashing this patch onto the commit we are discussing:

diff --git a/builtin/clean.c b/builtin/clean.c
index dd89737..6e21ca6 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -45,6 +45,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 	static const char **pathspec;
 	struct strbuf buf = STRBUF_INIT;
 	struct string_list exclude_list = STRING_LIST_INIT_NODUP;
+	struct exclude_list *el;
 	const char *qname;
 	char *seen = NULL;
 	struct option options[] = {
@@ -97,10 +98,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 	if (!ignored)
 		setup_standard_excludes(&dir);
 
-	add_exclude_list(&dir, EXC_CMDL);
+	el = add_exclude_list(&dir, EXC_CMDL);
 	for (i = 0; i < exclude_list.nr; i++)
-		add_exclude(exclude_list.items[i].string, "", 0,
-			    &dir.exclude_list_group[EXC_CMDL].el[0]);
+		add_exclude(exclude_list.items[i].string, "", 0, el);
 
 	pathspec = get_pathspec(prefix, argv);


and by adopting the same approach for ls-files.c:

 
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 0ca9d8e..0406adc 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -420,10 +420,11 @@ static int option_parse_z(const struct option *opt,
 static int option_parse_exclude(const struct option *opt,
 				const char *arg, int unset)
 {
-	struct exclude_list_group *group = opt->value;
+	struct string_list *exclude_list = opt->value;
 
 	exc_given = 1;
-	add_exclude(arg, "", 0, &group->el[0]);
+	string_list_append(exclude_list, arg);
+	fprintf(stderr, "append %s\n", arg);
 
 	return 0;
 }
@@ -452,9 +453,11 @@ static int option_parse_exclude_standard(const struct option *opt,
 
 int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 {
-	int require_work_tree = 0, show_tag = 0;
+	int require_work_tree = 0, show_tag = 0, i;
 	const char *max_prefix;
 	struct dir_struct dir;
+	struct exclude_list *el;
+	struct string_list exclude_list = STRING_LIST_INIT_NODUP;
 	struct option builtin_ls_files_options[] = {
 		{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
 			"paths are separated with NUL character",
@@ -489,7 +492,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 		OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
 			    "show resolve-undo information"),
 		{ OPTION_CALLBACK, 'x', "exclude",
-			&dir.exclude_list_group[EXC_CMDL], "pattern",
+			&exclude_list, "pattern",
 			"skip files matching pattern",
 			0, option_parse_exclude },
 		{ OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
@@ -524,9 +527,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 	if (read_cache() < 0)
 		die("index file corrupt");
 
-	add_exclude_list(&dir, EXC_CMDL);
 	argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
 			ls_files_usage, 0);
+	el = add_exclude_list(&dir, EXC_CMDL);
+	for (i = 0; i < exclude_list.nr; i++) {
+		fprintf(stderr, "adding exclude: %s\n", exclude_list.items[i].string);
+		add_exclude(exclude_list.items[i].string, "", 0, el);
+	}
 	if (show_tag || show_valid_bit) {
 		tag_cached = "H ";
 		tag_unmerged = "M ";

> The implementation of the machinery may find it more convenient if
> they can add one or more "sources" to an exclude_list_group before
> starting to add patterns to ->array[0] or ->array[1] or ->array[2],
> and a finer grained internal API that lets the caller pass an
> instance of "struct exclude_list" regardless of where in an
> exclude_list_group's ary[] that instance sits may be necessary to do
> so.
> 
> But that does not mean other existing callers has to be aware of
> such inner detail.  If the implementation of the machinery needs a
> helper function that adds an element to any struct exclude_list, not
> necessarily the one at the tip of an exclude_list_group, we can
> still do that by having the bulk of the logic in the internal, finer
> grained helper, say, add_pattern_to_exclude_list(), and keep the
> external API simpler by making it a thin wrapper around it, perhaps
> like:
> 
>    static void add_pattern_to_exclude_list(const char *pattern,
>    		    const char *base, int baselen,
>                     struct exclude_list *el);
> 
>    void add_exclude(const char *pattern,
>    		    const char *base, int baselen,
>                     struct exclude_list_group *group) {
> 	add_pattern_to_exclude_list(pattern, base, baselen, &group->ary[0]);

Presumably you mean

	add_pattern_to_exclude_list(pattern, base, baselen,
				    &group->ary[group->nr - 1]);

(although at your request, I already renamed 'ary' to 'el').

I have made a genuine attempt to implement your suggestion, but due to
the unpack_trees() case stated above, I don't see how it can be done.

^ permalink raw reply related	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2013-01-06 22:53         ` Adam Spiers
@ 2013-01-06 23:17           ` Adam Spiers
  2013-01-06 23:19           ` Junio C Hamano
  1 sibling, 0 replies; 55+ messages in thread
From: Adam Spiers @ 2013-01-06 23:17 UTC (permalink / raw)
  To: git list

On Sun, Jan 06, 2013 at 10:53:11PM +0000, Adam Spiers wrote:
> That's a valid point.  However, the ary[0] part which assumes external
> knowledge of the internal implementation can trivially be avoided by
> squashing this patch onto the commit we are discussing:

[snipped]

> diff --git a/builtin/ls-files.c b/builtin/ls-files.c
> index 0ca9d8e..0406adc 100644
> --- a/builtin/ls-files.c
> +++ b/builtin/ls-files.c
> @@ -420,10 +420,11 @@ static int option_parse_z(const struct option *opt,
>  static int option_parse_exclude(const struct option *opt,
>  				const char *arg, int unset)
>  {
> -	struct exclude_list_group *group = opt->value;
> +	struct string_list *exclude_list = opt->value;
>  
>  	exc_given = 1;
> -	add_exclude(arg, "", 0, &group->el[0]);
> +	string_list_append(exclude_list, arg);
> +	fprintf(stderr, "append %s\n", arg);

Whoops :-)

[snipped]

> @@ -524,9 +527,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
>  	if (read_cache() < 0)
>  		die("index file corrupt");
>  
> -	add_exclude_list(&dir, EXC_CMDL);
>  	argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
>  			ls_files_usage, 0);
> +	el = add_exclude_list(&dir, EXC_CMDL);
> +	for (i = 0; i < exclude_list.nr; i++) {
> +		fprintf(stderr, "adding exclude: %s\n", exclude_list.items[i].string);

Excluding those two fprintf() calls, of course :-)

I've removed them, and pushed to my github fork a new version of v4
with the fixed version of this patch inserted in the appropriate place
(and labelled with a "[SQUASH]" prefix):

    git://github.com/aspiers/git.git
    https://github.com/aspiers/git/commits/check-ignore

Since I sent v4 earlier today, to avoid spamming this list, I won't
resend the whole series yet - not until we have made some progress in
reviewing v4.

^ permalink raw reply	[flat|nested] 55+ messages in thread

* Re: [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes
  2013-01-06 22:53         ` Adam Spiers
  2013-01-06 23:17           ` Adam Spiers
@ 2013-01-06 23:19           ` Junio C Hamano
  1 sibling, 0 replies; 55+ messages in thread
From: Junio C Hamano @ 2013-01-06 23:19 UTC (permalink / raw)
  To: Adam Spiers; +Cc: git list

Adam Spiers <git@adamspiers.org> writes:

> That's a valid point.  However, the ary[0] part which assumes external
> knowledge of the internal implementation can trivially be avoided by
> squashing this patch onto the commit we are discussing:
>
> diff --git a/builtin/clean.c b/builtin/clean.c
> index dd89737..6e21ca6 100644
> --- a/builtin/clean.c
> +++ b/builtin/clean.c
> @@ -45,6 +45,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>  	static const char **pathspec;
>  	struct strbuf buf = STRBUF_INIT;
>  	struct string_list exclude_list = STRING_LIST_INIT_NODUP;
> +	struct exclude_list *el;
>  	const char *qname;
>  	char *seen = NULL;
>  	struct option options[] = {
> @@ -97,10 +98,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>  	if (!ignored)
>  		setup_standard_excludes(&dir);
>  
> -	add_exclude_list(&dir, EXC_CMDL);
> +	el = add_exclude_list(&dir, EXC_CMDL);
>  	for (i = 0; i < exclude_list.nr; i++)
> -		add_exclude(exclude_list.items[i].string, "", 0,
> -			    &dir.exclude_list_group[EXC_CMDL].el[0]);
> +		add_exclude(exclude_list.items[i].string, "", 0, el);
>  
>  	pathspec = get_pathspec(prefix, argv);
>
>
> and by adopting the same approach for ls-files.c:

That is _much_ more readable and easier to explain in the API
documentation, I think.

Thanks.

^ permalink raw reply	[flat|nested] 55+ messages in thread

end of thread, other threads:[~2013-01-06 23:19 UTC | newest]

Thread overview: 55+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-12-27  2:32 [PATCH v3 00/19] new git check-ignore sub-command Adam Spiers
2012-12-27  2:32 ` [PATCH v3 01/19] api-directory-listing.txt: update to match code Adam Spiers
2012-12-27  2:32 ` [PATCH v3 02/19] Improve documentation and comments regarding directory traversal API Adam Spiers
2013-01-01 20:52   ` Junio C Hamano
2013-01-02 12:54     ` Adam Spiers
2013-01-06 12:02       ` Adam Spiers
2012-12-27  2:32 ` [PATCH v3 03/19] dir.c: rename cryptic 'which' variable to more consistent name Adam Spiers
2012-12-27  2:32 ` [PATCH v3 04/19] dir.c: rename path_excluded() to is_path_excluded() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 05/19] dir.c: rename excluded_from_list() to is_excluded_from_list() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 06/19] dir.c: rename excluded() to is_excluded() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 07/19] dir.c: refactor is_excluded_from_list() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 08/19] dir.c: refactor is_excluded() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 09/19] dir.c: refactor is_path_excluded() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 10/19] dir.c: rename free_excludes() to clear_exclude_list() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
2013-01-04 21:03   ` Junio C Hamano
2013-01-05  7:54     ` Junio C Hamano
2013-01-06 15:27       ` Adam Spiers
2013-01-06 15:35         ` [PATCH] api-allocation-growing.txt: encourage better variable naming Adam Spiers
2013-01-06 20:29           ` Junio C Hamano
2013-01-06 20:52             ` Adam Spiers
2013-01-06 20:58               ` Junio C Hamano
2013-01-06 15:20     ` [PATCH v3 11/19] dir.c: use a single struct exclude_list per source of excludes Adam Spiers
2013-01-06 20:25       ` Junio C Hamano
2013-01-06 22:53         ` Adam Spiers
2013-01-06 23:17           ` Adam Spiers
2013-01-06 23:19           ` Junio C Hamano
2012-12-27  2:32 ` [PATCH v3 12/19] dir.c: keep track of where patterns came from Adam Spiers
2012-12-27  2:32 ` [PATCH v3 13/19] dir.c: provide clear_directory() for reclaiming dir_struct memory Adam Spiers
2012-12-27  2:32 ` [PATCH v3 14/19] add.c: refactor treat_gitlinks() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 15/19] add.c: remove unused argument from validate_pathspec() Adam Spiers
2012-12-27  2:32 ` [PATCH v3 16/19] pathspec.c: move reusable code from builtin/add.c Adam Spiers
2012-12-28 20:32   ` Junio C Hamano
2012-12-28 20:45     ` Adam Spiers
2012-12-29  0:40       ` Adam Spiers
2012-12-28 20:48   ` Junio C Hamano
2012-12-28 21:15     ` Adam Spiers
2012-12-27  2:32 ` [PATCH v3 17/19] pathspec.c: extract new validate_path() for reuse Adam Spiers
2012-12-28 20:44   ` Junio C Hamano
2012-12-28 21:08     ` Adam Spiers
2012-12-27  2:32 ` [PATCH v3 18/19] setup.c: document get_pathspec() Adam Spiers
2012-12-28 20:36   ` Junio C Hamano
2012-12-28 20:40     ` Adam Spiers
2012-12-29  0:52       ` Adam Spiers
2012-12-29  1:36         ` Junio C Hamano
2012-12-27  2:32 ` [PATCH v3 19/19] Add git-check-ignore sub-command Adam Spiers
2012-12-28 21:21   ` Junio C Hamano
2012-12-29  1:23     ` Adam Spiers
2012-12-29  3:32       ` Adam Spiers
2012-12-27  5:15 ` [PATCH v3 00/19] new git check-ignore sub-command Michael Leal
2012-12-28 18:50 ` Junio C Hamano
2012-12-28 19:39   ` Adam Spiers
2012-12-28 20:15     ` Antoine Pelisse
2012-12-28 21:31       ` Junio C Hamano
2012-12-28 21:23 ` Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).