{"thread":{"id":"64891","subject":"Bug: git add :!x . exits with error when x is in .gitignore","startedAt":"2026-01-31T19:43:28Z","lastAt":"2026-02-02T21:25:26Z","messageCount":3,"participants":["Remy D. Farley","Tian Yuchen"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"534924","messageId":"20260131194309.601838-1-one-d-wide@protonmail.com","threadId":"64891","inReplyTo":null,"subject":"Bug: git add :!x . exits with error when x is in .gitignore","fromName":"Remy D. Farley","fromEmail":"one-d-wide@protonmail.com","sentAt":"2026-01-31T19:43:20Z","receivedAt":"2026-01-31T19:43:28Z","isPatch":false,"sender":{"key":"one-d-wide@protonmail.com","avatar":null},"body":"`git add :!x .`, which is executed as part `git stash :!x`, seems to treat\npathspec with and without exclude magic the same, exiting with error when \"x\"\nis in gitignore file. If file \"x\" isn't present, `git add x` still exits with\nerror as expected (although with a different one), but `git add :!x .` succeeds.\n\nGit-add manpage doesn't specify that exclude pathspecs are treated anyhow\ndifferently from normal ones, so I'm not sure if it's really a bug. But it does\nseem like one. I originally tried to exclude certain files from scripted stashes\nusing pathspec, but had to switch to reusing core.excludesFile (which is\nprobably a better fit for my use case).\n\n\n```sh\n$ sh repro.sh\n[...]\n+ echo x >.gitignore\n+ echo x >x\n+ git add -- ':!x' .\nSaved working directory and index state WIP on main: c8a842d Init\nThe following paths are ignored by one of your .gitignore files:\nx\nhint: Use -f if you really want to add them.\nhint: Disable this message with \"git config set advice.addIgnoredFile false\"\n+ echo exited with code 1\nexited with code 1\n```\n\n\n```sh\n# repro.sh\nrm -rf repro; mkdir repro; cd repro\ntrap 'echo exited with code $?' EXIT\nset -euo pipefail -o xtrace\n\ngit init\ngit commit -m Init --allow-empty\n\n# If we comment out either of the following lines, git add/stash commands below succeed\necho x >.gitignore\necho x >x\n\n# Git add . is executed as part of git stash, as can be seen using strace -ffeexecve\ngit add -- \":!x\" . # fails\n# git stash --include-untracked -- \":!x\" # fails\n\necho ok\n```\n\n---\n dir.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/dir.c b/dir.c\nindex b00821f2..0026bd65 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2264,7 +2264,7 @@ static int exclude_matches_pathspec(const char *path, int pathlen,\n {\n \tint i;\n \n-\tif (!pathspec || !pathspec->nr)\n+\tif (!pathspec || !pathspec->nr || pathspec->magic & PATHSPEC_EXCLUDE)\n \t\treturn 0;\n \n \tGUARD_PATHSPEC(pathspec,\n-- \n2.51.2\n\n\n"},{"id":"534992","messageId":"1d560aa1-d452-47f5-aaf2-4cb1ccdab100@gmail.com","threadId":"64891","inReplyTo":"20260131194309.601838-1-one-d-wide@protonmail.com","subject":"Re: Bug: git add :!x . exits with error when x is in .gitignore","fromName":"Tian Yuchen","fromEmail":"a3205153416@gmail.com","sentAt":"2026-02-02T18:04:45Z","receivedAt":"2026-02-02T18:04:49Z","isPatch":false,"sender":{"key":"cat@malon.dev","avatar":"https://avatars.githubusercontent.com/u/232002048?v=4"},"body":"On 2/1/26 03:43, Remy D. Farley wrote:\n\nThanks for the report and the patch! I also agree that the behavior you \ndescribed seems like a bug.\n\nHowever, though I'm new to git community, I still have concerns about \nthe fix in 'dir.c':\n   > -\tif (!pathspec || !pathspec->nr)\n> +\tif (!pathspec || !pathspec->nr || pathspec->magic & PATHSPEC_EXCLUDE)\n>   \t\treturn 0;\n\nI tried to analyze the logic and I believe this patch breaks the basic \nfunctionality of the negative pathspec (:!):\n\n(I didn't check the caller of exclude_matches_pathspec and I only focus \non the function itself. Tell me if I'm wrong :)\n\nBy returning 0 whenever PATHSPEC_EXCLUDE is set globally, \nexclude_matches_pathspec will report \"no match\" for all files, \neffectively disabling the exclusion mechanism entirely.\n\nInstead of fixing the issue with ignored files, this patch causes valid \nexclusions to be ignored.\n\nWe must check if the specific path matches the exclude pattern, not \nshort-circuit based on the global flag.\n\nIt will be great if we set a test script for this. I will be working on \nit in the next few days.\n\nRegards,\n\nYuchen\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"},{"id":"535016","messageId":"20260202212420.1185803-1-one-d-wide@protonmail.com","threadId":"64891","inReplyTo":"1d560aa1-d452-47f5-aaf2-4cb1ccdab100@gmail.com","subject":"Re: Bug: git add :!x . exits with error when x is in .gitignore","fromName":"Remy D. Farley","fromEmail":"one-d-wide@protonmail.com","sentAt":"2026-02-02T21:25:17Z","receivedAt":"2026-02-02T21:25:26Z","isPatch":false,"sender":{"key":"one-d-wide@protonmail.com","avatar":null},"body":"> Thanks for the report and the patch! I also agree that the behavior you\n> described seems like a bug.\n> \n> However, though I'm new to git community, I still have concerns about\n> the fix in 'dir.c':\n> \n> > - if (!pathspec || !pathspec->nr)\n> > + if (!pathspec || !pathspec->nr || pathspec->magic & PATHSPEC_EXCLUDE)\n> > return 0;\n> \n> \n> I tried to analyze the logic and I believe this patch breaks the basic\n> functionality of the negative pathspec (:!):\n> \n> (I didn't check the caller of exclude_matches_pathspec and I only focus\n> on the function itself. Tell me if I'm wrong :)\n> \n> By returning 0 whenever PATHSPEC_EXCLUDE is set globally,\n> exclude_matches_pathspec will report \"no match\" for all files,\n> effectively disabling the exclusion mechanism entirely.\n> \n> Instead of fixing the issue with ignored files, this patch causes valid\n> exclusions to be ignored.\n> \n> We must check if the specific path matches the exclude pattern, not\n> short-circuit based on the global flag.\n\n\nOops. Somehow I was convinced struct pathspec represents only a single path.\nThis way something like this would silently succeed, without actually indexing x:\n\n  touch x\n  echo x >.gitignore\n  git add -n \":(exclude)smth/else\" x\n\nThank you for pointing out.\n\n\n> It will be great if we set a test script for this. I will be working on\n> it in the next few days.\n\n---\n dir.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/dir.c b/dir.c\nindex b00821f2..ed6b99e3 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2280,6 +2280,9 @@ static int exclude_matches_pathspec(const char *path, int pathlen,\n \t\tconst struct pathspec_item *item = &pathspec->items[i];\n \t\tint len = item->nowildcard_len;\n \n+\t\tif (item->magic & PATHSPEC_EXCLUDE)\n+\t\t\tcontinue;\n+\n \t\tif (len == pathlen &&\n \t\t    !ps_strncmp(item, item->match, path, pathlen))\n \t\t\treturn 1;\n-- \n2.51.2\n\n\n"}]}