Download raw body.
fix .{cvs,git}ignore handling
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
fix .{cvs,git}ignore handling