{"thread":{"id":"33221","subject":"[PATCH 1/8] dir.c: git-status --ignored: don't drop ignored directories","startedAt":"2013-03-18T20:28:29Z","lastAt":"2013-03-18T22:00:53Z","messageCount":10,"participants":["Karsten Blees","Eric Sunshine"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"211595","messageId":"514778ED.5090204@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 1/8] dir.c: git-status --ignored: don't drop ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:28:29Z","receivedAt":"2013-03-18T20:28:29Z","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.8021.g7e51819\n"},{"id":"211596","messageId":"514778F4.3070106@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 2/8] dir.c: git-status --ignored: don't list files in ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:28:36Z","receivedAt":"2013-03-18T20:28:36Z","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.8021.g7e51819\n"},{"id":"211597","messageId":"514778FB.20603@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 3/8] dir.c: git-status --ignored: don't list empty ignored directories","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:28:43Z","receivedAt":"2013-03-18T20:28:43Z","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.8021.g7e51819\n"},{"id":"211602","messageId":"51477901.9020804@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 4/8] dir.c: git-status --ignored: don't list empty directories as ignored","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:28:49Z","receivedAt":"2013-03-18T20:28:49Z","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 dirctory 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                      | 17 ++++++++---------\n t/t7061-wtstatus-ignore.sh | 26 ++++++++++++++++++++++++--\n 2 files changed, 32 insertions(+), 11 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex fd1f088..64457db 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1071,17 +1071,16 @@ 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-\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\tint untracked;\n+\t\tdir->flags &= ~(DIR_SHOW_IGNORED|DIR_SHOW_OTHER_DIRECTORIES);\n+\t\tuntracked = read_directory_recursive(dir, dirname, len, 1, simplify);\n+\t\tdir->flags |= DIR_SHOW_IGNORED|DIR_SHOW_OTHER_DIRECTORIES;\n+\n+\t\tif (untracked)\n+\t\t\treturn ignore_directory;\n \t}\n \tif (!(dir->flags & DIR_SHOW_IGNORED) &&\n \t    !(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.8021.g7e51819\n"},{"id":"211598","messageId":"51477907.7030302@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 5/8] dir.c: move prep_exclude and factor out parts of last_exclude_matching","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:28:55Z","receivedAt":"2013-03-18T20:28:55Z","isPatch":true,"sender":{"key":"karsten.blees@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1111200?v=4"},"body":"Move code around in preparation for the next patch.\n\nSigned-off-by: Karsten Blees <blees@dcon.de>\n---\n dir.c | 181 ++++++++++++++++++++++++++++++++++--------------------------------\n 1 file changed, 94 insertions(+), 87 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 64457db..417feaa 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@@ -751,25 +679,13 @@ int is_excluded_from_list(const char *pathname,\n \treturn -1; /* undecided */\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- * undecided.\n- */\n-static 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+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 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@@ -781,6 +697,97 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,\n \t\t}\n \t}\n \treturn NULL;\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+ * undecided.\n+ */\n+static 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+\tint pathlen = strlen(pathname);\n+\tconst char *basename = strrchr(pathname, '/');\n+\tbasename = (basename) ? basename+1 : pathname;\n+\n+\tprep_exclude(dir, pathname, basename-pathname);\n+\treturn last_exclude_matching_from_lists(dir, pathname, pathlen,\n+\t\t\tbasename, dtype_p);\n }\n \n /*\n-- \n1.8.1.2.8021.g7e51819\n"},{"id":"211599","messageId":"5147790E.1090107@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 6/8] dir.c: unify is_excluded and is_path_excluded APIs","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:29:02Z","receivedAt":"2013-03-18T20:29:02Z","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 acutally 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 | 106 ++++++++++++++++++++++++++----------------------------------------\n dir.h |   6 ++--\n 2 files changed, 45 insertions(+), 67 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 417feaa..16fee2c 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@@ -786,6 +808,8 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,\n \tbasename = (basename) ? basename+1 : pathname;\n \n \tprep_exclude(dir, pathname, basename-pathname);\n+\tif (dir->exclude)\n+\t\treturn dir->exclude;\n \treturn last_exclude_matching_from_lists(dir, pathname, pathlen,\n \t\t\tbasename, dtype_p);\n }\n@@ -808,13 +832,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@@ -830,49 +851,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.8021.g7e51819\n"},{"id":"211600","messageId":"5147791F.90008@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 7/8] dir.c: replace is_path_excluded with now equivalent is_excluded API","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:29:19Z","receivedAt":"2013-03-18T20:29:19Z","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 16fee2c..086a169 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@@ -819,7 +819,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@@ -828,47 +828,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@@ -1045,15 +1004,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@@ -1088,27 +1038,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@@ -1265,12 +1201,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.8021.g7e51819\n"},{"id":"211601","messageId":"51477927.9090500@gmail.com","threadId":"33221","inReplyTo":"514775FA.9080304@gmail.com","subject":"[PATCH 8/8] dir.c: git-status: avoid is_excluded checks for tracked files","fromName":"Karsten Blees","fromEmail":"karsten.blees@gmail.com","sentAt":"2013-03-18T20:29:27Z","receivedAt":"2013-03-18T20:29:27Z","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 086a169..c159000 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1026,28 +1026,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@@ -1170,7 +1148,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@@ -1182,9 +1169,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@@ -1201,7 +1185,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.8021.g7e51819\n"},{"id":"211610","messageId":"CAPig+cTzOtN128FOFAEui8YBoy1Q89Yb=iSGtVsgi2jLs7AeOQ@mail.gmail.com","threadId":"33221","inReplyTo":"51477901.9020804@gmail.com","subject":"Re: [PATCH 4/8] dir.c: git-status --ignored: don't list empty directories as ignored","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-03-18T21:59:29Z","receivedAt":"2013-03-18T21:59:29Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Mar 18, 2013 at 4:28 PM, Karsten Blees <karsten.blees@gmail.com> wrote:\n> When checking if a directory is already listed as untracked (i.e. shouldn't\n> be listed as ignored as well), don't assume that the dirctory has only\n\ns/dirctory/directory/\n\n> ignored files if it doesn't have untracked files, as the directory may be\n> empty.\n"},{"id":"211611","messageId":"CAPig+cRMndy=OLci6nZF9HDn7y9WgCZT=_f_w4CdQEno5h6VaQ@mail.gmail.com","threadId":"33221","inReplyTo":"5147790E.1090107@gmail.com","subject":"Re: [PATCH 6/8] dir.c: unify is_excluded and is_path_excluded APIs","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-03-18T22:00:53Z","receivedAt":"2013-03-18T22:00:53Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Mar 18, 2013 at 4:29 PM, Karsten Blees <karsten.blees@gmail.com> wrote:\n> Directories could also be excluded by exclude patterns specified on the\n> command line or .git/info/exclude, so we cannot simply skip prep_exclude\n> entirely if there's no .gitignore file name (dir_struct.exclude_per_dir).\n> Move this check to just before acutally reading the file.\n\ns/acutally/actually/\n"}]}