git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com>
To: git@vger.kernel.org
Cc: "Junio C Hamano" <gitster@pobox.com>,
	"Kevin Daudt" <me@ikke.info>,
	"Charles Strahan" <charles@cstrahan.com>,
	"Nguyễn Thái Ngọc Duy" <pclouds@gmail.com>
Subject: [PATCH] dir.c: fix dir re-inclusion rules with "NODIR" and "MUSTBEDIR"
Date: Wed,  9 Mar 2016 18:08:13 +0700	[thread overview]
Message-ID: <1457521693-26141-1-git-send-email-pclouds@gmail.com> (raw)
In-Reply-To: <xmqqy49siin2.fsf@gitster.mtv.corp.google.com>

For NODIR case, the patterns look like this

    *          # exclude dir, dir/file1 and dir/file2..
    !dir       # ..except that dir and everything inside is re-included..
    dir/file2  # ..except (again!) that dir/file2 is excluded
               # ..which means dir/file1 stays included

When we stay at topdir and test "dir", everything is hunky dory, current
code returns "re-include dir" as expected. When we stay in "dir" and
examine "dir/file1", however, match_basename() checks if the base name
component, which is "file1", matches "dir" from the second rule.

This is wrong. We contradict ourselves because earlier we decided to
re-include dir/file1 (from the second rule) when we were at toplevel.
Because the second rule is ignored, we hit the first one and return
"excluded" even though "dir/file1" should be re-included.

Side note, it's probably a bad idea to use basename matching on a
negative rule (ie. no slashes in the pattern) because what the pattern
says is "re-include _any_ directory named 'dir'", not just the directory
"dir" at right below this directory.

In the MUSTBEDIR case, the patterns look like this

    *          # exclude dir, dir/file1 and dir/file2..
    !dir/      #  ..except that dir and everything inside is re-included..
    dir/file2  # ..except (again!) that dir/file2 is excluded
               # ..which means dir/file1 stays included

Again, we're ok at the toplevel, then we enter "dir" and test
"dir/file1". The MUSTBEDIR code tests if the _full_ path (ie. "dir/file1")
is a directory. We want it to test the "dir" part from "dir/file1"
instead.

In both cases, we want to test if the pattern matches a parent
directory. match_dir_component() is for this purpose.

We do want to be careful not to get back too far. Given the file
foo/bar/.gitignore, we should only check as far back as foo/bar because
this .gitignore file can never match outside that directory, which is
"toplevel" in the above paragraphs, to begin with.

The remaining matching case (neither NODIR nor MUSTBEDIR is set) is
already fixed in a60ea8f (dir.c: fix match_pathname() - 2016-02-15)

Reported-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 The fix may look like this (I didn't think about the "**" trick in
 wildmatch, which makes things simpler).

 No it's not meant for 2.8.0. I didn't even optimize for the
 no-wildcard case, or add tests. But we can still stare at it in the
 meantime to see if I analyzed anything wrong. I almost thought I was
 wrong to the declare the bug while I was on my ride back home..

 dir.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 49 insertions(+), 2 deletions(-)

