Commit Diff


commit - 52ca43c16adaa0fec4ba91933386fe4970f6bacd
commit + 1b5d300f3ff325202e42e0a05b1f23f1d8b8e839
blob - b6cc16e8e5a4b02bf33a4f2af21bc0fd0f58c4bd
blob + 06d9ba522ae0001bd8bb697df46c9c952023dd09
--- got/got.1
+++ got/got.1
@@ -788,6 +788,9 @@ and
 .Pa .gitignore
 files in each traversed directory and will not display unversioned files
 which match these patterns.
+Ignore patterns which end with a slash,
+.Dq / ,
+will only match directories.
 As an extension to
 .Xr glob 7
 matching rules,
blob - 10b84a375cee68e9e2e2b67f9cb0b850e777eace
blob + c9f89f2a3d0213ed6de0e7ee94c89727f29a8b91
--- lib/worktree.c
+++ lib/worktree.c
@@ -3548,6 +3548,26 @@ done:
 		got_pathlist_free(ignorelist, GOT_PATHLIST_FREE_PATH);
 	}
 	return err;
+}
+
+static int
+match_path(const char *pattern, size_t pattern_len, const char *path,
+    int flags)
+{
+	char buf[PATH_MAX];
+
+	/*
+	 * Trailing slashes signify directories.
+	 * Append a * to make such patterns conform to fnmatch rules.
+	 */
+	if (pattern_len > 0 && pattern[pattern_len - 1] == '/') {
+		if (snprintf(buf, sizeof(buf), "%s*", pattern) >= sizeof(buf))
+			return FNM_NOMATCH; /* XXX */
+
+		return fnmatch(buf, path, flags);
+	}
+
+	return fnmatch(pattern, path, flags);
 }
 
 static int
@@ -3561,14 +3581,15 @@ match_ignores(struct got_pathlist_head *ignores, const
 		struct got_pathlist_entry *pi;
 
 		TAILQ_FOREACH(pi, ignorelist, entry) {
-			const char *p, *pattern = pi->path;
+			const char *p;
 
-			if (strncmp(pattern, "**/", 3) != 0)
+			if (pi->path_len < 3 ||
+			    strncmp(pi->path, "**/", 3) != 0)
 				continue;
-			pattern += 3;
 			p = path;
 			while (*p) {
-				if (fnmatch(pattern, p,
+				if (match_path(pi->path + 3,
+				    pi->path_len - 3, p,
 				    FNM_PATHNAME | FNM_LEADING_DIR)) {
 					/* Retry in next directory. */
 					while (*p && *p != '/')
@@ -3593,11 +3614,11 @@ match_ignores(struct got_pathlist_head *ignores, const
 			struct got_pathlist_head *ignorelist = pe->data;
 			struct got_pathlist_entry *pi;
 			TAILQ_FOREACH(pi, ignorelist, entry) {
-				const char *pattern = pi->path;
-				int flags = FNM_LEADING_DIR;
-				if (strstr(pattern, "/**/") == NULL)
+				int flags = FNM_LEADING_DIR;
+				if (strstr(pi->path, "/**/") == NULL)
 					flags |= FNM_PATHNAME;
-				if (fnmatch(pattern, path, flags))
+				if (match_path(pi->path, pi->path_len,
+				    path, flags))
 					continue;
 				return 1;
 			}
blob - d3e5e4f0989bbbafd54ee120bce99a472b048b81
blob + ea1578eb72862dd6ea902835f84e766890b1e94d
--- regress/cmdline/status.sh
+++ regress/cmdline/status.sh
@@ -709,17 +709,20 @@ test_status_gitignore_trailing_slashes() {
 	echo "unversioned file" > $testroot/wt/epsilon/bar
 	echo "unversioned file" > $testroot/wt/epsilon/boo
 	echo "unversioned file" > $testroot/wt/epsilon/moo
-	echo "epsilon/" > $testroot/wt/.gitignore
+	echo "unversioned file" > $testroot/wt/upsilon
 
+	# Match the directory epsilon but not the regular file upsilon
+	echo "*psilon/" > $testroot/wt/.gitignore
+
 	echo '?  .gitignore' > $testroot/stdout.expected
 	echo '?  foo' >> $testroot/stdout.expected
+	echo '?  upsilon' >> $testroot/stdout.expected
 	(cd $testroot/wt && got status > $testroot/stdout)
 
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
-		#diff -u $testroot/stdout.expected $testroot/stdout
-		ret="xfail trailing slashes not matched"
+		diff -u $testroot/stdout.expected $testroot/stdout
 	fi
 	test_done "$testroot" "$ret"
 }