{"thread":{"id":"33220","subject":"[PATCH 0/8] Improve git-status --ignored","startedAt":"2013-03-18T20:28:20Z","lastAt":"2013-04-17T22:03:55Z","messageCount":36,"participants":["Karsten Blees","Junio C Hamano","Duy Nguyen","Thomas Rast","Ramkumar Ramachandra"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"211594","messageId":"514778E4.1040607@gmail.com","threadId":"33220","inReplyTo":null,"subject":"[PATCH 0/8] Improve git-status --ignored","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:28:20Z","receivedAt":"2013-03-18T20:28:20Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"This patch series addresses several bugs and performance issues in .gitignore processing that came up in the inotify discussion.\n\nAlso available here:\nhttps://github.com/kblees/git/tree/kb/improve-git-status-ignored\ngit pull git://github.com/kblees/git.git kb/improve-git-status-ignored\n\n\nPatches 1 - 4 fix bugs in 'git-status --ignored' and add appropriate test cases.\n\nPatches 5 - 7 eliminate the is_path_excluded API, in favor of a slightly improved and faster is_excluded. This speeds up 'git-ls-files --cached --ignored' by factor 5 - 6.\n\nPatch 8 finally skips excluded checks for tracked files. With the bugs and is_path_excluded out of the way, it should be obvious that this can safely be done unconditionally without introducing regressions. Speeds up 'git-status [--ignored]' by factor 1.4 - 2.\n\n\nI still believe that 'git-status --ignored' shouldn't list \"ignored tracked\" directories, to be consistent with the listing of untracked directories, and because \"ignored tracked\" contradicts the very definition of ignored content in gitignore(5).\n\nCheers,\nKarsten\n\n\nKarsten Blees (8):\n  dir.c: git-status --ignored: don't drop ignored directories\n  dir.c: git-status --ignored: don't list files in ignored directories\n  dir.c: git-status --ignored: don't list empty ignored directories\n  dir.c: git-status --ignored: don't list empty directories as ignored\n  dir.c: move prep_exclude and factor out parts of last_exclude_matching\n  dir.c: unify is_excluded and is_path_excluded APIs\n  dir.c: replace is_path_excluded with now equivalent is_excluded API\n  dir.c: git-status: avoid is_excluded checks for tracked files\n\n builtin/add.c              |   5 +-\n builtin/check-ignore.c     |   6 +-\n builtin/ls-files.c         |  15 +-\n dir.c                      | 351 +++++++++++++++++----------------------------\n dir.h                      |  22 +--\n t/t7061-wtstatus-ignore.sh | 104 +++++++++++++-\n unpack-trees.c             |  10 +-\n unpack-trees.h             |   1 -\n 8 files changed, 243 insertions(+), 271 deletions(-)\n\n-- \n"},{"id":"211632","messageId":"7vsj3skp5b.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"514778E4.1040607@gmail.com","subject":"Re: [PATCH 0/8] Improve git-status --ignored","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-19T04:08:16Z","receivedAt":"2013-03-19T04:08:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karsten Blees <karsten.blees@gmail.com> writes:\n\n> This patch series addresses several bugs and performance issues in\n> .gitignore processing that came up in the inotify discussion.\n\nThanks.\n\nHow does this interact with the nd/read-directory-recursive-optim\ntopic that has been cooking for a while?\n"},{"id":"211635","messageId":"CACsJy8DShce6bXfyWyHk7pqg4PA-cAn1bKh0hgFYX=s486nwaA@mail.gmail.com","threadId":"33220","inReplyTo":"7vsj3skp5b.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 0/8] Improve git-status --ignored","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-03-19T05:20:39Z","receivedAt":"2013-03-19T05:20:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Mar 19, 2013 at 11:08 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Karsten Blees <karsten.blees@gmail.com> writes:\n>\n>> This patch series addresses several bugs and performance issues in\n>> .gitignore processing that came up in the inotify discussion.\n>\n> Thanks.\n>\n> How does this interact with the nd/read-directory-recursive-optim\n> topic that has been cooking for a while?\n\nI think 8/8 is another version of nd/read-directory-recursive-optim\n-- \nDuy\n"},{"id":"211656","messageId":"51484272.5070003@gmail.com","threadId":"33220","inReplyTo":"CACsJy8DShce6bXfyWyHk7pqg4PA-cAn1bKh0hgFYX=s486nwaA@mail.gmail.com","subject":"Re: [PATCH 0/8] Improve git-status --ignored","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-19T10:48:18Z","receivedAt":"2013-03-19T10:48:18Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Am 19.03.2013 06:20, schrieb Duy Nguyen:\n> On Tue, Mar 19, 2013 at 11:08 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Karsten Blees <karsten.blees@gmail.com> writes:\n>>\n>>> This patch series addresses several bugs and performance issues in\n>>> .gitignore processing that came up in the inotify discussion.\n>>\n>> Thanks.\n>>\n>> How does this interact with the nd/read-directory-recursive-optim\n>> topic that has been cooking for a while?\n> \n> I think 8/8 is another version of nd/read-directory-recursive-optim\n> \n\nYes. When reviewing Duy's patch, I wondered why we would need all those special cases (or: why is treat_file so damn complicated). It turned out that it gets much simpler after fixing some bugs and eliminating the is_excluded / is_path_excluded discrepancy. This variant also optimizes git-status --ignored.\n\nI tried to express my ideas here [1], but I guess this was a bit unstructured, sorry :-)\n\nNote that we could skip excluded checks for tracked directories as well if it weren't for the current notion of ignored tracked directories.\n\nI'd still like to eliminate the second directory scan in git-status --ignored (i.e. don't call fill_directory twice, which would save ~50ms for linux and ~300ms for WebKit), but that's a bit more involved...\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/217111\n"},{"id":"211672","messageId":"7vehfbla1y.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"CACsJy8DShce6bXfyWyHk7pqg4PA-cAn1bKh0hgFYX=s486nwaA@mail.gmail.com","subject":"Re: [PATCH 0/8] Improve git-status --ignored","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-19T14:48:57Z","receivedAt":"2013-03-19T14:48:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> On Tue, Mar 19, 2013 at 11:08 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Karsten Blees <karsten.blees@gmail.com> writes:\n>>\n>>> This patch series addresses several bugs and performance issues in\n>>> .gitignore processing that came up in the inotify discussion.\n>>\n>> Thanks.\n>>\n>> How does this interact with the nd/read-directory-recursive-optim\n>> topic that has been cooking for a while?\n>\n> I think 8/8 is another version of nd/read-directory-recursive-optim\n\nYeah, it seems so; even though the series is much larger, overall it\nlooks to me a cleaner solution without a specific special case.\n\nWould we we better off kicking nd/read-directory-recursive-optim\nback to 'pu' (and eventually ejecting it) and replacing it with a\nreroll of Karsten's series when it comes, perhaps?\n"},{"id":"211686","messageId":"CACsJy8BE8mD+-W2miwTMEnrR45zpuPMbPcvV54q_7k0famFjuw@mail.gmail.com","threadId":"33220","inReplyTo":"7vehfbla1y.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 0/8] Improve git-status --ignored","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-03-19T15:58:06Z","receivedAt":"2013-03-19T15:58:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Mar 19, 2013 at 9:48 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Would we we better off kicking nd/read-directory-recursive-optim\n> back to 'pu' (and eventually ejecting it) and replacing it with a\n> reroll of Karsten's series when it comes, perhaps?\n\nI have no problem with that. Whatever better should get in.\n-- \nDuy\n"},{"id":"214361","messageId":"516C4F27.30203@gmail.com","threadId":"33220","inReplyTo":"514778E4.1040607@gmail.com","subject":"[PATCH v2 00/14] Improve git-status --ignored","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:04:07Z","receivedAt":"2013-04-15T19:04:07Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"This patch series addresses several bugs and performance issues in\n.gitignore processing.\n\nPatches #1 - #6 fix bugs and add appropriate test cases.\n\nPatch #7 changes handling of \"ignored tracked\" directories, as I discovered\nthat with the current bahavior git-clean can delete tracked content.\n\nPatches #8 - #14 are performance optimizations.\n\n\nAlso available here:\nhttps://github.com/kblees/git/tree/kb/improve-git-status-ignored-v2\ngit pull git://github.com/kblees/git.git kb/improve-git-status-ignored-v2\n\n\nChanges since v1 (old#->new#: description):\n\n1->1: dir.c: git-status --ignored: don't drop ignored directories\n2->2: dir.c: git-status --ignored: don't list files in ignored directories\n3->3: dir.c: git-status --ignored: don't list empty ignored directories\n\n*->4: dir.c: git-ls-files --directories: don't hide empty directories\n      - new bugfix\n\n4->5: dir.c: git-status --ignored: don't list empty directories as ignored\n      - fixed typo in commit message thanks to Eric Sunshine\n      - patch is reduced to a one-liner (the DIR_HIDE_EMPTY_DIRECTORIES\n        flag has already been fixed in patch 4, renaming the variable and\n\ttweaking DIR_SHOW_OTHER_DIRECTORIES is not strictly necessary)\n\n*->6: dir.c: make 'git-status --ignored' work within leading directories\n      - new bugfix\n\n*->7: dir.c: git-clean -d -X: don't delete tracked directories\n      - changes handling of \"ignored tracked\" directories\n\n5->8: dir.c: factor out parts of last_exclude_matching for later reuse\n5->9: dir.c: move prep_exclude\n      - split in two patches for cleaner diff\n      - removed unnecessary ';' after '}'\n\n6->10: dir.c: unify is_excluded and is_path_excluded APIs\n       - fixed another typo in commit message\n\n7->11: dir.c: replace is_path_excluded with now equivalent is_excluded API\n8->12: dir.c: git-status: avoid is_excluded checks for tracked files\n\n*->13: dir.c: git-status --ignored: don't scan the work tree three times\n       - new optimization\n\n*->14: dir.c: git-status --ignored: don't scan the work tree twice\n       - new optimization\n\nKarsten Blees (14):\n  dir.c: git-status --ignored: don't drop ignored directories\n  dir.c: git-status --ignored: don't list files in ignored directories\n  dir.c: git-status --ignored: don't list empty ignored directories\n  dir.c: git-ls-files --directories: don't hide empty directories\n  dir.c: git-status --ignored: don't list empty directories as ignored\n  dir.c: make 'git-status --ignored' work within leading directories\n  dir.c: git-clean -d -X: don't delete tracked directories\n  dir.c: factor out parts of last_exclude_matching for later reuse\n  dir.c: move prep_exclude\n  dir.c: unify is_excluded and is_path_excluded APIs\n  dir.c: replace is_path_excluded with now equivalent is_excluded API\n  dir.c: git-status: avoid is_excluded checks for tracked files\n  dir.c: git-status --ignored: don't scan the work tree three times\n  dir.c: git-status --ignored: don't scan the work tree twice\n\n Documentation/technical/api-directory-listing.txt |  25 +-\n builtin/add.c                                     |   5 +-\n builtin/check-ignore.c                            |   7 +-\n builtin/ls-files.c                                |  15 +-\n dir.c                                             | 499 +++++++++-------------\n dir.h                                             |  25 +-\n t/t3001-ls-files-others-exclude.sh                |  49 +++\n t/t7061-wtstatus-ignore.sh                        | 125 +++++-\n t/t7300-clean.sh                                  |  34 ++\n unpack-trees.c                                    |  10 +-\n unpack-trees.h                                    |   1 -\n wt-status.c                                       |  24 +-\n 12 files changed, 455 insertions(+), 364 deletions(-)\n"},{"id":"214362","messageId":"516C4F6F.5020708@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 01/14] dir.c: git-status --ignored: don't drop ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:05:19Z","receivedAt":"2013-04-15T19:05:19Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored' drops ignored directories if they contain untracked\nfiles in an untracked sub directory.\n\nFix it by getting exact (recursive) excluded status in treat_directory.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c                      |  9 +++++++++\n t/t7061-wtstatus-ignore.sh | 27 +++++++++++++++++++++++++++\n 2 files changed, 36 insertions(+)\n\ndiff --git a/dir.c b/dir.c\nindex 57394e4..ec4eebf 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1060,6 +1060,15 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \n \t/* This is the \"show_other_directories\" case */\n \n+\t/* might be a sub directory in an excluded directory */\n+\tif (!exclude) {\n+\t\tstruct path_exclude_check check;\n+\t\tint dt = DT_DIR;\n+\t\tpath_exclude_check_init(&check, dir);\n+\t\texclude = is_path_excluded(&check, dirname, len, &dt);\n+\t\tpath_exclude_check_clear(&check);\n+\t}\n+\n \t/*\n \t * We are looking for ignored files and our directory is not ignored,\n \t * check if it contains only ignored files\ndiff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\nindex 0da1214..0f1034e 100755\n--- a/t/t7061-wtstatus-ignore.sh\n+++ b/t/t7061-wtstatus-ignore.sh\n@@ -143,4 +143,31 @@ test_expect_success 'status ignored tracked directory and uncommitted file with\n \ttest_cmp expected actual\n '\n \n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n+!! tracked/\n+EOF\n+\n+test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore' '\n+\trm -rf tracked/uncommitted &&\n+\tmkdir tracked/ignored &&\n+\t: >tracked/ignored/uncommitted &&\n+\tgit status --porcelain --ignored >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n+!! tracked/ignored/uncommitted\n+EOF\n+\n+test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' '\n+\tgit status --porcelain --ignored -u >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214363","messageId":"516C4FB6.8060703@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 02/14] dir.c: git-status --ignored: don't list files in ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:06:30Z","receivedAt":"2013-04-15T19:06:30Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored' lists both the ignored directory and the ignored\nfiles if the files are in a tracked sub directory.\n\nWhen recursing into sub directories in read_directory_recursive, pass on\nthe check_only parameter so that we don't accidentally add the files.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c                      |  4 +---\n t/t7061-wtstatus-ignore.sh | 27 +++++++++++++++++++++++++++\n 2 files changed, 28 insertions(+), 3 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex ec4eebf..7c9bc9c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1273,7 +1273,6 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\treturn path_ignored;\n \tcase DT_DIR:\n \t\tstrbuf_addch(path, '/');\n-\n \t\tswitch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {\n \t\tcase show_directory:\n \t\t\tbreak;\n@@ -1343,8 +1342,7 @@ static int read_directory_recursive(struct dir_struct *dir,\n \t\tswitch (treat_path(dir, de, &path, baselen, simplify)) {\n \t\tcase path_recurse:\n \t\t\tcontents += read_directory_recursive(dir, path.buf,\n-\t\t\t\t\t\t\t     path.len, 0,\n-\t\t\t\t\t\t\t     simplify);\n+\t\t\t\tpath.len, check_only, simplify);\n \t\t\tcontinue;\n \t\tcase path_ignored:\n \t\t\tcontinue;\ndiff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\nindex 0f1034e..4ece129 100755\n--- a/t/t7061-wtstatus-ignore.sh\n+++ b/t/t7061-wtstatus-ignore.sh\n@@ -170,4 +170,31 @@ test_expect_success 'status ignored tracked directory with uncommitted file in u\n \ttest_cmp expected actual\n '\n \n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n+!! tracked/\n+EOF\n+\n+test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore' '\n+\t: >tracked/ignored/committed &&\n+\tgit add -f tracked/ignored/committed &&\n+\tgit commit -m. &&\n+\tgit status --porcelain --ignored >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n+!! tracked/ignored/uncommitted\n+EOF\n+\n+test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore -u' '\n+\tgit status --porcelain --ignored -u >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214364","messageId":"516C4FE4.9090806@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 03/14] dir.c: git-status --ignored: don't list empty ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:07:16Z","receivedAt":"2013-04-15T19:07:16Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored' lists ignored tracked directories without any\nignored files if a tracked file happens to match an exclude pattern.\n\nAlways exclude tracked files.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c                      | 11 ++++-------\n t/t7061-wtstatus-ignore.sh | 24 ++++++++++++++++++++++++\n 2 files changed, 28 insertions(+), 7 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7c9bc9c..fd1f088 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1109,16 +1109,13 @@ static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude,\n \tstruct path_exclude_check check;\n \tint exclude_file = 0;\n \n+\t/* Always exclude indexed files */\n+\tif (index_name_exists(&the_index, path->buf, path->len, ignore_case))\n+\t\treturn 1;\n+\n \tif (exclude)\n \t\texclude_file = !(dir->flags & DIR_SHOW_IGNORED);\n \telse if (dir->flags & DIR_SHOW_IGNORED) {\n-\t\t/* Always exclude indexed files */\n-\t\tstruct cache_entry *ce = index_name_exists(&the_index,\n-\t\t    path->buf, path->len, ignore_case);\n-\n-\t\tif (ce)\n-\t\t\treturn 1;\n-\n \t\tpath_exclude_check_init(&check, dir);\n \n \t\tif (!is_path_excluded(&check, path->buf, path->len, dtype))\ndiff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\nindex 4ece129..28b7d95 100755\n--- a/t/t7061-wtstatus-ignore.sh\n+++ b/t/t7061-wtstatus-ignore.sh\n@@ -122,10 +122,34 @@ cat >expected <<\\EOF\n ?? .gitignore\n ?? actual\n ?? expected\n+EOF\n+\n+test_expect_success 'status ignored tracked directory and ignored file with --ignore' '\n+\techo \"committed\" >>.gitignore &&\n+\tgit status --porcelain --ignored >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n+EOF\n+\n+test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' '\n+\tgit status --porcelain --ignored -u >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n !! tracked/\n EOF\n \n test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' '\n+\techo \"tracked\" >.gitignore &&\n \t: >tracked/uncommitted &&\n \tgit status --porcelain --ignored >actual &&\n \ttest_cmp expected actual\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214365","messageId":"516C5012.80306@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 04/14] dir.c: git-ls-files --directories: don't hide empty directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:08:02Z","receivedAt":"2013-04-15T19:08:02Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-ls-files --ignored --directories' hides empty directories even though\n--no-empty-directory was not specified.\n\nTreat the DIR_HIDE_EMPTY_DIRECTORIES flag independently from\nDIR_SHOW_IGNORED to make all git-ls-files options work as expected.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c                              |  6 ++----\n t/t3001-ls-files-others-exclude.sh | 23 +++++++++++++++++++++++\n wt-status.c                        |  2 +-\n 3 files changed, 26 insertions(+), 5 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex fd1f088..1112b05 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1076,15 +1076,13 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \tif ((dir->flags & DIR_SHOW_IGNORED) && !exclude) {\n \t\tint ignored;\n \t\tdir->flags &= ~DIR_SHOW_IGNORED;\n-\t\tdir->flags |= DIR_HIDE_EMPTY_DIRECTORIES;\n \t\tignored = read_directory_recursive(dir, dirname, len, 1, simplify);\n-\t\tdir->flags &= ~DIR_HIDE_EMPTY_DIRECTORIES;\n \t\tdir->flags |= DIR_SHOW_IGNORED;\n \n \t\treturn ignored ? ignore_directory : show_directory;\n \t}\n-\tif (!(dir->flags & DIR_SHOW_IGNORED) &&\n-\t    !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))\n+\n+\tif (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))\n \t\treturn show_directory;\n \tif (!read_directory_recursive(dir, dirname, len, 1, simplify))\n \t\treturn ignore_directory;\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex efb7ebc..859da35 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -214,6 +214,29 @@ test_expect_success 'subdirectory ignore (l1)' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'show/hide empty ignored directory (setup)' '\n+\trm top/l1/l2/l1 &&\n+\trm top/l1/.gitignore\n+'\n+\n+test_expect_success 'show empty ignored directory with --directory' '\n+\t(\n+\t\tcd top &&\n+\t\tgit ls-files -o -i --exclude l1 --directory\n+\t) >actual &&\n+\techo l1/ >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'hide empty ignored directory with --no-empty-directory' '\n+\t(\n+\t\tcd top &&\n+\t\tgit ls-files -o -i --exclude l1 --directory --no-empty-directory\n+\t) >actual &&\n+\t>expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'pattern matches prefix completely' '\n \t: >expect &&\n \tgit ls-files -i -o --exclude \"/three/a.3[abc]\" >actual &&\ndiff --git a/wt-status.c b/wt-status.c\nindex ef405d0..79eb124 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -518,7 +518,7 @@ static void wt_status_collect_untracked(struct wt_status *s)\n \t\tdir.nr = 0;\n \t\tdir.flags = DIR_SHOW_IGNORED;\n \t\tif (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)\n-\t\t\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n+\t\t\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n \t\tfill_directory(&dir, s->pathspec);\n \t\tfor (i = 0; i < dir.nr; i++) {\n \t\t\tstruct dir_entry *ent = dir.entries[i];\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214366","messageId":"516C503A.5000000@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 05/14] dir.c: git-status --ignored: don't list empty directories as ignored","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:08:42Z","receivedAt":"2013-04-15T19:08:42Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored' lists empty untracked directories as ignored, even\nthough they don't have any ignored files.\n\nWhen checking if a directory is already listed as untracked (i.e. shouldn't\nbe listed as ignored as well), don't assume that the directory has only\nignored files if it doesn't have untracked files, as the directory may be\nempty.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c                      |  5 +++--\n t/t7061-wtstatus-ignore.sh | 26 ++++++++++++++++++++++++--\n 2 files changed, 27 insertions(+), 4 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 1112b05..c5705e3 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1071,7 +1071,7 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \n \t/*\n \t * We are looking for ignored files and our directory is not ignored,\n-\t * check if it contains only ignored files\n+\t * check if it contains untracked files (i.e. is listed as untracked)\n \t */\n \tif ((dir->flags & DIR_SHOW_IGNORED) && !exclude) {\n \t\tint ignored;\n@@ -1079,7 +1079,8 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \t\tignored = read_directory_recursive(dir, dirname, len, 1, simplify);\n \t\tdir->flags |= DIR_SHOW_IGNORED;\n \n-\t\treturn ignored ? ignore_directory : show_directory;\n+\t\tif (ignored)\n+\t\t\treturn ignore_directory;\n \t}\n \n \tif (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))\ndiff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\nindex 28b7d95..6171a49 100755\n--- a/t/t7061-wtstatus-ignore.sh\n+++ b/t/t7061-wtstatus-ignore.sh\n@@ -64,13 +64,35 @@ cat >expected <<\\EOF\n ?? .gitignore\n ?? actual\n ?? expected\n-!! untracked-ignored/\n EOF\n \n-test_expect_success 'status untracked directory with ignored files with --ignore' '\n+test_expect_success 'status empty untracked directory with --ignore' '\n \trm -rf ignored &&\n \tmkdir untracked-ignored &&\n \tmkdir untracked-ignored/test &&\n+\tgit status --porcelain --ignored >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n+EOF\n+\n+test_expect_success 'status empty untracked directory with --ignore -u' '\n+\tgit status --porcelain --ignored -u >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<\\EOF\n+?? .gitignore\n+?? actual\n+?? expected\n+!! untracked-ignored/\n+EOF\n+\n+test_expect_success 'status untracked directory with ignored files with --ignore' '\n \t: >untracked-ignored/ignored &&\n \t: >untracked-ignored/test/ignored &&\n \tgit status --porcelain --ignored >actual &&\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214367","messageId":"516C5065.7060008@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 06/14] dir.c: make 'git-status --ignored' work within leading directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:09:25Z","receivedAt":"2013-04-15T19:09:25Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored path/' doesn't list ignored files and directories\nwithin 'path' if some component of 'path' is classified as untracked.\n\nDisable the DIR_SHOW_OTHER_DIRECTORIES flag while traversing leading\ndirectories. This prevents treat_leading_path() with DIR_SHOW_IGNORED flag\nfrom aborting at the top level untracked directory.\n\nAs a side effect, this also eliminates a recursive directory scan per\nleading directory level, as treat_directory() can no longer call\nread_directory_recursive() when called from treat_leading_path().\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c                      |  3 +++\n t/t7061-wtstatus-ignore.sh | 19 +++++++++++++++++++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/dir.c b/dir.c\nindex c5705e3..822e501 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1403,12 +1403,14 @@ static int treat_leading_path(struct dir_struct *dir,\n \tstruct strbuf sb = STRBUF_INIT;\n \tint baselen, rc = 0;\n \tconst char *cp;\n+\tint old_flags = dir->flags;\n \n \twhile (len && path[len - 1] == '/')\n \t\tlen--;\n \tif (!len)\n \t\treturn 1;\n \tbaselen = 0;\n+\tdir->flags &= ~DIR_SHOW_OTHER_DIRECTORIES;\n \twhile (1) {\n \t\tcp = path + baselen + !!baselen;\n \t\tcp = memchr(cp, '/', path + len - cp);\n@@ -1431,6 +1433,7 @@ static int treat_leading_path(struct dir_struct *dir,\n \t\t}\n \t}\n \tstrbuf_release(&sb);\n+\tdir->flags = old_flags;\n \treturn rc;\n }\n \ndiff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\nindex 6171a49..4c6f145 100755\n--- a/t/t7061-wtstatus-ignore.sh\n+++ b/t/t7061-wtstatus-ignore.sh\n@@ -32,6 +32,25 @@ test_expect_success 'status untracked directory with --ignored -u' '\n \tgit status --porcelain --ignored -u >actual &&\n \ttest_cmp expected actual\n '\n+cat >expected <<\\EOF\n+?? untracked/uncommitted\n+!! untracked/ignored\n+EOF\n+\n+test_expect_success 'status prefixed untracked directory with --ignored' '\n+\tgit status --porcelain --ignored untracked/ >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<\\EOF\n+?? untracked/uncommitted\n+!! untracked/ignored\n+EOF\n+\n+test_expect_success 'status prefixed untracked sub-directory with --ignored -u' '\n+\tgit status --porcelain --ignored -u untracked/ >actual &&\n+\ttest_cmp expected actual\n+'\n \n cat >expected <<\\EOF\n ?? .gitignore\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214368","messageId":"516C508D.2050200@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 07/14] dir.c: git-clean -d -X: don't delete tracked directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:10:05Z","receivedAt":"2013-04-15T19:10:05Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"The notion of \"ignored tracked\" directories introduced in 721ac4ed \"dir.c:\nMake git-status --ignored more consistent\" has a few unwanted side effects:\n\n - git-clean -d -X: deletes ignored tracked directories. git-clean should\n   never delete tracked content.\n\n - git-ls-files --ignored --other --directory: lists ignored tracked\n   directories instead of \"other\" directories.\n\n - git-status --ignored: lists ignored tracked directories while contained\n   files may be listed as modified. Paths listed by git-status should be\n   disjoint (except in long format where a path may be listed in both the\n   staged and unstaged section).\n\nAdditionally, the current behaviour violates documentation in gitignore(5)\n(\"Specifies intentionally *untracked* files to ignore\") and Documentation/\ntechnical/api-directory-listing.txt (\"DIR_SHOW_OTHER_DIRECTORIES: Include\na directory that is *not tracked*.\").\n\nIn dir.c::treat_directory, remove the special handling of ignored tracked\ndirectories, so that the DIR_SHOW_OTHER_DIRECTORIES flag only affects\n\"other\" (i.e. untracked) directories. In dir.c::dir_add_name, check that\nadded paths are untracked even if DIR_SHOW_IGNORED is set.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c                              | 11 +++--------\n t/t3001-ls-files-others-exclude.sh | 26 ++++++++++++++++++++++++++\n t/t7061-wtstatus-ignore.sh         |  6 +++---\n t/t7300-clean.sh                   | 34 ++++++++++++++++++++++++++++++++++\n 4 files changed, 66 insertions(+), 11 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 822e501..f10fb69 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -897,8 +897,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)\n \n static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)\n {\n-\tif (!(dir->flags & DIR_SHOW_IGNORED) &&\n-\t    cache_name_exists(pathname, len, ignore_case))\n+\tif (cache_name_exists(pathname, len, ignore_case))\n \t\treturn NULL;\n \n \tALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);\n@@ -1000,9 +999,8 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)\n  * traversal routine.\n  *\n  * Case 1: If we *already* have entries in the index under that\n- * directory name, we recurse into the directory to see all the files,\n- * unless the directory is excluded and we want to show ignored\n- * directories\n+ * directory name, we always recurse into the directory to see\n+ * all the files.\n  *\n  * Case 2: If we *already* have that directory name as a gitlink,\n  * we always continue to see it as a gitlink, regardless of whether\n@@ -1037,9 +1035,6 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \t/* The \"len-1\" is to strip the final '/' */\n \tswitch (directory_exists_in_index(dirname, len-1)) {\n \tcase index_directory:\n-\t\tif ((dir->flags & DIR_SHOW_OTHER_DIRECTORIES) && exclude)\n-\t\t\tbreak;\n-\n \t\treturn recurse_into_directory;\n \n \tcase index_gitdir:\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex 859da35..ec4fae2 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -237,6 +237,32 @@ test_expect_success 'hide empty ignored directory with --no-empty-directory' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'show/hide empty ignored sub-directory (setup)' '\n+\t> top/l1/tracked &&\n+\t(\n+\t\tcd top &&\n+\t\tgit add -f l1/tracked\n+\t)\n+'\n+\n+test_expect_success 'show empty ignored sub-directory with --directory' '\n+\t(\n+\t\tcd top &&\n+\t\tgit ls-files -o -i --exclude l1 --directory\n+\t) >actual &&\n+\techo l1/l2/ >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'hide empty ignored sub-directory with --no-empty-directory' '\n+\t(\n+\t\tcd top &&\n+\t\tgit ls-files -o -i --exclude l1 --directory --no-empty-directory\n+\t) >actual &&\n+\t>expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'pattern matches prefix completely' '\n \t: >expect &&\n \tgit ls-files -i -o --exclude \"/three/a.3[abc]\" >actual &&\ndiff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\nindex 4c6f145..460789b 100755\n--- a/t/t7061-wtstatus-ignore.sh\n+++ b/t/t7061-wtstatus-ignore.sh\n@@ -186,7 +186,7 @@ cat >expected <<\\EOF\n ?? .gitignore\n ?? actual\n ?? expected\n-!! tracked/\n+!! tracked/uncommitted\n EOF\n \n test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' '\n@@ -212,7 +212,7 @@ cat >expected <<\\EOF\n ?? .gitignore\n ?? actual\n ?? expected\n-!! tracked/\n+!! tracked/ignored/\n EOF\n \n test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore' '\n@@ -239,7 +239,7 @@ cat >expected <<\\EOF\n ?? .gitignore\n ?? actual\n ?? expected\n-!! tracked/\n+!! tracked/ignored/uncommitted\n EOF\n \n test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore' '\ndiff --git a/t/t7300-clean.sh b/t/t7300-clean.sh\nindex ccfb54d..710be90 100755\n--- a/t/t7300-clean.sh\n+++ b/t/t7300-clean.sh\n@@ -298,6 +298,23 @@ test_expect_success 'git clean -d -x' '\n \n '\n \n+test_expect_success 'git clean -d -x with ignored tracked directory' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&\n+\tgit clean -d -x -e src &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f src/part3.c &&\n+\ttest ! -d docs &&\n+\ttest ! -f obj.o &&\n+\ttest ! -d build\n+\n+'\n+\n test_expect_success 'git clean -X' '\n \n \tmkdir -p build docs &&\n@@ -332,6 +349,23 @@ test_expect_success 'git clean -d -X' '\n \n '\n \n+test_expect_success 'git clean -d -X with ignored tracked directory' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&\n+\tgit clean -d -X -e src &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f obj.o &&\n+\ttest ! -d build\n+\n+'\n+\n test_expect_success 'clean.requireForce defaults to true' '\n \n \tgit config --unset clean.requireForce &&\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214369","messageId":"516C50C6.7070902@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 08/14] dir.c: factor out parts of last_exclude_matching for later reuse","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:11:02Z","receivedAt":"2013-04-15T19:11:02Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Signed-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c | 36 ++++++++++++++++++++++--------------\n 1 file changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex f10fb69..594307c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -751,6 +751,26 @@ int is_excluded_from_list(const char *pathname,\n \treturn -1; /* undecided */\n }\n \n+static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir,\n+\t\tconst char *pathname, int pathlen, const char *basename,\n+\t\tint *dtype_p)\n+{\n+\tint i, j;\n+\tstruct exclude_list_group *group;\n+\tstruct exclude *exclude;\n+\tfor (i = EXC_CMDL; i <= EXC_FILE; i++) {\n+\t\tgroup = &dir->exclude_list_group[i];\n+\t\tfor (j = group->nr - 1; j >= 0; j--) {\n+\t\t\texclude = last_exclude_matching_from_list(\n+\t\t\t\tpathname, pathlen, basename, dtype_p,\n+\t\t\t\t&group->el[j]);\n+\t\t\tif (exclude)\n+\t\t\t\treturn exclude;\n+\t\t}\n+\t}\n+\treturn NULL;\n+}\n+\n /*\n  * Loads the exclude lists for the directory containing pathname, then\n  * scans all exclude lists to determine whether pathname is excluded.\n@@ -762,25 +782,13 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,\n \t\t\t\t\t     int *dtype_p)\n {\n \tint pathlen = strlen(pathname);\n-\tint i, j;\n-\tstruct exclude_list_group *group;\n-\tstruct exclude *exclude;\n \tconst char *basename = strrchr(pathname, '/');\n \tbasename = (basename) ? basename+1 : pathname;\n \n \tprep_exclude(dir, pathname, basename-pathname);\n \n-\tfor (i = EXC_CMDL; i <= EXC_FILE; i++) {\n-\t\tgroup = &dir->exclude_list_group[i];\n-\t\tfor (j = group->nr - 1; j >= 0; j--) {\n-\t\t\texclude = last_exclude_matching_from_list(\n-\t\t\t\tpathname, pathlen, basename, dtype_p,\n-\t\t\t\t&group->el[j]);\n-\t\t\tif (exclude)\n-\t\t\t\treturn exclude;\n-\t\t}\n-\t}\n-\treturn NULL;\n+\treturn last_exclude_matching_from_lists(dir, pathname, pathlen,\n+\t\t\tbasename, dtype_p);\n }\n \n /*\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214370","messageId":"516C50E9.8010603@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 09/14] dir.c: move prep_exclude","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:11:37Z","receivedAt":"2013-04-15T19:11:37Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Move prep_exclude in preparation for the next patch.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c | 144 +++++++++++++++++++++++++++++++++---------------------------------\n 1 file changed, 72 insertions(+), 72 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 594307c..fcb3def 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -549,78 +549,6 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n \t\tdie(\"cannot use %s as an exclude file\", fname);\n }\n \n-/*\n- * Loads the per-directory exclude list for the substring of base\n- * which has a char length of baselen.\n- */\n-static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n-{\n-\tstruct exclude_list_group *group;\n-\tstruct exclude_list *el;\n-\tstruct exclude_stack *stk = NULL;\n-\tint current;\n-\n-\tif ((!dir->exclude_per_dir) ||\n-\t    (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))\n-\t\treturn; /* too long a path -- ignore */\n-\n-\tgroup = &dir->exclude_list_group[EXC_DIRS];\n-\n-\t/* Pop the exclude lists from the EXCL_DIRS exclude_list_group\n-\t * which originate from directories not in the prefix of the\n-\t * path being checked. */\n-\twhile ((stk = dir->exclude_stack) != NULL) {\n-\t\tif (stk->baselen <= baselen &&\n-\t\t    !strncmp(dir->basebuf, base, stk->baselen))\n-\t\t\tbreak;\n-\t\tel = &group->el[dir->exclude_stack->exclude_ix];\n-\t\tdir->exclude_stack = stk->prev;\n-\t\tfree((char *)el->src); /* see strdup() below */\n-\t\tclear_exclude_list(el);\n-\t\tfree(stk);\n-\t\tgroup->nr--;\n-\t}\n-\n-\t/* Read from the parent directories and push them down. */\n-\tcurrent = stk ? stk->baselen : -1;\n-\twhile (current < baselen) {\n-\t\tstruct exclude_stack *stk = xcalloc(1, sizeof(*stk));\n-\t\tconst char *cp;\n-\n-\t\tif (current < 0) {\n-\t\t\tcp = base;\n-\t\t\tcurrent = 0;\n-\t\t}\n-\t\telse {\n-\t\t\tcp = strchr(base + current + 1, '/');\n-\t\t\tif (!cp)\n-\t\t\t\tdie(\"oops in prep_exclude\");\n-\t\t\tcp++;\n-\t\t}\n-\t\tstk->prev = dir->exclude_stack;\n-\t\tstk->baselen = cp - base;\n-\t\tmemcpy(dir->basebuf + current, base + current,\n-\t\t       stk->baselen - current);\n-\t\tstrcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);\n-\t\t/*\n-\t\t * dir->basebuf gets reused by the traversal, but we\n-\t\t * need fname to remain unchanged to ensure the src\n-\t\t * member of each struct exclude correctly\n-\t\t * back-references its source file.  Other invocations\n-\t\t * of add_exclude_list provide stable strings, so we\n-\t\t * strdup() and free() here in the caller.\n-\t\t */\n-\t\tel = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));\n-\t\tstk->exclude_ix = group->nr - 1;\n-\t\tadd_excludes_from_file_to_list(dir->basebuf,\n-\t\t\t\t\t       dir->basebuf, stk->baselen,\n-\t\t\t\t\t       el, 1);\n-\t\tdir->exclude_stack = stk;\n-\t\tcurrent = stk->baselen;\n-\t}\n-\tdir->basebuf[baselen] = '\\0';\n-}\n-\n int match_basename(const char *basename, int basenamelen,\n \t\t   const char *pattern, int prefix, int patternlen,\n \t\t   int flags)\n@@ -772,6 +700,78 @@ static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir,\n }\n \n /*\n+ * Loads the per-directory exclude list for the substring of base\n+ * which has a char length of baselen.\n+ */\n+static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n+{\n+\tstruct exclude_list_group *group;\n+\tstruct exclude_list *el;\n+\tstruct exclude_stack *stk = NULL;\n+\tint current;\n+\n+\tif ((!dir->exclude_per_dir) ||\n+\t    (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))\n+\t\treturn; /* too long a path -- ignore */\n+\n+\tgroup = &dir->exclude_list_group[EXC_DIRS];\n+\n+\t/* Pop the exclude lists from the EXCL_DIRS exclude_list_group\n+\t * which originate from directories not in the prefix of the\n+\t * path being checked. */\n+\twhile ((stk = dir->exclude_stack) != NULL) {\n+\t\tif (stk->baselen <= baselen &&\n+\t\t    !strncmp(dir->basebuf, base, stk->baselen))\n+\t\t\tbreak;\n+\t\tel = &group->el[dir->exclude_stack->exclude_ix];\n+\t\tdir->exclude_stack = stk->prev;\n+\t\tfree((char *)el->src); /* see strdup() below */\n+\t\tclear_exclude_list(el);\n+\t\tfree(stk);\n+\t\tgroup->nr--;\n+\t}\n+\n+\t/* Read from the parent directories and push them down. */\n+\tcurrent = stk ? stk->baselen : -1;\n+\twhile (current < baselen) {\n+\t\tstruct exclude_stack *stk = xcalloc(1, sizeof(*stk));\n+\t\tconst char *cp;\n+\n+\t\tif (current < 0) {\n+\t\t\tcp = base;\n+\t\t\tcurrent = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tcp = strchr(base + current + 1, '/');\n+\t\t\tif (!cp)\n+\t\t\t\tdie(\"oops in prep_exclude\");\n+\t\t\tcp++;\n+\t\t}\n+\t\tstk->prev = dir->exclude_stack;\n+\t\tstk->baselen = cp - base;\n+\t\tmemcpy(dir->basebuf + current, base + current,\n+\t\t       stk->baselen - current);\n+\t\tstrcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);\n+\t\t/*\n+\t\t * dir->basebuf gets reused by the traversal, but we\n+\t\t * need fname to remain unchanged to ensure the src\n+\t\t * member of each struct exclude correctly\n+\t\t * back-references its source file.  Other invocations\n+\t\t * of add_exclude_list provide stable strings, so we\n+\t\t * strdup() and free() here in the caller.\n+\t\t */\n+\t\tel = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));\n+\t\tstk->exclude_ix = group->nr - 1;\n+\t\tadd_excludes_from_file_to_list(dir->basebuf,\n+\t\t\t\t\t       dir->basebuf, stk->baselen,\n+\t\t\t\t\t       el, 1);\n+\t\tdir->exclude_stack = stk;\n+\t\tcurrent = stk->baselen;\n+\t}\n+\tdir->basebuf[baselen] = '\\0';\n+}\n+\n+/*\n  * Loads the exclude lists for the directory containing pathname, then\n  * scans all exclude lists to determine whether pathname is excluded.\n  * Returns the exclude_list element which matched, or NULL for\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214371","messageId":"516C510E.5000606@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 10/14] dir.c: unify is_excluded and is_path_excluded APIs","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:12:14Z","receivedAt":"2013-04-15T19:12:14Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"The is_excluded and is_path_excluded APIs are very similar, except for a\nfew noteworthy differences:\n\nis_excluded doesn't handle ignored directories, results for paths within\nignored directories are incorrect. This is probably based on the premise\nthat recursive directory scans should stop at ignored directories, which\nis no longer true (in certain cases, read_directory_recursive currently\ncalls is_excluded *and* is_path_excluded to get correct ignored state).\n\nis_excluded caches parsed .gitignore files of the last directory in struct\ndir_struct. If the directory changes, it finds a common parent directory\nand is very careful to drop only as much state as necessary. On the other\nhand, is_excluded will also read and parse .gitignore files in already\nignored directories, which are completely irrelevant.\n\nis_path_excluded correctly handles ignored directories by checking if any\ncomponent in the path is excluded. As it uses is_excluded internally, this\nunfortunately forces is_excluded to drop and re-read all .gitignore files,\nas there is no common parent directory for the root dir.\n\nis_path_excluded tracks state in a separate struct path_exclude_check,\nwhich is essentially a wrapper of dir_struct with two more fields. However,\nas is_path_excluded also modifies dir_struct, it is not possible to e.g.\nuse multiple path_exclude_check structures with the same dir_struct in\nparallel. The additional structure just unnecessarily complicates the API.\n\nTeach is_excluded / prep_exclude about ignored directories: whenever\nentering a new directory, first check if the entire directory is excluded.\nRemember the excluded state in dir_struct. Don't traverse into already\nignored directories (i.e. don't read irrelevant .gitignore files).\n\nDirectories could also be excluded by exclude patterns specified on the\ncommand line or .git/info/exclude, so we cannot simply skip prep_exclude\nentirely if there's no .gitignore file name (dir_struct.exclude_per_dir).\nMove this check to just before actually reading the file.\n\nis_path_excluded is now equivalent to is_excluded, so we can simply\nredirect to it (the public API is cleaned up in the next patch).\n\nThe performance impact of the additional ignored check per directory is\nhardly noticeable when reading directories recursively (e.g. 'git status').\nHowever, performance of git commands using the is_path_excluded API (e.g.\n'git ls-files --cached --ignored --exclude-standard') is greatly improved\nas this no longer re-reads .gitignore files on each call.\n\nHere's some performance data from the linux and WebKit repos (best of 10\nruns on a Debian Linux on SSD, core.preloadIndex=true):\n\n       | ls-files -ci   |    status      | status --ignored\n       | linux | WebKit | linux | WebKit | linux | WebKit\n-------+-------+--------+-------+--------+-------+---------\nbefore | 0.506 |  6.539 | 0.212 |  1.555 | 0.323 |  2.541\nafter  | 0.080 |  1.191 | 0.218 |  1.583 | 0.321 |  2.579\ngain   | 6.325 |  5.490 | 0.972 |  0.982 | 1.006 |  0.985\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c | 107 +++++++++++++++++++++++++++---------------------------------------\n dir.h |   6 ++--\n 2 files changed, 46 insertions(+), 67 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex fcb3def..33bd019 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -710,10 +710,6 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \tstruct exclude_stack *stk = NULL;\n \tint current;\n \n-\tif ((!dir->exclude_per_dir) ||\n-\t    (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))\n-\t\treturn; /* too long a path -- ignore */\n-\n \tgroup = &dir->exclude_list_group[EXC_DIRS];\n \n \t/* Pop the exclude lists from the EXCL_DIRS exclude_list_group\n@@ -725,12 +721,17 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tbreak;\n \t\tel = &group->el[dir->exclude_stack->exclude_ix];\n \t\tdir->exclude_stack = stk->prev;\n+\t\tdir->exclude = NULL;\n \t\tfree((char *)el->src); /* see strdup() below */\n \t\tclear_exclude_list(el);\n \t\tfree(stk);\n \t\tgroup->nr--;\n \t}\n \n+\t/* Skip traversing into sub directories if the parent is excluded */\n+\tif (dir->exclude)\n+\t\treturn;\n+\n \t/* Read from the parent directories and push them down. */\n \tcurrent = stk ? stk->baselen : -1;\n \twhile (current < baselen) {\n@@ -749,22 +750,43 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t}\n \t\tstk->prev = dir->exclude_stack;\n \t\tstk->baselen = cp - base;\n+\t\tstk->exclude_ix = group->nr;\n+\t\tel = add_exclude_list(dir, EXC_DIRS, NULL);\n \t\tmemcpy(dir->basebuf + current, base + current,\n \t\t       stk->baselen - current);\n-\t\tstrcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);\n-\t\t/*\n-\t\t * dir->basebuf gets reused by the traversal, but we\n-\t\t * need fname to remain unchanged to ensure the src\n-\t\t * member of each struct exclude correctly\n-\t\t * back-references its source file.  Other invocations\n-\t\t * of add_exclude_list provide stable strings, so we\n-\t\t * strdup() and free() here in the caller.\n-\t\t */\n-\t\tel = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));\n-\t\tstk->exclude_ix = group->nr - 1;\n-\t\tadd_excludes_from_file_to_list(dir->basebuf,\n-\t\t\t\t\t       dir->basebuf, stk->baselen,\n-\t\t\t\t\t       el, 1);\n+\n+\t\t/* Abort if the directory is excluded */\n+\t\tif (stk->baselen) {\n+\t\t\tint dt = DT_DIR;\n+\t\t\tdir->basebuf[stk->baselen - 1] = 0;\n+\t\t\tdir->exclude = last_exclude_matching_from_lists(dir,\n+\t\t\t\tdir->basebuf, stk->baselen - 1,\n+\t\t\t\tdir->basebuf + current, &dt);\n+\t\t\tdir->basebuf[stk->baselen - 1] = '/';\n+\t\t\tif (dir->exclude) {\n+\t\t\t\tdir->basebuf[stk->baselen] = 0;\n+\t\t\t\tdir->exclude_stack = stk;\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* Try to read per-directory file unless path is too long */\n+\t\tif (dir->exclude_per_dir &&\n+\t\t    stk->baselen + strlen(dir->exclude_per_dir) < PATH_MAX) {\n+\t\t\tstrcpy(dir->basebuf + stk->baselen,\n+\t\t\t\t\tdir->exclude_per_dir);\n+\t\t\t/*\n+\t\t\t * dir->basebuf gets reused by the traversal, but we\n+\t\t\t * need fname to remain unchanged to ensure the src\n+\t\t\t * member of each struct exclude correctly\n+\t\t\t * back-references its source file.  Other invocations\n+\t\t\t * of add_exclude_list provide stable strings, so we\n+\t\t\t * strdup() and free() here in the caller.\n+\t\t\t */\n+\t\t\tel->src = strdup(dir->basebuf);\n+\t\t\tadd_excludes_from_file_to_list(dir->basebuf,\n+\t\t\t\t\tdir->basebuf, stk->baselen, el, 1);\n+\t\t}\n \t\tdir->exclude_stack = stk;\n \t\tcurrent = stk->baselen;\n \t}\n@@ -787,6 +809,9 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,\n \n \tprep_exclude(dir, pathname, basename-pathname);\n \n+\tif (dir->exclude)\n+\t\treturn dir->exclude;\n+\n \treturn last_exclude_matching_from_lists(dir, pathname, pathlen,\n \t\t\tbasename, dtype_p);\n }\n@@ -809,13 +834,10 @@ void path_exclude_check_init(struct path_exclude_check *check,\n \t\t\t     struct dir_struct *dir)\n {\n \tcheck->dir = dir;\n-\tcheck->exclude = NULL;\n-\tstrbuf_init(&check->path, 256);\n }\n \n void path_exclude_check_clear(struct path_exclude_check *check)\n {\n-\tstrbuf_release(&check->path);\n }\n \n /*\n@@ -831,49 +853,6 @@ struct exclude *last_exclude_matching_path(struct path_exclude_check *check,\n \t\t\t\t\t   const char *name, int namelen,\n \t\t\t\t\t   int *dtype)\n {\n-\tint i;\n-\tstruct strbuf *path = &check->path;\n-\tstruct exclude *exclude;\n-\n-\t/*\n-\t * we allow the caller to pass namelen as an optimization; it\n-\t * must match the length of the name, as we eventually call\n-\t * is_excluded() on the whole name string.\n-\t */\n-\tif (namelen < 0)\n-\t\tnamelen = strlen(name);\n-\n-\t/*\n-\t * If path is non-empty, and name is equal to path or a\n-\t * subdirectory of path, name should be excluded, because\n-\t * it's inside a directory which is already known to be\n-\t * excluded and was previously left in check->path.\n-\t */\n-\tif (path->len &&\n-\t    path->len <= namelen &&\n-\t    !memcmp(name, path->buf, path->len) &&\n-\t    (!name[path->len] || name[path->len] == '/'))\n-\t\treturn check->exclude;\n-\n-\tstrbuf_setlen(path, 0);\n-\tfor (i = 0; name[i]; i++) {\n-\t\tint ch = name[i];\n-\n-\t\tif (ch == '/') {\n-\t\t\tint dt = DT_DIR;\n-\t\t\texclude = last_exclude_matching(check->dir,\n-\t\t\t\t\t\t\tpath->buf, &dt);\n-\t\t\tif (exclude) {\n-\t\t\t\tcheck->exclude = exclude;\n-\t\t\t\treturn exclude;\n-\t\t\t}\n-\t\t}\n-\t\tstrbuf_addch(path, ch);\n-\t}\n-\n-\t/* An entry in the index; cannot be a directory with subentries */\n-\tstrbuf_setlen(path, 0);\n-\n \treturn last_exclude_matching(check->dir, name, dtype);\n }\n \ndiff --git a/dir.h b/dir.h\nindex c3eb4b5..cd166d0 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -110,9 +110,11 @@ struct dir_struct {\n \t *\n \t * exclude_stack points to the top of the exclude_stack, and\n \t * basebuf contains the full path to the current\n-\t * (sub)directory in the traversal.\n+\t * (sub)directory in the traversal. Exclude points to the\n+\t * matching exclude struct if the directory is excluded.\n \t */\n \tstruct exclude_stack *exclude_stack;\n+\tstruct exclude *exclude;\n \tchar basebuf[PATH_MAX];\n };\n \n@@ -156,8 +158,6 @@ extern int match_pathname(const char *, int,\n  */\n struct path_exclude_check {\n \tstruct dir_struct *dir;\n-\tstruct exclude *exclude;\n-\tstruct strbuf path;\n };\n extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);\n extern void path_exclude_check_clear(struct path_exclude_check *);\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214372","messageId":"516C5139.40102@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 11/14] dir.c: replace is_path_excluded with now equivalent is_excluded API","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:12:57Z","receivedAt":"2013-04-15T19:12:57Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Signed-off-by: Karsten Blees <blees@dcon.de>\n---\n builtin/add.c          |  5 +---\n builtin/check-ignore.c |  6 +---\n builtin/ls-files.c     | 15 +++-------\n dir.c                  | 79 ++++----------------------------------------------\n dir.h                  | 16 ++--------\n unpack-trees.c         | 10 +------\n unpack-trees.h         |  1 -\n 7 files changed, 16 insertions(+), 116 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex ab1c9e8..06f365d 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -444,9 +444,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \n \tif (pathspec) {\n \t\tint i;\n-\t\tstruct path_exclude_check check;\n \n-\t\tpath_exclude_check_init(&check, &dir);\n \t\tif (!seen)\n \t\t\tseen = find_pathspecs_matching_against_index(pathspec);\n \t\tfor (i = 0; pathspec[i]; i++) {\n@@ -454,7 +452,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\t    && !file_exists(pathspec[i])) {\n \t\t\t\tif (ignore_missing) {\n \t\t\t\t\tint dtype = DT_UNKNOWN;\n-\t\t\t\t\tif (is_path_excluded(&check, pathspec[i], -1, &dtype))\n+\t\t\t\t\tif (is_excluded(&dir, pathspec[i], &dtype))\n \t\t\t\t\t\tdir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));\n \t\t\t\t} else\n \t\t\t\t\tdie(_(\"pathspec '%s' did not match any files\"),\n@@ -462,7 +460,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t}\n \t\tfree(seen);\n-\t\tpath_exclude_check_clear(&check);\n \t}\n \n \tplug_bulk_checkin();\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 0240f99..7388346 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -59,7 +59,6 @@ static int check_ignore(const char *prefix, const char **pathspec)\n \tconst char *path, *full_path;\n \tchar *seen;\n \tint num_ignored = 0, dtype = DT_UNKNOWN, i;\n-\tstruct path_exclude_check check;\n \tstruct exclude *exclude;\n \n \t/* read_cache() is only necessary so we can watch out for submodules. */\n@@ -76,7 +75,6 @@ static int check_ignore(const char *prefix, const char **pathspec)\n \t\treturn 0;\n \t}\n \n-\tpath_exclude_check_init(&check, &dir);\n \t/*\n \t * look for pathspecs matching entries in the index, since these\n \t * should not be ignored, in order to be consistent with\n@@ -90,8 +88,7 @@ static int check_ignore(const char *prefix, const char **pathspec)\n \t\tfull_path = check_path_for_gitlink(full_path);\n \t\tdie_if_path_beyond_symlink(full_path, prefix);\n \t\tif (!seen[i]) {\n-\t\t\texclude = last_exclude_matching_path(&check, full_path,\n-\t\t\t\t\t\t\t     -1, &dtype);\n+\t\t\texclude = last_exclude_matching(&dir, full_path, &dtype);\n \t\t\tif (exclude) {\n \t\t\t\tif (!quiet)\n \t\t\t\t\toutput_exclude(path, exclude);\n@@ -101,7 +98,6 @@ static int check_ignore(const char *prefix, const char **pathspec)\n \t}\n \tfree(seen);\n \tclear_directory(&dir);\n-\tpath_exclude_check_clear(&check);\n \n \treturn num_ignored;\n }\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 175e6e3..2202072 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -201,19 +201,15 @@ static void show_ru_info(void)\n \t}\n }\n \n-static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce)\n+static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)\n {\n \tint dtype = ce_to_dtype(ce);\n-\treturn is_path_excluded(check, ce->name, ce_namelen(ce), &dtype);\n+\treturn is_excluded(dir, ce->name, &dtype);\n }\n \n static void show_files(struct dir_struct *dir)\n {\n \tint i;\n-\tstruct path_exclude_check check;\n-\n-\tif ((dir->flags & DIR_SHOW_IGNORED))\n-\t\tpath_exclude_check_init(&check, dir);\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -227,7 +223,7 @@ static void show_files(struct dir_struct *dir)\n \t\tfor (i = 0; i < active_nr; i++) {\n \t\t\tstruct cache_entry *ce = active_cache[i];\n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(&check, ce))\n+\t\t\t    !ce_excluded(dir, ce))\n \t\t\t\tcontinue;\n \t\t\tif (show_unmerged && !ce_stage(ce))\n \t\t\t\tcontinue;\n@@ -243,7 +239,7 @@ static void show_files(struct dir_struct *dir)\n \t\t\tstruct stat st;\n \t\t\tint err;\n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(&check, ce))\n+\t\t\t    !ce_excluded(dir, ce))\n \t\t\t\tcontinue;\n \t\t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\t\tcontinue;\n@@ -256,9 +252,6 @@ static void show_files(struct dir_struct *dir)\n \t\t\t\tshow_ce_entry(tag_modified, ce);\n \t\t}\n \t}\n-\n-\tif ((dir->flags & DIR_SHOW_IGNORED))\n-\t\tpath_exclude_check_clear(&check);\n }\n \n /*\ndiff --git a/dir.c b/dir.c\nindex 33bd019..67313bd 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -799,7 +799,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n  * Returns the exclude_list element which matched, or NULL for\n  * undecided.\n  */\n-static struct exclude *last_exclude_matching(struct dir_struct *dir,\n+struct exclude *last_exclude_matching(struct dir_struct *dir,\n \t\t\t\t\t     const char *pathname,\n \t\t\t\t\t     int *dtype_p)\n {\n@@ -821,7 +821,7 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,\n  * scans all exclude lists to determine whether pathname is excluded.\n  * Returns 1 if true, otherwise 0.\n  */\n-static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)\n+int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)\n {\n \tstruct exclude *exclude =\n \t\tlast_exclude_matching(dir, pathname, dtype_p);\n@@ -830,47 +830,6 @@ static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_\n \treturn 0;\n }\n \n-void path_exclude_check_init(struct path_exclude_check *check,\n-\t\t\t     struct dir_struct *dir)\n-{\n-\tcheck->dir = dir;\n-}\n-\n-void path_exclude_check_clear(struct path_exclude_check *check)\n-{\n-}\n-\n-/*\n- * For each subdirectory in name, starting with the top-most, checks\n- * to see if that subdirectory is excluded, and if so, returns the\n- * corresponding exclude structure.  Otherwise, checks whether name\n- * itself (which is presumably a file) is excluded.\n- *\n- * A path to a directory known to be excluded is left in check->path to\n- * optimize for repeated checks for files in the same excluded directory.\n- */\n-struct exclude *last_exclude_matching_path(struct path_exclude_check *check,\n-\t\t\t\t\t   const char *name, int namelen,\n-\t\t\t\t\t   int *dtype)\n-{\n-\treturn last_exclude_matching(check->dir, name, dtype);\n-}\n-\n-/*\n- * Is this name excluded?  This is for a caller like show_files() that\n- * do not honor directory hierarchy and iterate through paths that are\n- * possibly in an ignored directory.\n- */\n-int is_path_excluded(struct path_exclude_check *check,\n-\t\t  const char *name, int namelen, int *dtype)\n-{\n-\tstruct exclude *exclude =\n-\t\tlast_exclude_matching_path(check, name, namelen, dtype);\n-\tif (exclude)\n-\t\treturn exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;\n-\treturn 0;\n-}\n-\n static struct dir_entry *dir_entry_new(const char *pathname, int len)\n {\n \tstruct dir_entry *ent;\n@@ -1042,15 +1001,6 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \n \t/* This is the \"show_other_directories\" case */\n \n-\t/* might be a sub directory in an excluded directory */\n-\tif (!exclude) {\n-\t\tstruct path_exclude_check check;\n-\t\tint dt = DT_DIR;\n-\t\tpath_exclude_check_init(&check, dir);\n-\t\texclude = is_path_excluded(&check, dirname, len, &dt);\n-\t\tpath_exclude_check_clear(&check);\n-\t}\n-\n \t/*\n \t * We are looking for ignored files and our directory is not ignored,\n \t * check if it contains untracked files (i.e. is listed as untracked)\n@@ -1085,27 +1035,13 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n  *\n  * Return 1 for exclude, 0 for include.\n  */\n-static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype)\n+static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude)\n {\n-\tstruct path_exclude_check check;\n-\tint exclude_file = 0;\n-\n \t/* Always exclude indexed files */\n \tif (index_name_exists(&the_index, path->buf, path->len, ignore_case))\n \t\treturn 1;\n \n-\tif (exclude)\n-\t\texclude_file = !(dir->flags & DIR_SHOW_IGNORED);\n-\telse if (dir->flags & DIR_SHOW_IGNORED) {\n-\t\tpath_exclude_check_init(&check, dir);\n-\n-\t\tif (!is_path_excluded(&check, path->buf, path->len, dtype))\n-\t\t\texclude_file = 1;\n-\n-\t\tpath_exclude_check_clear(&check);\n-\t}\n-\n-\treturn exclude_file;\n+\treturn exclude == !(dir->flags & DIR_SHOW_IGNORED);\n }\n \n /*\n@@ -1262,12 +1198,9 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\tbreak;\n \tcase DT_REG:\n \tcase DT_LNK:\n-\t\tswitch (treat_file(dir, path, exclude, &dtype)) {\n-\t\tcase 1:\n+\t\tif (treat_file(dir, path, exclude))\n \t\t\treturn path_ignored;\n-\t\tdefault:\n-\t\t\tbreak;\n-\t\t}\n+\t\tbreak;\n \t}\n \treturn path_handled;\n }\ndiff --git a/dir.h b/dir.h\nindex cd166d0..bfe726e 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -151,20 +151,10 @@ extern int match_pathname(const char *, int,\n \t\t\t  const char *, int,\n \t\t\t  const char *, int, int, int);\n \n-/*\n- * The is_excluded() API is meant for callers that check each level of leading\n- * directory hierarchies with is_excluded() to avoid recursing into excluded\n- * directories.  Callers that do not do so should use this API instead.\n- */\n-struct path_exclude_check {\n-\tstruct dir_struct *dir;\n-};\n-extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);\n-extern void path_exclude_check_clear(struct path_exclude_check *);\n-extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *,\n-\t\t\t\t\t\t  int namelen, int *dtype);\n-extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);\n+extern struct exclude *last_exclude_matching(struct dir_struct *dir,\n+\t\t\t\t\t     const char *name, int *dtype);\n \n+extern int is_excluded(struct dir_struct *dir, const char *name, int *dtype);\n \n extern struct exclude_list *add_exclude_list(struct dir_struct *dir,\n \t\t\t\t\t     int group_type, const char *src);\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 09e53df..ede4299 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1026,10 +1026,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\t\to->el = &el;\n \t}\n \n-\tif (o->dir) {\n-\t\to->path_exclude_check = xmalloc(sizeof(struct path_exclude_check));\n-\t\tpath_exclude_check_init(o->path_exclude_check, o->dir);\n-\t}\n \tmemset(&o->result, 0, sizeof(o->result));\n \to->result.initialized = 1;\n \to->result.timestamp.sec = o->src_index->timestamp.sec;\n@@ -1155,10 +1151,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \n done:\n \tclear_exclude_list(&el);\n-\tif (o->path_exclude_check) {\n-\t\tpath_exclude_check_clear(o->path_exclude_check);\n-\t\tfree(o->path_exclude_check);\n-\t}\n \treturn ret;\n \n return_failed:\n@@ -1375,7 +1367,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,\n \t\treturn 0;\n \n \tif (o->dir &&\n-\t    is_path_excluded(o->path_exclude_check, name, -1, &dtype))\n+\t    is_excluded(o->dir, name, &dtype))\n \t\t/*\n \t\t * ce->name is explicitly excluded, so it is Ok to\n \t\t * overwrite it.\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex ec74a9f..5e432f5 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -52,7 +52,6 @@ struct unpack_trees_options {\n \tconst char *prefix;\n \tint cache_bottom;\n \tstruct dir_struct *dir;\n-\tstruct path_exclude_check *path_exclude_check;\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214374","messageId":"516C515F.1070206@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 12/14] dir.c: git-status: avoid is_excluded checks for tracked files","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:13:35Z","receivedAt":"2013-04-15T19:13:35Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Checking if a file is in the index is much faster (hashtable lookup) than\nchecking if the file is excluded (linear search over exclude patterns).\n\nSkip is_excluded checks for files: move the cache_name_exists check from\ntreat_file to treat_one_path and return early if the file is tracked.\n\nThis can safely be done as all other code paths also return path_ignored\nfor tracked files, and dir_add_ignored skips tracked files as well.\n\nThere's just one line left in treat_file, so move this to treat_one_path\nas well.\n\nHere's some performance data for git-status from the linux and WebKit\nrepos (best of 10 runs on a Debian Linux on SSD, core.preloadIndex=true):\n\n       |    status      | status --ignored\n       | linux | WebKit | linux | WebKit\n-------+-------+--------+-------+---------\nbefore | 0.218 |  1.583 | 0.321 |  2.579\nafter  | 0.156 |  0.988 | 0.202 |  1.279\ngain   | 1.397 |  1.602 | 1.589 |  2.016\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c | 38 +++++++++++---------------------------\n 1 file changed, 11 insertions(+), 27 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 67313bd..5ae5722 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1023,28 +1023,6 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n }\n \n /*\n- * Decide what to do when we find a file while traversing the\n- * filesystem. Mostly two cases:\n- *\n- *  1. We are looking for ignored files\n- *   (a) File is ignored, include it\n- *   (b) File is in ignored path, include it\n- *   (c) File is not ignored, exclude it\n- *\n- *  2. Other scenarios, include the file if not excluded\n- *\n- * Return 1 for exclude, 0 for include.\n- */\n-static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude)\n-{\n-\t/* Always exclude indexed files */\n-\tif (index_name_exists(&the_index, path->buf, path->len, ignore_case))\n-\t\treturn 1;\n-\n-\treturn exclude == !(dir->flags & DIR_SHOW_IGNORED);\n-}\n-\n-/*\n  * This is an inexact early pruning of any recursive directory\n  * reading - if the path cannot possibly be in the pathspec,\n  * return true, and we'll skip it early.\n@@ -1167,7 +1145,16 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\t\t\t\t  const struct path_simplify *simplify,\n \t\t\t\t\t  int dtype, struct dirent *de)\n {\n-\tint exclude = is_excluded(dir, path->buf, &dtype);\n+\tint exclude;\n+\tif (dtype == DT_UNKNOWN)\n+\t\tdtype = get_dtype(de, path->buf, path->len);\n+\n+\t/* Always exclude indexed files */\n+\tif (dtype != DT_DIR &&\n+\t    cache_name_exists(path->buf, path->len, ignore_case))\n+\t\treturn path_ignored;\n+\n+\texclude = is_excluded(dir, path->buf, &dtype);\n \tif (exclude && (dir->flags & DIR_COLLECT_IGNORED)\n \t    && exclude_matches_pathspec(path->buf, path->len, simplify))\n \t\tdir_add_ignored(dir, path->buf, path->len);\n@@ -1179,9 +1166,6 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \tif (exclude && !(dir->flags & DIR_SHOW_IGNORED))\n \t\treturn path_ignored;\n \n-\tif (dtype == DT_UNKNOWN)\n-\t\tdtype = get_dtype(de, path->buf, path->len);\n-\n \tswitch (dtype) {\n \tdefault:\n \t\treturn path_ignored;\n@@ -1198,7 +1182,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\tbreak;\n \tcase DT_REG:\n \tcase DT_LNK:\n-\t\tif (treat_file(dir, path, exclude))\n+\t\tif (exclude == !(dir->flags & DIR_SHOW_IGNORED))\n \t\t\treturn path_ignored;\n \t\tbreak;\n \t}\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214375","messageId":"516C518E.1000405@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 13/14] dir.c: git-status --ignored: don't scan the work tree three times","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:14:22Z","receivedAt":"2013-04-15T19:14:22Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored' recursively scans directories up to three times:\n\n 1. To collect untracked files.\n\n 2. To collect ignored files.\n\n 3. When collecting ignored files, to check that an untracked directory\n    that potentially contains ignored files doesn't also contain untracked\n    files (i.e. isn't already listed as untracked).\n\nLet's get rid of case 3 first.\n\nCurrently, read_directory_recursive returns a boolean whether a directory\ncontains the requested files or not (actually, it returns the number of\nfiles, but no caller actually needs that), and DIR_SHOW_IGNORED specifies\nwhat we're looking for.\n\nTo be able to test for both untracked and ignored files in a single scan,\nwe need to return a bit more info, and the result must be independent of\nthe DIR_SHOW_IGNORED flag.\n\nReuse the path_treatment enum as return value of read_directory_recursive.\nSplit path_handled in two separate values path_excluded and path_untracked\nthat don't change their meaning with the DIR_SHOW_IGNORED flag. We don't\nneed an extra value path_untracked_and_excluded, as directories with both\nuntracked and ignored files should be listed as untracked.\n\nRename path_ignored to path_none for clarity (i.e. \"don't treat that path\"\nin contrast to \"the path is ignored and should be treated according to\nDIR_SHOW_IGNORED\").\n\nReplace enum directory_treatment with path_treatment. That's just another\nenum with the same meaning, no need to translate back and forth.\n\nIn treat_directory, get rid of the extra read_directory_recursive call and\nall the DIR_SHOW_IGNORED-specific code.\n\nIn read_directory_recursive, decide whether to dir_add_name path_excluded\nor path_untracked paths based on the DIR_SHOW_IGNORED flag.\n\nThe return value of read_directory_recursive is the maximum path_treatment\nof all files and sub-directories. In the check_only case, abort when we've\nreached the most significant value (path_untracked).\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c | 146 +++++++++++++++++++++++++++++++++---------------------------------\n 1 file changed, 72 insertions(+), 74 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 5ae5722..5770ed4 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -17,7 +17,21 @@ struct path_simplify {\n \tconst char *path;\n };\n \n-static int read_directory_recursive(struct dir_struct *dir, const char *path, int len,\n+/*\n+ * Tells read_directory_recursive how a file or directory should be treated.\n+ * Values are ordered by significance, e.g. if a directory contains both\n+ * excluded and untracked files, it is listed as untracked because\n+ * path_untracked > path_excluded.\n+ */\n+enum path_treatment {\n+\tpath_none = 0,\n+\tpath_recurse,\n+\tpath_excluded,\n+\tpath_untracked\n+};\n+\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tconst char *path, int len,\n \tint check_only, const struct path_simplify *simplify);\n static int get_dtype(struct dirent *de, const char *path, int len);\n \n@@ -958,35 +972,26 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)\n  *\n  *  (a) if \"show_other_directories\" is true, we show it as\n  *      just a directory, unless \"hide_empty_directories\" is\n- *      also true and the directory is empty, in which case\n- *      we just ignore it entirely.\n- *      if we are looking for ignored directories, look if it\n- *      contains only ignored files to decide if it must be shown as\n- *      ignored or not.\n+ *      also true, in which case we need to check if it contains any\n+ *      untracked and / or ignored files.\n  *  (b) if it looks like a git directory, and we don't have\n  *      'no_gitlinks' set we treat it as a gitlink, and show it\n  *      as a directory.\n  *  (c) otherwise, we recurse into it.\n  */\n-enum directory_treatment {\n-\tshow_directory,\n-\tignore_directory,\n-\trecurse_into_directory\n-};\n-\n-static enum directory_treatment treat_directory(struct dir_struct *dir,\n+static enum path_treatment treat_directory(struct dir_struct *dir,\n \tconst char *dirname, int len, int exclude,\n \tconst struct path_simplify *simplify)\n {\n \t/* The \"len-1\" is to strip the final '/' */\n \tswitch (directory_exists_in_index(dirname, len-1)) {\n \tcase index_directory:\n-\t\treturn recurse_into_directory;\n+\t\treturn path_recurse;\n \n \tcase index_gitdir:\n \t\tif (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)\n-\t\t\treturn ignore_directory;\n-\t\treturn show_directory;\n+\t\t\treturn path_none;\n+\t\treturn path_untracked;\n \n \tcase index_nonexistent:\n \t\tif (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)\n@@ -994,32 +999,17 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \t\tif (!(dir->flags & DIR_NO_GITLINKS)) {\n \t\t\tunsigned char sha1[20];\n \t\t\tif (resolve_gitlink_ref(dirname, \"HEAD\", sha1) == 0)\n-\t\t\t\treturn show_directory;\n+\t\t\t\treturn path_untracked;\n \t\t}\n-\t\treturn recurse_into_directory;\n+\t\treturn path_recurse;\n \t}\n \n \t/* This is the \"show_other_directories\" case */\n \n-\t/*\n-\t * We are looking for ignored files and our directory is not ignored,\n-\t * check if it contains untracked files (i.e. is listed as untracked)\n-\t */\n-\tif ((dir->flags & DIR_SHOW_IGNORED) && !exclude) {\n-\t\tint ignored;\n-\t\tdir->flags &= ~DIR_SHOW_IGNORED;\n-\t\tignored = read_directory_recursive(dir, dirname, len, 1, simplify);\n-\t\tdir->flags |= DIR_SHOW_IGNORED;\n-\n-\t\tif (ignored)\n-\t\t\treturn ignore_directory;\n-\t}\n-\n \tif (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))\n-\t\treturn show_directory;\n-\tif (!read_directory_recursive(dir, dirname, len, 1, simplify))\n-\t\treturn ignore_directory;\n-\treturn show_directory;\n+\t\treturn exclude ? path_excluded : path_untracked;\n+\n+\treturn read_directory_recursive(dir, dirname, len, 1, simplify);\n }\n \n /*\n@@ -1134,12 +1124,6 @@ static int get_dtype(struct dirent *de, const char *path, int len)\n \treturn dtype;\n }\n \n-enum path_treatment {\n-\tpath_ignored,\n-\tpath_handled,\n-\tpath_recurse\n-};\n-\n static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\t\t\t\t  struct strbuf *path,\n \t\t\t\t\t  const struct path_simplify *simplify,\n@@ -1152,7 +1136,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t/* Always exclude indexed files */\n \tif (dtype != DT_DIR &&\n \t    cache_name_exists(path->buf, path->len, ignore_case))\n-\t\treturn path_ignored;\n+\t\treturn path_none;\n \n \texclude = is_excluded(dir, path->buf, &dtype);\n \tif (exclude && (dir->flags & DIR_COLLECT_IGNORED)\n@@ -1164,29 +1148,19 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t * ignored files, ignore it\n \t */\n \tif (exclude && !(dir->flags & DIR_SHOW_IGNORED))\n-\t\treturn path_ignored;\n+\t\treturn path_excluded;\n \n \tswitch (dtype) {\n \tdefault:\n-\t\treturn path_ignored;\n+\t\treturn path_none;\n \tcase DT_DIR:\n \t\tstrbuf_addch(path, '/');\n-\t\tswitch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {\n-\t\tcase show_directory:\n-\t\t\tbreak;\n-\t\tcase recurse_into_directory:\n-\t\t\treturn path_recurse;\n-\t\tcase ignore_directory:\n-\t\t\treturn path_ignored;\n-\t\t}\n-\t\tbreak;\n+\t\treturn treat_directory(dir, path->buf, path->len, exclude,\n+\t\t\tsimplify);\n \tcase DT_REG:\n \tcase DT_LNK:\n-\t\tif (exclude == !(dir->flags & DIR_SHOW_IGNORED))\n-\t\t\treturn path_ignored;\n-\t\tbreak;\n+\t\treturn exclude ? path_excluded : path_untracked;\n \t}\n-\treturn path_handled;\n }\n \n static enum path_treatment treat_path(struct dir_struct *dir,\n@@ -1198,11 +1172,11 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n \tint dtype;\n \n \tif (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, \".git\"))\n-\t\treturn path_ignored;\n+\t\treturn path_none;\n \tstrbuf_setlen(path, baselen);\n \tstrbuf_addstr(path, de->d_name);\n \tif (simplify_away(path->buf, path->len, simplify))\n-\t\treturn path_ignored;\n+\t\treturn path_none;\n \n \tdtype = DTYPE(de);\n \treturn treat_one_path(dir, path, simplify, dtype, de);\n@@ -1216,14 +1190,16 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n  *\n  * Also, we ignore the name \".git\" (even if it is not a directory).\n  * That likely will not change.\n+ *\n+ * Returns the most significant path_treatment value encountered in the scan.\n  */\n-static int read_directory_recursive(struct dir_struct *dir,\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\t\t\t    const char *base, int baselen,\n \t\t\t\t    int check_only,\n \t\t\t\t    const struct path_simplify *simplify)\n {\n \tDIR *fdir;\n-\tint contents = 0;\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n \tstruct dirent *de;\n \tstruct strbuf path = STRBUF_INIT;\n \n@@ -1234,26 +1210,48 @@ static int read_directory_recursive(struct dir_struct *dir,\n \t\tgoto out;\n \n \twhile ((de = readdir(fdir)) != NULL) {\n-\t\tswitch (treat_path(dir, de, &path, baselen, simplify)) {\n-\t\tcase path_recurse:\n-\t\t\tcontents += read_directory_recursive(dir, path.buf,\n+\t\t/* check how the file or directory should be treated */\n+\t\tstate = treat_path(dir, de, &path, baselen, simplify);\n+\t\tif (state > dir_state)\n+\t\t\tdir_state = state;\n+\n+\t\t/* recurse into subdir if instructed by treat_path */\n+\t\tif (state == path_recurse) {\n+\t\t\tsubdir_state = read_directory_recursive(dir, path.buf,\n \t\t\t\tpath.len, check_only, simplify);\n+\t\t\tif (subdir_state > dir_state)\n+\t\t\t\tdir_state = subdir_state;\n+\t\t}\n+\n+\t\tif (check_only) {\n+\t\t\t/* abort early if maximum state has been reached */\n+\t\t\tif (dir_state == path_untracked)\n+\t\t\t\tbreak;\n+\t\t\t/* skip the dir_add_* part */\n \t\t\tcontinue;\n-\t\tcase path_ignored:\n-\t\t\tcontinue;\n-\t\tcase path_handled:\n-\t\t\tbreak;\n \t\t}\n-\t\tcontents++;\n-\t\tif (check_only)\n+\n+\t\t/* add the path to the appropriate result list */\n+\t\tswitch (state) {\n+\t\tcase path_excluded:\n+\t\t\tif (dir->flags & DIR_SHOW_IGNORED)\n+\t\t\t\tdir_add_name(dir, path.buf, path.len);\n+\t\t\tbreak;\n+\n+\t\tcase path_untracked:\n+\t\t\tif (!(dir->flags & DIR_SHOW_IGNORED))\n+\t\t\t\tdir_add_name(dir, path.buf, path.len);\n \t\t\tbreak;\n-\t\tdir_add_name(dir, path.buf, path.len);\n+\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n \t}\n \tclosedir(fdir);\n  out:\n \tstrbuf_release(&path);\n \n-\treturn contents;\n+\treturn dir_state;\n }\n \n static int cmp_name(const void *p1, const void *p2)\n@@ -1324,7 +1322,7 @@ static int treat_leading_path(struct dir_struct *dir,\n \t\tif (simplify_away(sb.buf, sb.len, simplify))\n \t\t\tbreak;\n \t\tif (treat_one_path(dir, &sb, simplify,\n-\t\t\t\t   DT_DIR, NULL) == path_ignored)\n+\t\t\t\t   DT_DIR, NULL) == path_none)\n \t\t\tbreak; /* do not recurse into it */\n \t\tif (len <= baselen) {\n \t\t\trc = 1;\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214376","messageId":"516C51B7.7080305@gmail.com","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"[PATCH v2 14/14] dir.c: git-status --ignored: don't scan the work tree twice","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T19:15:03Z","receivedAt":"2013-04-15T19:15:03Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored' still scans the work tree twice to collect\nuntracked and ignored files, respectively.\n\nfill_directory / read_directory already supports collecting untracked and\nignored files in a single directory scan. However, the DIR_COLLECT_IGNORED\nflag to enable this has some git-add specific side-effects (e.g. it\ndoesn't recurse into ignored directories, so listing ignored files with\n--untracked=all doesn't work).\n\nThe DIR_SHOW_IGNORED flag doesn't list untracked files and returns ignored\nfiles in dir_struct.entries[] (instead of dir_struct.ignored[] as\nDIR_COLLECT_IGNORED). DIR_SHOW_IGNORED is used all throughout git.\n\nWe don't want to break the existing API, so lets introduce a new flag\nDIR_SHOW_IGNORED_TOO that lists untracked as well as ignored files similar\nto DIR_COLLECT_FILES, but will recurse into sub-directories based on the\nother flags as DIR_SHOW_IGNORED does.\n\nIn dir.c::read_directory_recursive, add ignored files to either\ndir_struct.entries[] or dir_struct.ignored[] based on the flags. Also move\nthe DIR_COLLECT_IGNORED case here so that filling result lists is in a\ncommon place.\n\nIn wt-status.c::wt_status_collect_untracked, use the new flag and read\nresults from dir_struct.ignored[]. Remove the extra fill_directory call.\n\nbuiltin/check-ignore.c doesn't call fill_directory, setting the git-add\nspecific DIR_COLLECT_IGNORED flag has no effect here. Remove for clarity.\n\nUpdate API documentation to reflect the changes.\n\nPerformance: with this patch, 'git-status --ignored' is typically as fast\nas 'git-status'.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n Documentation/technical/api-directory-listing.txt | 25 ++++++++++++++++++++---\n builtin/check-ignore.c                            |  1 -\n dir.c                                             | 10 +++++----\n dir.h                                             |  3 ++-\n wt-status.c                                       | 24 ++++++++++------------\n 5 files changed, 41 insertions(+), 22 deletions(-)\n\ndiff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt\nindex 1f349b2..7f8e78d 100644\n--- a/Documentation/technical/api-directory-listing.txt\n+++ b/Documentation/technical/api-directory-listing.txt\n@@ -22,12 +22,23 @@ The notable options are:\n \n `flags`::\n \n-\tA bit-field of options:\n+\tA bit-field of options (the `*IGNORED*` flags are mutually exclusive):\n \n `DIR_SHOW_IGNORED`:::\n \n-\tThe traversal is for finding just ignored files, not unignored\n-\tfiles.\n+\tReturn just ignored files in `entries[]`, not untracked files.\n+\n+`DIR_SHOW_IGNORED_TOO`:::\n+\n+\tSimilar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`\n+\tin addition to untracked files in `entries[]`.\n+\n+`DIR_COLLECT_IGNORED`:::\n+\n+\tSpecial mode for git-add. Return ignored files in `ignored[]` and\n+\tuntracked files in `entries[]`. Only returns ignored files that match\n+\tpathspec exactly (no wildcards). Does not recurse into ignored\n+\tdirectories.\n \n `DIR_SHOW_OTHER_DIRECTORIES`:::\n \n@@ -57,6 +68,14 @@ The result of the enumeration is left in these fields:\n \n \tInternal use; keeps track of allocation of `entries[]` array.\n \n+`ignored[]`::\n+\n+\tAn array of `struct dir_entry`, used for ignored paths with the\n+\t`DIR_SHOW_IGNORED_TOO` and `DIR_COLLECT_IGNORED` flags.\n+\n+`ignored_nr`::\n+\n+\tThe number of members in `ignored[]` array.\n \n Calling sequence\n ----------------\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 7388346..854a88a 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -66,7 +66,6 @@ static int check_ignore(const char *prefix, const char **pathspec)\n \t\tdie(_(\"index file corrupt\"));\n \n \tmemset(&dir, 0, sizeof(dir));\n-\tdir.flags |= DIR_COLLECT_IGNORED;\n \tsetup_standard_excludes(&dir);\n \n \tif (!pathspec || !*pathspec) {\ndiff --git a/dir.c b/dir.c\nindex 5770ed4..847e2f2 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1139,15 +1139,12 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\treturn path_none;\n \n \texclude = is_excluded(dir, path->buf, &dtype);\n-\tif (exclude && (dir->flags & DIR_COLLECT_IGNORED)\n-\t    && exclude_matches_pathspec(path->buf, path->len, simplify))\n-\t\tdir_add_ignored(dir, path->buf, path->len);\n \n \t/*\n \t * Excluded? If we don't explicitly want to show\n \t * ignored files, ignore it\n \t */\n-\tif (exclude && !(dir->flags & DIR_SHOW_IGNORED))\n+\tif (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))\n \t\treturn path_excluded;\n \n \tswitch (dtype) {\n@@ -1236,6 +1233,11 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\tcase path_excluded:\n \t\t\tif (dir->flags & DIR_SHOW_IGNORED)\n \t\t\t\tdir_add_name(dir, path.buf, path.len);\n+\t\t\telse if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||\n+\t\t\t\t((dir->flags & DIR_COLLECT_IGNORED) &&\n+\t\t\t\texclude_matches_pathspec(path.buf, path.len,\n+\t\t\t\t\tsimplify)))\n+\t\t\t\tdir_add_ignored(dir, path.buf, path.len);\n \t\t\tbreak;\n \n \t\tcase path_untracked:\ndiff --git a/dir.h b/dir.h\nindex bfe726e..3d6b80c 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -79,7 +79,8 @@ struct dir_struct {\n \t\tDIR_SHOW_OTHER_DIRECTORIES = 1<<1,\n \t\tDIR_HIDE_EMPTY_DIRECTORIES = 1<<2,\n \t\tDIR_NO_GITLINKS = 1<<3,\n-\t\tDIR_COLLECT_IGNORED = 1<<4\n+\t\tDIR_COLLECT_IGNORED = 1<<4,\n+\t\tDIR_SHOW_IGNORED_TOO = 1<<5\n \t} flags;\n \tstruct dir_entry **entries;\n \tstruct dir_entry **ignored;\ndiff --git a/wt-status.c b/wt-status.c\nindex 79eb124..7b3cc8e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -503,9 +503,12 @@ static void wt_status_collect_untracked(struct wt_status *s)\n \tif (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)\n \t\tdir.flags |=\n \t\t\tDIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n+\tif (s->show_ignored_files)\n+\t\tdir.flags |= DIR_SHOW_IGNORED_TOO;\n \tsetup_standard_excludes(&dir);\n \n \tfill_directory(&dir, s->pathspec);\n+\n \tfor (i = 0; i < dir.nr; i++) {\n \t\tstruct dir_entry *ent = dir.entries[i];\n \t\tif (cache_name_is_other(ent->name, ent->len) &&\n@@ -514,22 +517,17 @@ static void wt_status_collect_untracked(struct wt_status *s)\n \t\tfree(ent);\n \t}\n \n-\tif (s->show_ignored_files) {\n-\t\tdir.nr = 0;\n-\t\tdir.flags = DIR_SHOW_IGNORED;\n-\t\tif (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)\n-\t\t\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n-\t\tfill_directory(&dir, s->pathspec);\n-\t\tfor (i = 0; i < dir.nr; i++) {\n-\t\t\tstruct dir_entry *ent = dir.entries[i];\n-\t\t\tif (cache_name_is_other(ent->name, ent->len) &&\n-\t\t\t    match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))\n-\t\t\t\tstring_list_insert(&s->ignored, ent->name);\n-\t\t\tfree(ent);\n-\t\t}\n+\tfor (i = 0; i < dir.ignored_nr; i++) {\n+\t\tstruct dir_entry *ent = dir.ignored[i];\n+\t\tif (cache_name_is_other(ent->name, ent->len) &&\n+\t\t    match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))\n+\t\t\tstring_list_insert(&s->ignored, ent->name);\n+\t\tfree(ent);\n \t}\n \n \tfree(dir.entries);\n+\tfree(dir.ignored);\n+\tclear_directory(&dir);\n }\n \n void wt_status_collect(struct wt_status *s)\n-- \n1.8.1.2.8026.g2b66448.dirty\n"},{"id":"214377","messageId":"7v1uabsin7.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"516C4F27.30203@gmail.com","subject":"Re: [PATCH v2 00/14] Improve git-status --ignored","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-15T19:23:08Z","receivedAt":"2013-04-15T19:23:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karsten Blees <karsten.blees@gmail.com> writes:\n\n> This patch series addresses several bugs and performance issues in\n> .gitignore processing.\n\nA 8-patch series ending at 5d765dc7888b (dir.c: git-status: avoid\nis_excluded checks for tracked files, 2013-03-18) has been cooking\nin 'next'; in general we won't revert and requeue a new round for a\ntopic that has already merged to 'next'.\n\nIs this \"v2\" an update for that topic?\n"},{"id":"214378","messageId":"7vwqs3r3m4.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"7v1uabsin7.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2 00/14] Improve git-status --ignored","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-15T19:33:07Z","receivedAt":"2013-04-15T19:33:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karsten Blees <karsten.blees@gmail.com> writes:\n>\n>> This patch series addresses several bugs and performance issues in\n>> .gitignore processing.\n>\n> A 8-patch series ending at 5d765dc7888b (dir.c: git-status: avoid\n> is_excluded checks for tracked files, 2013-03-18) has been cooking\n> in 'next'; in general we won't revert and requeue a new round for a\n> topic that has already merged to 'next'.\n>\n> Is this \"v2\" an update for that topic?\n\nHmph, it seems to be the case.  I was hoping that the one in 'next'\nwas basically in a good shape, not so broken to require a wholesale\nreplacement like this.  Overall, this round of reroll seems to be\nbetter structured.\n\nLet's revert the v1 from 'next', queue this one to 'pu', and make it\nadvance slooowly this time.\n\nThanks.\n"},{"id":"214379","messageId":"516C5DBF.1010102@gmail.com","threadId":"33220","inReplyTo":"7vwqs3r3m4.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2 00/14] Improve git-status --ignored","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-15T20:06:23Z","receivedAt":"2013-04-15T20:06:23Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Am 15.04.2013 21:33, schrieb Junio C Hamano:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n>> Karsten Blees <karsten.blees@gmail.com> writes:\n>>\n>>> This patch series addresses several bugs and performance issues in\n>>> .gitignore processing.\n>>\n>> A 8-patch series ending at 5d765dc7888b (dir.c: git-status: avoid\n>> is_excluded checks for tracked files, 2013-03-18) has been cooking\n>> in 'next'; in general we won't revert and requeue a new round for a\n>> topic that has already merged to 'next'.\n>>\n\nI'm sorry to have caused such trouble. I thought you were expecting a reroll from this:\n\nAm 19.03.2013 15:48, schrieb Junio C Hamano:\n> Would we we better off kicking nd/read-directory-recursive-optim\n> back to 'pu' (and eventually ejecting it) and replacing it with a\n> reroll of Karsten's series when it comes, perhaps?\n\n\n>> Is this \"v2\" an update for that topic?\n> \n> Hmph, it seems to be the case.  I was hoping that the one in 'next'\n> was basically in a good shape, not so broken to require a wholesale\n> replacement like this.  Overall, this round of reroll seems to be\n> better structured.\n> \n> Let's revert the v1 from 'next', queue this one to 'pu', and make it\n> advance slooowly this time.\n> \n\nThanks. I don't plan any more rerolls unless required by upcoming discussion.\n"},{"id":"214382","messageId":"7vli8jr17i.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"516C5DBF.1010102@gmail.com","subject":"Re: [PATCH v2 00/14] Improve git-status --ignored","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-15T20:25:05Z","receivedAt":"2013-04-15T20:25:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karsten Blees <karsten.blees@gmail.com> writes:\n\n> Am 15.04.2013 21:33, schrieb Junio C Hamano:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>> \n>>> Karsten Blees <karsten.blees@gmail.com> writes:\n>>>\n>>>> This patch series addresses several bugs and performance issues in\n>>>> .gitignore processing.\n>>>\n>>> A 8-patch series ending at 5d765dc7888b (dir.c: git-status: avoid\n>>> is_excluded checks for tracked files, 2013-03-18) has been cooking\n>>> in 'next'; in general we won't revert and requeue a new round for a\n>>> topic that has already merged to 'next'.\n>>>\n>\n> I'm sorry to have caused such trouble. I thought you were expecting a reroll from this:\n\nHeh, that was an ancient history.\n\nIt is not such a big deal to revert stuff from 'next'.  I've tried\nto queue this reroll, but tentatively ejected the result from 'pu'\nfor today's integration run, as as/check-ignore topic has an\nunpleasant conflict with this.\n\nThanks.\n"},{"id":"214390","messageId":"7vwqs3pjdj.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"516C510E.5000606@gmail.com","subject":"Re: [PATCH v2 10/14] dir.c: unify is_excluded and is_path_excluded APIs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-15T21:35:36Z","receivedAt":"2013-04-15T21:35:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karsten Blees <karsten.blees@gmail.com> writes:\n\n> is_excluded doesn't handle ignored directories, results for paths within\n> ignored directories are incorrect. This is probably based on the premise\n> that recursive directory scans should stop at ignored directories,...\n\nCorrect.  Long time ago, we used to have strange rules in a case\nlike this:\n\n\t.gitignore has \"d/\" to exclude it;\n        d/.gitignore says \"!f\" to cause d/f included.\n\nand when you ask about d/f, we would say \"d/ is ignored, so we\nwouldn't even bother checking and declare it ignored\", while asked\nabout \"f\" from within \"d/\" by first chdir'ing to it, we said \"the\nfact that you are inside d/ alone means you are interested in\nlooking at its contents, and checking d/.gitignore tells us that you\nare interested in f\". \"recursion stops at ignored paths\" mattered\nback then.\n\nI think we consistently honors higher level .gitignore files these\ndays.\n\n> Teach is_excluded / prep_exclude about ignored directories: whenever\n> entering a new directory, first check if the entire directory is excluded.\n> Remember the excluded state in dir_struct. Don't traverse into already\n> ignored directories (i.e. don't read irrelevant .gitignore files).\n> ...\n> Here's some performance data from the linux and WebKit repos (best of 10\n> runs on a Debian Linux on SSD, core.preloadIndex=true):\n>\n>        | ls-files -ci   |    status      | status --ignored\n>        | linux | WebKit | linux | WebKit | linux | WebKit\n> -------+-------+--------+-------+--------+-------+---------\n> before | 0.506 |  6.539 | 0.212 |  1.555 | 0.323 |  2.541\n> after  | 0.080 |  1.191 | 0.218 |  1.583 | 0.321 |  2.579\n> gain   | 6.325 |  5.490 | 0.972 |  0.982 | 1.006 |  0.985\n\nNice.\n"},{"id":"214473","messageId":"c68f7df5367d254360ddf184fa1eaf6b8c3bef18.1366106134.git.trast@inf.ethz.ch","threadId":"33220","inReplyTo":"516C4FB6.8060703@gmail.com","subject":"[PATCH] read_revisions_from_stdin: make copies for handle_revision_arg","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-04-16T09:57:45Z","receivedAt":"2013-04-16T09:57:45Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"read_revisions_from_stdin() has passed pointers to its read buffer\ndown to handle_revision_arg() since its inception way back in 42cabc3\n(Teach rev-list an option to read revs from the standard input.,\n2006-09-05).  Even back then, this was a bug: through\nadd_pending_object, the argument was recorded in the object_array's\n'name' field.\n\nFix it by making a copy whenever read_revisions_from_stdin() passes an\nargument down the callchain.  The other caller runs handle_revision_arg()\non argv[], where it would be redundant to make a copy.\n\nSigned-off-by: Thomas Rast <trast@inf.ethz.ch>\n---\n\n> So I changed my mind.  Your \"easy fix\" looks to me the right thing\n> to do.\n\nSo here's the same with a commit message and signoff.  I hope I got my\nhistory right; I didn't look too long if it had any users, but it was\ndefinitely recorded.\n\n\n revision.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/revision.c b/revision.c\nindex 3a20c96..181a8db 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1277,7 +1277,8 @@ static void read_revisions_from_stdin(struct rev_info *revs,\n \t\t\t}\n \t\t\tdie(\"options not supported in --stdin mode\");\n \t\t}\n-\t\tif (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))\n+\t\tif (handle_revision_arg(xstrdup(sb.buf), revs, 0,\n+\t\t\t\t\tREVARG_CANNOT_BE_FILENAME))\n \t\t\tdie(\"bad revision '%s'\", sb.buf);\n \t}\n \tif (seen_dashdash)\n-- \n1.8.2.1.703.g2535f49\n"},{"id":"214501","messageId":"CALkWK0=0MbKnEWc+uLkbynJZXYYiRyfJ539HjtPTajHotB4q9A@mail.gmail.com","threadId":"33220","inReplyTo":"516C4F6F.5020708@gmail.com","subject":"Re: [PATCH v2 01/14] dir.c: git-status --ignored: don't drop ignored directories","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2013-04-16T17:33:40Z","receivedAt":"2013-04-16T17:33:40Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Firstly, great work on the series! I've just started looking into it,\nso please don't take my comments too seriously: some of them may be\nqueries, and others may be minor suggestions, but I can't say I\nunderstand the area you're patching.  I know Junio doesn't like me\nmixing queries in reviews, but I don't fully agree with his policy.\n\nKarsten Blees wrote:\n> 'git-status --ignored' drops ignored directories if they contain untracked\n> files in an untracked sub directory.\n\nWait, ignored directories will always contain untracked\nsubdirectories, unless you add -f them, right?  Why are you saying\nuntracked files in an _untracked_ subdirectory?  We don't track\ndirectories anyway, and I would call a directory \"tracked\" if there's\natleast one file inside it is tracked.  So, my understanding of this\nis:\n\n    quux/\n       bar\n        baz/\n             foo\n\nIn this example, if quux is ignored and untracked, git status\n--ignored currently shows quux/.  If quux/bar is tracked (say with add\n-f), but baz/ is untracked, git status --ignored doesn't show me\nanything.  What exactly is the bug you're fixing?  I'll try to look at\nthe tests to infer this, but your commit message could probably be\nclearer.\n\nNit: please s/git-status/git status/\n\n> Fix it by getting exact (recursive) excluded status in treat_directory.\n\nOkay, so you're patching treat_directory() in dir.c to do some\nrecursive exclude handling.  Let's see what this is.\n\n> diff --git a/dir.c b/dir.c\n> index 57394e4..ec4eebf 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -1060,6 +1060,15 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n>\n>         /* This is the \"show_other_directories\" case */\n>\n> +       /* might be a sub directory in an excluded directory */\n> +       if (!exclude) {\n> +               struct path_exclude_check check;\n> +               int dt = DT_DIR;\n> +               path_exclude_check_init(&check, dir);\n> +               exclude = is_path_excluded(&check, dirname, len, &dt);\n> +               path_exclude_check_clear(&check);\n> +       }\n> +\n\nSo, I'm guessing that DT_DIR refers to a value that a field in struct\ndirent can take; that value could be one of DIR (directory), REG\n(regular file?), LNK (symbolic link?).  I don't get much of this, but\nwhat I do get is that you're setting exclude for the rest of the code\nin this function.\n\nSorry that I'm not able to do a more thorough review.\n\n> diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\n> index 0da1214..0f1034e 100755\n> --- a/t/t7061-wtstatus-ignore.sh\n> +++ b/t/t7061-wtstatus-ignore.sh\n> @@ -143,4 +143,31 @@ test_expect_success 'status ignored tracked directory and uncommitted file with\n>         test_cmp expected actual\n>  '\n>\n> +cat >expected <<\\EOF\n> +?? .gitignore\n> +?? actual\n> +?? expected\n> +!! tracked/\n> +EOF\n\nPlease put these segments inside the test_expect_success block, so\nit's easy to think about those blocks in isolation.  I know you're\njust following the existing conventions existing in this test, but\nthose are not necessarily good conventions.\n\n> +test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore' '\n> +       rm -rf tracked/uncommitted &&\n> +       mkdir tracked/ignored &&\n> +       : >tracked/ignored/uncommitted &&\n> +       git status --porcelain --ignored >actual &&\n> +       test_cmp expected actual\n> +'\n\nThis is very confusing.  How is tracked a tracked directory?  Oh,\nright: some previous test git add'ed tracked/committed.  How do I know\nabout that in this test?\n\nYeah, changes to tracked ignored directories are not shown, but the\ncommit message didn't tell me this.\n\n> +cat >expected <<\\EOF\n> +?? .gitignore\n> +?? actual\n> +?? expected\n> +!! tracked/ignored/uncommitted\n> +EOF\n> +\n> +test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' '\n> +       git status --porcelain --ignored -u >actual &&\n> +       test_cmp expected actual\n> +'\n> +\n>  test_done\n\nI suppose the commit message told me about this one vaguely, but I\nthink it could be much clearer overall.\n\nThanks.\n"},{"id":"214504","messageId":"CALkWK0mG6bzWu7dqdP1mBe+AZfUDK=Mx4+ZkDOc2saXr3gBsUQ@mail.gmail.com","threadId":"33220","inReplyTo":"516C4FE4.9090806@gmail.com","subject":"Re: [PATCH v2 03/14] dir.c: git-status --ignored: don't list empty ignored directories","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2013-04-16T17:48:06Z","receivedAt":"2013-04-16T17:48:06Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Karsten Blees wrote:\n> 'git-status --ignored' lists ignored tracked directories without any\n> ignored files if a tracked file happens to match an exclude pattern.\n\nHere, I have:\n\n    quux/\n        bar\n        baz/\n            foo\n\nSo, quux is an ignored tracked directory.  bar is tracked, but matches\nan ignore pattern.  Currently, git status --ignored lists quux/.  I'm\nconfused.\n\n> Always exclude tracked files.\n\n\"exclude\" it from the 'git status --ignored' output, I presume?\nThere's already an _exclude_ pattern in your previous sentence, so you\ncan see why the reader might be confused about what you're talking\nabout.\n\n> diff --git a/dir.c b/dir.c\n> index 7c9bc9c..fd1f088 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -1109,16 +1109,13 @@ static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude,\n>         struct path_exclude_check check;\n>         int exclude_file = 0;\n>\n> +       /* Always exclude indexed files */\n> +       if (index_name_exists(&the_index, path->buf, path->len, ignore_case))\n> +               return 1;\n> +\n>         if (exclude)\n>                 exclude_file = !(dir->flags & DIR_SHOW_IGNORED);\n>         else if (dir->flags & DIR_SHOW_IGNORED) {\n> -               /* Always exclude indexed files */\n> -               struct cache_entry *ce = index_name_exists(&the_index,\n> -                   path->buf, path->len, ignore_case);\n> -\n> -               if (ce)\n> -                       return 1;\n> -\n\nOkay, so you just moved this segment outside the else if()\nconditional.  Can you explain what the old logic was doing, and what\nthe rationale for it was?\n\n> diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\n> index 4ece129..28b7d95 100755\n> --- a/t/t7061-wtstatus-ignore.sh\n> +++ b/t/t7061-wtstatus-ignore.sh\n> @@ -122,10 +122,34 @@ cat >expected <<\\EOF\n>  ?? .gitignore\n>  ?? actual\n>  ?? expected\n> +EOF\n> +\n> +test_expect_success 'status ignored tracked directory and ignored file with --ignore' '\n> +       echo \"committed\" >>.gitignore &&\n> +       git status --porcelain --ignored >actual &&\n> +       test_cmp expected actual\n> +'\n\nUm, didn't really get this one.  You have three untracked files, and\ngit status seems to be showing them fine.  What am I missing?\n\n> +cat >expected <<\\EOF\n> +?? .gitignore\n> +?? actual\n> +?? expected\n> +EOF\n> +\n> +test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' '\n> +       git status --porcelain --ignored -u >actual &&\n> +       test_cmp expected actual\n> +'\n\nI didn't understand why you're invoking -u here (doesn't it imply\n\"all\", as opposed to \"normal\" when unspecified?).  There are really no\ndirectories, so I don't know what I'm expected to see.\n\n> +cat >expected <<\\EOF\n> +?? .gitignore\n> +?? actual\n> +?? expected\n>  !! tracked/\n>  EOF\n>\n>  test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' '\n> +       echo \"tracked\" >.gitignore &&\n>         : >tracked/uncommitted &&\n>         git status --porcelain --ignored >actual &&\n>         test_cmp expected actual\n\nDidn't we test this in the last patch?  Okay, I'm completely confused now.\n\nOnce again, apologies for my inexperienced comments:  I'm contributing\nwhatever little I can to the review process.\n"},{"id":"214513","messageId":"7vzjwyl4q4.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"c68f7df5367d254360ddf184fa1eaf6b8c3bef18.1366106134.git.trast@inf.ethz.ch","subject":"Re: [PATCH] read_revisions_from_stdin: make copies for handle_revision_arg","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-16T18:17:55Z","receivedAt":"2013-04-16T18:17:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@inf.ethz.ch> writes:\n\n> read_revisions_from_stdin() has passed pointers to its read buffer\n> down to handle_revision_arg() since its inception way back in 42cabc3\n> (Teach rev-list an option to read revs from the standard input.,\n> 2006-09-05).  Even back then, this was a bug: through\n> add_pending_object, the argument was recorded in the object_array's\n> 'name' field.\n>\n> Fix it by making a copy whenever read_revisions_from_stdin() passes an\n> argument down the callchain.  The other caller runs handle_revision_arg()\n> on argv[], where it would be redundant to make a copy.\n>\n> Signed-off-by: Thomas Rast <trast@inf.ethz.ch>\n> ---\n>\n>> So I changed my mind.  Your \"easy fix\" looks to me the right thing\n>> to do.\n>\n> So here's the same with a commit message and signoff.  I hope I got my\n> history right; I didn't look too long if it had any users, but it was\n> definitely recorded.\n\nThanks.\n\n\n>\n>\n>  revision.c | 3 ++-\n>  1 file changed, 2 insertions(+), 1 deletion(-)\n>\n> diff --git a/revision.c b/revision.c\n> index 3a20c96..181a8db 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -1277,7 +1277,8 @@ static void read_revisions_from_stdin(struct rev_info *revs,\n>  \t\t\t}\n>  \t\t\tdie(\"options not supported in --stdin mode\");\n>  \t\t}\n> -\t\tif (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))\n> +\t\tif (handle_revision_arg(xstrdup(sb.buf), revs, 0,\n> +\t\t\t\t\tREVARG_CANNOT_BE_FILENAME))\n>  \t\t\tdie(\"bad revision '%s'\", sb.buf);\n>  \t}\n>  \tif (seen_dashdash)\n"},{"id":"214561","messageId":"516DED45.2050600@gmail.com","threadId":"33220","inReplyTo":"CALkWK0=0MbKnEWc+uLkbynJZXYYiRyfJ539HjtPTajHotB4q9A@mail.gmail.com","subject":"Re: [PATCH v2 01/14] dir.c: git-status --ignored: don't drop ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-17T00:31:01Z","receivedAt":"2013-04-17T00:31:01Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Am 16.04.2013 19:33, schrieb Ramkumar Ramachandra:\n> Firstly, great work on the series! I've just started looking into it,\n> so please don't take my comments too seriously: some of them may be\n> queries, and others may be minor suggestions, but I can't say I\n> understand the area you're patching.  I know Junio doesn't like me\n> mixing queries in reviews, but I don't fully agree with his policy.\n> \n> Karsten Blees wrote:\n>> 'git-status --ignored' drops ignored directories if they contain untracked\n>> files in an untracked sub directory.\n> \n> Wait, ignored directories will always contain untracked\n> subdirectories, unless you add -f them, right?  Why are you saying\n> untracked files in an _untracked_ subdirectory?  We don't track\n\nIIRC i mentioned untracked subdirectory explicitly because the problem doesn't occur if the subdirectory is tracked (i.e. contains a tracked file).\n\n> directories anyway, and I would call a directory \"tracked\" if there's\n> atleast one file inside it is tracked.  So, my understanding of this\n> is:\n> \n>     quux/\n>        bar\n>         baz/\n>              foo\n> \n> In this example, if quux is ignored and untracked, git status\n> --ignored currently shows quux/.  If quux/bar is tracked (say with add\n> -f), but baz/ is untracked, git status --ignored doesn't show me\n> anything.  What exactly is the bug you're fixing?\n\nWith your example, you get this:\n\n1) git-status --ignored -uall: quux/baz/foo\n2) git-status --ignored -unormal: <empty>\n\nWithout the untracked directory 'baz' between 'quux' and 'foo':\n\n\tquux/\n\t\tbar\n\t\tfoo\n\nyou get this:\n\n3) git status --ignored -uall: quux/foo\n4) git status --ignored -unormal: quux/\n\nThe bug is in case (2), this should be:\n\n2) git-status --ignored -unormal: quux/\n\nI.e. ignored directories with untracked files in untracked sub directories should be listed as ignored, because they contain ignored files. Currently these directories are not listed. I realize this sounds pretty much like the original commit message, but I don't know how to describe it any better.\n\n> I'll try to look at\n> the tests to infer this, but your commit message could probably be\n> clearer.\n> \n> Nit: please s/git-status/git status/\n\nBoth notations (as well as just 'status') seem to be widely used. Does this justify a reroll of the entire series with changed commit messages (I believe I've used git-status / git-ls-files / git-clean consistently in all patches)?\n\n> \n>> Fix it by getting exact (recursive) excluded status in treat_directory.\n> \n> Okay, so you're patching treat_directory() in dir.c to do some\n> recursive exclude handling.  Let's see what this is.\n> \n>> diff --git a/dir.c b/dir.c\n>> index 57394e4..ec4eebf 100644\n>> --- a/dir.c\n>> +++ b/dir.c\n>> @@ -1060,6 +1060,15 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n>>\n>>         /* This is the \"show_other_directories\" case */\n>>\n>> +       /* might be a sub directory in an excluded directory */\n>> +       if (!exclude) {\n>> +               struct path_exclude_check check;\n>> +               int dt = DT_DIR;\n>> +               path_exclude_check_init(&check, dir);\n>> +               exclude = is_path_excluded(&check, dirname, len, &dt);\n>> +               path_exclude_check_clear(&check);\n>> +       }\n>> +\n> \n> So, I'm guessing that DT_DIR refers to a value that a field in struct\n> dirent can take; that value could be one of DIR (directory), REG\n> (regular file?), LNK (symbolic link?).  I don't get much of this, but\n> what I do get is that you're setting exclude for the rest of the code\n> in this function.\n> \n\nThis simply does a recursive excluded check via is_path_excluded, because is_excluded is not good enough. The rest is just boilerplate code (i.e. initialize and clear the check structure and passing a dtype == DT_DIR because dirname obviously is a directory here). We already have similar code in treat_file (and all other places that use is_path_excluded).\n\n> Sorry that I'm not able to do a more thorough review.\n> \n>> diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\n>> index 0da1214..0f1034e 100755\n>> --- a/t/t7061-wtstatus-ignore.sh\n>> +++ b/t/t7061-wtstatus-ignore.sh\n>> @@ -143,4 +143,31 @@ test_expect_success 'status ignored tracked directory and uncommitted file with\n>>         test_cmp expected actual\n>>  '\n>>\n>> +cat >expected <<\\EOF\n>> +?? .gitignore\n>> +?? actual\n>> +?? expected\n>> +!! tracked/\n>> +EOF\n> \n> Please put these segments inside the test_expect_success block, so\n> it's easy to think about those blocks in isolation.  I know you're\n> just following the existing conventions existing in this test, but\n> those are not necessarily good conventions.\n> \n\n$ grep -e '^cat .*EOF$' t*.sh | wc -l\n    743\n$ grep -e '^    cat .*EOF$' t*.sh | wc -l\n     22\n\nSetting up expected results outside test_expect_* (i.e. unindented) seems to be the norm in git tests.\n\n>> +test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore' '\n>> +       rm -rf tracked/uncommitted &&\n>> +       mkdir tracked/ignored &&\n>> +       : >tracked/ignored/uncommitted &&\n>> +       git status --porcelain --ignored >actual &&\n>> +       test_cmp expected actual\n>> +'\n> \n> This is very confusing.  How is tracked a tracked directory?  Oh,\n> right: some previous test git add'ed tracked/committed.  How do I know\n> about that in this test?\n> \n\nThis test expands on the previous test 'status ignored tracked directory and uncommitted file with --ignore', i.e. the test just adds the 'in untracked subdir' part (and thus moves the 'uncommitted' file into a sub directory). Expanding on previous state is the whole point of putting several tests in a single t*.sh file, right? If I wanted to set up the test fixture from scratch I would have started a new t7063-whatever.sh.\n\n> Yeah, changes to tracked ignored directories are not shown, but the\n> commit message didn't tell me this.\n> \n>> +cat >expected <<\\EOF\n>> +?? .gitignore\n>> +?? actual\n>> +?? expected\n>> +!! tracked/ignored/uncommitted\n>> +EOF\n>> +\n>> +test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' '\n>> +       git status --porcelain --ignored -u >actual &&\n>> +       test_cmp expected actual\n>> +'\n>> +\n>>  test_done\n> \n> I suppose the commit message told me about this one vaguely, but I\n> think it could be much clearer overall.\n> \n\nNote that all tests in t7061 come in pairs '--ignored' (i.e. --untracked-files=normal) and '--ignored -u' (i.e. --untracked-files=all). If '-uall' lists individual ignored files, then '-unormal' should list the ignored directory.\n"},{"id":"214560","messageId":"516DED4A.5050800@gmail.com","threadId":"33220","inReplyTo":"CALkWK0mG6bzWu7dqdP1mBe+AZfUDK=Mx4+ZkDOc2saXr3gBsUQ@mail.gmail.com","subject":"Re: [PATCH v2 03/14] dir.c: git-status --ignored: don't list empty ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-17T00:31:06Z","receivedAt":"2013-04-17T00:31:06Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Am 16.04.2013 19:48, schrieb Ramkumar Ramachandra:\n> Karsten Blees wrote:\n>> 'git-status --ignored' lists ignored tracked directories without any\n>> ignored files if a tracked file happens to match an exclude pattern.\n> \n> Here, I have:\n> \n>     quux/\n>         bar\n>         baz/\n>             foo\n> \n> So, quux is an ignored tracked directory.  bar is tracked, but matches\n> an ignore pattern.  Currently, git status --ignored lists quux/.  I'm\n> confused.\n> \n\nThe crucial part is 'without any ignored files'. So if you 'rm quux/baz/foo' or 'git-add -f quux/baz/foo', you get this:\n\n1) git-status --ignored -uall: <empty>\n2) git-status --ignored -unormal: quux/\n\nIn case 2), quux/ is listed as ignored even though there are no ignored files and the DIR_HIDE_EMPTY_DIRECTORIES flag should kick in.\n\n>> Always exclude tracked files.\n> \n> \"exclude\" it from the 'git status --ignored' output, I presume?\n> There's already an _exclude_ pattern in your previous sentence, so you\n> can see why the reader might be confused about what you're talking\n> about.\n> \n\nI see, but \"Always *ignore* tracked files\" is also ambiguous. Suggestions?\n\n>> diff --git a/dir.c b/dir.c\n>> index 7c9bc9c..fd1f088 100644\n>> --- a/dir.c\n>> +++ b/dir.c\n>> @@ -1109,16 +1109,13 @@ static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude,\n>>         struct path_exclude_check check;\n>>         int exclude_file = 0;\n>>\n>> +       /* Always exclude indexed files */\n>> +       if (index_name_exists(&the_index, path->buf, path->len, ignore_case))\n>> +               return 1;\n>> +\n>>         if (exclude)\n>>                 exclude_file = !(dir->flags & DIR_SHOW_IGNORED);\n>>         else if (dir->flags & DIR_SHOW_IGNORED) {\n>> -               /* Always exclude indexed files */\n>> -               struct cache_entry *ce = index_name_exists(&the_index,\n>> -                   path->buf, path->len, ignore_case);\n>> -\n>> -               if (ce)\n>> -                       return 1;\n>> -\n> \n> Okay, so you just moved this segment outside the else if()\n> conditional.  Can you explain what the old logic was doing, and what\n> the rationale for it was?\n> \n\nQuoting Antoine's patch from January:\n+\t\t/*\n+\t\t * Optimization:\n+\t\t * Don't spend time on indexed files, they won't be\n+\t\t * added to the list anyway\n+\t\t */\n\nIn other words: don't do the (expensive) recursive is_path_excluded check if the file will be dropped later anyway (see the additional 'cache_name_is_other' checks in wt_status.c::wt_status_collect_untracked and builtin/ls-files.c::show_other_files).\n\nIn the DIR_SHOW_OTHER_DIRECTORIES case, we use read_directory_recursive to just *check* for ignored / untracked files, all within dir.c. So the results must be correct *here*, we cannot rely on some other modules to fix up incorrect files later.\n\n>> diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh\n>> index 4ece129..28b7d95 100755\n>> --- a/t/t7061-wtstatus-ignore.sh\n>> +++ b/t/t7061-wtstatus-ignore.sh\n>> @@ -122,10 +122,34 @@ cat >expected <<\\EOF\n>>  ?? .gitignore\n>>  ?? actual\n>>  ?? expected\n>> +EOF\n>> +\n>> +test_expect_success 'status ignored tracked directory and ignored file with --ignore' '\n>> +       echo \"committed\" >>.gitignore &&\n>> +       git status --porcelain --ignored >actual &&\n>> +       test_cmp expected actual\n>> +'\n> \n> Um, didn't really get this one.  You have three untracked files, and\n> git status seems to be showing them fine.  What am I missing?\n> \n\nWe have this:\n\n\ttracked/\n\t\tcommitted\n\n.gitignore:\ntracked\ncommitted\n\nBoth the directory and tracked file match an exclude pattern. There are no untracked ignored files, yet before this patch, git-status --ignored would list tracked/ as ignored directory.\n\n>> +cat >expected <<\\EOF\n>> +?? .gitignore\n>> +?? actual\n>> +?? expected\n>> +EOF\n>> +\n>> +test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' '\n>> +       git status --porcelain --ignored -u >actual &&\n>> +       test_cmp expected actual\n>> +'\n> \n> I didn't understand why you're invoking -u here (doesn't it imply\n> \"all\", as opposed to \"normal\" when unspecified?).  There are really no\n> directories, so I don't know what I'm expected to see.\n> \n>> +cat >expected <<\\EOF\n>> +?? .gitignore\n>> +?? actual\n>> +?? expected\n>>  !! tracked/\n>>  EOF\n>>\n>>  test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' '\n>> +       echo \"tracked\" >.gitignore &&\n>>         : >tracked/uncommitted &&\n>>         git status --porcelain --ignored >actual &&\n>>         test_cmp expected actual\n> \n> Didn't we test this in the last patch?  Okay, I'm completely confused now.\n> \n\n'echo \"tracked\" >.gitignore' reverts the 'echo \"committed\" >>.gitignore' from above.\n"},{"id":"214634","messageId":"516EFCEC.1090803@gmail.com","threadId":"33220","inReplyTo":"7vli8jr17i.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2 00/14] Improve git-status --ignored","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-17T19:50:04Z","receivedAt":"2013-04-17T19:50:04Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Am 15.04.2013 22:25, schrieb Junio C Hamano:\n> Karsten Blees <karsten.blees@gmail.com> writes:\n> \n>> Am 15.04.2013 21:33, schrieb Junio C Hamano:\n>>> Junio C Hamano <gitster@pobox.com> writes:\n>>>\n>>>> Karsten Blees <karsten.blees@gmail.com> writes:\n>>>>\n>>>>> This patch series addresses several bugs and performance issues in\n>>>>> .gitignore processing.\n>>>>\n>>>> A 8-patch series ending at 5d765dc7888b (dir.c: git-status: avoid\n>>>> is_excluded checks for tracked files, 2013-03-18) has been cooking\n>>>> in 'next'; in general we won't revert and requeue a new round for a\n>>>> topic that has already merged to 'next'.\n>>>>\n>>\n>> I'm sorry to have caused such trouble. I thought you were expecting a reroll from this:\n> \n> Heh, that was an ancient history.\n\nMy git time is scarce, so progress is slow...guess I need some performance improvements, too :-)\n\n> \n> It is not such a big deal to revert stuff from 'next'.  I've tried\n> to queue this reroll, but tentatively ejected the result from 'pu'\n> for today's integration run, as as/check-ignore topic has an\n> unpleasant conflict with this.\n> \n\nI'll send fixups for #11 and #14, or you can pick the entire series rebased to pu from:\nhttps://github.com/kblees/git/tree/kb/improve-git-status-ignored-v2-pu\ngit pull git://github.com/kblees/git.git kb/improve-git-status-ignored-v2-pu\n\nHTH, Karsten\n"},{"id":"214635","messageId":"516EFD21.2050306@gmail.com","threadId":"33220","inReplyTo":"7vli8jr17i.fsf@alter.siamese.dyndns.org","subject":"[PATCH v2-pu 11/14] dir.c: replace is_path_excluded with now equivalent is_excluded API","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-17T19:50:57Z","receivedAt":"2013-04-17T19:50:57Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Signed-off-by: Karsten Blees <blees@dcon.de>\n---\n builtin/add.c          |  5 +---\n builtin/check-ignore.c | 16 ++++------\n builtin/ls-files.c     | 15 +++-------\n dir.c                  | 79 ++++----------------------------------------------\n dir.h                  | 16 ++--------\n unpack-trees.c         | 10 +------\n unpack-trees.h         |  1 -\n 7 files changed, 21 insertions(+), 121 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex ddd5e38..050330e 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -419,9 +419,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \n \tif (pathspec) {\n \t\tint i;\n-\t\tstruct path_exclude_check check;\n \n-\t\tpath_exclude_check_init(&check, &dir);\n \t\tif (!seen)\n \t\t\tseen = find_pathspecs_matching_against_index(pathspec);\n \t\tfor (i = 0; pathspec[i]; i++) {\n@@ -429,7 +427,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\t    && !file_exists(pathspec[i])) {\n \t\t\t\tif (ignore_missing) {\n \t\t\t\t\tint dtype = DT_UNKNOWN;\n-\t\t\t\t\tif (is_path_excluded(&check, pathspec[i], -1, &dtype))\n+\t\t\t\t\tif (is_excluded(&dir, pathspec[i], &dtype))\n \t\t\t\t\t\tdir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));\n \t\t\t\t} else\n \t\t\t\t\tdie(_(\"pathspec '%s' did not match any files\"),\n@@ -437,7 +435,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t}\n \t\tfree(seen);\n-\t\tpath_exclude_check_clear(&check);\n \t}\n \n \tplug_bulk_checkin();\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex c00a7d6..a4357fb 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -63,7 +63,7 @@ static void output_exclude(const char *path, struct exclude *exclude)\n \t}\n }\n \n-static int check_ignore(struct path_exclude_check *check,\n+static int check_ignore(struct dir_struct *dir,\n \t\t\tconst char *prefix, const char **pathspec)\n {\n \tconst char *path, *full_path;\n@@ -91,8 +91,7 @@ static int check_ignore(struct path_exclude_check *check,\n \t\tdie_if_path_beyond_symlink(full_path, prefix);\n \t\texclude = NULL;\n \t\tif (!seen[i]) {\n-\t\t\texclude = last_exclude_matching_path(check, full_path,\n-\t\t\t\t\t\t\t     -1, &dtype);\n+\t\t\texclude = last_exclude_matching(dir, full_path, &dtype);\n \t\t}\n \t\tif (!quiet && (exclude || show_non_matching))\n \t\t\toutput_exclude(path, exclude);\n@@ -104,7 +103,7 @@ static int check_ignore(struct path_exclude_check *check,\n \treturn num_ignored;\n }\n \n-static int check_ignore_stdin_paths(struct path_exclude_check *check, const char *prefix)\n+static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)\n {\n \tstruct strbuf buf, nbuf;\n \tchar *pathspec[2] = { NULL, NULL };\n@@ -121,7 +120,7 @@ static int check_ignore_stdin_paths(struct path_exclude_check *check, const char\n \t\t\tstrbuf_swap(&buf, &nbuf);\n \t\t}\n \t\tpathspec[0] = buf.buf;\n-\t\tnum_ignored += check_ignore(check, prefix, (const char **)pathspec);\n+\t\tnum_ignored += check_ignore(dir, prefix, (const char **)pathspec);\n \t\tmaybe_flush_or_die(stdout, \"check-ignore to stdout\");\n \t}\n \tstrbuf_release(&buf);\n@@ -133,7 +132,6 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)\n {\n \tint num_ignored;\n \tstruct dir_struct dir;\n-\tstruct path_exclude_check check;\n \n \tgit_config(git_default_config, NULL);\n \n@@ -166,16 +164,14 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)\n \tdir.flags |= DIR_COLLECT_IGNORED;\n \tsetup_standard_excludes(&dir);\n \n-\tpath_exclude_check_init(&check, &dir);\n \tif (stdin_paths) {\n-\t\tnum_ignored = check_ignore_stdin_paths(&check, prefix);\n+\t\tnum_ignored = check_ignore_stdin_paths(&dir, prefix);\n \t} else {\n-\t\tnum_ignored = check_ignore(&check, prefix, argv);\n+\t\tnum_ignored = check_ignore(&dir, prefix, argv);\n \t\tmaybe_flush_or_die(stdout, \"ignore to stdout\");\n \t}\n \n \tclear_directory(&dir);\n-\tpath_exclude_check_clear(&check);\n \n \treturn !num_ignored;\n }\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 175e6e3..2202072 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -201,19 +201,15 @@ static void show_ru_info(void)\n \t}\n }\n \n-static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce)\n+static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)\n {\n \tint dtype = ce_to_dtype(ce);\n-\treturn is_path_excluded(check, ce->name, ce_namelen(ce), &dtype);\n+\treturn is_excluded(dir, ce->name, &dtype);\n }\n \n static void show_files(struct dir_struct *dir)\n {\n \tint i;\n-\tstruct path_exclude_check check;\n-\n-\tif ((dir->flags & DIR_SHOW_IGNORED))\n-\t\tpath_exclude_check_init(&check, dir);\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -227,7 +223,7 @@ static void show_files(struct dir_struct *dir)\n \t\tfor (i = 0; i < active_nr; i++) {\n \t\t\tstruct cache_entry *ce = active_cache[i];\n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(&check, ce))\n+\t\t\t    !ce_excluded(dir, ce))\n \t\t\t\tcontinue;\n \t\t\tif (show_unmerged && !ce_stage(ce))\n \t\t\t\tcontinue;\n@@ -243,7 +239,7 @@ static void show_files(struct dir_struct *dir)\n \t\t\tstruct stat st;\n \t\t\tint err;\n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(&check, ce))\n+\t\t\t    !ce_excluded(dir, ce))\n \t\t\t\tcontinue;\n \t\t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\t\tcontinue;\n@@ -256,9 +252,6 @@ static void show_files(struct dir_struct *dir)\n \t\t\t\tshow_ce_entry(tag_modified, ce);\n \t\t}\n \t}\n-\n-\tif ((dir->flags & DIR_SHOW_IGNORED))\n-\t\tpath_exclude_check_clear(&check);\n }\n \n /*\ndiff --git a/dir.c b/dir.c\nindex 7e68a1c..16e1c07 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -843,7 +843,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n  * Returns the exclude_list element which matched, or NULL for\n  * undecided.\n  */\n-static struct exclude *last_exclude_matching(struct dir_struct *dir,\n+struct exclude *last_exclude_matching(struct dir_struct *dir,\n \t\t\t\t\t     const char *pathname,\n \t\t\t\t\t     int *dtype_p)\n {\n@@ -865,7 +865,7 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,\n  * scans all exclude lists to determine whether pathname is excluded.\n  * Returns 1 if true, otherwise 0.\n  */\n-static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)\n+int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)\n {\n \tstruct exclude *exclude =\n \t\tlast_exclude_matching(dir, pathname, dtype_p);\n@@ -874,47 +874,6 @@ static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_\n \treturn 0;\n }\n \n-void path_exclude_check_init(struct path_exclude_check *check,\n-\t\t\t     struct dir_struct *dir)\n-{\n-\tcheck->dir = dir;\n-}\n-\n-void path_exclude_check_clear(struct path_exclude_check *check)\n-{\n-}\n-\n-/*\n- * For each subdirectory in name, starting with the top-most, checks\n- * to see if that subdirectory is excluded, and if so, returns the\n- * corresponding exclude structure.  Otherwise, checks whether name\n- * itself (which is presumably a file) is excluded.\n- *\n- * A path to a directory known to be excluded is left in check->path to\n- * optimize for repeated checks for files in the same excluded directory.\n- */\n-struct exclude *last_exclude_matching_path(struct path_exclude_check *check,\n-\t\t\t\t\t   const char *name, int namelen,\n-\t\t\t\t\t   int *dtype)\n-{\n-\treturn last_exclude_matching(check->dir, name, dtype);\n-}\n-\n-/*\n- * Is this name excluded?  This is for a caller like show_files() that\n- * do not honor directory hierarchy and iterate through paths that are\n- * possibly in an ignored directory.\n- */\n-int is_path_excluded(struct path_exclude_check *check,\n-\t\t  const char *name, int namelen, int *dtype)\n-{\n-\tstruct exclude *exclude =\n-\t\tlast_exclude_matching_path(check, name, namelen, dtype);\n-\tif (exclude)\n-\t\treturn exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;\n-\treturn 0;\n-}\n-\n static struct dir_entry *dir_entry_new(const char *pathname, int len)\n {\n \tstruct dir_entry *ent;\n@@ -1086,15 +1045,6 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n \n \t/* This is the \"show_other_directories\" case */\n \n-\t/* might be a sub directory in an excluded directory */\n-\tif (!exclude) {\n-\t\tstruct path_exclude_check check;\n-\t\tint dt = DT_DIR;\n-\t\tpath_exclude_check_init(&check, dir);\n-\t\texclude = is_path_excluded(&check, dirname, len, &dt);\n-\t\tpath_exclude_check_clear(&check);\n-\t}\n-\n \t/*\n \t * We are looking for ignored files and our directory is not ignored,\n \t * check if it contains untracked files (i.e. is listed as untracked)\n@@ -1129,27 +1079,13 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,\n  *\n  * Return 1 for exclude, 0 for include.\n  */\n-static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype)\n+static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude)\n {\n-\tstruct path_exclude_check check;\n-\tint exclude_file = 0;\n-\n \t/* Always exclude indexed files */\n \tif (index_name_exists(&the_index, path->buf, path->len, ignore_case))\n \t\treturn 1;\n \n-\tif (exclude)\n-\t\texclude_file = !(dir->flags & DIR_SHOW_IGNORED);\n-\telse if (dir->flags & DIR_SHOW_IGNORED) {\n-\t\tpath_exclude_check_init(&check, dir);\n-\n-\t\tif (!is_path_excluded(&check, path->buf, path->len, dtype))\n-\t\t\texclude_file = 1;\n-\n-\t\tpath_exclude_check_clear(&check);\n-\t}\n-\n-\treturn exclude_file;\n+\treturn exclude == !(dir->flags & DIR_SHOW_IGNORED);\n }\n \n /*\n@@ -1306,12 +1242,9 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\tbreak;\n \tcase DT_REG:\n \tcase DT_LNK:\n-\t\tswitch (treat_file(dir, path, exclude, &dtype)) {\n-\t\tcase 1:\n+\t\tif (treat_file(dir, path, exclude))\n \t\t\treturn path_ignored;\n-\t\tdefault:\n-\t\t\tbreak;\n-\t\t}\n+\t\tbreak;\n \t}\n \treturn path_handled;\n }\ndiff --git a/dir.h b/dir.h\nindex cd166d0..bfe726e 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -151,20 +151,10 @@ extern int match_pathname(const char *, int,\n \t\t\t  const char *, int,\n \t\t\t  const char *, int, int, int);\n \n-/*\n- * The is_excluded() API is meant for callers that check each level of leading\n- * directory hierarchies with is_excluded() to avoid recursing into excluded\n- * directories.  Callers that do not do so should use this API instead.\n- */\n-struct path_exclude_check {\n-\tstruct dir_struct *dir;\n-};\n-extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);\n-extern void path_exclude_check_clear(struct path_exclude_check *);\n-extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *,\n-\t\t\t\t\t\t  int namelen, int *dtype);\n-extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);\n+extern struct exclude *last_exclude_matching(struct dir_struct *dir,\n+\t\t\t\t\t     const char *name, int *dtype);\n \n+extern int is_excluded(struct dir_struct *dir, const char *name, int *dtype);\n \n extern struct exclude_list *add_exclude_list(struct dir_struct *dir,\n \t\t\t\t\t     int group_type, const char *src);\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 09e53df..ede4299 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1026,10 +1026,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\t\to->el = &el;\n \t}\n \n-\tif (o->dir) {\n-\t\to->path_exclude_check = xmalloc(sizeof(struct path_exclude_check));\n-\t\tpath_exclude_check_init(o->path_exclude_check, o->dir);\n-\t}\n \tmemset(&o->result, 0, sizeof(o->result));\n \to->result.initialized = 1;\n \to->result.timestamp.sec = o->src_index->timestamp.sec;\n@@ -1155,10 +1151,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \n done:\n \tclear_exclude_list(&el);\n-\tif (o->path_exclude_check) {\n-\t\tpath_exclude_check_clear(o->path_exclude_check);\n-\t\tfree(o->path_exclude_check);\n-\t}\n \treturn ret;\n \n return_failed:\n@@ -1375,7 +1367,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,\n \t\treturn 0;\n \n \tif (o->dir &&\n-\t    is_path_excluded(o->path_exclude_check, name, -1, &dtype))\n+\t    is_excluded(o->dir, name, &dtype))\n \t\t/*\n \t\t * ce->name is explicitly excluded, so it is Ok to\n \t\t * overwrite it.\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex ec74a9f..5e432f5 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -52,7 +52,6 @@ struct unpack_trees_options {\n \tconst char *prefix;\n \tint cache_bottom;\n \tstruct dir_struct *dir;\n-\tstruct path_exclude_check *path_exclude_check;\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n-- \n1.8.1.2.8038.ge6fb49b.dirty\n"},{"id":"214636","messageId":"516EFD5B.7000602@gmail.com","threadId":"33220","inReplyTo":"7vli8jr17i.fsf@alter.siamese.dyndns.org","subject":"[PATCH v2-pu 14/14] dir.c: git-status --ignored: don't scan the work tree twice","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-04-17T19:51:55Z","receivedAt":"2013-04-17T19:51:55Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"'git-status --ignored' still scans the work tree twice to collect\nuntracked and ignored files, respectively.\n\nfill_directory / read_directory already supports collecting untracked and\nignored files in a single directory scan. However, the DIR_COLLECT_IGNORED\nflag to enable this has some git-add specific side-effects (e.g. it\ndoesn't recurse into ignored directories, so listing ignored files with\n--untracked=all doesn't work).\n\nThe DIR_SHOW_IGNORED flag doesn't list untracked files and returns ignored\nfiles in dir_struct.entries[] (instead of dir_struct.ignored[] as\nDIR_COLLECT_IGNORED). DIR_SHOW_IGNORED is used all throughout git.\n\nWe don't want to break the existing API, so lets introduce a new flag\nDIR_SHOW_IGNORED_TOO that lists untracked as well as ignored files similar\nto DIR_COLLECT_FILES, but will recurse into sub-directories based on the\nother flags as DIR_SHOW_IGNORED does.\n\nIn dir.c::read_directory_recursive, add ignored files to either\ndir_struct.entries[] or dir_struct.ignored[] based on the flags. Also move\nthe DIR_COLLECT_IGNORED case here so that filling result lists is in a\ncommon place.\n\nIn wt-status.c::wt_status_collect_untracked, use the new flag and read\nresults from dir_struct.ignored[]. Remove the extra fill_directory call.\n\nbuiltin/check-ignore.c doesn't call fill_directory, setting the git-add\nspecific DIR_COLLECT_IGNORED flag has no effect here. Remove for clarity.\n\nUpdate API documentation to reflect the changes.\n\nPerformance: with this patch, 'git-status --ignored' is typically as fast\nas 'git-status'.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n Documentation/technical/api-directory-listing.txt | 25 ++++++++++++++++++++---\n builtin/check-ignore.c                            |  1 -\n dir.c                                             | 10 +++++----\n dir.h                                             |  3 ++-\n wt-status.c                                       | 24 ++++++++++------------\n 5 files changed, 41 insertions(+), 22 deletions(-)\n\ndiff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt\nindex 1f349b2..7f8e78d 100644\n--- a/Documentation/technical/api-directory-listing.txt\n+++ b/Documentation/technical/api-directory-listing.txt\n@@ -22,12 +22,23 @@ The notable options are:\n \n `flags`::\n \n-\tA bit-field of options:\n+\tA bit-field of options (the `*IGNORED*` flags are mutually exclusive):\n \n `DIR_SHOW_IGNORED`:::\n \n-\tThe traversal is for finding just ignored files, not unignored\n-\tfiles.\n+\tReturn just ignored files in `entries[]`, not untracked files.\n+\n+`DIR_SHOW_IGNORED_TOO`:::\n+\n+\tSimilar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`\n+\tin addition to untracked files in `entries[]`.\n+\n+`DIR_COLLECT_IGNORED`:::\n+\n+\tSpecial mode for git-add. Return ignored files in `ignored[]` and\n+\tuntracked files in `entries[]`. Only returns ignored files that match\n+\tpathspec exactly (no wildcards). Does not recurse into ignored\n+\tdirectories.\n \n `DIR_SHOW_OTHER_DIRECTORIES`:::\n \n@@ -57,6 +68,14 @@ The result of the enumeration is left in these fields:\n \n \tInternal use; keeps track of allocation of `entries[]` array.\n \n+`ignored[]`::\n+\n+\tAn array of `struct dir_entry`, used for ignored paths with the\n+\t`DIR_SHOW_IGNORED_TOO` and `DIR_COLLECT_IGNORED` flags.\n+\n+`ignored_nr`::\n+\n+\tThe number of members in `ignored[]` array.\n \n Calling sequence\n ----------------\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex a4357fb..4a8fc70 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -161,7 +161,6 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"index file corrupt\"));\n \n \tmemset(&dir, 0, sizeof(dir));\n-\tdir.flags |= DIR_COLLECT_IGNORED;\n \tsetup_standard_excludes(&dir);\n \n \tif (stdin_paths) {\ndiff --git a/dir.c b/dir.c\nindex 2088891..ee4d04d 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1183,15 +1183,12 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\treturn path_none;\n \n \texclude = is_excluded(dir, path->buf, &dtype);\n-\tif (exclude && (dir->flags & DIR_COLLECT_IGNORED)\n-\t    && exclude_matches_pathspec(path->buf, path->len, simplify))\n-\t\tdir_add_ignored(dir, path->buf, path->len);\n \n \t/*\n \t * Excluded? If we don't explicitly want to show\n \t * ignored files, ignore it\n \t */\n-\tif (exclude && !(dir->flags & DIR_SHOW_IGNORED))\n+\tif (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))\n \t\treturn path_excluded;\n \n \tswitch (dtype) {\n@@ -1280,6 +1277,11 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\tcase path_excluded:\n \t\t\tif (dir->flags & DIR_SHOW_IGNORED)\n \t\t\t\tdir_add_name(dir, path.buf, path.len);\n+\t\t\telse if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||\n+\t\t\t\t((dir->flags & DIR_COLLECT_IGNORED) &&\n+\t\t\t\texclude_matches_pathspec(path.buf, path.len,\n+\t\t\t\t\tsimplify)))\n+\t\t\t\tdir_add_ignored(dir, path.buf, path.len);\n \t\t\tbreak;\n \n \t\tcase path_untracked:\ndiff --git a/dir.h b/dir.h\nindex bfe726e..3d6b80c 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -79,7 +79,8 @@ struct dir_struct {\n \t\tDIR_SHOW_OTHER_DIRECTORIES = 1<<1,\n \t\tDIR_HIDE_EMPTY_DIRECTORIES = 1<<2,\n \t\tDIR_NO_GITLINKS = 1<<3,\n-\t\tDIR_COLLECT_IGNORED = 1<<4\n+\t\tDIR_COLLECT_IGNORED = 1<<4,\n+\t\tDIR_SHOW_IGNORED_TOO = 1<<5\n \t} flags;\n \tstruct dir_entry **entries;\n \tstruct dir_entry **ignored;\ndiff --git a/wt-status.c b/wt-status.c\nindex 676b058..bf84a86 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -511,9 +511,12 @@ static void wt_status_collect_untracked(struct wt_status *s)\n \tif (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)\n \t\tdir.flags |=\n \t\t\tDIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n+\tif (s->show_ignored_files)\n+\t\tdir.flags |= DIR_SHOW_IGNORED_TOO;\n \tsetup_standard_excludes(&dir);\n \n \tfill_directory(&dir, s->pathspec);\n+\n \tfor (i = 0; i < dir.nr; i++) {\n \t\tstruct dir_entry *ent = dir.entries[i];\n \t\tif (cache_name_is_other(ent->name, ent->len) &&\n@@ -522,22 +525,17 @@ static void wt_status_collect_untracked(struct wt_status *s)\n \t\tfree(ent);\n \t}\n \n-\tif (s->show_ignored_files) {\n-\t\tdir.nr = 0;\n-\t\tdir.flags = DIR_SHOW_IGNORED;\n-\t\tif (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)\n-\t\t\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n-\t\tfill_directory(&dir, s->pathspec);\n-\t\tfor (i = 0; i < dir.nr; i++) {\n-\t\t\tstruct dir_entry *ent = dir.entries[i];\n-\t\t\tif (cache_name_is_other(ent->name, ent->len) &&\n-\t\t\t    match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))\n-\t\t\t\tstring_list_insert(&s->ignored, ent->name);\n-\t\t\tfree(ent);\n-\t\t}\n+\tfor (i = 0; i < dir.ignored_nr; i++) {\n+\t\tstruct dir_entry *ent = dir.ignored[i];\n+\t\tif (cache_name_is_other(ent->name, ent->len) &&\n+\t\t    match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))\n+\t\t\tstring_list_insert(&s->ignored, ent->name);\n+\t\tfree(ent);\n \t}\n \n \tfree(dir.entries);\n+\tfree(dir.ignored);\n+\tclear_directory(&dir);\n \n \tif (advice_status_u_option) {\n \t\tstruct timeval t_end;\n-- \n1.8.1.2.8038.ge6fb49b.dirty\n"},{"id":"214646","messageId":"7vobdcerw4.fsf@alter.siamese.dyndns.org","threadId":"33220","inReplyTo":"516EFCEC.1090803@gmail.com","subject":"Re: [PATCH v2 00/14] Improve git-status --ignored","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-17T22:03:55Z","receivedAt":"2013-04-17T22:03:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karsten Blees <karsten.blees@gmail.com> writes:\n\n> I'll send fixups for #11 and #14, or you can pick the entire series rebased to pu from:\n> https://github.com/kblees/git/tree/kb/improve-git-status-ignored-v2-pu\n> git pull git://github.com/kblees/git.git kb/improve-git-status-ignored-v2-pu\n\nWill take a look; very much appreciated.\n"}]}