commit 1b5d300f3ff325202e42e0a05b1f23f1d8b8e839 from: Stefan Sperling via: Thomas Adam date: Mon Feb 20 16:18:17 2023 UTC have ignore patterns with trailing slashes match directories only ok jamsek 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" }