From: Renato Aguiar Subject: Re: fix .{cvs,git}ignore handling To: Mikhail Pchelin Cc: gameoftrees@openbsd.org Date: Thu, 13 Aug 2026 12:09:55 -0700 On Thu, Aug 13 2026, Stefan Sperling wrote: > On Sat, Aug 08, 2026 at 06:02:01PM +0300, Mikhail Pchelin wrote: >> 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. > > The intention was to merge patterns from both files into a single > ignores list. Your regression test is good, but the fix isn't quite > right. Instead of using two lists, I think read_ignores() needs to be > changed such that it will append to the existing list if a list already > exists for the given path. Do you agree? > I've been working on a fix for this as well, and that was the direction I was going. I don't think it is ready for review/commit yet, but since you are also working on the same thing I though of sharing what I have so far: diff refs/remotes/origin/main refs/heads/fix-multiple-ignore-files commit - ae25db3f82d89c28505e5fc1f58b8ba693282f5b commit + 2b2810911a91a7239769f9ae40f67be81bfebe79 blob - 8232df1c967a331ed394daaa474c7547e2c93fda blob + 973b0b7690b7d8a2af6816535babe785ebf5653a --- include/got_path.h +++ include/got_path.h @@ -74,6 +74,15 @@ RB_HEAD(got_pathlist_head, got_pathlist_entry); RB_PROTOTYPE(got_pathlist_head, got_pathlist_entry, entry, got_pathlist_cmp); /* + * Find element in list of paths. Return 1 if element is found, 0 otherwise. + * When first argument is not NULL, set it to a pointer to the found element, or + * to a NULL pointer in case the element is not found in the list. + */ +int +got_pathlist_find(struct got_pathlist_entry **, + struct got_pathlist_head *, const char *); + +/* * Insert a path into the list of paths in a predictable order. * The caller should already have initialized the list head. This list stores * the pointer to the path as-is, i.e. the path is not copied internally and blob - b43f45fcda4659959a1775f2a328234862001f60 blob + bfb8711b78840ab74356661b42faceec5e262b22 --- lib/path.c +++ lib/path.c @@ -245,6 +245,26 @@ got_pathlist_insert(struct got_pathlist_entry **insert return NULL; } +int +got_pathlist_find(struct got_pathlist_entry **elem, + struct got_pathlist_head *pathlist, const char *path) +{ + struct got_pathlist_entry pe_query; + struct got_pathlist_entry *pe; + + pe_query.path = path; + pe_query.path_len = strlen(path); + pe = RB_FIND(got_pathlist_head, pathlist, &pe_query); + + if (elem) + *elem = pe; + + if (pe == NULL) + return 0; + + return 1; +} + void got_pathlist_free(struct got_pathlist_head *pathlist, int freemask) { blob - fca04b3b62b7ecd7035e827c334fa35ad977b7a3 blob + d172b0b0a2eadf9c78b2fc631fca49b292ffdfc2 --- lib/worktree.c +++ lib/worktree.c @@ -3795,16 +3795,35 @@ read_ignores(struct got_pathlist_head *ignores, const { const struct got_error *err = NULL; struct got_pathlist_entry *pe = NULL; - struct got_pathlist_head *ignorelist; + struct got_pathlist_head *ignorelist = NULL; char *line = NULL, *pattern, *dirpath = NULL; size_t linesize = 0; ssize_t linelen; - ignorelist = calloc(1, sizeof(*ignorelist)); - if (ignorelist == NULL) - return got_error_from_errno("calloc"); - RB_INIT(ignorelist); + got_pathlist_find(&pe, ignores, path); + if (pe == NULL) { + ignorelist = calloc(1, sizeof(*ignorelist)); + if (ignorelist == NULL) { + err = got_error_from_errno("calloc"); + goto done; + } + RB_INIT(ignorelist); + + dirpath = strdup(path); + if (dirpath == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + + err = got_pathlist_insert(&pe, ignores, dirpath, ignorelist); + if (err || pe == NULL) + goto done; + + dirpath = NULL; + ignorelist = NULL; + } + while ((linelen = getline(&line, &linesize, f)) != -1) { if (linelen > 0 && line[linelen - 1] == '\n') line[linelen - 1] = '\0'; @@ -3826,7 +3845,7 @@ read_ignores(struct got_pathlist_head *ignores, const err = got_error_from_errno("asprintf"); goto done; } - err = got_pathlist_insert(NULL, ignorelist, pattern, NULL); + err = got_pathlist_insert(NULL, pe->data, pattern, NULL); if (err) goto done; } @@ -3835,16 +3854,12 @@ read_ignores(struct got_pathlist_head *ignores, const goto done; } - dirpath = strdup(path); - if (dirpath == NULL) { - err = got_error_from_errno("strdup"); - goto done; - } - err = got_pathlist_insert(&pe, ignores, dirpath, ignorelist); done: - free(line); - if (err || pe == NULL) { + if (line != NULL) + free(line); + if (dirpath != NULL) free(dirpath); + if (ignorelist != NULL) { got_pathlist_free(ignorelist, GOT_PATHLIST_FREE_PATH); free(ignorelist); } @@ -3981,9 +3996,9 @@ add_ignores(struct got_pathlist_head *ignores, const c } if (ignoresfile && fclose(ignoresfile) == EOF && err == NULL) - err = got_error_from_errno2("fclose", path); + err = got_error_from_errno2("fclose", ignorespath); if (fd != -1 && close(fd) == -1 && err == NULL) - err = got_error_from_errno2("close", path); + err = got_error_from_errno2("close", ignorespath); free(ignorespath); return err; } blob - 0874b88152ef2725e3b0e312b01cda5d76c37b91 blob + f2e12edf0a7afe18e462832a82b33f8bb46615f4 --- regress/cmdline/status.sh +++ regress/cmdline/status.sh @@ -717,6 +717,36 @@ EOF test_done "$testroot" "$ret" } +test_status_gitignore_and_cvsignore() { + local testroot=`test_init status_gitignore_and_cvsignore` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo "cvs would ignore this" > $testroot/wt/baz + mkdir -p $testroot/wt/foo + echo "git would ignore this" > $testroot/wt/foo/bar + mkdir -p $testroot/wt/boo/foo + echo "git would ignore this" > $testroot/wt/boo/foo/bar + echo "baz" > $testroot/wt/.cvsignore + echo "**/foo" > $testroot/wt/.gitignore + + echo '? .cvsignore' > $testroot/stdout.expected + echo '? .gitignore' >> $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 + fi + test_done "$testroot" "$ret" +} + test_status_gitignore_leading_slashes() { local testroot=`test_init status_gitignore_leading_slashes` @@ -1243,6 +1273,7 @@ run_test test_status_empty_dir_unversioned_file run_test test_status_many_paths run_test test_status_cvsignore run_test test_status_gitignore +run_test test_status_gitignore_and_cvsignore run_test test_status_gitignore_leading_slashes run_test test_status_gitignore_trailing_slashes run_test test_status_gitignore_comments