{"thread":{"id":"11710","subject":"Why does git track directory listed in .gitignore/\".git/info/exclude\"?","startedAt":"2008-01-23T13:54:44Z","lastAt":"2008-02-01T08:56:43Z","messageCount":20,"participants":["pradeep singh rautela","Wayne Davison","Linus Torvalds","Adam Piatyszek","Junio C Hamano","Jeff King","Johannes Schindelin","Andreas Ericsson"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"66398","messageId":"6bc632150801230554l3b24e1e4lb4641bf7c16857c0@mail.gmail.com","threadId":"11710","inReplyTo":null,"subject":"Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"pradeep singh rautela","fromEmail":"rautelap@gmail.com","sentAt":"2008-01-23T13:54:44Z","receivedAt":"2008-01-23T13:54:44Z","isPatch":false,"sender":{"key":"rautelap@gmail.com","avatar":null},"body":"Hi All,\n\nI have an source directory, which in turn has some other directories too.\nI do not want to track one of these directories.\nSo i added the directory name in .gitignore as well as in\n.git/info/exclude for my repo.\n\ni.e i have added following line to both of them -\nxen-3.1.0-src/\n\nI copied xen-3.1.0-src from archives in the git repo's base directory.\n\nNow when i do a git-status i get\n# On branch master\n# Untracked files:\n#   (use \"git add <file>...\" to include in what will be committed)\n#\n#       xen-3.1.0-src/\nnothing added to commit but untracked files present (use \"git add\" to track)\n\nWhy is git seeing xen-3.1.0-src directory at all?\nIs this the expected behaviour?\n\nI thought i should not get this message after adding relevant entries\nin .gitignore or in info/exclude .\n\nWhat am i doing wrong here?\nIs there a way that this can be done without having to witness this\nmessage everytime i do a git status?\n\nPlease CC me as I am not subscribed to the list.\n\nThanks,\n         ~Pradeep\n-- \n--\npradeep singh rautela\nhttp://eagain.wordpress.com\nhttp://emptydomain.googlepages.com\n"},{"id":"66400","messageId":"6bc632150801230604p2589c893pa05bb6f27e482de8@mail.gmail.com","threadId":"11710","inReplyTo":"6bc632150801230554l3b24e1e4lb4641bf7c16857c0@mail.gmail.com","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"pradeep singh rautela","fromEmail":"rautelap@gmail.com","sentAt":"2008-01-23T14:04:19Z","receivedAt":"2008-01-23T14:04:19Z","isPatch":false,"sender":{"key":"rautelap@gmail.com","avatar":null},"body":"Apologies to all.Kindly pardon my novice experiments with git.\nSome more trial and error method led to find that you have to put a *\nat the end of the directory too.\ni.e xen-3.1.0-src/*\n\nBut i still would like to ask git gurus here.\nIsn't it fine to include a directory name as\n\n   $directory_name/\n    instead of\n   $directory_name/*\n\n?\nThoughts?\n\nThanks,\n          --Pradeep\nOn 23/01/2008, pradeep singh rautela <rautelap@gmail.com> wrote:\n> Hi All,\n>\n> I have an source directory, which in turn has some other directories too.\n> I do not want to track one of these directories.\n> So i added the directory name in .gitignore as well as in\n> .git/info/exclude for my repo.\n>\n> i.e i have added following line to both of them -\n> xen-3.1.0-src/\n>\n> I copied xen-3.1.0-src from archives in the git repo's base directory.\n>\n> Now when i do a git-status i get\n> # On branch master\n> # Untracked files:\n> #   (use \"git add <file>...\" to include in what will be committed)\n> #\n> #       xen-3.1.0-src/\n> nothing added to commit but untracked files present (use \"git add\" to track)\n>\n> Why is git seeing xen-3.1.0-src directory at all?\n> Is this the expected behaviour?\n>\n> I thought i should not get this message after adding relevant entries\n> in .gitignore or in info/exclude .\n>\n> What am i doing wrong here?\n> Is there a way that this can be done without having to witness this\n> message everytime i do a git status?\n>\n> Please CC me as I am not subscribed to the list.\n>\n> Thanks,\n>          ~Pradeep\n> --\n> --\n> pradeep singh rautela\n> http://eagain.wordpress.com\n> http://emptydomain.googlepages.com\n>\n\n\n-- \n--\npradeep singh rautela\nhttp://eagain.wordpress.com\nhttp://emptydomain.googlepages.com\n"},{"id":"66428","messageId":"20080123211138.GB21300@blorf.net","threadId":"11710","inReplyTo":"6bc632150801230554l3b24e1e4lb4641bf7c16857c0@mail.gmail.com","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"Wayne Davison","fromEmail":"wayne@opencoder.net","sentAt":"2008-01-23T21:11:38Z","receivedAt":"2008-01-23T21:11:38Z","isPatch":false,"sender":{"key":"wayne@opencoder.net","avatar":"https://gravatar.com/avatar/d55d81825271b1bfe65e57e4e04297d4119aa03c6a1c71d2ff2812b9b4be9f45?d=mp&s=160"},"body":"On Wed, Jan 23, 2008 at 07:24:44PM +0530, pradeep singh rautela wrote:\n> i.e i have added following line to both of them -\n> xen-3.1.0-src/\n\nThat doesn't match the directory due to the trailing slash.  If you\nremove that slash, it will match the dir, and then ignore anything\nyou place inside.\n\n..wayne..\n"},{"id":"66429","messageId":"alpine.LFD.1.00.0801231313590.2803@woody.linux-foundation.org","threadId":"11710","inReplyTo":"6bc632150801230604p2589c893pa05bb6f27e482de8@mail.gmail.com","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2008-01-23T21:17:28Z","receivedAt":"2008-01-23T21:17:28Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Wed, 23 Jan 2008, pradeep singh rautela wrote:\n>\n> Apologies to all.Kindly pardon my novice experiments with git.\n> Some more trial and error method led to find that you have to put a *\n> at the end of the directory too.\n>\n> i.e xen-3.1.0-src/*\n> \n> But i still would like to ask git gurus here.\n> Isn't it fine to include a directory name as\n> \n>    $directory_name/\n>     instead of\n>    $directory_name/*\n\nHeh.\n\nI think your problem is that \"/\" itself. By adding it, the exclude \ninformation does *not* match the directory entry itself (because the \ndirectory entry itself is called just \"xen-3.1.0-src\" - note no slash!), \nand since you added it, it also doesn't match any names _under_ that \ndirectory exactly.\n\nSo what you *should* have done is to just tell git to ignore the directory \nnamed \"xen-3.1.0-src\", and you'd have been ok.\n\nUsing \"xen-3.1.0-src/*\" works too, but it is heavy-handed and unnecessary.\n\n\t\tLinus\n"},{"id":"66471","messageId":"6bc632150801240244i6833a918y3d003ff8fa79b15c@mail.gmail.com","threadId":"11710","inReplyTo":"alpine.LFD.1.00.0801231313590.2803@woody.linux-foundation.org","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"pradeep singh rautela","fromEmail":"rautelap@gmail.com","sentAt":"2008-01-24T10:44:16Z","receivedAt":"2008-01-24T10:44:16Z","isPatch":false,"sender":{"key":"rautelap@gmail.com","avatar":null},"body":"Hi Linus,\n\nOn 24/01/2008, Linus Torvalds <torvalds@linux-foundation.org> wrote:\n[...]\n> > Isn't it fine to include a directory name as\n> >\n> >    $directory_name/\n> >     instead of\n> >    $directory_name/*\n>\n> Heh.\n>\n> I think your problem is that \"/\" itself. By adding it, the exclude\n> information does *not* match the directory entry itself (because the\n> directory entry itself is called just \"xen-3.1.0-src\" - note no slash!),\n> and since you added it, it also doesn't match any names _under_ that\n> directory exactly.\n\nGot that.\nThanks a lot for explaining that to me Linus.\n\nBest Regards,\n                --Pradeep\n>\n> So what you *should* have done is to just tell git to ignore the directory\n> named \"xen-3.1.0-src\", and you'd have been ok.\n>\n> Using \"xen-3.1.0-src/*\" works too, but it is heavy-handed and unnecessary.\n>\n>                 Linus\n>\n\n\n-- \n--\npradeep singh rautela\nhttp://eagain.wordpress.com\nhttp://emptydomain.googlepages.com\n"},{"id":"66914","messageId":"47A06EF9.60704@users.sourceforge.net","threadId":"11710","inReplyTo":"alpine.LFD.1.00.0801231313590.2803@woody.linux-foundation.org","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"Adam Piatyszek","fromEmail":"ediap@users.sourceforge.net","sentAt":"2008-01-30T12:35:05Z","receivedAt":"2008-01-30T12:35:05Z","isPatch":false,"sender":{"key":"ediap@users.sourceforge.net","avatar":null},"body":"* Linus Torvalds [23 I 2008 22:17]:\n> On Wed, 23 Jan 2008, pradeep singh rautela wrote:\n>> But i still would like to ask git gurus here.\n>> Isn't it fine to include a directory name as\n>>\n>>    $directory_name/\n>>     instead of\n>>    $directory_name/*\n> \n> Heh.\n> \n> I think your problem is that \"/\" itself. By adding it, the exclude \n> information does *not* match the directory entry itself (because the \n> directory entry itself is called just \"xen-3.1.0-src\" - note no slash!), \n> and since you added it, it also doesn't match any names _under_ that \n> directory exactly.\n> \n> So what you *should* have done is to just tell git to ignore the directory \n> named \"xen-3.1.0-src\", and you'd have been ok.\n> \n> Using \"xen-3.1.0-src/*\" works too, but it is heavy-handed and unnecessary.\n\nHi Linus, Pradeep and All,\n\nIn my opinion, the exclude matching routine should convert \"dir/\" to \n\"dir\", especially that the \"git status\" command lists untracked \ndirectories with the trailing slash \"/\", e.g:\n\n   ediap@lespaul ~/git/acm_ofdm $ git status\n   # On branch master\n   # Untracked files:\n   #   (use \"git add <file>...\" to include in what will be committed)\n   #\n   #       ldpc13.bm\n   #       results/\n\nSo, most newbies will try to add \"dir/\" to .gitignore or \n.git/info/exclude instead of \"dir\" in such a case.\n\nCan you seen any drawbacks of such modification?\n\nBR,\n/Adam\n\n-- \n.:.  Adam Piatyszek (ediap)  .:.....................................:.\n.:.  ediap@users.sourceforge.net  .:................................:.\n"},{"id":"66923","messageId":"7vprvjgi9v.fsf@gitster.siamese.dyndns.org","threadId":"11710","inReplyTo":"47A06EF9.60704@users.sourceforge.net","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-30T20:39:24Z","receivedAt":"2008-01-30T20:39:24Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Adam Piatyszek <ediap@users.sourceforge.net> writes:\n\n> In my opinion, the exclude matching routine should convert \"dir/\" to\n> \"dir\", especially that the \"git status\" command lists untracked\n> directories with the trailing slash \"/\", e.g:\n>\n>   ediap@lespaul ~/git/acm_ofdm $ git status\n>   # On branch master\n>   # Untracked files:\n>   #   (use \"git add <file>...\" to include in what will be committed)\n>   #\n>   #       ldpc13.bm\n>   #       results/\n>\n> So, most newbies will try to add \"dir/\" to .gitignore or\n> .git/info/exclude instead of \"dir\" in such a case.\n>\n> Can you seen any drawbacks of such modification?\n\nI do not see a problem if you are saying:\n\n\twhen the user has an entry 'dir/' in .gitignore, it\n\tshould match directory 'dir'.\n\nHowever, there is a subtle problem in a naive implementation of\nthat.  IOW,\n\n\twhen the user has an entry 'dir/' in .gitignore, behave\n\tas if the entry were 'dir' instead.\n\nis wrong.\n\nWhen you say \"foo\", you mean \"I want either 'foo' that is a\nnon-directory, or everything under 'foo' if that is a\ndirectory\".  When you say \"foo/\", you are saying \"I do not want\n'foo' if it is a non-directory.  I want everything under 'foo'\nif and only if that is a directory\".  Compare:\n\n\tgit ls-files -s Makefile/\n        git ls-files -s Makefile\n\nThe first one is silent, and the latter answers.  On the other\nhand, for a directory, both of these give you the same:\n\n\tgit ls-files Documentation/\n        git ls-files Documentation\n"},{"id":"66927","messageId":"7vfxwfgh0o.fsf@gitster.siamese.dyndns.org","threadId":"11710","inReplyTo":"7vprvjgi9v.fsf@gitster.siamese.dyndns.org","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-30T21:06:31Z","receivedAt":"2008-01-30T21:06:31Z","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> Adam Piatyszek <ediap@users.sourceforge.net> writes:\n>...\n>> Can you seen any drawbacks of such modification?\n>\n> I do not see a problem if you are saying:\n>\n> \twhen the user has an entry 'dir/' in .gitignore, it\n> \tshould match directory 'dir'.\n>\n> However, there is a subtle problem in a naive implementation of\n> that.  IOW,\n>\n> \twhen the user has an entry 'dir/' in .gitignore, behave\n> \tas if the entry were 'dir' instead.\n>\n> is wrong.\n>\n> When you say \"foo\", you mean \"I want either 'foo' that is a\n> non-directory, or everything under 'foo' if that is a\n> directory\".  When you say \"foo/\", you are saying \"I do not want\n> 'foo' if it is a non-directory.  I want everything under 'foo'\n> if and only if that is a directory\".  Compare:\n>\n> \tgit ls-files -s Makefile/\n>       git ls-files -s Makefile\n>\n> The first one is silent, and the latter answers.  On the other\n> hand, for a directory, both of these give you the same:\n>\n> \tgit ls-files Documentation/\n>       git ls-files Documentation\n\nPerhaps \"wrong\" might have been too strong a word, and I should\nhave said \"inconsistent with other parts of the system.\"\n\nIt could be that people may find pathspec \"Makefile/\" meant\nexactly the same thing as \"Makefile\" in ls-files and other\ncommands.  If that is the case, then we could uniformly strip\nthe trailing slash, both in all of these commands _and_ .gitignore\nentries.\n\nIn any case, their behaviour should be consistent.\n"},{"id":"66962","messageId":"47A1733E.9040103@users.sourceforge.net","threadId":"11710","inReplyTo":"7vprvjgi9v.fsf@gitster.siamese.dyndns.org","subject":"Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"Adam Piatyszek","fromEmail":"ediap@users.sourceforge.net","sentAt":"2008-01-31T07:05:34Z","receivedAt":"2008-01-31T07:05:34Z","isPatch":false,"sender":{"key":"ediap@users.sourceforge.net","avatar":null},"body":"Hi Junio,\n\n* Junio C Hamano [30 I 2008 21:39]:\n> When you say \"foo\", you mean \"I want either 'foo' that is a\n> non-directory, or everything under 'foo' if that is a\n> directory\".  When you say \"foo/\", you are saying \"I do not want\n> 'foo' if it is a non-directory.  I want everything under 'foo'\n> if and only if that is a directory\".  Compare:\n> \n> \tgit ls-files -s Makefile/\n>         git ls-files -s Makefile\n> \n> The first one is silent, and the latter answers.  On the other\n> hand, for a directory, both of these give you the same:\n> \n> \tgit ls-files Documentation/\n>         git ls-files Documentation\n> \n\nAs you said above both \"Documentation/\" and \"Documentation\" match the \nexisting tracked directory named \"Documentation\". That is how ls-files \nworks and it is the only sane way. The problem is that I expect that \ndirectory entries ending with \"/\" in .gitignore and .git/info/exclude \nfiles are treated in a similar way, i.e. they are being _ignored_ with \nall the stuff in them, in the same way as directory entries without the \nending slash. Unfortunately this is not the case. See this example:\n\nediap@lespaul ~/tmp $ mkdir repo && cd repo\nediap@lespaul ~/tmp/repo $ git init\nInitialized empty Git repository in .git/\nediap@lespaul ~/tmp/repo $ touch a.txt\nediap@lespaul ~/tmp/repo $ git add a.txt\nediap@lespaul ~/tmp/repo $ git commit -m \"a file\"\nCreated initial commit 1712595: a file\n  0 files changed, 0 insertions(+), 0 deletions(-)\n  create mode 100644 a.txt\nediap@lespaul ~/tmp/repo $ mkdir d\nediap@lespaul ~/tmp/repo $ touch d/b.txt\nediap@lespaul ~/tmp/repo $ git status\n# On branch master\n# Untracked files:\n#   (use \"git add <file>...\" to include in what will be committed)\n#\n#       d/\nnothing added to commit but untracked files present (use \"git add\" to track)\nediap@lespaul ~/tmp/repo $ echo \"d/\" > .gitignore\nediap@lespaul ~/tmp/repo $ git add .gitignore\nediap@lespaul ~/tmp/repo $ git commit -m \"ignore\"\nCreated commit 29ebf4d: ignore\n  1 files changed, 1 insertions(+), 0 deletions(-)\n  create mode 100644 .gitignore\nediap@lespaul ~/tmp/repo $ git status\n# On branch master\n# Untracked files:\n#   (use \"git add <file>...\" to include in what will be committed)\n#\n#       d/\nnothing added to commit but untracked files present (use \"git add\" to track)\n\n\nBut:\n\nediap@lespaul ~/tmp/repo $ echo \"d\" > .gitignore\nediap@lespaul ~/tmp/repo $ git add .gitignore\nediap@lespaul ~/tmp/repo $ git commit --amend -m \"ignore\"\nCreated commit 43198d4: ignore\n  1 files changed, 1 insertions(+), 0 deletions(-)\n  create mode 100644 .gitignore\nediap@lespaul ~/tmp/repo $ git status\n# On branch master\nnothing to commit (working directory clean)\n\n\nI hope you now understand what I was trying to express in my previous \nemail. :-)\n\nBR,\n/Adam\n\n\n-- \n.:.  Adam Piatyszek (ediap)  .:.....................................:.\n.:.  ediap@users.sourceforge.net  .:................................:.\n"},{"id":"66963","messageId":"7vhcgue5nr.fsf_-_@gitster.siamese.dyndns.org","threadId":"11710","inReplyTo":"47A1733E.9040103@users.sourceforge.net","subject":"*Re: Why does git track directory listed in .gitignore/\".git/info/exclude\"?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-31T08:54:48Z","receivedAt":"2008-01-31T08:54:48Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Adam Piatyszek <ediap@users.sourceforge.net> writes:\n\n> I hope you now understand what I was trying to express in my previous\n> email. :-)\n\nI am afraid that it is _you_ who doesn't understand.\n\nWe both know that a pattern \"foo\" in the ignore list matches\ndirectory \"foo/\" and everything under it.  We also both know\nthat such a pattern also matches a regular file or a symbolic\nlink \"foo\", too.\n\nI already said that it would make sense to make a pattern in the\nignore list \"foo/\" to match directory \"foo/\" and everything\nunder it, in addition to the above.  Currently it doesn't.  We\nagreed that it might be a good idea to allow it as well.\n\nMy point was that a naive implementation to make \"foo/\" match\ndirectory \"foo/\" by pretending as if the user said \"foo\" is not\ngood, because it would also make such a user-supplied pattern\n\"foo/\" to match a regular file \"foo\".\n\nIn other words, you would need to do something like the attached\npatch, if you wanted to solve this correctly.\n\nNote that this is not tested heavily.  It just looks obvious\nenough, but (1) testing is for wimps, (2) I do not care deeply\nabout having to say \"foo\" instead of \"foo/\", and (3) we are in\npre-release freeze to fix obvious bugs and regressions.\n\nWimps ^W People who care deeply enough can apply this to their\ntrees, use it for a week or so to make sure it does not break\nother things, and then send the patch back with Tested-by: if\neverything works out Ok ;-).\n\nI also strongly suspect that we would need a similar change to\ngitattributes side (attr.c) for consistency, but I haven't\nlooked at it.\n\n-- >8 --\n[PATCH] Allow \"foo/\" in ignore list to match directory \"foo\"\n\nA naive implementation that pretends the user said \"foo\" would\nnot work well as it would make \"foo/\" match a non directory\n\"foo\" as well.  This passes down the type of path being checked\nto excluded() function to make sure that \"foo/\" matches\ndirectory \"foo/\" and not regular file \"foo\".\n\nA downside is that recursive directory walk may need to run\nlstat(2) more often on systems whose \"struct dirent\" does not\ngive the type of the entry; earlier it did not have to for an\nexcluded path, but we now need to figure out if a path is a\ndirectory before deciding to exclude it.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n\n---\n\n builtin-ls-files.c                 |   19 ++++++++++++++-\n cache.h                            |   12 ++++++++++\n dir.c                              |   42 ++++++++++++++++++++++++++---------\n dir.h                              |    3 +-\n t/t3001-ls-files-others-exclude.sh |   41 +++++++++++++++++++++++++++++++++++\n unpack-trees.c                     |    2 +-\n 6 files changed, 104 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin-ls-files.c b/builtin-ls-files.c\nindex 3801cf4..24646ef 100644\n--- a/builtin-ls-files.c\n+++ b/builtin-ls-files.c\n@@ -218,6 +218,19 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)\n \twrite_name_quoted(ce->name + offset, stdout, line_terminator);\n }\n \n+static int ce_mode_to_dtype(struct cache_entry *ce)\n+{\n+\tunsigned ce_mode = ce->ce_mode;\n+\tif (S_ISREG(ce_mode))\n+\t\treturn DT_REG;\n+\telse if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))\n+\t\treturn DT_DIR;\n+\telse if (S_ISLNK(ce_mode))\n+\t\treturn DT_LNK;\n+\telse\n+\t\treturn DT_UNKNOWN;\n+}\n+\n static void show_files(struct dir_struct *dir, const char *prefix)\n {\n \tint i;\n@@ -238,7 +251,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)\n \tif (show_cached | show_stage) {\n \t\tfor (i = 0; i < active_nr; i++) {\n \t\t\tstruct cache_entry *ce = active_cache[i];\n-\t\t\tif (excluded(dir, ce->name) != dir->show_ignored)\n+\t\t\tif (excluded(dir, ce->name, ce_mode_to_dtype(ce)) !=\n+\t\t\t    dir->show_ignored)\n \t\t\t\tcontinue;\n \t\t\tif (show_unmerged && !ce_stage(ce))\n \t\t\t\tcontinue;\n@@ -252,7 +266,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)\n \t\t\tstruct cache_entry *ce = active_cache[i];\n \t\t\tstruct stat st;\n \t\t\tint err;\n-\t\t\tif (excluded(dir, ce->name) != dir->show_ignored)\n+\t\t\tif (excluded(dir, ce->name, ce_mode_to_dtype(ce)) !=\n+\t\t\t    dir->show_ignored)\n \t\t\t\tcontinue;\n \t\t\terr = lstat(ce->name, &st);\n \t\t\tif (show_deleted && err)\ndiff --git a/cache.h b/cache.h\nindex 549f4bb..c282021 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -141,6 +141,18 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in\n \t}\n \treturn create_ce_mode(mode);\n }\n+static inline int ce_to_dtype(const struct cache_entry *ce)\n+{\n+\tunsigned ce_mode = ce->ce_mode;\n+\tif (S_ISREG(ce_mode))\n+\t\treturn DT_REG;\n+\telse if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))\n+\t\treturn DT_DIR;\n+\telse if (S_ISLNK(ce_mode))\n+\t\treturn DT_LNK;\n+\telse\n+\t\treturn DT_UNKNOWN;\n+}\n #define canon_mode(mode) \\\n \t(S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \\\n \tS_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)\ndiff --git a/dir.c b/dir.c\nindex 3e345c2..354c8f0 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -126,18 +126,34 @@ static int no_wildcard(const char *string)\n void add_exclude(const char *string, const char *base,\n \t\t int baselen, struct exclude_list *which)\n {\n-\tstruct exclude *x = xmalloc(sizeof (*x));\n+\tstruct exclude *x; \n+\tsize_t len;\n+\tint to_exclude = 1;\n+\tint flags = 0;\n \n-\tx->to_exclude = 1;\n \tif (*string == '!') {\n-\t\tx->to_exclude = 0;\n+\t\tto_exclude = 0;\n \t\tstring++;\n \t}\n-\tx->pattern = string;\n+\tlen = strlen(string);\n+\tif (len && string[len - 1] == '/') {\n+\t\tchar *s;\n+\t\tx = xmalloc(sizeof(*x) + len);\n+\t\ts = (char*)(x+1);\n+\t\tmemcpy(s, string, len - 1);\n+\t\ts[len - 1] = '\\0';\n+\t\tstring = s;\n+\t\tx->pattern = s;\n+\t\tflags = EXC_FLAG_MUSTBEDIR;\n+\t} else {\n+\t\tx = xmalloc(sizeof(*x));\n+\t\tx->pattern = string;\n+\t}\n+\tx->to_exclude = to_exclude;\n \tx->patternlen = strlen(string);\n \tx->base = base;\n \tx->baselen = baselen;\n-\tx->flags = 0;\n+\tx->flags = flags;\n \tif (!strchr(string, '/'))\n \t\tx->flags |= EXC_FLAG_NODIR;\n \tif (no_wildcard(string))\n@@ -261,7 +277,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n  * Return 1 for exclude, 0 for include and -1 for undecided.\n  */\n static int excluded_1(const char *pathname,\n-\t\t      int pathlen, const char *basename,\n+\t\t      int pathlen, const char *basename, int dtype,\n \t\t      struct exclude_list *el)\n {\n \tint i;\n@@ -272,6 +288,10 @@ static int excluded_1(const char *pathname,\n \t\t\tconst char *exclude = x->pattern;\n \t\t\tint to_exclude = x->to_exclude;\n \n+\t\t\tif ((x->flags & EXC_FLAG_MUSTBEDIR) &&\n+\t\t\t    (dtype != DT_DIR))\n+\t\t\t\tcontinue;\n+\n \t\t\tif (x->flags & EXC_FLAG_NODIR) {\n \t\t\t\t/* match basename */\n \t\t\t\tif (x->flags & EXC_FLAG_NOWILDCARD) {\n@@ -314,7 +334,7 @@ static int excluded_1(const char *pathname,\n \treturn -1; /* undecided */\n }\n \n-int excluded(struct dir_struct *dir, const char *pathname)\n+int excluded(struct dir_struct *dir, const char *pathname, int dtype)\n {\n \tint pathlen = strlen(pathname);\n \tint st;\n@@ -323,7 +343,8 @@ int excluded(struct dir_struct *dir, const char *pathname)\n \n \tprep_exclude(dir, pathname, basename-pathname);\n \tfor (st = EXC_CMDL; st <= EXC_FILE; st++) {\n-\t\tswitch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {\n+\t\tswitch (excluded_1(pathname, pathlen, basename,\n+\t\t\t\t   dtype, &dir->exclude_list[st])) {\n \t\tcase 0:\n \t\t\treturn 0;\n \t\tcase 1:\n@@ -560,7 +581,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n \t\t\tif (simplify_away(fullname, baselen + len, simplify))\n \t\t\t\tcontinue;\n \n-\t\t\texclude = excluded(dir, fullname);\n+\t\t\tdtype = get_dtype(de, fullname);\n+\t\t\texclude = excluded(dir, fullname, dtype);\n \t\t\tif (exclude && dir->collect_ignored\n \t\t\t    && in_pathspec(fullname, baselen + len, simplify))\n \t\t\t\tdir_add_ignored(dir, fullname, baselen + len);\n@@ -572,8 +594,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n \t\t\tif (exclude && !dir->show_ignored)\n \t\t\t\tcontinue;\n \n-\t\t\tdtype = get_dtype(de, fullname);\n-\n \t\t\t/*\n \t\t\t * Do we want to see just the ignored files?\n \t\t\t * We still need to recurse into directories,\ndiff --git a/dir.h b/dir.h\nindex d8814dc..10d72b5 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -9,6 +9,7 @@ struct dir_entry {\n #define EXC_FLAG_NODIR 1\n #define EXC_FLAG_NOWILDCARD 2\n #define EXC_FLAG_ENDSWITH 4\n+#define EXC_FLAG_MUSTBEDIR 8\n \n struct exclude_list {\n \tint nr;\n@@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,\n \n extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);\n \n-extern int excluded(struct dir_struct *, const char *);\n+extern int excluded(struct dir_struct *, const char *, int);\n 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);\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex e25b255..b4297ba 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -99,4 +99,45 @@ EOF\n test_expect_success 'git-status honours core.excludesfile' \\\n \t'diff -u expect output'\n \n+test_expect_success 'trailing slash in exclude allows directory match(1)' '\n+\n+\tgit ls-files --others --exclude=one/ >output &&\n+\tif grep \"^one/\" output\n+\tthen\n+\t\techo Ooops\n+\t\tfalse\n+\telse\n+\t\t: happy\n+\tfi\n+\n+'\n+\n+test_expect_success 'trailing slash in exclude allows directory match (2)' '\n+\n+\tgit ls-files --others --exclude=one/two/ >output &&\n+\tif grep \"^one/two/\" output\n+\tthen\n+\t\techo Ooops\n+\t\tfalse\n+\telse\n+\t\t: happy\n+\tfi\n+\n+'\n+\n+test_expect_success 'trailing slash in exclude forces directory match (1)' '\n+\n+\t>two\n+\tgit ls-files --others --exclude=two/ >output &&\n+\tgrep \"^two\" output\n+\n+'\n+\n+test_expect_success 'trailing slash in exclude forces directory match (2)' '\n+\n+\tgit ls-files --others --exclude=one/a.1/ >output &&\n+\tgrep \"^one/a.1\" output\n+\n+'\n+\n test_done\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex aa2513e..11af263 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -523,7 +523,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,\n \tif (!lstat(ce->name, &st)) {\n \t\tint cnt;\n \n-\t\tif (o->dir && excluded(o->dir, ce->name))\n+\t\tif (o->dir && excluded(o->dir, ce->name, ce_to_dtype(ce)))\n \t\t\t/*\n \t\t\t * ce->name is explicitly excluded, so it is Ok to\n \t\t\t * overwrite it.\n"},{"id":"66965","messageId":"7v63xae4lf.fsf_-_@gitster.siamese.dyndns.org","threadId":"11710","inReplyTo":"7vhcgue5nr.fsf_-_@gitster.siamese.dyndns.org","subject":"[PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-31T09:17:48Z","receivedAt":"2008-01-31T09:17:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"A pattern \"foo/\" in the exclude list did not match directory\n\"foo\", but a pattern \"foo\" did.  This attempts to extend the\nexclude mechanism so that it would while not matching a regular\nfile or a symbolic link \"foo\".  In order to differentiate a\ndirectory and non directory, this passes down the type of path\nbeing checked to excluded() function.\n\nA downside is that the recursive directory walk may need to run\nlstat(2) more often on systems whose \"struct dirent\" do not give\nthe type of the entry; earlier it did not have to do so for an\nexcluded path, but we now need to figure out if a path is a\ndirectory before deciding to exclude it.  This is especially bad\nbecause an idea similar to the earlier CE_UPTODATE optimization\nto reduce number of lstat(2) calls would by definition not apply\nto the codepaths involved, as (1) directories will not be\nregistered in the index, and (2) excluded paths will not be in\nthe index anyway.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * This replaces the earlier patch, which depended on Linus's\n   in-core index patch that makes ce->ce_mode the host endian.\n   It also comes with a documentation update, and applies to\n   master.\n\n Documentation/gitignore.txt        |    6 +++++\n builtin-ls-files.c                 |    6 +++-\n cache.h                            |   12 ++++++++++\n dir.c                              |   42 ++++++++++++++++++++++++++---------\n dir.h                              |    3 +-\n t/t3001-ls-files-others-exclude.sh |   41 +++++++++++++++++++++++++++++++++++\n unpack-trees.c                     |    2 +-\n 7 files changed, 97 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt\nindex 08373f5..0290bdb 100644\n--- a/Documentation/gitignore.txt\n+++ b/Documentation/gitignore.txt\n@@ -57,6 +57,12 @@ Patterns have the following format:\n    included again.  If a negated pattern matches, this will\n    override lower precedence patterns sources.\n \n+ - If the pattern ends with a slash, it is removed for the\n+   purpose of the following description, but it would find match\n+   only with a directory.  In other words, `foo/` will match a\n+   directory `foo` and paths underneath it, but will not match a\n+   regular file or a symbolic link `foo`.\n+\n  - If the pattern does not contain a slash '/', git treats it as\n    a shell glob pattern and checks for a match against the\n    pathname without leading directories.\ndiff --git a/builtin-ls-files.c b/builtin-ls-files.c\nindex 3801cf4..3089978 100644\n--- a/builtin-ls-files.c\n+++ b/builtin-ls-files.c\n@@ -238,7 +238,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)\n \tif (show_cached | show_stage) {\n \t\tfor (i = 0; i < active_nr; i++) {\n \t\t\tstruct cache_entry *ce = active_cache[i];\n-\t\t\tif (excluded(dir, ce->name) != dir->show_ignored)\n+\t\t\tif (excluded(dir, ce->name, ce_to_dtype(ce)) !=\n+\t\t\t    dir->show_ignored)\n \t\t\t\tcontinue;\n \t\t\tif (show_unmerged && !ce_stage(ce))\n \t\t\t\tcontinue;\n@@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)\n \t\t\tstruct cache_entry *ce = active_cache[i];\n \t\t\tstruct stat st;\n \t\t\tint err;\n-\t\t\tif (excluded(dir, ce->name) != dir->show_ignored)\n+\t\t\tif (excluded(dir, ce->name, ce_to_dtype(ce)) !=\n+\t\t\t    dir->show_ignored)\n \t\t\t\tcontinue;\n \t\t\terr = lstat(ce->name, &st);\n \t\t\tif (show_deleted && err)\ndiff --git a/cache.h b/cache.h\nindex 549f4bb..5529830 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -141,6 +141,18 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in\n \t}\n \treturn create_ce_mode(mode);\n }\n+static inline int ce_to_dtype(const struct cache_entry *ce)\n+{\n+\tunsigned ce_mode = ntohl(ce->ce_mode);\n+\tif (S_ISREG(ce_mode))\n+\t\treturn DT_REG;\n+\telse if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))\n+\t\treturn DT_DIR;\n+\telse if (S_ISLNK(ce_mode))\n+\t\treturn DT_LNK;\n+\telse\n+\t\treturn DT_UNKNOWN;\n+}\n #define canon_mode(mode) \\\n \t(S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \\\n \tS_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)\ndiff --git a/dir.c b/dir.c\nindex 3e345c2..a4f8c25 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -126,18 +126,34 @@ static int no_wildcard(const char *string)\n void add_exclude(const char *string, const char *base,\n \t\t int baselen, struct exclude_list *which)\n {\n-\tstruct exclude *x = xmalloc(sizeof (*x));\n+\tstruct exclude *x;\n+\tsize_t len;\n+\tint to_exclude = 1;\n+\tint flags = 0;\n \n-\tx->to_exclude = 1;\n \tif (*string == '!') {\n-\t\tx->to_exclude = 0;\n+\t\tto_exclude = 0;\n \t\tstring++;\n \t}\n-\tx->pattern = string;\n+\tlen = strlen(string);\n+\tif (len && string[len - 1] == '/') {\n+\t\tchar *s;\n+\t\tx = xmalloc(sizeof(*x) + len);\n+\t\ts = (char*)(x+1);\n+\t\tmemcpy(s, string, len - 1);\n+\t\ts[len - 1] = '\\0';\n+\t\tstring = s;\n+\t\tx->pattern = s;\n+\t\tflags = EXC_FLAG_MUSTBEDIR;\n+\t} else {\n+\t\tx = xmalloc(sizeof(*x));\n+\t\tx->pattern = string;\n+\t}\n+\tx->to_exclude = to_exclude;\n \tx->patternlen = strlen(string);\n \tx->base = base;\n \tx->baselen = baselen;\n-\tx->flags = 0;\n+\tx->flags = flags;\n \tif (!strchr(string, '/'))\n \t\tx->flags |= EXC_FLAG_NODIR;\n \tif (no_wildcard(string))\n@@ -261,7 +277,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n  * Return 1 for exclude, 0 for include and -1 for undecided.\n  */\n static int excluded_1(const char *pathname,\n-\t\t      int pathlen, const char *basename,\n+\t\t      int pathlen, const char *basename, int dtype,\n \t\t      struct exclude_list *el)\n {\n \tint i;\n@@ -272,6 +288,10 @@ static int excluded_1(const char *pathname,\n \t\t\tconst char *exclude = x->pattern;\n \t\t\tint to_exclude = x->to_exclude;\n \n+\t\t\tif ((x->flags & EXC_FLAG_MUSTBEDIR) &&\n+\t\t\t    (dtype != DT_DIR))\n+\t\t\t\tcontinue;\n+\n \t\t\tif (x->flags & EXC_FLAG_NODIR) {\n \t\t\t\t/* match basename */\n \t\t\t\tif (x->flags & EXC_FLAG_NOWILDCARD) {\n@@ -314,7 +334,7 @@ static int excluded_1(const char *pathname,\n \treturn -1; /* undecided */\n }\n \n-int excluded(struct dir_struct *dir, const char *pathname)\n+int excluded(struct dir_struct *dir, const char *pathname, int dtype)\n {\n \tint pathlen = strlen(pathname);\n \tint st;\n@@ -323,7 +343,8 @@ int excluded(struct dir_struct *dir, const char *pathname)\n \n \tprep_exclude(dir, pathname, basename-pathname);\n \tfor (st = EXC_CMDL; st <= EXC_FILE; st++) {\n-\t\tswitch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {\n+\t\tswitch (excluded_1(pathname, pathlen, basename,\n+\t\t\t\t   dtype, &dir->exclude_list[st])) {\n \t\tcase 0:\n \t\t\treturn 0;\n \t\tcase 1:\n@@ -560,7 +581,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n \t\t\tif (simplify_away(fullname, baselen + len, simplify))\n \t\t\t\tcontinue;\n \n-\t\t\texclude = excluded(dir, fullname);\n+\t\t\tdtype = get_dtype(de, fullname);\n+\t\t\texclude = excluded(dir, fullname, dtype);\n \t\t\tif (exclude && dir->collect_ignored\n \t\t\t    && in_pathspec(fullname, baselen + len, simplify))\n \t\t\t\tdir_add_ignored(dir, fullname, baselen + len);\n@@ -572,8 +594,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n \t\t\tif (exclude && !dir->show_ignored)\n \t\t\t\tcontinue;\n \n-\t\t\tdtype = get_dtype(de, fullname);\n-\n \t\t\t/*\n \t\t\t * Do we want to see just the ignored files?\n \t\t\t * We still need to recurse into directories,\ndiff --git a/dir.h b/dir.h\nindex d8814dc..10d72b5 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -9,6 +9,7 @@ struct dir_entry {\n #define EXC_FLAG_NODIR 1\n #define EXC_FLAG_NOWILDCARD 2\n #define EXC_FLAG_ENDSWITH 4\n+#define EXC_FLAG_MUSTBEDIR 8\n \n struct exclude_list {\n \tint nr;\n@@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,\n \n extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);\n \n-extern int excluded(struct dir_struct *, const char *);\n+extern int excluded(struct dir_struct *, const char *, int);\n 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);\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex e25b255..b4297ba 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -99,4 +99,45 @@ EOF\n test_expect_success 'git-status honours core.excludesfile' \\\n \t'diff -u expect output'\n \n+test_expect_success 'trailing slash in exclude allows directory match(1)' '\n+\n+\tgit ls-files --others --exclude=one/ >output &&\n+\tif grep \"^one/\" output\n+\tthen\n+\t\techo Ooops\n+\t\tfalse\n+\telse\n+\t\t: happy\n+\tfi\n+\n+'\n+\n+test_expect_success 'trailing slash in exclude allows directory match (2)' '\n+\n+\tgit ls-files --others --exclude=one/two/ >output &&\n+\tif grep \"^one/two/\" output\n+\tthen\n+\t\techo Ooops\n+\t\tfalse\n+\telse\n+\t\t: happy\n+\tfi\n+\n+'\n+\n+test_expect_success 'trailing slash in exclude forces directory match (1)' '\n+\n+\t>two\n+\tgit ls-files --others --exclude=two/ >output &&\n+\tgrep \"^two\" output\n+\n+'\n+\n+test_expect_success 'trailing slash in exclude forces directory match (2)' '\n+\n+\tgit ls-files --others --exclude=one/a.1/ >output &&\n+\tgrep \"^one/a.1\" output\n+\n+'\n+\n test_done\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex aa2513e..11af263 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -523,7 +523,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,\n \tif (!lstat(ce->name, &st)) {\n \t\tint cnt;\n \n-\t\tif (o->dir && excluded(o->dir, ce->name))\n+\t\tif (o->dir && excluded(o->dir, ce->name, ce_to_dtype(ce)))\n \t\t\t/*\n \t\t\t * ce->name is explicitly excluded, so it is Ok to\n \t\t\t * overwrite it.\n-- \n1.5.4.rc5.16.gc0279\n"},{"id":"66972","messageId":"20080131094124.GA25546@coredump.intra.peff.net","threadId":"11710","inReplyTo":"7v63xae4lf.fsf_-_@gitster.siamese.dyndns.org","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2008-01-31T09:41:25Z","receivedAt":"2008-01-31T09:41:25Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Jan 31, 2008 at 01:17:48AM -0800, Junio C Hamano wrote:\n\n> A downside is that the recursive directory walk may need to run\n> lstat(2) more often on systems whose \"struct dirent\" do not give\n> the type of the entry; earlier it did not have to do so for an\n> excluded path, but we now need to figure out if a path is a\n> directory before deciding to exclude it.  This is especially bad\n\nYou can at least lazily do the stat so that only users of foo/ need to\npay the penalty. Something like this (completely untested):\n\ndiff --git a/dir.c b/dir.c\nindex a4f8c25..9487908 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -17,6 +17,7 @@ struct path_simplify {\n static int read_directory_recursive(struct dir_struct *dir,\n \tconst char *path, const char *base, int baselen,\n \tint check_only, const struct path_simplify *simplify);\n+static int get_dtype(struct dirent *de, const char *path, int try_stat);\n \n int common_prefix(const char **pathspec)\n {\n@@ -288,9 +289,12 @@ static int excluded_1(const char *pathname,\n \t\t\tconst char *exclude = x->pattern;\n \t\t\tint to_exclude = x->to_exclude;\n \n-\t\t\tif ((x->flags & EXC_FLAG_MUSTBEDIR) &&\n-\t\t\t    (dtype != DT_DIR))\n-\t\t\t\tcontinue;\n+\t\t\tif (x->flags & EXC_FLAG_MUSTBEDIR) {\n+\t\t\t\tif (dtype == DT_UNKNOWN)\n+\t\t\t\t\tdtype = get_dtype(NULL, pathname, 1);\n+\t\t\t\tif (dtype != DT_DIR)\n+\t\t\t\t\tcontinue;\n+\t\t\t}\n \n \t\t\tif (x->flags & EXC_FLAG_NODIR) {\n \t\t\t\t/* match basename */\n@@ -527,13 +531,15 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si\n \treturn 0;\n }\n \n-static int get_dtype(struct dirent *de, const char *path)\n+static int get_dtype(struct dirent *de, const char *path, int try_stat)\n {\n-\tint dtype = DTYPE(de);\n+\tint dtype = de ? DTYPE(de) : DT_UNKNOWN;\n \tstruct stat st;\n \n \tif (dtype != DT_UNKNOWN)\n \t\treturn dtype;\n+\tif (!try_stat)\n+\t\treturn DT_UNKNOWN;\n \tif (lstat(path, &st))\n \t\treturn dtype;\n \tif (S_ISREG(st.st_mode))\n@@ -581,7 +587,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n \t\t\tif (simplify_away(fullname, baselen + len, simplify))\n \t\t\t\tcontinue;\n \n-\t\t\tdtype = get_dtype(de, fullname);\n+\t\t\tdtype = get_dtype(de, fullname, 0);\n \t\t\texclude = excluded(dir, fullname, dtype);\n \t\t\tif (exclude && dir->collect_ignored\n \t\t\t    && in_pathspec(fullname, baselen + len, simplify))\n"},{"id":"66978","messageId":"7vfxwecmfe.fsf@gitster.siamese.dyndns.org","threadId":"11710","inReplyTo":"20080131094124.GA25546@coredump.intra.peff.net","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-31T10:35:33Z","receivedAt":"2008-01-31T10:35:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> You can at least lazily do the stat so that only users of foo/ need to\n> pay the penalty. Something like this (completely untested):\n\nWithout \"foo/\", you do not have to pay the price, so I think\nthat is a sane optimization, but at the same time it would make\nit worse if \"foo/\" is actually used.  excluded_1() is called for\nthe same pathname from a loop to check for a match and you would\nend up running lstat(2) three times (once each for EXC_CMDL,\nEXC_DIRS and EXC_FILE).\n\nBut maybe people who want \"foo/\" deserve it.  I dunno.\n\nIn any case, if you do this...\n\n> @@ -581,7 +587,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n>  \t\t\tif (simplify_away(fullname, baselen + len, simplify))\n>  \t\t\t\tcontinue;\n>  \n> -\t\t\tdtype = get_dtype(de, fullname);\n> +\t\t\tdtype = get_dtype(de, fullname, 0);\n>  \t\t\texclude = excluded(dir, fullname, dtype);\n>  \t\t\tif (exclude && dir->collect_ignored\n>  \t\t\t    && in_pathspec(fullname, baselen + len, simplify))\n\n... I think you would need to get the real dtype again in later\npart of this function after exclude() decides it should not\nignore it, before the \"switch (dtype)\" really uses it, on\nsystems with NO_D_TYPE_IN_DIRENT.\n"},{"id":"66982","messageId":"20080131104256.GF25546@coredump.intra.peff.net","threadId":"11710","inReplyTo":"7vfxwecmfe.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2008-01-31T10:42:56Z","receivedAt":"2008-01-31T10:42:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Jan 31, 2008 at 02:35:33AM -0800, Junio C Hamano wrote:\n\n> Without \"foo/\", you do not have to pay the price, so I think\n> that is a sane optimization, but at the same time it would make\n> it worse if \"foo/\" is actually used.  excluded_1() is called for\n> the same pathname from a loop to check for a match and you would\n> end up running lstat(2) three times (once each for EXC_CMDL,\n> EXC_DIRS and EXC_FILE).\n> \n> But maybe people who want \"foo/\" deserve it.  I dunno.\n\nAh, I didn't look at it that closely.\n\nTo do the laziness right, I think you would need to pass a pointer to\nthe dtype around, and just fill it in the first time it is needed.\n\n-Peff\n"},{"id":"66985","messageId":"alpine.LSU.1.00.0801311128190.23907@racer.site","threadId":"11710","inReplyTo":"20080131104256.GF25546@coredump.intra.peff.net","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-31T11:38:55Z","receivedAt":"2008-01-31T11:38:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 31 Jan 2008, Jeff King wrote:\n\n> On Thu, Jan 31, 2008 at 02:35:33AM -0800, Junio C Hamano wrote:\n> \n> > Without \"foo/\", you do not have to pay the price, so I think that is a \n> > sane optimization, but at the same time it would make it worse if \n> > \"foo/\" is actually used.  excluded_1() is called for the same pathname \n> > from a loop to check for a match and you would end up running lstat(2) \n> > three times (once each for EXC_CMDL, EXC_DIRS and EXC_FILE).\n> > \n> > But maybe people who want \"foo/\" deserve it.  I dunno.\n> \n> Ah, I didn't look at it that closely.\n> \n> To do the laziness right, I think you would need to pass a pointer to \n> the dtype around, and just fill it in the first time it is needed.\n\nJust to add my two eurocents: I think the patch is complicated enough that \nwe could go the other way round: while parsing the ignore entries, we can \nplainly state that entries with a trailing slash are ignored:\n\n-- snipsnap --\n[PATCH] Warn if an ignore/exclude entry ends in a slash\n\nGit does not like ignore entries ending in a slash; they will be ignored.\nSo just be honest and warn the user about it.\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\n---\n\n dir.c |    5 +++++\n 1 files changed, 5 insertions(+), 0 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 1b9cc7a..c3e9a0d 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -135,6 +135,11 @@ void add_exclude(const char *string, const char *base,\n \t}\n \tx->pattern = string;\n \tx->patternlen = strlen(string);\n+\tif (x->patternlen && x->pattern[x->patternlen - 1] == '/') {\n+\t\twarning(\"Ignoring ignore entry because of trailing slash: %s\",\n+\t\t\tstring);\n+\t\treturn;\n+\t}\n \tx->base = base;\n \tx->baselen = baselen;\n \tx->flags = 0;\n"},{"id":"66986","messageId":"6bc632150801310356w1b2fa019n87d92986aed807c5@mail.gmail.com","threadId":"11710","inReplyTo":"alpine.LSU.1.00.0801311128190.23907@racer.site","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"pradeep singh rautela","fromEmail":"rautelap@gmail.com","sentAt":"2008-01-31T11:56:18Z","receivedAt":"2008-01-31T11:56:18Z","isPatch":true,"sender":{"key":"rautelap@gmail.com","avatar":null},"body":"On 31/01/2008, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n[snip]\n>\n> Just to add my two eurocents: I think the patch is complicated enough that\n> we could go the other way round: while parsing the ignore entries, we can\n> plainly state that entries with a trailing slash are ignored:\n>\n> -- snipsnap --\n> [PATCH] Warn if an ignore/exclude entry ends in a slash\n>\n> Git does not like ignore entries ending in a slash; they will be ignored.\n> So just be honest and warn the user about it.\n>\n> Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n>\n> ---\n>\n>  dir.c |    5 +++++\n>  1 files changed, 5 insertions(+), 0 deletions(-)\n>\n> diff --git a/dir.c b/dir.c\n> index 1b9cc7a..c3e9a0d 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -135,6 +135,11 @@ void add_exclude(const char *string, const char *base,\n>         }\n>         x->pattern = string;\n>         x->patternlen = strlen(string);\n> +       if (x->patternlen && x->pattern[x->patternlen - 1] == '/') {\n> +               warning(\"Ignoring ignore entry because of trailing slash: %s\",\n> +                       string);\n\nHow about something like,\n                  warning(\"Ignoring ignore entry because of trailing\nslash: %s\\n Remove the trailing slash from the directory name to\nignore it\", string);\n\nMay be this will help absolute git newbies.\nPlease ignore this if it sounds like a \"too trivial, everyone should\nknow this\" case.\n\nThanks,\n           --Pradeep\n> +               return;\n> +       }\n>         x->base = base;\n>         x->baselen = baselen;\n>         x->flags = 0;\n>\n\n\n-- \nPradeep Singh Rautela\nhttp://eagain.wordpress.com\nhttp://emptydomain.googlepages.com\n"},{"id":"66987","messageId":"47A1BF27.7080802@users.sourceforge.net","threadId":"11710","inReplyTo":"alpine.LSU.1.00.0801311128190.23907@racer.site","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Adam Piatyszek","fromEmail":"ediap@users.sourceforge.net","sentAt":"2008-01-31T12:29:27Z","receivedAt":"2008-01-31T12:29:27Z","isPatch":true,"sender":{"key":"ediap@users.sourceforge.net","avatar":null},"body":"* Johannes Schindelin [31 I 2008 12:38]:\n> Just to add my two eurocents: I think the patch is complicated enough that \n> we could go the other way round: while parsing the ignore entries, we can \n> plainly state that entries with a trailing slash are ignored:\n> \n> -- snipsnap --\n> [PATCH] Warn if an ignore/exclude entry ends in a slash\n> \n> Git does not like ignore entries ending in a slash; they will be ignored.\n> So just be honest and warn the user about it.\n> \n> Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\nI agree that this is a reasonable remedy for this issue. So:\n\nAcked-by: Adam Piątyszek <ediap@users.sourceforge.net>\n\nBTW, the warning message is a bit \"hidden\" between the \"Changed\" and \n\"Untracked\" parts of a status message, e.g.:\n\n===== >8 =====\n# On branch master\n# Changed but not updated:\n#   (use \"git add <file>...\" to update what will be committed)\n#\n#       modified:   src/Makefile\n#       modified:   src/ofdm.cpp\n#\nwarning: Ignoring ignore entry because of trailing slash: results/\n# Untracked files:\n#   (use \"git add <file>...\" to include in what will be committed)\n#\n#       results/\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n===== >8 =====\n\nIs it possible to make warnings displayed in red or yellow colour on \nterminals that support colours?\n\nBR,\n/Adam\n\n\n-- \n.:.  Adam Piatyszek (ediap)  .:.....................................:.\n.:.  ediap@users.sourceforge.net  .:................................:.\n"},{"id":"66994","messageId":"7vr6fxbr5a.fsf@gitster.siamese.dyndns.org","threadId":"11710","inReplyTo":"6bc632150801310356w1b2fa019n87d92986aed807c5@mail.gmail.com","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-31T21:51:13Z","receivedAt":"2008-01-31T21:51:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"pradeep singh rautela\" <rautelap@gmail.com> writes:\n\n> How about something like,\n>                   warning(\"Ignoring ignore entry because of trailing\n> slash: %s\\n Remove the trailing slash from the directory name to\n> ignore it\", string);\n> May be this will help absolute git newbies.\n\nI am afraid that this is leading us in the wrong direction.\n\nWhat would be the first reaction if somebody sees such a\nmessage?\n\n    The message implies that the user said \"foo/\" which would be\n    ignored and the right substitution is \"foo\".  If that is the\n    right substitution, why doesn't the stupid \"git\" program do\n    that for the user automatically?!?!?!?!\n\nSee?\n\n\"Remove the trailing\" suggestion assumes that we would want \"foo/\"\nand \"foo\" to mean the same thing.\n\nMaybe we do, but we usually match both directory \"foo/\" and\nregular file \"foo\" when you say \"foo\", and we match only\ndirectory \"foo/\" when you say \"foo/\", as you saw in the ls-files\nexample.\n\nWhile I am not 100% convinced that we want to keep the\ndistinction between these two forms, I am far from thinking that\nthe existing distinction in other parts of the system is useless\nand should be removed.\n\nMaybe we would want to drop this distinction in the gitignore\nentries, and the apparent inconsistency may not hurt in reality.\nIf that is what we would want, that is fine, but then we\nshouldn't give a warning with a stupid piece of advice, but\ninstead just do it ourselves.\n\nLike this on top of 'master' (i.e. discarding all the previous\npatches), perhaps...\n\n-- >8 --\n[PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"\n\nA pattern \"foo/\" in the exclude list did not match directory\n\"foo\", but a pattern \"foo\" did.  This just strips the trailing\nslash from such input.\n\nThis makes the behaviour slightly inconsistent with that of\npathspecs, where \"foo/\" only matches directory \"foo\" and not\nregular file \"foo\" and make \"foo/\" in the ignore list match\nregular file \"foo\" happily.  This may hopefully does not matter\nin practice.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/gitignore.txt        |    3 +++\n dir.c                              |   22 ++++++++++++++++++----\n t/t3001-ls-files-others-exclude.sh |   26 ++++++++++++++++++++++++++\n 3 files changed, 47 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt\nindex 08373f5..081a4df 100644\n--- a/Documentation/gitignore.txt\n+++ b/Documentation/gitignore.txt\n@@ -57,6 +57,9 @@ Patterns have the following format:\n    included again.  If a negated pattern matches, this will\n    override lower precedence patterns sources.\n \n+ - If the pattern ends with a slash, it is removed for the\n+   purpose of the following description.\n+\n  - If the pattern does not contain a slash '/', git treats it as\n    a shell glob pattern and checks for a match against the\n    pathname without leading directories.\ndiff --git a/dir.c b/dir.c\nindex 3e345c2..fe51829 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -126,14 +126,28 @@ static int no_wildcard(const char *string)\n void add_exclude(const char *string, const char *base,\n \t\t int baselen, struct exclude_list *which)\n {\n-\tstruct exclude *x = xmalloc(sizeof (*x));\n+\tstruct exclude *x;\n+\tsize_t len;\n+\tint to_exclude = 1;\n \n-\tx->to_exclude = 1;\n \tif (*string == '!') {\n-\t\tx->to_exclude = 0;\n+\t\tto_exclude = 0;\n \t\tstring++;\n \t}\n-\tx->pattern = string;\n+\tlen = strlen(string);\n+\tif (len && string[len - 1] == '/') {\n+\t\tchar *s;\n+\t\tx = xmalloc(sizeof(*x) + len);\n+\t\ts = (char*)(x+1);\n+\t\tmemcpy(s, string, len - 1);\n+\t\ts[len - 1] = '\\0';\n+\t\tstring = s;\n+\t\tx->pattern = s;\n+\t} else {\n+\t\tx = xmalloc(sizeof(*x));\n+\t\tx->pattern = string;\n+\t}\n+\tx->to_exclude = to_exclude;\n \tx->patternlen = strlen(string);\n \tx->base = base;\n \tx->baselen = baselen;\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex e25b255..5bc4885 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -99,4 +99,30 @@ EOF\n test_expect_success 'git-status honours core.excludesfile' \\\n \t'diff -u expect output'\n \n+test_expect_success 'trailing slash in exclude allows directory match(1)' '\n+\n+\tgit ls-files --others --exclude=one/ >output &&\n+\tif grep \"^one/\" output\n+\tthen\n+\t\techo Ooops\n+\t\tfalse\n+\telse\n+\t\t: happy\n+\tfi\n+\n+'\n+\n+test_expect_success 'trailing slash in exclude allows directory match (2)' '\n+\n+\tgit ls-files --others --exclude=one/two/ >output &&\n+\tif grep \"^one/two/\" output\n+\tthen\n+\t\techo Ooops\n+\t\tfalse\n+\telse\n+\t\t: happy\n+\tfi\n+\n+'\n+\n test_done\n-- \n1.5.4.rc5.16.gc0279\n"},{"id":"66998","messageId":"47A25180.5000903@users.sourceforge.net","threadId":"11710","inReplyTo":"7vr6fxbr5a.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Adam Piatyszek","fromEmail":"ediap@users.sourceforge.net","sentAt":"2008-01-31T22:53:52Z","receivedAt":"2008-01-31T22:53:52Z","isPatch":true,"sender":{"key":"ediap@users.sourceforge.net","avatar":null},"body":"* Junio C Hamano [31 I 2008 22:51]:\n> [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"\n> \n> A pattern \"foo/\" in the exclude list did not match directory\n> \"foo\", but a pattern \"foo\" did.  This just strips the trailing\n> slash from such input.\n> \n> This makes the behaviour slightly inconsistent with that of\n> pathspecs, where \"foo/\" only matches directory \"foo\" and not\n> regular file \"foo\" and make \"foo/\" in the ignore list match\n> regular file \"foo\" happily.  This may hopefully does not matter\n> in practice.\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\nThis is more or less what I suggested originally. ;-)\n\nAnyway, even if this new behaviour is not consistent with that of \npathspecs, it is not worse than the current behaviour of git. I.e. now \nyou have to use \"foo\" to ignore the \"foo\" directory and its contents, \nbut it does not protect you from masking the file \"foo\" in the same \nrepository.\n\nHowever, it is not possible to have both the \"foo\" directory and \"foo\" \nfile in the same directory level of a repository at the same time. So, \nthe problem with this patch might be only when one replaces the ignored \ndirectory \"foo\" with a file using the same name and forgets to remove \nthe \"foo/\" entry from .gitignore or .git/info/exclude. But exactly the \nsame situation can occur for the current implementation.\n\nSo, I tend to agree that your latest patch is a sensible solution for \n99.9% of cases.\n\nBR,\n/Adam\n\n\n-- \n.:.  Adam Piatyszek (ediap)  .:.....................................:.\n.:.  ediap@users.sourceforge.net  .:................................:.\n"},{"id":"67015","messageId":"47A2DECB.5040007@op5.se","threadId":"11710","inReplyTo":"7vr6fxbr5a.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2008-02-01T08:56:43Z","receivedAt":"2008-02-01T08:56:43Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Junio C Hamano wrote:\n> \"pradeep singh rautela\" <rautelap@gmail.com> writes:\n> \n>> How about something like,\n>>                   warning(\"Ignoring ignore entry because of trailing\n>> slash: %s\\n Remove the trailing slash from the directory name to\n>> ignore it\", string);\n>> May be this will help absolute git newbies.\n> \n> I am afraid that this is leading us in the wrong direction.\n> \n> What would be the first reaction if somebody sees such a\n> message?\n> \n>     The message implies that the user said \"foo/\" which would be\n>     ignored and the right substitution is \"foo\".  If that is the\n>     right substitution, why doesn't the stupid \"git\" program do\n>     that for the user automatically?!?!?!?!\n> \n> See?\n> \n> \"Remove the trailing\" suggestion assumes that we would want \"foo/\"\n> and \"foo\" to mean the same thing.\n> \n> Maybe we do, but we usually match both directory \"foo/\" and\n> regular file \"foo\" when you say \"foo\", and we match only\n> directory \"foo/\" when you say \"foo/\", as you saw in the ls-files\n> example.\n> \n> While I am not 100% convinced that we want to keep the\n> distinction between these two forms, I am far from thinking that\n> the existing distinction in other parts of the system is useless\n> and should be removed.\n> \n> Maybe we would want to drop this distinction in the gitignore\n> entries, and the apparent inconsistency may not hurt in reality.\n> If that is what we would want, that is fine, but then we\n> shouldn't give a warning with a stupid piece of advice, but\n> instead just do it ourselves.\n> \n> Like this on top of 'master' (i.e. discarding all the previous\n> patches), perhaps...\n> \n> -- >8 --\n> [PATCH] gitignore(5): Allow \"foo/\" in ignore list to match directory \"foo\"\n> \n> A pattern \"foo/\" in the exclude list did not match directory\n> \"foo\", but a pattern \"foo\" did.  This just strips the trailing\n> slash from such input.\n> \n> This makes the behaviour slightly inconsistent with that of\n> pathspecs, where \"foo/\" only matches directory \"foo\" and not\n> regular file \"foo\" and make \"foo/\" in the ignore list match\n> regular file \"foo\" happily.  This may hopefully does not matter\n> in practice.\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  Documentation/gitignore.txt        |    3 +++\n>  dir.c                              |   22 ++++++++++++++++++----\n>  t/t3001-ls-files-others-exclude.sh |   26 ++++++++++++++++++++++++++\n>  3 files changed, 47 insertions(+), 4 deletions(-)\n> \n> diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt\n> index 08373f5..081a4df 100644\n> --- a/Documentation/gitignore.txt\n> +++ b/Documentation/gitignore.txt\n> @@ -57,6 +57,9 @@ Patterns have the following format:\n>     included again.  If a negated pattern matches, this will\n>     override lower precedence patterns sources.\n>  \n> + - If the pattern ends with a slash,\n\nthat slash\n\n> is removed for the\n> +   purpose of the following description.\n> +\n>   - If the pattern does not contain a slash '/', git treats it as\n>     a shell glob pattern and checks for a match against the\n>     pathname without leading directories.\n\nOtherwise it sounds as if the entire pattern is removed.\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n"}]}