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

Re: [regression?] trailing slash required in .gitattributes

From
Jeff King <peff@peff.net>
Date
Mar 23, 2013, 08:39 UTC
Message-ID
<20130323083927.GA25600@sigill.intra.peff.net>
In-Reply-To
<7vzjxv3uef.fsf@alter.siamese.dyndns.org>
On Fri, Mar 22, 2013 at 04:08:08PM -0700, Junio C Hamano wrote:
Show 14 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> >   if (pathlen && pathname[pathlen-1] == '/')
> >           pathlen--;
> >
> > would work. But it seems that match_basename, despite taking the length
> > of all of the strings we pass it, will happily use NUL-terminated
> > functions like strcmp or fnmatch. Converting the former to check lengths
> > should be pretty straightforward. But there is no version of fnmatch
> > that does what we want. I wonder if we using wildmatch can get around
> > this limitation.
> 
> Or save away pathname[pathlen], temporarily NUL terminate and call
> these functions?

Yeah, that is a possibility, though it involves casting away some constness. Patch is below, which seems to work.

It still feels really ugly to me, and like match_basename is misdesigned and should respect the lengths we pass it. Also, if it does respect the lengths, it should be able to go much faster (e.g., in the common case, we can drop a ton of strcmp_icase calls if we just check the lengths beforehand). I feel like Duy was working on something like this recently, but I don't see anything in pu.

---
diff --git a/attr.c b/attr.c
index e2f9377..bd00a78 100644
--- a/attr.c
+++ b/attr.c
@@ -663,20 +663,58 @@ static int path_matches(const char *pathname, int pathlen,
 {
 	const char *pattern = pat->pattern;
 	int prefix = pat->nowildcardlen;
+	char path_munge = 0;
+	char pattern_munge = 0;
+	int ret;
 
 	if ((pat->flags & EXC_FLAG_MUSTBEDIR) &&
 	    ((!pathlen) || (pathname[pathlen-1] != '/')))
 		return 0;
 
+	/*
+	 * Drop trailing slash from path, as we would want
+	 * an unadorned pattern like "foo" to match both the
+	 * file "foo" and the directory "foo/".
+	 */
+	if (pathlen && pathname[pathlen-1] == '/') {
+		pathlen--;
+
+		/*
+		 * The match_* functions, despite taking a string length, will
+		 * happily read all the way up to the NUL-terminating character.
+		 * So we must not only shrink pathlen, but munge the buffer
+		 * to NUL-terminate it.
+		 */
+		path_munge = pathname[pathlen];
+		((char *)pathname)[pathlen] = '\0';
+	}
+
+	/*
+	 * The pattern up to patternlen will not include a
+	 * trailing slash, but it may still be present in the string.
+	 * And since the match_* functions will read up to the NUL,
+	 * we need to terminate the buffer.
+	 */
+	pattern_munge = pattern[pat->patternlen];
+	((char *)pattern)[pat->patternlen] = '\0';
+
 	if (pat->flags & EXC_FLAG_NODIR) {
-		return match_basename(basename,
-				      pathlen - (basename - pathname),
-				      pattern, prefix,
-				      pat->patternlen, pat->flags);
-	}
-	return match_pathname(pathname, pathlen,
-			      base, baselen,
-			      pattern, prefix, pat->patternlen, pat->flags);
+		ret = match_basename(basename,
+				     pathlen - (basename - pathname),
+				     pattern, prefix,
+				     pat->patternlen, pat->flags);
+	}
+	else {
+		ret = match_pathname(pathname, pathlen,
+				     base, baselen,
+				     pattern, prefix,
+				     pat->patternlen, pat->flags);
+	}
+
+	if (path_munge)
+		((char *)pathname)[pathlen] = path_munge;
+	((char *)pattern)[pat->patternlen] = pattern_munge;
+	return ret;
 }
 
 static int macroexpand_one(int attr_nr, int rem);
diff --git a/t/t5002-archive-attr-pattern.sh b/t/t5002-archive-attr-pattern.sh
index 0c847fb..3be809c 100755
--- a/t/t5002-archive-attr-pattern.sh
+++ b/t/t5002-archive-attr-pattern.sh
@@ -27,6 +27,10 @@ test_expect_success 'setup' '
 	echo ignored-only-if-dir/ export-ignore >>.git/info/attributes &&
 	git add ignored-only-if-dir &&
 
+	mkdir -p ignored-without-slash &&
+	echo ignored without slash >ignored-without-slash/foo &&
+	git add ignored-without-slash/foo &&
+	echo ignored-without-slash export-ignore >>.git/info/attributes &&
 
 	mkdir -p one-level-lower/two-levels-lower/ignored-only-if-dir &&
 	echo ignored by ignored dir >one-level-lower/two-levels-lower/ignored-only-if-dir/ignored-by-ignored-dir &&
@@ -49,6 +53,8 @@ test_expect_missing	archive/ignored-ony-if-dir/ignored-by-ignored-dir
 test_expect_exists	archive/not-ignored-dir/
 test_expect_missing	archive/ignored-only-if-dir/
 test_expect_missing	archive/ignored-ony-if-dir/ignored-by-ignored-dir
