From: Stefan Sperling Subject: status walk regression fix To: gameoftrees@openbsd.org Date: Tue, 12 Oct 2021 14:41:16 +0200 My commit which made 'got status' skip ignore directories has introduced a regression. In my /usr/src tree I see a missing file being reported: $ got status ! gnu/llvm/utils/lit/tests/Inputs/exec-discovery-in-tree/obj/lit.site.cfg ? sys/dev/acpi/pchgpio.c.orig ? sys/dev/pci/if_iwx.c.orig ? sys/dev/pci/if_iwx.c.rej ? sys/dev/pci/if_iwxvar.h.orig $ The missing file does exist on disk, however. It ends up being reported as missing because the 'obj' dir is ignored and therefore not visited. What happens internally is that we detect the path 'obj/lit.site.cfg' as missing from directory gnu/llvm/utils/lit/tests/Inputs/exec-discovery-in-tree, which is obviously bogus. (Curiously, this file only exists in the Git repository, not in CVS. In CVS it was at the same path without the 'obj' dir: gnu/llvm/utils/lit/tests/Inputs/exec-discovery-in-tree/lit.site.cfg and this file was removed from CVS with the llvm 8.0.1 upgrade). I could reproduce this problem in the test suite by tweaking an existing test slightly: test_status_cvsignore --- /tmp/got-test-status_cvsignore-BrSSQK9f/stdout.expected Tue Oct 12 14:37:07 2021 +++ /tmp/got-test-status_cvsignore-BrSSQK9f/stdout Tue Oct 12 14:37:08 2021 @@ -2,3 +2,4 @@ ? epsilon/.cvsignore ? epsilon/boo ? foop +! gamma/delta test failed; leaving test data in /tmp/got-test-status_cvsignore-BrSSQK9f To fix this we need to traverse any ignored directory which contains a file which is tracked by the file index. Fortunately, looking up such files can be done efficiently by walking the file index RB tree. Patch below. ok? diff 79b1ba5c34990019a1e23d9bbac6188c342d4550 8b9504f54dfa79e560860c0a8f03116992f0c051 blob - fba8eb73431c038f4a91c744d8f6f0c55855c7e0 blob + 91a35e46fce2547ae0c44ba7edf82da9edd9e097 --- lib/fileindex.c +++ lib/fileindex.c @@ -987,6 +987,30 @@ free_dirlist(struct got_pathlist_head *dirlist) got_pathlist_free(dirlist); } +static int +have_tracked_file_in_dir(struct got_fileindex *fileindex, const char *path) +{ + struct got_fileindex_entry *ie; + size_t path_len = strlen(path); + int cmp; + + ie = RB_ROOT(&fileindex->entries); + while (ie) { + if (got_path_is_child(ie->path, path, path_len)) + return 1; + cmp = got_path_cmp(path, ie->path, path_len, + got_fileindex_entry_path_len(ie)); + if (cmp < 0) + ie = RB_LEFT(ie, entry); + else if (cmp > 0) + ie = RB_RIGHT(ie, entry); + else + break; + } + + return 0; +} + static const struct got_error * walk_dir(struct got_pathlist_entry **next, struct got_fileindex *fileindex, struct got_fileindex_entry **ie, struct got_pathlist_entry *dle, int fd, @@ -1013,6 +1037,11 @@ walk_dir(struct got_pathlist_entry **next, struct got_ } else type = de->d_type; + /* Must traverse ignored directories if they contain tracked files. */ + if (type == DT_DIR && ignore && + have_tracked_file_in_dir(fileindex, path)) + ignore = 0; + if (type == DT_DIR && !ignore) { char *subpath; char *subdirpath; blob - 8b67476fd9377314c10033f7fe555651af4cda1a blob + 9e581724fe4e03c1b4d098200691d364a658da87 --- regress/cmdline/status.sh +++ regress/cmdline/status.sh @@ -531,6 +531,7 @@ test_status_cvsignore() { mkdir -p $testroot/wt/epsilon/new/ echo "unversioned file" > $testroot/wt/epsilon/new/foo echo "**/foo" > $testroot/wt/.cvsignore + echo "**/gamma" >> $testroot/wt/.cvsignore echo "bar" > $testroot/wt/epsilon/.cvsignore echo "moo" >> $testroot/wt/epsilon/.cvsignore