threads / bug / 64891

Bug: git add :!x . exits with error when x is in .gitignore

Subject: Bug: git add :!x . exits with error when x is in .gitignore

## tl;dr

3 messages between Jan 31, 2026 and Feb 2, 2026.

replies: 2people: 2as markdown or json

Remy D. Farley· Jan 31, 2026, 19:43 UTC · lore

`git add :!x .`, which is executed as part `git stash :!x`, seems to treat pathspec with and without exclude magic the same, exiting with error when "x" is in gitignore file. If file "x" isn't present, `git add x` still exits with error as expected (although with a different one), but `git add :!x .` succeeds.

Git-add manpage doesn't specify that exclude pathspecs are treated anyhow differently from normal ones, so I'm not sure if it's really a bug. But it does seem like one. I originally tried to exclude certain files from scripted stashes using pathspec, but had to switch to reusing core.excludesFile (which is probably a better fit for my use case).

```sh
$ sh repro.sh
[...]
+ echo x >.gitignore
+ echo x >x
+ git add -- ':!x' .
Saved working directory and index state WIP on main: c8a842d Init
The following paths are ignored by one of your .gitignore files:
x
hint: Use -f if you really want to add them.
hint: Disable this message with "git config set advice.addIgnoredFile false"
+ echo exited with code 1
exited with code 1
```

```sh # repro.sh rm -rf repro; mkdir repro; cd repro trap 'echo exited with code $?' EXIT set -euo pipefail -o xtrace

git init git commit -m Init --allow-empty

# If we comment out either of the following lines, git add/stash commands below succeed echo x >.gitignore echo x >x

# Git add . is executed as part of git stash, as can be seen using strace -ffeexecve git add -- ":!x" . # fails # git stash --include-untracked -- ":!x" # fails

echo ok ```

---
 dir.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dir.c b/dir.c
index b00821f2..0026bd65 100644
--- a/dir.c
+++ b/dir.c
@@ -2264,7 +2264,7 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
 {
 	int i;
 
-	if (!pathspec || !pathspec->nr)
+	if (!pathspec || !pathspec->nr || pathspec->magic & PATHSPEC_EXCLUDE)
 		return 0;
 
 	GUARD_PATHSPEC(pathspec,
-- 
2.51.2
Tian Yuchen· Feb 2, 2026, 18:04 UTC · re: Remy D. Farley · lore

Re: Bug: git add :!x . exits with error when x is in .gitignore

On 2/1/26 03:43, Remy D. Farley wrote:

Thanks for the report and the patch! I also agree that the behavior you described seems like a bug.

However, though I'm new to git community, I still have concerns about 
the fix in 'dir.c':
   > -	if (!pathspec || !pathspec->nr)
> +	if (!pathspec || !pathspec->nr || pathspec->magic & PATHSPEC_EXCLUDE)
>   		return 0;

I tried to analyze the logic and I believe this patch breaks the basic functionality of the negative pathspec (:!):

(I didn't check the caller of exclude_matches_pathspec and I only focus on the function itself. Tell me if I'm wrong :)

By returning 0 whenever PATHSPEC_EXCLUDE is set globally, exclude_matches_pathspec will report "no match" for all files, effectively disabling the exclusion mechanism entirely.

Instead of fixing the issue with ignored files, this patch causes valid exclusions to be ignored.

We must check if the specific path matches the exclude pattern, not short-circuit based on the global flag.

It will be great if we set a test script for this. I will be working on it in the next few days.

Regards,
Yuchen
Remy D. Farley· Feb 2, 2026, 21:25 UTC · re: Tian Yuchen · lore

Re: Bug: git add :!x . exits with error when x is in .gitignore

Show 26 quoted lines
> Thanks for the report and the patch! I also agree that the behavior you
> described seems like a bug.
> 
> However, though I'm new to git community, I still have concerns about
> the fix in 'dir.c':
> 
> > - if (!pathspec || !pathspec->nr)
> > + if (!pathspec || !pathspec->nr || pathspec->magic & PATHSPEC_EXCLUDE)
> > return 0;
> 
> 
> I tried to analyze the logic and I believe this patch breaks the basic
> functionality of the negative pathspec (:!):
> 
> (I didn't check the caller of exclude_matches_pathspec and I only focus
> on the function itself. Tell me if I'm wrong :)
> 
> By returning 0 whenever PATHSPEC_EXCLUDE is set globally,
> exclude_matches_pathspec will report "no match" for all files,
> effectively disabling the exclusion mechanism entirely.
> 
> Instead of fixing the issue with ignored files, this patch causes valid
> exclusions to be ignored.
> 
> We must check if the specific path matches the exclude pattern, not
> short-circuit based on the global flag.

Oops. Somehow I was convinced struct pathspec represents only a single path. This way something like this would silently succeed, without actually indexing x:

  touch x
  echo x >.gitignore
  git add -n ":(exclude)smth/else" x
Thank you for pointing out.
> It will be great if we set a test script for this. I will be working on
> it in the next few days.
---
 dir.c | 3 +++
 1 file changed, 3 insertions(+)
diff --git a/dir.c b/dir.c
index b00821f2..ed6b99e3 100644
--- a/dir.c
+++ b/dir.c
@@ -2280,6 +2280,9 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
 		const struct pathspec_item *item = &pathspec->items[i];
 		int len = item->nowildcard_len;
 
+		if (item->magic & PATHSPEC_EXCLUDE)
+			continue;
+
 		if (len == pathlen &&
 		    !ps_strncmp(item, item->match, path, pathlen))
 			return 1;
-- 
2.51.2

← back to recent threads