{"thread":{"id":"6151","subject":"git-add ignores .gitignore","startedAt":"2006-12-29T15:51:37Z","lastAt":"2006-12-30T14:50:03Z","messageCount":4,"participants":["Brian Gernhardt","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"30475","messageId":"033682AF-B324-4049-B331-8A8AF2335E4D@silverinsanity.com","threadId":"6151","inReplyTo":null,"subject":"git-add ignores .gitignore","fromName":"Brian Gernhardt","fromEmail":"benji@silverinsanity.com","sentAt":"2006-12-29T15:51:37Z","receivedAt":"2006-12-29T15:51:37Z","isPatch":false,"sender":{"key":"benji@silverinsanity.com","avatar":"https://gravatar.com/avatar/e06c101dbc25c68114d859b4a9ec7cf8a2c52fd2b0270ef0eac0e2e63ff22311?d=mp&s=160"},"body":"I just ran \"git add .\" and got a very surprising result. Test case:\n\n$ mkdir tmp\n$ git init-db\n$ mkdir a\n$ touch a/b c\n$ cat > .gitignore << EOF\na\nEOF\n$ git status\n# On branch refs/heads/master\n#\n# Initial commit\n#\n# Untracked files:\n#   (use \"git add file1 file2\" to include for commit)\n#\n#       .gitignore\n#       c\nnothing to commit (use \"git add file1 file2\" to include for commit)\n$ git add .\n$ git status\n# On branch refs/heads/master\n#\n# Initial commit\n#\n# Added but not yet committed:\n#   (will commit)\n#\n#       new file: .gitignore\n#       new file: a/b\n#       new file: c\n#\n$ git --version\ngit version 1.5.0.rc0.g04509\n\nHuh?  I'm deep into my own code so can't diagnose it myself right  \nnow, but thought maybe someone on list could.  I really hope that's  \nnot the expected behavior.  Same thing happens with \"git add *\"\n\n~~ Brian\n"},{"id":"30483","messageId":"7vpsa2msx2.fsf@assigned-by-dhcp.cox.net","threadId":"6151","inReplyTo":"033682AF-B324-4049-B331-8A8AF2335E4D@silverinsanity.com","subject":"[PATCH] Fix 'git add' with .gitignore (Re: git-add ignores .gitignore)","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-12-29T18:57:29Z","receivedAt":"2006-12-29T18:57:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks for noticing.\n\nThe commit 4888c534 tried to mark the path it returns so that\nthe caller can tell if it is ignored or not, but botched the\ncase where a directory is ignored.  It should have marked the\ncontents as ignored if higher level directory was.\n\nBut I think the approach that change makes is more expensive\nthan the original code and is not necessary.  How about this\npatch, after you revert that commit?\n\n-- >8 --\n[PATCH] Fix 'git add' with .gitignore\n\nWhen '*.ig' is ignored, and you have two files f.ig and d.ig/foo\nin the working tree,\n\n\t$ git add .\n\ncorrectly ignored f.ig but failed to ignore d.ig/foo.  This was\ncaused by a thinko in an earlier commit 4888c534, when we tried\nto allow adding otherwise ignored files.\n\nAfter reverting that commit, this takes a much simpler approach.\nWhen we have an unmatched pathspec that talks about an existing\npathname, we know it is an ignored path the user tried to add,\nso we include it in the set of paths directory walker returned.\n\nThis does not let you say \"git add -f D\" on an ignored directory\nD and add everything under D.  People can submit a patch to\nfurther allow it if they want to, but I think it is a saner\nbehaviour to require explicit paths to be spelled out in such a\ncase.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n\n---\n builtin-add.c  |   52 +++++++++++++++++++++++++++-------------------------\n dir.c          |    8 +++++---\n dir.h          |    5 ++++-\n t/t3700-add.sh |   33 +++++++++++++++++++++++++++++++++\n 4 files changed, 69 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin-add.c b/builtin-add.c\nindex 8ed4a6a..e7a1b4d 100644\n--- a/builtin-add.c\n+++ b/builtin-add.c\n@@ -26,18 +26,9 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p\n \ti = dir->nr;\n \twhile (--i >= 0) {\n \t\tstruct dir_entry *entry = *src++;\n-\t\tint how = match_pathspec(pathspec, entry->name, entry->len,\n-\t\t\t\t\t prefix, seen);\n-\t\t/*\n-\t\t * ignored entries can be added with exact match,\n-\t\t * but not with glob nor recursive.\n-\t\t */\n-\t\tif (!how ||\n-\t\t    (entry->ignored_entry && how != MATCHED_EXACTLY)) {\n-\t\t\tfree(entry);\n-\t\t\tcontinue;\n-\t\t}\n-\t\t*dst++ = entry;\n+\t\tif (match_pathspec(pathspec, entry->name, entry->len,\n+\t\t\t\t   prefix, seen))\n+\t\t\t*dst++ = entry;\n \t}\n \tdir->nr = dst - dir->entries;\n \n@@ -47,10 +38,20 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p\n \t\tif (seen[i])\n \t\t\tcontinue;\n \n-\t\t/* Existing file? We must have ignored it */\n \t\tmatch = pathspec[i];\n-\t\tif (!match[0] || !lstat(match, &st))\n+\t\tif (!match[0])\n \t\t\tcontinue;\n+\n+\t\t/* Existing file? We must have ignored it */\n+\t\tif (!lstat(match, &st)) {\n+\t\t\tstruct dir_entry *ent;\n+\n+\t\t\tent = dir_add_name(dir, match, strlen(match));\n+\t\t\tent->ignored = 1;\n+\t\t\tif (S_ISDIR(st.st_mode))\n+\t\t\t\tent->ignored_dir = 1;\n+\t\t\tcontinue;\n+\t\t}\n \t\tdie(\"pathspec '%s' did not match any files\", match);\n \t}\n }\n@@ -62,8 +63,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)\n \n \t/* Set up the default git porcelain excludes */\n \tmemset(dir, 0, sizeof(*dir));\n-\tif (pathspec)\n-\t\tdir->show_both = 1;\n \tdir->exclude_per_dir = \".gitignore\";\n \tpath = git_path(\"info/exclude\");\n \tif (!access(path, R_OK))\n@@ -154,7 +153,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \tif (show_only) {\n \t\tconst char *sep = \"\", *eof = \"\";\n \t\tfor (i = 0; i < dir.nr; i++) {\n-\t\t\tif (!ignored_too && dir.entries[i]->ignored_entry)\n+\t\t\tif (!ignored_too && dir.entries[i]->ignored)\n \t\t\t\tcontinue;\n \t\t\tprintf(\"%s%s\", sep, dir.entries[i]->name);\n \t\t\tsep = \" \";\n@@ -168,16 +167,19 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\tdie(\"index file corrupt\");\n \n \tif (!ignored_too) {\n-\t\tint has_ignored = -1;\n-\t\tfor (i = 0; has_ignored < 0 && i < dir.nr; i++)\n-\t\t\tif (dir.entries[i]->ignored_entry)\n-\t\t\t\thas_ignored = i;\n-\t\tif (0 <= has_ignored) {\n+\t\tint has_ignored = 0;\n+\t\tfor (i = 0; i < dir.nr; i++)\n+\t\t\tif (dir.entries[i]->ignored)\n+\t\t\t\thas_ignored = 1;\n+\t\tif (has_ignored) {\n \t\t\tfprintf(stderr, ignore_warning);\n-\t\t\tfor (i = has_ignored; i < dir.nr; i++) {\n-\t\t\t\tif (!dir.entries[i]->ignored_entry)\n+\t\t\tfor (i = 0; i < dir.nr; i++) {\n+\t\t\t\tif (!dir.entries[i]->ignored)\n \t\t\t\t\tcontinue;\n-\t\t\t\tfprintf(stderr, \"%s\\n\", dir.entries[i]->name);\n+\t\t\t\tfprintf(stderr, \"%s\", dir.entries[i]->name);\n+\t\t\t\tif (dir.entries[i]->ignored_dir)\n+\t\t\t\t\tfprintf(stderr, \" (directory)\");\n+\t\t\t\tfputc('\\n', stderr);\n \t\t\t}\n \t\t\tfprintf(stderr,\n \t\t\t\t\"Use -f if you really want to add them.\\n\");\ndiff --git a/dir.c b/dir.c\nindex 8477472..0338d6c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -260,12 +260,12 @@ int excluded(struct dir_struct *dir, const char *pathname)\n \treturn 0;\n }\n \n-static void add_name(struct dir_struct *dir, const char *pathname, int len)\n+struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)\n {\n \tstruct dir_entry *ent;\n \n \tif (cache_name_pos(pathname, len) >= 0)\n-\t\treturn;\n+\t\treturn NULL;\n \n \tif (dir->nr == dir->alloc) {\n \t\tint alloc = alloc_nr(dir->alloc);\n@@ -273,10 +273,12 @@ static void add_name(struct dir_struct *dir, const char *pathname, int len)\n \t\tdir->entries = xrealloc(dir->entries, alloc*sizeof(ent));\n \t}\n \tent = xmalloc(sizeof(*ent) + len + 1);\n+\tent->ignored = ent->ignored_dir = 0;\n \tent->len = len;\n \tmemcpy(ent->name, pathname, len);\n \tent->name[len] = 0;\n \tdir->entries[dir->nr++] = ent;\n+\treturn ent;\n }\n \n static int dir_exists(const char *dirname, int len)\n@@ -364,7 +366,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n \t\t\tif (check_only)\n \t\t\t\tgoto exit_early;\n \t\t\telse\n-\t\t\t\tadd_name(dir, fullname, baselen + len);\n+\t\t\t\tdir_add_name(dir, fullname, baselen + len);\n \t\t}\n exit_early:\n \t\tclosedir(fdir);\ndiff --git a/dir.h b/dir.h\nindex c919727..7233d65 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -13,7 +13,9 @@\n \n \n struct dir_entry {\n-\tint len;\n+\tunsigned int ignored : 1;\n+\tunsigned int ignored_dir : 1;\n+\tunsigned int len : 30;\n \tchar name[FLEX_ARRAY]; /* more */\n };\n \n@@ -55,5 +57,6 @@ extern void add_excludes_from_file(struct dir_struct *, const char *fname);\n extern void add_exclude(const char *string, const char *base,\n \t\t\tint baselen, struct exclude_list *which);\n extern int file_exists(const char *);\n+extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len);\n \n #endif\ndiff --git a/t/t3700-add.sh b/t/t3700-add.sh\nindex c09c53f..e98786d 100755\n--- a/t/t3700-add.sh\n+++ b/t/t3700-add.sh\n@@ -51,4 +51,37 @@ test_expect_success \\\n \t *) echo fail; git-ls-files --stage xfoo3; (exit 1);;\n \t esac'\n \n+test_expect_success '.gitignore test setup' '\n+\techo \"*.ig\" >.gitignore &&\n+\tmkdir c.if d.ig &&\n+\t>a.ig && >b.if &&\n+\t>c.if/c.if && >c.if/c.ig &&\n+\t>d.ig/d.if && >d.ig/d.ig\n+'\n+\n+test_expect_success '.gitignore is honored' '\n+\tgit-add . &&\n+\t! git-ls-files | grep \"\\\\.ig\"\n+'\n+\n+test_expect_success 'error out when attempting to add ignored ones without -f' '\n+\t! git-add a.?? &&\n+\t! git-ls-files | grep \"\\\\.ig\"\n+'\n+\n+test_expect_success 'error out when attempting to add ignored ones without -f' '\n+\t! git-add d.?? &&\n+\t! git-ls-files | grep \"\\\\.ig\"\n+'\n+\n+test_expect_success 'add ignored ones with -f' '\n+\tgit-add -f a.?? &&\n+\tgit-ls-files --error-unmatch a.ig\n+'\n+\n+test_expect_success 'add ignored ones with -f' '\n+\tgit-add -f d.??/* &&\n+\tgit-ls-files --error-unmatch d.ig/d.if d.ig/d.ig\n+'\n+\n test_done\n"},{"id":"30495","messageId":"8D9102A3-C5A7-40B7-AE11-EE091A516775@silverinsanity.com","threadId":"6151","inReplyTo":"7vpsa2msx2.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Fix 'git add' with .gitignore (Re: git-add ignores .gitignore)","fromName":"Brian Gernhardt","fromEmail":"benji@silverinsanity.com","sentAt":"2006-12-30T04:42:32Z","receivedAt":"2006-12-30T04:42:32Z","isPatch":true,"sender":{"key":"benji@silverinsanity.com","avatar":"https://gravatar.com/avatar/e06c101dbc25c68114d859b4a9ec7cf8a2c52fd2b0270ef0eac0e2e63ff22311?d=mp&s=160"},"body":"\nOn Dec 29, 2006, at 1:57 PM, Junio C Hamano wrote:\n\n> Thanks for noticing.\n\nHard not to.  Git suddenly tried to add a couple hundred files from a  \nlibrary I don't want to track.  ;-)\n\n> How about this patch, after you revert that commit?\n\nI'm on vacation at my in-laws, so I'll try it when I get a chance but  \nit may take a few days.  (I'm only checking e-mail and that's just so  \nI'm not buried in git list mails on Tuesday.\n\n~~ Brian\n"},{"id":"30502","messageId":"497AFE26-3192-4265-820D-EED80B3452DC@silverinsanity.com","threadId":"6151","inReplyTo":"7vpsa2msx2.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Fix 'git add' with .gitignore (Re: git-add ignores .gitignore)","fromName":"Brian Gernhardt","fromEmail":"benji@silverinsanity.com","sentAt":"2006-12-30T14:50:03Z","receivedAt":"2006-12-30T14:50:03Z","isPatch":true,"sender":{"key":"benji@silverinsanity.com","avatar":"https://gravatar.com/avatar/e06c101dbc25c68114d859b4a9ec7cf8a2c52fd2b0270ef0eac0e2e63ff22311?d=mp&s=160"},"body":"\nOn Dec 29, 2006, at 1:57 PM, Junio C Hamano wrote:\n\n> Thanks for noticing.\n>\n> The commit 4888c534 tried to mark the path it returns so that\n> the caller can tell if it is ignored or not, but botched the\n> case where a directory is ignored.  It should have marked the\n> contents as ignored if higher level directory was.\n>\n> But I think the approach that change makes is more expensive\n> than the original code and is not necessary.  How about this\n> patch, after you revert that commit?\n\nTried it and it works for me!  Thanks.\n\n~~ Brian\n"}]}