{"thread":{"id":"64438","subject":"git-add ignores exclude markers for ignored files","startedAt":"2025-11-04T16:43:07Z","lastAt":"2026-01-20T20:46:21Z","messageCount":4,"participants":["rahn","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"530204","messageId":"MN0PR01MB761143093D1A4D226E8883A380C4A@MN0PR01MB7611.prod.exchangelabs.com","threadId":"64438","inReplyTo":null,"subject":"git-add ignores exclude markers for ignored files","fromName":"","fromEmail":"rahn","sentAt":"2025-11-04T15:59:41Z","receivedAt":"2025-11-04T16:43:07Z","isPatch":false,"sender":{"key":"rahn","avatar":null},"body":"Hey all,\n\nI’ve run into a strange behavior of git-add that I believe is not correct. I couldn’t find something on the mailing list regarding this, so I am writing a new mail. The issue is that if you use git-add with an explicit pathspec with an exclude pattern of a file that is ignored by some .gitignore file (locally or globally) the git-add command errors with the following message:\n \ngit add --\":(exclude)ignored.txt\"\nThe following paths are ignored by one of your \".gitignore\" files:\nignored.txt\nNote: Use -f if you really want to add them.\nNote: Disable this message with \"git config set advice.addIgnoredFile false\"\n\nSteps to reproduce:\n\nmkdir git-add-test \ncd git-add-test\ngit init\ntouch .gitignore\necho “ignored.txt” >>.gitignore\ntouch ignored.txt\ngit add --“:(exclude)ignored.txt”\n \nExpected behavior:\n \nThe git add command recognizes that the file ignored.txt is already excluded despite of it being ignored by some gitignore and thus does not check if it is ignored or not. It simply will not be added. \nNote forcing git-add will do the trick, but this could also have side effects for some files that are ignored but not present in the list of excluded files.\nHence, this can’t be the right solution.\n\nAny advice or confirmation are much appreciated.\n \nThank you\nRené Rahn\n"},{"id":"530223","messageId":"xmqqtsz9o3cn.fsf@gitster.g","threadId":"64438","inReplyTo":"MN0PR01MB761143093D1A4D226E8883A380C4A@MN0PR01MB7611.prod.exchangelabs.com","subject":"Re: git-add ignores exclude markers for ignored files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-04T22:11:20Z","receivedAt":"2025-11-04T22:11:22Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Rahn, René\" <Rene.Rahn@pfizer.com> writes:\n\n> Steps to reproduce:\n>\n> mkdir git-add-test\n> cd git-add-test\n> git init\n> touch .gitignore\n> echo “ignored.txt” >>.gitignore\n> touch ignored.txt\n> git add --“:(exclude)ignored.txt”\n> \n> Expected behavior:\n> \n> The git add command recognizes that the file ignored.txt is\n> already excluded despite of it being ignored by some gitignore and\n> thus does not check if it is ignored or not. It simply will not be\n> added.  Note forcing git-add will do the trick, but this could\n> also have side effects for some files that are ignored but not\n> present in the list of excluded files.  Hence, this can’t be the\n> right solution.\n\nIt is not quite clear what you want to see.  The command would not\nadd ignored.text even if you give \":(exclude)ignored.txt\" from the\ncommand line, would it?\n\nThis may be an ancient regression when e1b8c7bd (dir: remove struct\npath_simplify, 2017-01-04) was rewritten exclude_matches_pathspec()\nfunction, which was written in 29209cbe (dir: fix COLLECT_IGNORED on\nexcluded prefixes, 2010-03-11), back in the days before \":(exclude)\"\nand other pathspec magic was even invented.\n\nPerhaps try this patch?\n\nI have no idea what the ramifications of the change is, though.\nThere may be unintended fallouts in some distant corner, even though\nit does not seem to break any existing tests.\n\n----- >8 -----\nSubject: dir.c: do not be fooled by :(exclude) pathspec elements\n\nWhen exclude_matches_pathspec() tries to determine if an otherwise\nexcluded item matches the pathspec given, it goes through each\npathspec element and declares a hit, without checking if the element\nis a negative \":(exclude)\" element.  Fix it be applying the usual \"a\npath matches if it matches any one of positive pathspec element, and\nif it matches none of negative pathspec elements\" rule in the\nfunction.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\ndiff --git a/dir.c b/dir.c\nindex 5b2181e589..5b1258a09d 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2219,6 +2219,8 @@ static int exclude_matches_pathspec(const char *path, int pathlen,\n \t\t\t\t    const struct pathspec *pathspec)\n {\n \tint i;\n+\tint matches_exclude_magic = 0;\n+\tint matches_pathspec_elem = 0;\n \n \tif (!pathspec || !pathspec->nr)\n \t\treturn 0;\n@@ -2235,15 +2237,23 @@ static int exclude_matches_pathspec(const char *path, int pathlen,\n \tfor (i = 0; i < pathspec->nr; i++) {\n \t\tconst struct pathspec_item *item = &pathspec->items[i];\n \t\tint len = item->nowildcard_len;\n+\t\tint *matches;\n+\n+\t\tif (item->magic & PATHSPEC_EXCLUDE)\n+\t\t\tmatches = &matches_exclude_magic;\n+\t\telse\n+\t\t\tmatches = &matches_pathspec_elem;\n \n \t\tif (len == pathlen &&\n \t\t    !ps_strncmp(item, item->match, path, pathlen))\n-\t\t\treturn 1;\n+\t\t\t*matches = 1;\n \t\tif (len > pathlen &&\n \t\t    item->match[pathlen] == '/' &&\n \t\t    !ps_strncmp(item, item->match, path, pathlen))\n-\t\t\treturn 1;\n+\t\t\t*matches = 1;\n \t}\n+\tif (matches_pathspec_elem && !matches_exclude_magic)\n+\t\treturn 1;\n \treturn 0;\n }\n \ndiff --git a/t/t2204-add-ignored.sh b/t/t2204-add-ignored.sh\nindex 31eb233df5..aa55b219ab 100755\n--- a/t/t2204-add-ignored.sh\n+++ b/t/t2204-add-ignored.sh\n@@ -89,4 +89,21 @@ do\n \t'\n done\n \n+test_expect_success \"exclude magic would not interfere with .gitignore\" '\n+\ttest_write_lines dir file sub ign err out \"*.o\" >.gitignore &&\n+\t>foo.o &&\n+\t>foo.c &&\n+\ttest_must_fail git add foo.o 2>err &&\n+\ttest_grep \"are ignored by one\" err &&\n+\ttest_grep \"hint: Use -f\" err &&\n+\n+\tgit add \":(exclude)foo.o\" &&\n+\tgit ls-files >actual &&\n+\tcat >expect <<-\\EOF &&\n+\t.gitignore\n+\tfoo.c\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n"},{"id":"530231","messageId":"xmqq4ir9nvtj.fsf@gitster.g","threadId":"64438","inReplyTo":"xmqqtsz9o3cn.fsf@gitster.g","subject":"Re: git-add ignores exclude markers for ignored files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-05T00:54:00Z","receivedAt":"2025-11-05T00:54:03Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> This may be an ancient regression when e1b8c7bd (dir: remove struct\n> path_simplify, 2017-01-04) was rewritten exclude_matches_pathspec()\n> function, which was written in 29209cbe (dir: fix COLLECT_IGNORED on\n> excluded prefixes, 2010-03-11), back in the days before \":(exclude)\"\n> and other pathspec magic was even invented.\n>\n> Perhaps try this patch?\n>\n> I have no idea what the ramifications of the change is, though.\n> There may be unintended fallouts in some distant corner, even though\n> it does not seem to break any existing tests.\n\nOne thing that I am extremely uncomfortable about this change is\nthat I do not quite see why we use this function to begin with.\nGiven a <path, pathlen>, it seems if the pathspec matches, but why\naren't we using dir.c:match_pathspec() for and have our own separate\nand much simple-minded variant instead?\n"},{"id":"534290","messageId":"xmqqwm1cxbad.fsf@gitster.g","threadId":"64438","inReplyTo":"xmqqtsz9o3cn.fsf@gitster.g","subject":"Re: git-add ignores exclude markers for ignored files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-20T20:46:18Z","receivedAt":"2026-01-20T20:46:21Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Rahn, René\" <Rene.Rahn@pfizer.com> writes:\n>\n>> Steps to reproduce:\n>>\n>> mkdir git-add-test\n>> cd git-add-test\n>> git init\n>> touch .gitignore\n>> echo “ignored.txt” >>.gitignore\n>> touch ignored.txt\n>> git add --“:(exclude)ignored.txt”\n>> \n>> Expected behavior:\n>> \n>> The git add command recognizes that the file ignored.txt is\n>> already excluded despite of it being ignored by some gitignore and\n>> thus does not check if it is ignored or not. It simply will not be\n>> added.  Note forcing git-add will do the trick, but this could\n>> also have side effects for some files that are ignored but not\n>> present in the list of excluded files.  Hence, this can’t be the\n>> right solution.\n>\n> It is not quite clear what you want to see.  The command would not\n> add ignored.text even if you give \":(exclude)ignored.txt\" from the\n> command line, would it?\n>\n> This may be an ancient regression when e1b8c7bd (dir: remove struct\n> path_simplify, 2017-01-04) was rewritten exclude_matches_pathspec()\n> function, which was written in 29209cbe (dir: fix COLLECT_IGNORED on\n> excluded prefixes, 2010-03-11), back in the days before \":(exclude)\"\n> and other pathspec magic was even invented.\n>\n> Perhaps try this patch?\n>\n> I have no idea what the ramifications of the change is, though.\n> There may be unintended fallouts in some distant corner, even though\n> it does not seem to break any existing tests.\n\nAs I haven't heard from anybody if this patch improves the reported\nsituation or there are unintended behaviour changes that degrades\nthe system, I'll drop this topic from 'seen' soonish, as this topic\nis not exactly my itch.  That does not mean that it is unwelcome if\nmotivated others pick up the patch and polish it to bring it to the\nfinish line.  Anybody doing so should consult the original\ndiscussion thread [*].\n\nThanks.\n\n\n[Reference]\n\n* https://lore.kernel.org/git/xmqqtsz9o3cn.fsf@gitster.g/\n"}]}