From: Mikhail Pchelin Subject: fix .{cvs,git}ignore handling To: gameoftrees@openbsd.org Date: Sat, 8 Aug 2026 18:02:01 +0300 If both .cvsignore and .gitignore files exist in a directory, only .cvsignore will be respected and ignores from the .gitignore will be dropped, inlined patch fixes that, both files are respected now, the test case is included. This work is part of my larger patch where I'd want to add support for .gitconfig core.excludesfile, this the feature I wanted for a long time, because in every project I have either Session.vim or cscope.* files, and it's not feasible to ask to add those to their ignore files, excludesfile helps here a lot to keep the 'status' output clean. I decided to split excludesfile proposal in two patches for easier review, I'll send the second one later. diff refs/heads/main 4afdcfff1150279725f55deefa98bc97a858eebb commit - ae25db3f82d89c28505e5fc1f58b8ba693282f5b commit + 4afdcfff1150279725f55deefa98bc97a858eebb blob - fca04b3b62b7ecd7035e827c334fa35ad977b7a3 blob + 74a2ceb62249e0a6b2c282d6644bb777c03415b5 --- lib/worktree.c +++ lib/worktree.c @@ -3662,6 +3662,11 @@ done: return err; } +struct got_ignores { + struct got_pathlist_head cvs; + struct got_pathlist_head git; +}; + struct diff_dir_cb_arg { struct got_fileindex *fileindex; struct got_worktree *worktree; @@ -3672,8 +3677,7 @@ struct diff_dir_cb_arg { void *status_arg; got_cancel_cb cancel_cb; void *cancel_arg; - /* A pathlist containing per-directory pathlists of ignore patterns. */ - struct got_pathlist_head *ignores; + struct got_ignores *ignores; int report_unchanged; int no_ignores; }; @@ -3778,7 +3782,7 @@ status_old(void *arg, struct got_fileindex_entry *ie, } static void -free_ignores(struct got_pathlist_head *ignores) +free_ignore_list(struct got_pathlist_head *ignores) { struct got_pathlist_entry *pe; @@ -3790,6 +3794,13 @@ free_ignores(struct got_pathlist_head *ignores) got_pathlist_free(ignores, GOT_PATHLIST_FREE_ALL); } +static void +free_ignores(struct got_ignores *ignores) +{ + free_ignore_list(&ignores->cvs); + free_ignore_list(&ignores->git); +} + static const struct got_error * read_ignores(struct got_pathlist_head *ignores, const char *path, FILE *f) { @@ -3882,7 +3893,7 @@ match_path(const char *pattern, size_t pattern_len, co } static int -match_ignores(struct got_pathlist_head *ignores, const char *path) +match_dir_ignores(struct got_pathlist_head *ignores, const char *path) { struct got_pathlist_entry *pe; @@ -3940,6 +3951,16 @@ match_ignores(struct got_pathlist_head *ignores, const return 0; } +static int +match_ignores(struct got_ignores *ignores, const char *path) +{ + if (match_dir_ignores(&ignores->cvs, path)) + return 1; + if (match_dir_ignores(&ignores->git, path)) + return 1; + return 0; +} + static const struct got_error * add_ignores(struct got_pathlist_head *ignores, const char *root_path, const char *path, int dirfd, const char *ignores_filename) @@ -4034,12 +4055,12 @@ status_traverse(void *arg, const char *path, int dirfd if (a->no_ignores) return NULL; - err = add_ignores(a->ignores, a->worktree->root_path, + err = add_ignores(&a->ignores->cvs, a->worktree->root_path, path, dirfd, ".cvsignore"); if (err) return err; - err = add_ignores(a->ignores, a->worktree->root_path, path, + err = add_ignores(&a->ignores->git, a->worktree->root_path, path, dirfd, ".gitignore"); return err; @@ -4049,7 +4070,7 @@ static const struct got_error * report_single_file_status(const char *path, const char *ondisk_path, struct got_fileindex *fileindex, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo, int report_unchanged, - struct got_pathlist_head *ignores, int no_ignores) + struct got_ignores *ignores, int no_ignores) { struct got_fileindex_entry *ie; struct stat sb; @@ -4077,18 +4098,18 @@ report_single_file_status(const char *path, const char } static const struct got_error * -add_ignores_from_parent_paths(struct got_pathlist_head *ignores, +add_ignores_from_parent_paths(struct got_ignores *ignores, const char *root_path, const char *path) { const struct got_error *err; char *parent_path, *next_parent_path = NULL; - err = add_ignores(ignores, root_path, "", -1, + err = add_ignores(&ignores->cvs, root_path, "", -1, ".cvsignore"); if (err) return err; - err = add_ignores(ignores, root_path, "", -1, + err = add_ignores(&ignores->git, root_path, "", -1, ".gitignore"); if (err) return err; @@ -4100,11 +4121,11 @@ add_ignores_from_parent_paths(struct got_pathlist_head return err; } for (;;) { - err = add_ignores(ignores, root_path, parent_path, -1, + err = add_ignores(&ignores->cvs, root_path, parent_path, -1, ".cvsignore"); if (err) break; - err = add_ignores(ignores, root_path, parent_path, -1, + err = add_ignores(&ignores->git, root_path, parent_path, -1, ".gitignore"); if (err) break; @@ -4156,7 +4177,7 @@ static const struct got_error * report_children(struct got_pathlist_head *children, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, int is_root_dir, int report_unchanged, - struct got_pathlist_head *ignores, int no_ignores, + struct got_ignores *ignores, int no_ignores, got_worktree_status_cb status_cb, void *status_arg, got_cancel_cb cancel_cb, void *cancel_arg) { @@ -4204,10 +4225,12 @@ worktree_status(struct got_worktree *worktree, const c struct got_fileindex_diff_dir_cb fdiff_cb; struct diff_dir_cb_arg arg; char *ondisk_path = NULL; - struct got_pathlist_head ignores, missing_children; + struct got_ignores ignores; + struct got_pathlist_head missing_children; struct got_fileindex_entry *ie; - RB_INIT(&ignores); + RB_INIT(&ignores.cvs); + RB_INIT(&ignores.git); RB_INIT(&missing_children); if (asprintf(&ondisk_path, "%s%s%s", blob - 0874b88152ef2725e3b0e312b01cda5d76c37b91 blob + e8ac4403e2d35fac28fe1b01bfe6242b5ede36c4 --- regress/cmdline/status.sh +++ regress/cmdline/status.sh @@ -875,6 +875,88 @@ test_status_multiple_gitignore_files() { test_done "$testroot" "$ret" } +test_status_cvsignore_and_gitignore_together() { + local testroot=`test_init status_cvsignore_and_gitignore_together` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo "unversioned file" > $testroot/wt/foo + echo "unversioned file" > $testroot/wt/bar + echo "unversioned file" > $testroot/wt/baz + echo "unversioned file" > $testroot/wt/epsilon/foo + echo "unversioned file" > $testroot/wt/epsilon/bar + echo "unversioned file" > $testroot/wt/epsilon/baz + + # .{cvs,git}ignore together + echo "foo" > $testroot/wt/.cvsignore + echo "bar" > $testroot/wt/.gitignore + echo "foo" > $testroot/wt/epsilon/.cvsignore + echo "bar" > $testroot/wt/epsilon/.gitignore + + echo '? .cvsignore' > $testroot/stdout.expected + echo '? .gitignore' >> $testroot/stdout.expected + echo '? baz' >> $testroot/stdout.expected + echo '? epsilon/.cvsignore' >> $testroot/stdout.expected + echo '? epsilon/.gitignore' >> $testroot/stdout.expected + echo '? epsilon/baz' >> $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 + test_done "$testroot" "$ret" + return 1 + fi + + # .{cvs,git}ignore in subdir + echo '? epsilon/.cvsignore' > $testroot/stdout.expected + echo '? epsilon/.gitignore' >> $testroot/stdout.expected + echo '? epsilon/baz' >> $testroot/stdout.expected + (cd $testroot/wt && got status epsilon > $testroot/stdout) + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # -I should show files from both ignores + cat > $testroot/stdout.expected < $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "got status failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + test_status_status_code() { local testroot=`test_init status_status_code` @@ -1247,6 +1329,7 @@ run_test test_status_gitignore_leading_slashes run_test test_status_gitignore_trailing_slashes run_test test_status_gitignore_comments run_test test_status_multiple_gitignore_files +run_test test_status_cvsignore_and_gitignore_together run_test test_status_status_code run_test test_status_suppress run_test test_status_empty_file