diff --git a/dir.c b/dir.c
index 69e0be6..5123483 100644
--- a/dir.c
+++ b/dir.c
@@ -992,6 +992,51 @@ static struct exclude *should_descend(const char *pathname, int pathlen,
 }
 
 /*
+ * Given a "NODIR" pattern, check if it matches any directory
+ * component after the x->base part.
+ *
+ * If the pattern is not NODIR (ie. pathname matching) _and_ is
+ * MUSTBE, check if it matches a directory as well.
+ *
+ * Note that "path" and "len" must cover the basename part as well,
+ * looking from last_exclude_matching_from_list(), not just the dirname.
+ */
+static int match_dir_component(const char *path, int len, struct exclude *x)
+{
+	struct strbuf new_pattern = STRBUF_INIT;
+	int ret;
+
+	if (x->flags & EXC_FLAG_NODIR) {
+		if (!x->baselen) {
+			strbuf_addf(&new_pattern, "%s/**", x->pattern);
+			ret = !fnmatch_icase_mem(new_pattern.buf, new_pattern.len,
+						 path, strlen(path),
+						 WM_PATHNAME);
+			strbuf_reset(&new_pattern);
+			if (ret)
+				return ret;
+		}
+
+		strbuf_addf(&new_pattern, "%.*s**/%s/**", x->baselen, x->base, x->pattern);
+		ret = !fnmatch_icase_mem(new_pattern.buf, new_pattern.len,
+					 path, strlen(path),
+					 WM_PATHNAME);
+		strbuf_reset(&new_pattern);
+		return ret;
+	}
+
+	assert(x->flags & EXC_FLAG_MUSTBEDIR);
+	strbuf_addf(&new_pattern, "%.*s%s/**",
+		    x->baselen, x->base,
+		    *x->pattern == '/' ? x->pattern+1 : x->pattern);
+	ret = !fnmatch_icase_mem(new_pattern.buf, new_pattern.len,
+				 path, strlen(path),
+				 WM_PATHNAME);
+	strbuf_release(&new_pattern);
+	return ret;
+}
+
+/*
  * 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
@@ -1033,7 +1078,8 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
 		if (x->flags & EXC_FLAG_MUSTBEDIR) {
 			if (*dtype == DT_UNKNOWN)
 				*dtype = get_dtype(NULL, pathname, pathlen);
-			if (*dtype != DT_DIR)
+			if (*dtype != DT_DIR &&
+			    !match_dir_component(pathname, strlen(pathname), x))
 				continue;
 		}
 
@@ -1041,7 +1087,8 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
 			if (match_basename(basename,
 					   pathlen - (basename - pathname),
 					   exclude, prefix, x->patternlen,
-					   x->flags)) {
+					   x->flags) ||
+			    match_dir_component(pathname, strlen(pathname), x)) {
 				exc = x;
 				break;
 			}
-- 
2.8.0.rc0.208.g69d9f93

  reply	other threads:[~2016-03-09 11:08 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-03-04  2:11 Change in .gitignore handling: intended or bug? Charles Strahan
2016-03-04  3:09 ` Duy Nguyen
2016-03-04  5:51 ` Kevin Daudt
2016-03-04  6:12   ` Charles Strahan
2016-03-04 11:56     ` Kevin Daudt
2016-03-04 12:36       ` Duy Nguyen
2016-03-04 17:28         ` Junio C Hamano
2016-03-04 19:12           ` Junio C Hamano
2016-03-05  0:43           ` Duy Nguyen
2016-03-05  0:50             ` Duy Nguyen
2016-03-05  1:00               ` Charles Strahan
2016-03-05  1:35                 ` Junio C Hamano
2016-03-07 21:11             ` Junio C Hamano
2016-03-08  6:14               ` Junio C Hamano
2016-03-08 10:19                 ` Duy Nguyen
2016-03-08 18:10                   ` Junio C Hamano
2016-03-09  0:18                     ` Duy Nguyen
2016-03-09  0:32                       ` Junio C Hamano
2016-03-09  0:45                         ` Junio C Hamano
2016-03-09 11:08                           ` Nguyễn Thái Ngọc Duy [this message]
2016-03-09 17:55                             ` [PATCH] dir.c: fix dir re-inclusion rules with "NODIR" and "MUSTBEDIR" Junio C Hamano
2016-03-10  0:39                               ` Duy Nguyen
2016-03-09  9:48                     ` Change in .gitignore handling: intended or bug? Duy Nguyen
2016-03-09 18:02                       ` Junio C Hamano
2016-03-10  0:26                         ` Duy Nguyen
2016-03-10  0:37                           ` Junio C Hamano
2016-03-10  0:59                             ` Junio C Hamano
2016-03-10 10:56                               ` Duy Nguyen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1457521693-26141-1-git-send-email-pclouds@gmail.com \
    --to=pclouds@gmail.com \
    --cc=charles@cstrahan.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=me@ikke.info \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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 NNTP newsgroup(s).