From: Stefan Sperling Subject: Re: ignored files can't handle trailing slashes To: Lucas Cc: gameoftrees@openbsd.org Date: Sun, 19 Feb 2023 21:40:36 +0100 On Sun, Feb 05, 2023 at 02:05:30PM +0000, Lucas wrote: > Hello list, > > As I shared on IRC, when `got import -I dir/` is used, `dir/` is *not* > ignored: fnmatch is called with the name in dirent's d_name, which > doesn't include a trailing slash if the entry is of type directory. > Do note that calling got_path_strip_trailing_slashes on `-I` argument > is not an option: 'dir*/' can have some nasty side effects. The patch > for regress/cmdline/import.sh below makes this explicit. > > Same can be observed for .{cvs,git}ignore. This patch fixes the same problem for 'got status'. ok? "return FNM_NOMATCH; /* XXX */" in the diff is because there is no way to return struct got_error from within this function without further refactoring. I think that is fine for now because paths longer than PATH_MAX can probably not even be checked out, and such long patterns will be very rare if they exist at all. make 'got status' ignore patterns with trailing slashes match on directories diff d0f874e072cfb4119033a71b9f162ae02eca44ab a6e726d31e2e25a79e7f5d4b6b747d2778b15c5d commit - d0f874e072cfb4119033a71b9f162ae02eca44ab commit + a6e726d31e2e25a79e7f5d4b6b747d2778b15c5d blob - b6cc16e8e5a4b02bf33a4f2af21bc0fd0f58c4bd blob + 06d9ba522ae0001bd8bb697df46c9c952023dd09 --- got/got.1 +++ got/got.1 @@ -788,6 +788,9 @@ As an extension to .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 - 2d75c470b84a9c0f07ca7ccd39595267ab24fd49 blob + a5e3af7acc609d51f0bd553fae407d173c3df895 --- lib/worktree.c +++ lib/worktree.c @@ -3559,6 +3559,26 @@ match_ignores(struct got_pathlist_head *ignores, const } 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 match_ignores(struct got_pathlist_head *ignores, const char *path) { struct got_pathlist_entry *pe; @@ -3569,14 +3589,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 != '/') @@ -3601,11 +3622,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) + 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 - f992b65b13667e8a421e2e410937179db7c27aac blob + f2d3d5dfe1904cd50bfc165b2d05f293cb4fad5f --- 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" }