+test_expect_missing     archive/ignored-without-slash/ &&
+test_expect_missing     archive/ignored-without-slash/foo &&
 test_expect_exists	archive/one-level-lower/
 test_expect_missing	archive/one-level-lower/two-levels-lower/ignored-only-if-dir/
 test_expect_missing	archive/one-level-lower/two-levels-lower/ignored-ony-if-dir/ignored-by-ignored-dir
Previous: Junio C HamanoNext: Junio C Hamano
Message 6 of 66 in “[regression?] trailing slash required in .gitattributes”
  1. Jeff KingMar 19, 2013
  2. Junio C HamanoMar 19, 2013
  3. Jeff KingMar 19, 2013
  4. Jeff KingMar 22, 2013
  5. Junio C HamanoMar 22, 2013
  6. Jeff KingMar 23, 2013
  7. Junio C HamanoMar 24, 2013
  8. 0/4 attribute regression fix for maint-1.8.1 and upwardJunio C Hamano, Mar 26, 2013
  9. 1/4 attr.c::path_matches(): the basename is part of the pathnameJunio C Hamano, Mar 26, 2013
  10. Jeff KingMar 26, 2013
  11. Duy NguyenMar 27, 2013
  12. 2/4 dir.c::match_basename(): pay attention to the length of string parametersJunio C Hamano, Mar 26, 2013
  13. Jeff KingMar 26, 2013
  14. Jeff KingMar 26, 2013
  15. Junio C HamanoMar 26, 2013
  16. Jeff KingMar 26, 2013
  17. Junio C HamanoMar 26, 2013
  18. Jeff KingMar 27, 2013
  19. 3/4 attr.c::path_matches(): special case paths that end with a slashJunio C Hamano, Mar 26, 2013
  20. Jeff KingMar 26, 2013
  21. Jeff KingMar 26, 2013
  22. Duy NguyenMar 27, 2013
  23. Jeff KingMar 28, 2013
  24. 4/4 make sure a pattern without trailing slash matches a directoryJunio C Hamano, Mar 26, 2013
  25. Jeff KingMar 26, 2013
  26. Duy NguyenMar 27, 2013
  27. Junio C HamanoMar 27, 2013
  28. Duy NguyenMar 27, 2013
  29. 0/6 attribute regression fix for maint-1.8.1 and upwardJeff King, Mar 28, 2013
  30. 1/6 attr.c::path_matches(): the basename is part of the pathnameJeff King, Mar 28, 2013
  31. 2/6 dir.c::match_basename(): pay attention to the length of string parametersJeff King, Mar 28, 2013
  32. Jeff KingMar 28, 2013
  33. Jeff KingMar 28, 2013
  34. Junio C HamanoMar 28, 2013
  35. Duy NguyenMar 28, 2013
  36. Duy NguyenMar 29, 2013
  37. Jeff KingMar 29, 2013
  38. Junio C HamanoMar 29, 2013
  39. 3/6 dir.c::match_pathname(): adjust patternlen when shifting patternJeff King, Mar 28, 2013
  40. 4/6 dir.c::match_pathname(): pay attention to the length of string parametersJeff King, Mar 28, 2013
  41. Junio C HamanoMar 28, 2013
  42. Duy NguyenMar 29, 2013
  43. Duy NguyenMar 29, 2013
  44. Torsten BögershausenMar 29, 2013
  45. Duy NguyenMar 29, 2013
  46. Jeff KingMar 29, 2013
  47. Duy NguyenMar 29, 2013
  48. Junio C HamanoMar 29, 2013
  49. Jeff KingMar 29, 2013
  50. Junio C HamanoMar 29, 2013
  51. Jeff KingMar 29, 2013
  52. Duy NguyenMar 30, 2013
  53. 5/6 attr.c::path_matches(): special case paths that end with a slashJeff King, Mar 28, 2013
  54. 6/6 t: check that a pattern without trailing slash matches a directoryJeff King, Mar 28, 2013
  55. Eric SunshineMar 28, 2013
  56. Jeff KingMar 28, 2013
  57. Duy NguyenMar 23, 2013
  58. Duy NguyenMar 23, 2013
  59. 0/4 attr directory matching regressionNguyễn Thái Ngọc Duy, Mar 25, 2013
  60. 1/4 wildmatch: do not require "text" to be NUL-terminatedNguyễn Thái Ngọc Duy, Mar 25, 2013
  61. 2/4 attr.c: fix pattern{,len} inconsistency in struct match_attrNguyễn Thái Ngọc Duy, Mar 25, 2013
  62. 3/4 dir.c: make match_{base,path}name respect {basename,path}lenNguyễn Thái Ngọc Duy, Mar 25, 2013
  63. 4/4 attr.c: fix matching "subdir" without the trailing slashNguyễn Thái Ngọc Duy, Mar 25, 2013
  64. Duy NguyenMar 25, 2013
  65. Duy NguyenMar 25, 2013
  66. Junio C HamanoMar 26, 2013

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

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