{"thread":{"id":"37820","subject":"[PATCH 00/19] Untracked cache to speed up \"git status\"","startedAt":"2014-10-27T12:10:27Z","lastAt":"2014-11-17T20:35:28Z","messageCount":65,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Duy Nguyen","Torsten Bögershausen","Eric Sunshine","brian m. carlson","David Turner"],"isPatch":true,"patchVersion":1,"patchTotal":19},"messages":[{"id":"251096","messageId":"1414411846-4450-1-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":null,"subject":"[PATCH 00/19] Untracked cache to speed up \"git status\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:27Z","receivedAt":"2014-10-27T12:10:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The last post was five months ago [1]. It's probably time for a resend\nin case I'm hit by a bus. Numbers are in 17/19, saving about 40% time\non \"git status\". Details are in 02/19 and 06/19. Still on the table:\n\n - index-helper series [2] probably helps save about 10-15% in total\n - watchman support to lower the numbers even more, but not for Windows\n\nI still need to see how watchman can be made on top of this.\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/248306\n[2] http://thread.gmane.org/gmane.comp.version-control.git/254314/focus=254318\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251097","messageId":"1414411846-4450-2-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 01/19] dir.c: optionally compute sha-1 of a .gitignore file","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:28Z","receivedAt":"2014-10-27T12:10:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is not used anywhere yet. But the goal is to compare quickly if a\n.gitignore file has changed when we have the SHA-1 of both old (cached\nsomewhere) and new (from index or a tree) versions.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 50 +++++++++++++++++++++++++++++++++++++++++++-------\n dir.h |  5 +++++\n 2 files changed, 48 insertions(+), 7 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex bd274a7..33a35c1 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -466,7 +466,8 @@ void add_exclude(const char *string, const char *base,\n \tx->el = el;\n }\n \n-static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n+static void *read_skip_worktree_file_from_index(const char *path, size_t *size,\n+\t\t\t\t\t\tstruct sha1_stat *ss)\n {\n \tint pos, len;\n \tunsigned long sz;\n@@ -485,6 +486,10 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n \t\treturn NULL;\n \t}\n \t*size = xsize_t(sz);\n+\tif (ss) {\n+\t\tmemset(&ss->stat, 0, sizeof(ss->stat));\n+\t\thashcpy(ss->sha1, active_cache[pos]->sha1);\n+\t}\n \treturn data;\n }\n \n@@ -529,11 +534,18 @@ static void trim_trailing_spaces(char *buf)\n \t\t*last_space = '\\0';\n }\n \n-int add_excludes_from_file_to_list(const char *fname,\n-\t\t\t\t   const char *base,\n-\t\t\t\t   int baselen,\n-\t\t\t\t   struct exclude_list *el,\n-\t\t\t\t   int check_index)\n+/*\n+ * Given a file with name \"fname\", read it (either from disk, or from\n+ * the index if \"check_index\" is non-zero), parse it and store the\n+ * exclude rules in \"el\".\n+ *\n+ * If \"ss\" is not NULL, compute SHA-1 of the exclude file and fill\n+ * stat data from disk (only valid if add_excludes returns zero). If\n+ * ss_valid is non-zero, \"ss\" must contain good value as input.\n+ */\n+static int add_excludes(const char *fname, const char *base, int baselen,\n+\t\t\tstruct exclude_list *el, int check_index,\n+\t\t\tstruct sha1_stat *ss, int ss_valid)\n {\n \tstruct stat st;\n \tint fd, i, lineno = 1;\n@@ -547,7 +559,7 @@ int add_excludes_from_file_to_list(const char *fname,\n \t\tif (0 <= fd)\n \t\t\tclose(fd);\n \t\tif (!check_index ||\n-\t\t    (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)\n+\t\t    (buf = read_skip_worktree_file_from_index(fname, &size, ss)) == NULL)\n \t\t\treturn -1;\n \t\tif (size == 0) {\n \t\t\tfree(buf);\n@@ -560,6 +572,10 @@ int add_excludes_from_file_to_list(const char *fname,\n \t} else {\n \t\tsize = xsize_t(st.st_size);\n \t\tif (size == 0) {\n+\t\t\tif (ss) {\n+\t\t\t\tfill_stat_data(&ss->stat, &st);\n+\t\t\t\thashcpy(ss->sha1, EMPTY_BLOB_SHA1_BIN);\n+\t\t\t}\n \t\t\tclose(fd);\n \t\t\treturn 0;\n \t\t}\n@@ -571,6 +587,19 @@ int add_excludes_from_file_to_list(const char *fname,\n \t\t}\n \t\tbuf[size++] = '\\n';\n \t\tclose(fd);\n+\t\tif (ss) {\n+\t\t\tint pos;\n+\t\t\tif (ss_valid && !match_stat_data(&ss->stat, &st))\n+\t\t\t\t; /* no content change, ss->sha1 still good */\n+\t\t\telse if (check_index &&\n+\t\t\t\t (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&\n+\t\t\t\t !ce_stage(active_cache[pos]) &&\n+\t\t\t\t ce_uptodate(active_cache[pos]))\n+\t\t\t\thashcpy(ss->sha1, active_cache[pos]->sha1);\n+\t\t\telse\n+\t\t\t\thash_sha1_file(buf, size, \"blob\", ss->sha1);\n+\t\t\tfill_stat_data(&ss->stat, &st);\n+\t\t}\n \t}\n \n \tel->filebuf = buf;\n@@ -589,6 +618,13 @@ int add_excludes_from_file_to_list(const char *fname,\n \treturn 0;\n }\n \n+int add_excludes_from_file_to_list(const char *fname, const char *base,\n+\t\t\t\t   int baselen, struct exclude_list *el,\n+\t\t\t\t   int check_index)\n+{\n+\treturn add_excludes(fname, base, baselen, el, check_index, NULL, 0);\n+}\n+\n struct exclude_list *add_exclude_list(struct dir_struct *dir,\n \t\t\t\t      int group_type, const char *src)\n {\ndiff --git a/dir.h b/dir.h\nindex 6c45e9d..032d197 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -73,6 +73,11 @@ struct exclude_list_group {\n \tstruct exclude_list *el;\n };\n \n+struct sha1_stat {\n+\tstruct stat_data stat;\n+\tunsigned char sha1[20];\n+};\n+\n struct dir_struct {\n \tint nr, alloc;\n \tint ignored_nr, ignored_alloc;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251098","messageId":"1414411846-4450-3-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 02/19] untracked cache: record .gitignore information and dir hierarchy","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:29Z","receivedAt":"2014-10-27T12:10:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The idea is if we can capture all input and (non-rescursive) output of\nread_directory_recursive(), and can verify later that all the input is\nthe same, then the second r_d_r() should produce the same output as in\nthe first run.\n\nThe requirement for this to work is stat info of a directory MUST\nchange if an entry is added to or removed from that directory (and\nshould not change often otherwise). If your OS and filesytem do not\nmeet this requirement, untracked cache is not for you. Most file\nsystems on *nix should be fine. On Windows, NTFS is fine while FAT may\nbe not [1] even though FAT on Linux seems to be fine.\n\nThe list of input of r_d_r() is in the big comment block in dir.h. In\nshort, the output of a directory (not counting subdirs) mainly depends\non stat info of the directory in question, all .gitignore leading to\nit and the check_only flag when r_d_r() is called recursively. This\npatch records all this info (and the output) as r_d_r() runs.\n\nTwo hash_sha1_file() are required for $GIT_DIR/info/exclude and\ncore.excludesfile unless their stat data matches. hash_sha1_file() is\nonly needed when .gitignore files in the worktree are modified,\notherwise their SHA-1 in index is used (see the previous patch).\n\nWe could store stat data for .gitignore files so we don't have to\nrehash them if their content is different from index, but I think\n.gitignore files are rarely modified, so not worth extra cache data\n(and hashing penalty read-cache.c:verify_hdr(), as we will be storing\nthis as an index extension).\n\nThe implication is, if you change .gitignore, you better add it to the\nindex soon or you lose all the benefit of untracked cache because a\nmodified .gitignore invalidates all subdirs recursively. This is\nespecially bad for .gitignore at root.\n\nThis cached output is about untracked files only, not ignored files\nbecause the number of tracked files is usually small, so small cache\noverhead, while the number of ignored files could go really high\n(e.g. *.o files mixing with source code).\n\n[1] \"Description of NTFS date and time stamps for files and folders\"\n    http://support.microsoft.com/kb/299648\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------\n dir.h |  60 +++++++++++++++++++++++++++\n 2 files changed, 189 insertions(+), 21 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 33a35c1..25d8c5d 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -32,7 +32,7 @@ enum path_treatment {\n };\n \n static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n-\tconst char *path, int len,\n+\tconst char *path, int len, struct untracked_cache_dir *untracked,\n \tint check_only, const struct path_simplify *simplify);\n static int get_dtype(struct dirent *de, const char *path, int len);\n \n@@ -535,6 +535,53 @@ static void trim_trailing_spaces(char *buf)\n }\n \n /*\n+ * Given a subdirectory name and \"dir\" of the current directory,\n+ * search the subdir in \"dir\" and return it, or create a new one if it\n+ * does not exist in \"dir\".\n+ *\n+ * If \"name\" has the trailing slash, it'll be excluded in the search.\n+ */\n+static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,\n+\t\t\t\t\t\t    struct untracked_cache_dir *dir,\n+\t\t\t\t\t\t    const char *name, int len)\n+{\n+\tint first, last;\n+\tstruct untracked_cache_dir *d;\n+\tif (!dir)\n+\t\treturn NULL;\n+\tif (len && name[len - 1] == '/')\n+\t\tlen--;\n+\tfirst = 0;\n+\tlast = dir->dirs_nr;\n+\twhile (last > first) {\n+\t\tint cmp, next = (last + first) >> 1;\n+\t\td = dir->dirs[next];\n+\t\tcmp = strncmp(name, d->name, len);\n+\t\tif (!cmp && strlen(d->name) > len)\n+\t\t\tcmp = -1;\n+\t\tif (!cmp)\n+\t\t\treturn d;\n+\t\tif (cmp < 0) {\n+\t\t\tlast = next;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tfirst = next+1;\n+\t}\n+\n+\tuc->dir_created++;\n+\td = xmalloc(sizeof(*d) + len);\n+\tmemset(d, 0, sizeof(*d) + len);\n+\tmemcpy(d->name, name, len);\n+\n+\tALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);\n+\tmemmove(dir->dirs + first + 1, dir->dirs + first,\n+\t\t(dir->dirs_nr - first) * sizeof(*dir->dirs));\n+\tdir->dirs_nr++;\n+\tdir->dirs[first] = d;\n+\treturn d;\n+}\n+\n+/*\n  * Given a file with name \"fname\", read it (either from disk, or from\n  * the index if \"check_index\" is non-zero), parse it and store the\n  * exclude rules in \"el\".\n@@ -642,14 +689,20 @@ struct exclude_list *add_exclude_list(struct dir_struct *dir,\n /*\n  * Used to set up core.excludesfile and .git/info/exclude lists.\n  */\n-void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n+static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n+\t\t\t\t     struct sha1_stat *ss, int ss_valid)\n {\n \tstruct exclude_list *el;\n \tel = add_exclude_list(dir, EXC_FILE, fname);\n-\tif (add_excludes_from_file_to_list(fname, \"\", 0, el, 0) < 0)\n+\tif (add_excludes(fname, \"\", 0, el, 0, ss, ss_valid) < 0)\n \t\tdie(\"cannot use %s as an exclude file\", fname);\n }\n \n+void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n+{\n+\tadd_excludes_from_file_1(dir, fname, NULL, 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@@ -824,6 +877,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \tstruct exclude_list_group *group;\n \tstruct exclude_list *el;\n \tstruct exclude_stack *stk = NULL;\n+\tstruct untracked_cache_dir *untracked;\n \tint current;\n \n \tgroup = &dir->exclude_list_group[EXC_DIRS];\n@@ -861,9 +915,15 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t/* Read from the parent directories and push them down. */\n \tcurrent = stk ? stk->baselen : -1;\n \tstrbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);\n+\tif (dir->untracked)\n+\t\tuntracked = stk ? stk->ucd : dir->untracked->root;\n+\telse\n+\t\tuntracked = NULL;\n+\n \twhile (current < baselen) {\n \t\tstruct exclude_stack *stk = xcalloc(1, sizeof(*stk));\n \t\tconst char *cp;\n+\t\tstruct sha1_stat ss;\n \n \t\tif (current < 0) {\n \t\t\tcp = base;\n@@ -873,10 +933,15 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tif (!cp)\n \t\t\t\tdie(\"oops in prep_exclude\");\n \t\t\tcp++;\n+\t\t\tuntracked =\n+\t\t\t\tlookup_untracked(dir->untracked, untracked,\n+\t\t\t\t\t\t base + current,\n+\t\t\t\t\t\t cp - base - current);\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\tstk->ucd = untracked;\n \t\tel = add_exclude_list(dir, EXC_DIRS, NULL);\n \t\tstrbuf_add(&dir->basebuf, base + current, stk->baselen - current);\n \t\tassert(stk->baselen == dir->basebuf.len);\n@@ -899,6 +964,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t}\n \n \t\t/* Try to read per-directory file */\n+\t\thashclr(ss.sha1);\n \t\tif (dir->exclude_per_dir) {\n \t\t\t/*\n \t\t\t * dir->basebuf gets reused by the traversal, but we\n@@ -912,8 +978,11 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tstrbuf_addbuf(&sb, &dir->basebuf);\n \t\t\tstrbuf_addstr(&sb, dir->exclude_per_dir);\n \t\t\tel->src = strbuf_detach(&sb, NULL);\n-\t\t\tadd_excludes_from_file_to_list(el->src, el->src,\n-\t\t\t\t\t\t       stk->baselen, el, 1);\n+\t\t\tadd_excludes(el->src, el->src, stk->baselen, el, 1,\n+\t\t\t\t     untracked ? &ss : NULL, 0);\n+\t\t}\n+\t\tif (untracked) {\n+\t\t\thashcpy(untracked->exclude_sha1, ss.sha1);\n \t\t}\n \t\tdir->exclude_stack = stk;\n \t\tcurrent = stk->baselen;\n@@ -1094,6 +1163,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)\n  *  (c) otherwise, we recurse into it.\n  */\n static enum path_treatment treat_directory(struct dir_struct *dir,\n+\tstruct untracked_cache_dir *untracked,\n \tconst char *dirname, int len, int exclude,\n \tconst struct path_simplify *simplify)\n {\n@@ -1121,7 +1191,9 @@ static enum path_treatment treat_directory(struct dir_struct *dir,\n \tif (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))\n \t\treturn exclude ? path_excluded : path_untracked;\n \n-\treturn read_directory_recursive(dir, dirname, len, 1, simplify);\n+\tuntracked = lookup_untracked(dir->untracked, untracked, dirname, len);\n+\treturn read_directory_recursive(dir, dirname, len,\n+\t\t\t\t\tuntracked, 1, simplify);\n }\n \n /*\n@@ -1237,6 +1309,7 @@ static int get_dtype(struct dirent *de, const char *path, int len)\n }\n \n static enum path_treatment treat_one_path(struct dir_struct *dir,\n+\t\t\t\t\t  struct untracked_cache_dir *untracked,\n \t\t\t\t\t  struct strbuf *path,\n \t\t\t\t\t  const struct path_simplify *simplify,\n \t\t\t\t\t  int dtype, struct dirent *de)\n@@ -1289,7 +1362,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\treturn path_none;\n \tcase DT_DIR:\n \t\tstrbuf_addch(path, '/');\n-\t\treturn treat_directory(dir, path->buf, path->len, exclude,\n+\t\treturn treat_directory(dir, untracked, path->buf, path->len, exclude,\n \t\t\tsimplify);\n \tcase DT_REG:\n \tcase DT_LNK:\n@@ -1298,6 +1371,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n }\n \n static enum path_treatment treat_path(struct dir_struct *dir,\n+\t\t\t\t      struct untracked_cache_dir *untracked,\n \t\t\t\t      struct dirent *de,\n \t\t\t\t      struct strbuf *path,\n \t\t\t\t      int baselen,\n@@ -1313,7 +1387,16 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n \t\treturn path_none;\n \n \tdtype = DTYPE(de);\n-\treturn treat_one_path(dir, path, simplify, dtype, de);\n+\treturn treat_one_path(dir, untracked, path, simplify, dtype, de);\n+}\n+\n+static void add_untracked(struct untracked_cache_dir *dir, const char *name)\n+{\n+\tif (!dir)\n+\t\treturn;\n+\tALLOC_GROW(dir->untracked, dir->untracked_nr + 1,\n+\t\t   dir->untracked_alloc);\n+\tdir->untracked[dir->untracked_nr++] = xstrdup(name);\n }\n \n /*\n@@ -1329,7 +1412,7 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n  */\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    struct untracked_cache_dir *untracked, int check_only,\n \t\t\t\t    const struct path_simplify *simplify)\n {\n \tDIR *fdir;\n@@ -1343,24 +1426,36 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \tif (!fdir)\n \t\tgoto out;\n \n+\tif (untracked)\n+\t\tuntracked->check_only = !!check_only;\n+\n \twhile ((de = readdir(fdir)) != NULL) {\n \t\t/* check how the file or directory should be treated */\n-\t\tstate = treat_path(dir, de, &path, baselen, simplify);\n+\t\tstate = treat_path(dir, untracked, de, &path, baselen, simplify);\n+\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\tstruct untracked_cache_dir *ud;\n+\t\t\tud = lookup_untracked(dir->untracked, untracked,\n+\t\t\t\t\t      path.buf + baselen,\n+\t\t\t\t\t      path.len - baselen);\n+\t\t\tsubdir_state =\n+\t\t\t\tread_directory_recursive(dir, path.buf, path.len,\n+\t\t\t\t\t\t\t ud, 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\tif (dir_state == path_untracked) {\n+\t\t\t\tif (untracked)\n+\t\t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\t\tbreak;\n+\t\t\t}\n \t\t\t/* skip the dir_add_* part */\n \t\t\tcontinue;\n \t\t}\n@@ -1378,8 +1473,11 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\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\tif (dir->flags & DIR_SHOW_IGNORED)\n+\t\t\t\tbreak;\n+\t\t\tdir_add_name(dir, path.buf, path.len);\n+\t\t\tif (untracked)\n+\t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\tbreak;\n \n \t\tdefault:\n@@ -1456,7 +1554,7 @@ static int treat_leading_path(struct dir_struct *dir,\n \t\t\tbreak;\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\tif (treat_one_path(dir, NULL, &sb, simplify,\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@@ -1496,7 +1594,9 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \t */\n \tsimplify = create_simplify(pathspec ? pathspec->_raw : NULL);\n \tif (!len || treat_leading_path(dir, path, len, simplify))\n-\t\tread_directory_recursive(dir, path, len, 0, simplify);\n+\t\tread_directory_recursive(dir, path, len,\n+\t\t\t\t\t dir->untracked ? dir->untracked->root : NULL,\n+\t\t\t\t\t 0, simplify);\n \tfree_simplify(simplify);\n \tqsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);\n \tqsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);\n@@ -1666,10 +1766,18 @@ void setup_standard_excludes(struct dir_struct *dir)\n \t\thome_config_paths(NULL, &xdg_path, \"ignore\");\n \t\texcludes_file = xdg_path;\n \t}\n-\tif (!access_or_warn(path, R_OK, 0))\n-\t\tadd_excludes_from_file(dir, path);\n-\tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n-\t\tadd_excludes_from_file(dir, excludes_file);\n+\tif (!access_or_warn(path, R_OK, 0)) {\n+\t\tstruct sha1_stat *ss = NULL;\n+\t\tif (dir->untracked)\n+\t\t\tss = &dir->ss_info_exclude;\n+\t\tadd_excludes_from_file_1(dir, path, ss, 0);\n+\t}\n+\tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) {\n+\t\tstruct sha1_stat *ss = NULL;\n+\t\tif (dir->untracked)\n+\t\t\tss = &dir->ss_excludes_file;\n+\t\tadd_excludes_from_file_1(dir, excludes_file, ss, 0);\n+\t}\n }\n \n int remove_path(const char *name)\ndiff --git a/dir.h b/dir.h\nindex 032d197..f282501 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -66,6 +66,7 @@ struct exclude_stack {\n \tstruct exclude_stack *prev; /* the struct exclude_stack for the parent directory */\n \tint baselen;\n \tint exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */\n+\tstruct untracked_cache_dir *ucd;\n };\n \n struct exclude_list_group {\n@@ -78,6 +79,60 @@ struct sha1_stat {\n \tunsigned char sha1[20];\n };\n \n+/*\n+ *  Untracked cache\n+ *\n+ *  The following inputs are sufficient to determine what files in a\n+ *  directory are excluded:\n+ *\n+ *   - The list of files and directories of the direction in question\n+ *   - The $GIT_DIR/index\n+ *   - dir_struct flags\n+ *   - The content of $GIT_DIR/info/exclude\n+ *   - The content of core.excludesfile\n+ *   - The content (or the lack) of .gitignore of all parent directories\n+ *     from $GIT_WORK_TREE\n+ *   - The check_only flag in read_directory_recursive (for\n+ *     DIR_HIDE_EMPTY_DIRECTORIES)\n+ *\n+ *  The first input can be checked using directory mtime. In many\n+ *  filesystems, directory mtime (stat_data field) is updated when its\n+ *  files or direct subdirs are added or removed.\n+ *\n+ *  The second one can be hooked from cache_tree_invalidate_path().\n+ *  Whenever a file (or a submodule) is added or removed from a\n+ *  directory, we invalidate that directory.\n+ *\n+ *  The remaining inputs are easy, their SHA-1 could be used to verify\n+ *  their contents (exclude_sha1[], info_exclude_sha1[] and\n+ *  excludes_file_sha1[])\n+ */\n+struct untracked_cache_dir {\n+\tstruct untracked_cache_dir **dirs;\n+\tchar **untracked;\n+\t/* null SHA-1 means this directory does not have .gitignore */\n+\tunsigned char exclude_sha1[20];\n+\tstruct stat_data stat_data;\n+\tunsigned int check_only : 1;\n+\tunsigned int untracked_nr : 29;\n+\tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n+\tchar name[1];\n+};\n+\n+struct untracked_cache {\n+\tstruct sha1_stat ss_info_exclude;\n+\tstruct sha1_stat ss_excludes_file;\n+\tconst char *exclude_per_dir;\n+\t/*\n+\t * dir_struct#flags must match dir_flags or the untracked\n+\t * cache is ignored.\n+\t */\n+\tunsigned dir_flags;\n+\tstruct untracked_cache_dir *root;\n+\t/* Statistics */\n+\tint dir_created;\n+};\n+\n struct dir_struct {\n \tint nr, alloc;\n \tint ignored_nr, ignored_alloc;\n@@ -125,6 +180,11 @@ struct dir_struct {\n \tstruct exclude_stack *exclude_stack;\n \tstruct exclude *exclude;\n \tstruct strbuf basebuf;\n+\n+\t/* Enable untracked file cache if set */\n+\tstruct untracked_cache *untracked;\n+\tstruct sha1_stat ss_info_exclude;\n+\tstruct sha1_stat ss_excludes_file;\n };\n \n /*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251099","messageId":"1414411846-4450-4-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 03/19] untracked cache: initial untracked cache validation","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:30Z","receivedAt":"2014-10-27T12:10:30Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Make sure the starting conditions and all global exclude files are\ngood to go. If not, either disable untracked cache completely, or wipe\nout the cache and start fresh.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n dir.h |   4 +++\n 2 files changed, 114 insertions(+), 3 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 25d8c5d..5161f74 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -581,6 +581,22 @@ static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,\n \treturn d;\n }\n \n+static void do_invalidate_gitignore(struct untracked_cache_dir *dir)\n+{\n+\tint i;\n+\tdir->valid = 0;\n+\tdir->untracked_nr = 0;\n+\tfor (i = 0; i < dir->dirs_nr; i++)\n+\t\tdo_invalidate_gitignore(dir->dirs[i]);\n+}\n+\n+static void invalidate_gitignore(struct untracked_cache *uc,\n+\t\t\t\t struct untracked_cache_dir *dir)\n+{\n+\tuc->gitignore_invalidated++;\n+\tdo_invalidate_gitignore(dir);\n+}\n+\n /*\n  * Given a file with name \"fname\", read it (either from disk, or from\n  * the index if \"check_index\" is non-zero), parse it and store the\n@@ -693,6 +709,13 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n \t\t\t\t     struct sha1_stat *ss, int ss_valid)\n {\n \tstruct exclude_list *el;\n+\t/*\n+\t * catch setup_standard_excludes() that's called before\n+\t * dir->untracked is assigned. That function behaves\n+\t * differently when dir->untracked is non-NULL.\n+\t */\n+\tif (!dir->untracked)\n+\t\tdir->unmanaged_exclude_files++;\n \tel = add_exclude_list(dir, EXC_FILE, fname);\n \tif (add_excludes(fname, \"\", 0, el, 0, ss, ss_valid) < 0)\n \t\tdie(\"cannot use %s as an exclude file\", fname);\n@@ -700,6 +723,7 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n \n void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n {\n+\tdir->unmanaged_exclude_files++; /* see validate_untracked_cache() */\n \tadd_excludes_from_file_1(dir, fname, NULL, 0);\n }\n \n@@ -1567,9 +1591,87 @@ static int treat_leading_path(struct dir_struct *dir,\n \treturn rc;\n }\n \n+static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,\n+\t\t\t\t\t\t      int base_len,\n+\t\t\t\t\t\t      const struct pathspec *pathspec)\n+{\n+\tstruct untracked_cache_dir *root;\n+\n+\tif (!dir->untracked)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * We only support $GIT_DIR/info/exclude and core.excludesfile\n+\t * as the global ignore rule files. Any other additions\n+\t * (e.g. from command line) invalidate the cache. This\n+\t * condition also catches running setup_standard_excludes()\n+\t * before setting dir->untracked!\n+\t */\n+\tif (dir->unmanaged_exclude_files)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * Optimize for the main use case only: whole-tree git\n+\t * status. More work involved in treat_leading_path() if we\n+\t * use cache on just a subset of the worktree. pathspec\n+\t * support could make the matter even worse.\n+\t */\n+\tif (base_len || (pathspec && pathspec->nr))\n+\t\treturn NULL;\n+\n+\t/* Different set of flags may produce different results */\n+\tif (dir->flags != dir->untracked->dir_flags ||\n+\t    /*\n+\t     * See treat_directory(), case index_nonexistent. Without\n+\t     * this flag, we may need to also cache .git file content\n+\t     * for the resolve_gitlink_ref() call, which we don't.\n+\t     */\n+\t    !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||\n+\t    /* We don't support collecting ignore files */\n+\t    (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |\n+\t\t\t   DIR_COLLECT_IGNORED)))\n+\t\treturn NULL;\n+\n+\t/*\n+\t * If we use .gitignore in the cache and now you change it to\n+\t * .gitexclude, everything will go wrong.\n+\t */\n+\tif (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&\n+\t    strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))\n+\t\treturn NULL;\n+\n+\t/*\n+\t * EXC_CMDL is not considered in the cache. If people set it,\n+\t * skip the cache.\n+\t */\n+\tif (dir->exclude_list_group[EXC_CMDL].nr)\n+\t\treturn NULL;\n+\n+\tif (!dir->untracked->root) {\n+\t\tconst int len = sizeof(*dir->untracked->root);\n+\t\tdir->untracked->root = xmalloc(len);\n+\t\tmemset(dir->untracked->root, 0, len);\n+\t}\n+\n+\t/* Validate $GIT_DIR/info/exclude and core.excludesfile */\n+\troot = dir->untracked->root;\n+\tif (hashcmp(dir->ss_info_exclude.sha1,\n+\t\t    dir->untracked->ss_info_exclude.sha1)) {\n+\t\tinvalidate_gitignore(dir->untracked, root);\n+\t\tdir->untracked->ss_info_exclude = dir->ss_info_exclude;\n+\t}\n+\tif (hashcmp(dir->ss_excludes_file.sha1,\n+\t\t    dir->untracked->ss_excludes_file.sha1)) {\n+\t\tinvalidate_gitignore(dir->untracked, root);\n+\t\tdir->untracked->ss_excludes_file = dir->ss_excludes_file;\n+\t}\n+\treturn root;\n+}\n+\n int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)\n {\n \tstruct path_simplify *simplify;\n+\tstruct untracked_cache_dir *untracked;\n \n \t/*\n \t * Check out create_simplify()\n@@ -1593,10 +1695,15 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \t * create_simplify().\n \t */\n \tsimplify = create_simplify(pathspec ? pathspec->_raw : NULL);\n+\tuntracked = validate_untracked_cache(dir, len, pathspec);\n+\tif (!untracked)\n+\t\t/*\n+\t\t * make sure untracked cache code path is disabled,\n+\t\t * e.g. prep_exclude()\n+\t\t */\n+\t\tdir->untracked = NULL;\n \tif (!len || treat_leading_path(dir, path, len, simplify))\n-\t\tread_directory_recursive(dir, path, len,\n-\t\t\t\t\t dir->untracked ? dir->untracked->root : NULL,\n-\t\t\t\t\t 0, simplify);\n+\t\tread_directory_recursive(dir, path, len, untracked, 0, simplify);\n \tfree_simplify(simplify);\n \tqsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);\n \tqsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);\ndiff --git a/dir.h b/dir.h\nindex f282501..35701b2 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -114,6 +114,8 @@ struct untracked_cache_dir {\n \tunsigned char exclude_sha1[20];\n \tstruct stat_data stat_data;\n \tunsigned int check_only : 1;\n+\t/* all data in this struct are good */\n+\tunsigned int valid : 1;\n \tunsigned int untracked_nr : 29;\n \tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n \tchar name[1];\n@@ -131,6 +133,7 @@ struct untracked_cache {\n \tstruct untracked_cache_dir *root;\n \t/* Statistics */\n \tint dir_created;\n+\tint gitignore_invalidated;\n };\n \n struct dir_struct {\n@@ -185,6 +188,7 @@ struct dir_struct {\n \tstruct untracked_cache *untracked;\n \tstruct sha1_stat ss_info_exclude;\n \tstruct sha1_stat ss_excludes_file;\n+\tunsigned unmanaged_exclude_files;\n };\n \n /*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251100","messageId":"1414411846-4450-5-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 04/19] untracked cache: invalidate dirs recursively if .gitignore changes","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:31Z","receivedAt":"2014-10-27T12:10:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"It's easy to see that if an existing .gitignore changes, its SHA-1\nwould be different and invalidate_gitignore() is called.\n\nIf .gitignore is removed, add_excludes() will treat it like an empty\n.gitignore, which again should invalidate the cached directory data.\n\nif .gitignore is added, lookup_untracked() already fills initial\n.gitignore SHA-1 as \"empty file\", so again invalidate_gitignore() is\ncalled.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 17 ++++++++++++++++-\n 1 file changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/dir.c b/dir.c\nindex 5161f74..a0a0fa8 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1005,7 +1005,22 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tadd_excludes(el->src, el->src, stk->baselen, el, 1,\n \t\t\t\t     untracked ? &ss : NULL, 0);\n \t\t}\n-\t\tif (untracked) {\n+\t\t/*\n+\t\t * NEEDSWORK: when untracked cache is enabled, prep_exclude()\n+\t\t * will first be called in valid_cached_dir() then maybe many\n+\t\t * times more in last_exclude_matching(). When the cache is\n+\t\t * used, last_exclude_matching() will not be called and\n+\t\t * reading .gitignore content will be a waste.\n+\t\t *\n+\t\t * So when it's called by valid_cached_dir() and we can get\n+\t\t * .gitignore SHA-1 from the index (i.e. .gitignore is not\n+\t\t * modified on work tree), we could delay reading the\n+\t\t * .gitignore content until we absolutely need it in\n+\t\t * last_exclude_matching(). Be careful about ignore rule\n+\t\t * order, though, if you do that.\n+\t\t */\n+\t\tif (untracked && hashcmp(ss.sha1, untracked->exclude_sha1)) {\n+\t\t\tinvalidate_gitignore(dir->untracked, untracked);\n \t\t\thashcpy(untracked->exclude_sha1, ss.sha1);\n \t\t}\n \t\tdir->exclude_stack = stk;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251101","messageId":"1414411846-4450-6-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 05/19] untracked cache: make a wrapper around {open,read,close}dir()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:32Z","receivedAt":"2014-10-27T12:10:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows us to feed different info to read_directory_recursive()\nbased on untracked cache in the next patch.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++--------\n 1 file changed, 47 insertions(+), 8 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex a0a0fa8..2793e57 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -31,6 +31,15 @@ enum path_treatment {\n \tpath_untracked\n };\n \n+/*\n+ * Support data structure for our opendir/readdir/closedir wrappers\n+ */\n+struct cached_dir {\n+\tDIR *fdir;\n+\tstruct untracked_cache_dir *untracked;\n+\tstruct dirent *de;\n+};\n+\n static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \tconst char *path, int len, struct untracked_cache_dir *untracked,\n \tint check_only, const struct path_simplify *simplify);\n@@ -1411,12 +1420,13 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \n static enum path_treatment treat_path(struct dir_struct *dir,\n \t\t\t\t      struct untracked_cache_dir *untracked,\n-\t\t\t\t      struct dirent *de,\n+\t\t\t\t      struct cached_dir *cdir,\n \t\t\t\t      struct strbuf *path,\n \t\t\t\t      int baselen,\n \t\t\t\t      const struct path_simplify *simplify)\n {\n \tint dtype;\n+\tstruct dirent *de = cdir->de;\n \n \tif (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, \".git\"))\n \t\treturn path_none;\n@@ -1438,6 +1448,37 @@ static void add_untracked(struct untracked_cache_dir *dir, const char *name)\n \tdir->untracked[dir->untracked_nr++] = xstrdup(name);\n }\n \n+static int open_cached_dir(struct cached_dir *cdir,\n+\t\t\t   struct dir_struct *dir,\n+\t\t\t   struct untracked_cache_dir *untracked,\n+\t\t\t   struct strbuf *path,\n+\t\t\t   int check_only)\n+{\n+\tmemset(cdir, 0, sizeof(*cdir));\n+\tcdir->untracked = untracked;\n+\tcdir->fdir = opendir(path->len ? path->buf : \".\");\n+\tif (!cdir->fdir)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+int read_cached_dir(struct cached_dir *cdir)\n+{\n+\tif (cdir->fdir) {\n+\t\tcdir->de = readdir(cdir->fdir);\n+\t\tif (!cdir->de)\n+\t\t\treturn -1;\n+\t\treturn 0;\n+\t}\n+\treturn -1;\n+}\n+\n+static void close_cached_dir(struct cached_dir *cdir)\n+{\n+\tif (cdir->fdir)\n+\t\tclosedir(cdir->fdir);\n+}\n+\n /*\n  * Read a directory tree. We currently ignore anything but\n  * directories, regular files and symlinks. That's because git\n@@ -1454,23 +1495,21 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\t\t\t    struct untracked_cache_dir *untracked, int check_only,\n \t\t\t\t    const struct path_simplify *simplify)\n {\n-\tDIR *fdir;\n+\tstruct cached_dir cdir;\n \tenum path_treatment state, subdir_state, dir_state = path_none;\n-\tstruct dirent *de;\n \tstruct strbuf path = STRBUF_INIT;\n \n \tstrbuf_add(&path, base, baselen);\n \n-\tfdir = opendir(path.len ? path.buf : \".\");\n-\tif (!fdir)\n+\tif (open_cached_dir(&cdir, dir, untracked, &path, check_only))\n \t\tgoto out;\n \n \tif (untracked)\n \t\tuntracked->check_only = !!check_only;\n \n-\twhile ((de = readdir(fdir)) != NULL) {\n+\twhile (!read_cached_dir(&cdir)) {\n \t\t/* check how the file or directory should be treated */\n-\t\tstate = treat_path(dir, untracked, de, &path, baselen, simplify);\n+\t\tstate = treat_path(dir, untracked, &cdir, &path, baselen, simplify);\n \n \t\tif (state > dir_state)\n \t\t\tdir_state = state;\n@@ -1523,7 +1562,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\t\tbreak;\n \t\t}\n \t}\n-\tclosedir(fdir);\n+\tclose_cached_dir(&cdir);\n  out:\n \tstrbuf_release(&path);\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251102","messageId":"1414411846-4450-7-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 06/19] untracked cache: record/validate dir mtime and reuse cached output","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:33Z","receivedAt":"2014-10-27T12:10:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The main readdir loop in read_directory_recursive() is replaced with a\nnew one that checks if cached results of a directory is still valid.\n\nIf a file is added or removed from the index, the containing directory\nis invalidated (but not its subdirs). If directory's mtime is changed,\nthe same happens. If a .gitignore is updated, the containing directory\nand all subdirs are invalidated recursively. If dir_struct#flags or\nother conditions change, the cache is ignored.\n\nIf a directory is invalidated, we opendir/readdir/closedir and run the\nexclude machinery on that directory listing as usual. If untracked\ncache is also enabled, we'll update the cache along the way. If a\ndirectory is validated, we simply pull the untracked listing out from\nthe cache. The cache also records the list of direct subdirs that we\nhave to recurse in. Fully excluded directories are seen as \"untracked\nfiles\".\n\nIn the best case when no dirs are invalidated, read_directory()\nbecomes a series of\n\n  stat(dir), open(.gitignore), fstat(), read(), close() and optionally\n  hash_sha1_file()\n\nFor comparison, standard read_directory() is a sequence of\n\n  opendir(), readdir(), open(.gitignore), fstat(), read(), close(), the\n  expensive last_exclude_matching() and closedir().\n\nWe already try not to open(.gitignore) if we know it does not exist,\nso open/fstat/read/close sequence does not apply to every\ndirectory. The sequence could be reduced further, as noted in\nprep_exclude() in another patch. So in theory, the entire best-case\nread_directory sequence could be reduced to a series of stat() and\nnothing else.\n\nThis is not a silver bullet approach. When you compile a C file, for\nexample, the old .o file is removed and a new one with the same name\ncreated, effectively invalidating the containing directory's cache\n(but not its subdirectories). If your build process touches every\ndirectory, this cache adds extra overhead for nothing, so it's a good\nidea to separate generated files from tracked files.. Editors may use\nthe same strategy for saving files. And of course you're out of luck\nrunning your repo on an unsupported filesytem and/or operating system.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n dir.h |   2 ++\n 2 files changed, 123 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 2793e57..55780a7 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -37,7 +37,12 @@ enum path_treatment {\n struct cached_dir {\n \tDIR *fdir;\n \tstruct untracked_cache_dir *untracked;\n+\tint nr_files;\n+\tint nr_dirs;\n+\n \tstruct dirent *de;\n+\tconst char *file;\n+\tstruct untracked_cache_dir *ucd;\n };\n \n static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n@@ -606,6 +611,14 @@ static void invalidate_gitignore(struct untracked_cache *uc,\n \tdo_invalidate_gitignore(dir);\n }\n \n+static void invalidate_directory(struct untracked_cache *uc,\n+\t\t\t\t struct untracked_cache_dir *dir)\n+{\n+\tuc->dir_invalidated++;\n+\tdir->valid = 0;\n+\tdir->untracked_nr = 0;\n+}\n+\n /*\n  * Given a file with name \"fname\", read it (either from disk, or from\n  * the index if \"check_index\" is non-zero), parse it and store the\n@@ -1418,6 +1431,41 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t}\n }\n \n+static enum path_treatment treat_path_fast(struct dir_struct *dir,\n+\t\t\t\t\t   struct untracked_cache_dir *untracked,\n+\t\t\t\t\t   struct cached_dir *cdir,\n+\t\t\t\t\t   struct strbuf *path,\n+\t\t\t\t\t   int baselen,\n+\t\t\t\t\t   const struct path_simplify *simplify)\n+{\n+\tif (!cdir->ucd) {\n+\t\tstrbuf_setlen(path, baselen);\n+\t\tstrbuf_addstr(path, cdir->file);\n+\t\treturn path_untracked;\n+\t}\n+\tstrbuf_setlen(path, baselen);\n+\tstrbuf_addstr(path, cdir->ucd->name);\n+\t/* treat_one_path() does this before it calls treat_directory() */\n+\tif (path->buf[path->len - 1] != '/')\n+\t\tstrbuf_addch(path, '/');\n+\tif (cdir->ucd->check_only)\n+\t\t/*\n+\t\t * check_only is set as a result of treat_directory() getting\n+\t\t * to its bottom. Verify again the same set of directories\n+\t\t * with check_only set.\n+\t\t */\n+\t\treturn read_directory_recursive(dir, path->buf, path->len,\n+\n+\t\t\t\t\t\tcdir->ucd, 1, simplify);\n+\t/*\n+\t * We get path_recurse in the first run when\n+\t * directory_exists_in_index() returns index_nonexistent. We\n+\t * are sure that new changes in the index does not impact the\n+\t * outcome. Return now.\n+\t */\n+\treturn path_recurse;\n+}\n+\n static enum path_treatment treat_path(struct dir_struct *dir,\n \t\t\t\t      struct untracked_cache_dir *untracked,\n \t\t\t\t      struct cached_dir *cdir,\n@@ -1428,6 +1476,9 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n \tint dtype;\n \tstruct dirent *de = cdir->de;\n \n+\tif (!de)\n+\t\treturn treat_path_fast(dir, untracked, cdir, path,\n+\t\t\t\t       baselen, simplify);\n \tif (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, \".git\"))\n \t\treturn path_none;\n \tstrbuf_setlen(path, baselen);\n@@ -1448,6 +1499,52 @@ static void add_untracked(struct untracked_cache_dir *dir, const char *name)\n \tdir->untracked[dir->untracked_nr++] = xstrdup(name);\n }\n \n+static int valid_cached_dir(struct dir_struct *dir,\n+\t\t\t    struct untracked_cache_dir *untracked,\n+\t\t\t    struct strbuf *path,\n+\t\t\t    int check_only)\n+{\n+\tstruct stat st;\n+\n+\tif (!untracked)\n+\t\treturn 0;\n+\n+\tif (stat(path->len ? path->buf : \".\", &st)) {\n+\t\tinvalidate_directory(dir->untracked, untracked);\n+\t\tmemset(&untracked->stat_data, 0, sizeof(untracked->stat_data));\n+\t\treturn 0;\n+\t}\n+\tif (!untracked->valid ||\n+\t    match_stat_data(&untracked->stat_data, &st)) {\n+\t\tif (untracked->valid)\n+\t\t\tinvalidate_directory(dir->untracked, untracked);\n+\t\tfill_stat_data(&untracked->stat_data, &st);\n+\t\treturn 0;\n+\t}\n+\n+\tif (untracked->check_only != !!check_only) {\n+\t\tinvalidate_directory(dir->untracked, untracked);\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * prep_exclude will be called eventually on this directory,\n+\t * but it's called much later in last_exclude_matching(). We\n+\t * need it now to determine the validity of the cache for this\n+\t * path. The next calls will be nearly no-op, the way\n+\t * prep_exclude() is designed.\n+\t */\n+\tif (path->len && path->buf[path->len - 1] != '/') {\n+\t\tstrbuf_addch(path, '/');\n+\t\tprep_exclude(dir, path->buf, path->len);\n+\t\tstrbuf_setlen(path, path->len - 1);\n+\t} else\n+\t\tprep_exclude(dir, path->buf, path->len);\n+\n+\t/* hopefully prep_exclude() haven't invalidated this entry... */\n+\treturn untracked->valid;\n+}\n+\n static int open_cached_dir(struct cached_dir *cdir,\n \t\t\t   struct dir_struct *dir,\n \t\t\t   struct untracked_cache_dir *untracked,\n@@ -1456,7 +1553,11 @@ static int open_cached_dir(struct cached_dir *cdir,\n {\n \tmemset(cdir, 0, sizeof(*cdir));\n \tcdir->untracked = untracked;\n+\tif (valid_cached_dir(dir, untracked, path, check_only))\n+\t\treturn 0;\n \tcdir->fdir = opendir(path->len ? path->buf : \".\");\n+\tif (dir->untracked)\n+\t\tdir->untracked->dir_opened++;\n \tif (!cdir->fdir)\n \t\treturn -1;\n \treturn 0;\n@@ -1470,6 +1571,18 @@ int read_cached_dir(struct cached_dir *cdir)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n+\twhile (cdir->nr_dirs < cdir->untracked->dirs_nr) {\n+\t\tstruct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];\n+\t\tcdir->ucd = d;\n+\t\tcdir->nr_dirs++;\n+\t\treturn 0;\n+\t}\n+\tcdir->ucd = NULL;\n+\tif (cdir->nr_files < cdir->untracked->untracked_nr) {\n+\t\tstruct untracked_cache_dir *d = cdir->untracked;\n+\t\tcdir->file = d->untracked[cdir->nr_files++];\n+\t\treturn 0;\n+\t}\n \treturn -1;\n }\n \n@@ -1477,6 +1590,12 @@ static void close_cached_dir(struct cached_dir *cdir)\n {\n \tif (cdir->fdir)\n \t\tclosedir(cdir->fdir);\n+\t/*\n+\t * We have gone through this directory and found no untracked\n+\t * entries. Mark it valid.\n+\t */\n+\tif (cdir->untracked && !cdir->untracked->valid)\n+\t\tcdir->untracked->valid = 1;\n }\n \n /*\n@@ -1530,7 +1649,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\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\tif (untracked)\n+\t\t\t\tif (cdir.fdir)\n \t\t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1554,7 +1673,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\t\tif (dir->flags & DIR_SHOW_IGNORED)\n \t\t\t\tbreak;\n \t\t\tdir_add_name(dir, path.buf, path.len);\n-\t\t\tif (untracked)\n+\t\t\tif (cdir.fdir)\n \t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\tbreak;\n \ndiff --git a/dir.h b/dir.h\nindex 35701b2..1fefd4e 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -134,6 +134,8 @@ struct untracked_cache {\n \t/* Statistics */\n \tint dir_created;\n \tint gitignore_invalidated;\n+\tint dir_invalidated;\n+\tint dir_opened;\n };\n \n struct dir_struct {\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251103","messageId":"1414411846-4450-8-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 07/19] untracked cache: mark what dirs should be recursed/saved","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:34Z","receivedAt":"2014-10-27T12:10:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If we redo this thing in a functional style, we would have one struct\nuntracked_dir as input tree and another as output. The input is used\nfor verification. The output is a brand new tree, reflecting current\nworktree.\n\nBut that means recreate a lot of dir nodes even if a lot could be\nshared between input and output trees in good cases. So we go with the\nmessy but efficient way, combining both input and output trees into\none. We need a way to know which node in this combined tree belongs to\nthe output. This is the purpose of this \"recurse\" flag.\n\n\"valid\" bit can't be used for this because it's about data of the node\nexcept the subdirs. When we invalidate a directory, we want to keep\ncached data of the subdirs intact even though we don't really know\nwhat subdir still exists (yet). Then we check worktree to see what\nactual subdir remains on disk. Those will have 'recurse' bit set\nagain. If cached data for those are still valid, we may be able to\navoid computing exclude files for them. Those subdirs that are deleted\nwill have 'recurse' remained clear and their 'valid' bits do not\nmatter.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 14 +++++++++++++-\n dir.h |  3 ++-\n 2 files changed, 15 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 55780a7..d9675c8 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -614,9 +614,12 @@ static void invalidate_gitignore(struct untracked_cache *uc,\n static void invalidate_directory(struct untracked_cache *uc,\n \t\t\t\t struct untracked_cache_dir *dir)\n {\n+\tint i;\n \tuc->dir_invalidated++;\n \tdir->valid = 0;\n \tdir->untracked_nr = 0;\n+\tfor (i = 0; i < dir->dirs_nr; i++)\n+\t\tdir->dirs[i]->recurse = 0;\n }\n \n /*\n@@ -1573,6 +1576,10 @@ int read_cached_dir(struct cached_dir *cdir)\n \t}\n \twhile (cdir->nr_dirs < cdir->untracked->dirs_nr) {\n \t\tstruct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];\n+\t\tif (!d->recurse) {\n+\t\t\tcdir->nr_dirs++;\n+\t\t\tcontinue;\n+\t\t}\n \t\tcdir->ucd = d;\n \t\tcdir->nr_dirs++;\n \t\treturn 0;\n@@ -1594,8 +1601,10 @@ static void close_cached_dir(struct cached_dir *cdir)\n \t * We have gone through this directory and found no untracked\n \t * entries. Mark it valid.\n \t */\n-\tif (cdir->untracked && !cdir->untracked->valid)\n+\tif (cdir->untracked) {\n \t\tcdir->untracked->valid = 1;\n+\t\tcdir->untracked->recurse = 1;\n+\t}\n }\n \n /*\n@@ -1838,6 +1847,9 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \t\tinvalidate_gitignore(dir->untracked, root);\n \t\tdir->untracked->ss_excludes_file = dir->ss_excludes_file;\n \t}\n+\n+\t/* Make sure this directory is not dropped out at saving phase */\n+\troot->recurse = 1;\n \treturn root;\n }\n \ndiff --git a/dir.h b/dir.h\nindex 1fefd4e..c302dcb 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -113,8 +113,9 @@ struct untracked_cache_dir {\n \t/* null SHA-1 means this directory does not have .gitignore */\n \tunsigned char exclude_sha1[20];\n \tstruct stat_data stat_data;\n+\tunsigned int recurse : 1;\n \tunsigned int check_only : 1;\n-\t/* all data in this struct are good */\n+\t/* all data except 'dirs' in this struct are good */\n \tunsigned int valid : 1;\n \tunsigned int untracked_nr : 29;\n \tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251104","messageId":"1414411846-4450-9-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 08/19] untracked cache: don't open non-existent .gitignore","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:35Z","receivedAt":"2014-10-27T12:10:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This cuts down a signficant number of open(.gitignore) because most\ndirectories usually don't have .gitignore files.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 26 +++++++++++++++++++++++++-\n 1 file changed, 25 insertions(+), 1 deletion(-)\n\ndiff --git a/dir.c b/dir.c\nindex d9675c8..d4f9830 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1014,7 +1014,21 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \n \t\t/* Try to read per-directory file */\n \t\thashclr(ss.sha1);\n-\t\tif (dir->exclude_per_dir) {\n+\t\tif (dir->exclude_per_dir &&\n+\t\t    /*\n+\t\t     * If we know that no files have been added in\n+\t\t     * this directory (i.e. valid_cached_dir() has\n+\t\t     * been executed and set untracked->valid) ..\n+\t\t     */\n+\t\t    (!untracked || !untracked->valid ||\n+\t\t     /*\n+\t\t      * .. and .gitignore does not exist before\n+\t\t      * (i.e. null exclude_sha1 and skip_worktree is\n+\t\t      * not set). Then we can skip loading .gitignore,\n+\t\t      * which would result in ENOENT anyway.\n+\t\t      * skip_worktree is taken care in read_directory()\n+\t\t      */\n+\t\t     !is_null_sha1(untracked->exclude_sha1))) {\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@@ -1778,6 +1792,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \t\t\t\t\t\t      const struct pathspec *pathspec)\n {\n \tstruct untracked_cache_dir *root;\n+\tint i;\n \n \tif (!dir->untracked)\n \t\treturn NULL;\n@@ -1829,6 +1844,15 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \tif (dir->exclude_list_group[EXC_CMDL].nr)\n \t\treturn NULL;\n \n+\t/*\n+\t * An optimization in prep_exclude() does not play well with\n+\t * CE_SKIP_WORKTREE. It's a rare case anyway, if a single\n+\t * entry has that bit set, disable the whole untracked cache.\n+\t */\n+\tfor (i = 0; i < active_nr; i++)\n+\t\tif (ce_skip_worktree(active_cache[i]))\n+\t\t\treturn NULL;\n+\n \tif (!dir->untracked->root) {\n \t\tconst int len = sizeof(*dir->untracked->root);\n \t\tdir->untracked->root = xmalloc(len);\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251105","messageId":"1414411846-4450-10-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 09/19] untracked cache: save to an index extension","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:36Z","receivedAt":"2014-10-27T12:10:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h      |  3 +++\n dir.c        | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n dir.h        |  1 +\n read-cache.c | 12 +++++++++\n 4 files changed, 101 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 5b86065..4f903fa 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -298,6 +298,8 @@ static inline unsigned int canon_mode(unsigned int mode)\n #define SPLIT_INDEX_ORDERED\t(1 << 6)\n \n struct split_index;\n+struct untracked_cache;\n+\n struct index_state {\n \tstruct cache_entry **cache;\n \tunsigned int version;\n@@ -311,6 +313,7 @@ struct index_state {\n \tstruct hashmap name_hash;\n \tstruct hashmap dir_hash;\n \tunsigned char sha1[20];\n+\tstruct untracked_cache *untracked;\n };\n \n extern struct index_state the_index;\ndiff --git a/dir.c b/dir.c\nindex d4f9830..d696388 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -12,6 +12,7 @@\n #include \"refs.h\"\n #include \"wildmatch.h\"\n #include \"pathspec.h\"\n+#include \"varint.h\"\n \n struct path_simplify {\n \tint len;\n@@ -2145,3 +2146,87 @@ void clear_directory(struct dir_struct *dir)\n \t}\n \tstrbuf_release(&dir->basebuf);\n }\n+\n+struct ondisk_untracked_cache {\n+\tstruct stat_data info_exclude_stat;\n+\tstruct stat_data excludes_file_stat;\n+\tuint32_t dir_flags;\n+\tunsigned char info_exclude_sha1[20];\n+\tunsigned char excludes_file_sha1[20];\n+\tchar exclude_per_dir[1];\n+};\n+\n+static void stat_data_to_disk(struct stat_data *to, const struct stat_data *from)\n+{\n+\tto->sd_ctime.sec  = htonl(from->sd_ctime.sec);\n+\tto->sd_ctime.nsec = htonl(from->sd_ctime.nsec);\n+\tto->sd_mtime.sec  = htonl(from->sd_mtime.sec);\n+\tto->sd_mtime.nsec = htonl(from->sd_mtime.nsec);\n+\tto->sd_dev\t  = htonl(from->sd_dev);\n+\tto->sd_ino\t  = htonl(from->sd_ino);\n+\tto->sd_uid\t  = htonl(from->sd_uid);\n+\tto->sd_gid\t  = htonl(from->sd_gid);\n+\tto->sd_size\t  = htonl(from->sd_size);\n+}\n+\n+static void write_one_dir(struct strbuf *out, struct untracked_cache_dir *untracked)\n+{\n+\tstruct stat_data stat_data;\n+\tunsigned char intbuf[16];\n+\tunsigned int intlen, value;\n+\tint i;\n+\n+\tstat_data_to_disk(&stat_data, &untracked->stat_data);\n+\tstrbuf_add(out, &stat_data, sizeof(stat_data));\n+\tstrbuf_add(out, untracked->exclude_sha1, 20);\n+\n+\t/*\n+\t * untracked_nr should be reset whenever valid is clear, but\n+\t * for safety..\n+\t */\n+\tif (!untracked->valid) {\n+\t\tuntracked->untracked_nr = 0;\n+\t\tuntracked->check_only = 0;\n+\t}\n+\n+\tvalue  = untracked->valid;\n+\tvalue |= untracked->check_only   << 1;\n+\tvalue |= untracked->untracked_nr << 2;\n+\tintlen = encode_varint(value, intbuf);\n+\tstrbuf_add(out, intbuf, intlen);\n+\n+\t/* skip non-recurse directories */\n+\tfor (i = 0, value = 0; i < untracked->dirs_nr; i++)\n+\t\tif (untracked->dirs[i]->recurse)\n+\t\t\tvalue++;\n+\tintlen = encode_varint(value, intbuf);\n+\tstrbuf_add(out, intbuf, intlen);\n+\n+\tstrbuf_add(out, untracked->name, strlen(untracked->name) + 1);\n+\n+\tfor (i = 0; i < untracked->untracked_nr; i++)\n+\t\tstrbuf_add(out, untracked->untracked[i],\n+\t\t\t   strlen(untracked->untracked[i]) + 1);\n+\n+\tfor (i = 0; i < untracked->dirs_nr; i++)\n+\t\tif (untracked->dirs[i]->recurse)\n+\t\t\twrite_one_dir(out, untracked->dirs[i]);\n+}\n+\n+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)\n+{\n+\tstruct ondisk_untracked_cache *ouc;\n+\tint len = 0;\n+\tif (untracked->exclude_per_dir)\n+\t\tlen = strlen(untracked->exclude_per_dir);\n+\touc = xmalloc(sizeof(*ouc) + len);\n+\tstat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);\n+\tstat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);\n+\thashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);\n+\thashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);\n+\touc->dir_flags = htonl(untracked->dir_flags);\n+\tmemcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);\n+\tstrbuf_add(out, ouc, sizeof(*ouc) + len);\n+\tif (untracked->root)\n+\t\twrite_one_dir(out, untracked->root);\n+}\ndiff --git a/dir.h b/dir.h\nindex c302dcb..7ef0f63 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -297,4 +297,5 @@ static inline int dir_path_match(const struct dir_entry *ent,\n \t\t\t      has_trailing_dir);\n }\n \n+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);\n #endif\ndiff --git a/read-cache.c b/read-cache.c\nindex 8f3e9eb..efff4e2 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -38,6 +38,7 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,\n #define CACHE_EXT_TREE 0x54524545\t/* \"TREE\" */\n #define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* \"REUC\" */\n #define CACHE_EXT_LINK 0x6c696e6b\t  /* \"link\" */\n+#define CACHE_EXT_UNTRACKED 0x554E5452\t  /* \"UNTR\" */\n \n /* changes that can be kept in $GIT_DIR/index (basically all extensions) */\n #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \\\n@@ -2035,6 +2036,17 @@ static int do_write_index(struct index_state *istate, int newfd,\n \t\tif (err)\n \t\t\treturn -1;\n \t}\n+\tif (istate->untracked) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\twrite_untracked_extension(&sb, istate->untracked);\n+\t\terr = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED,\n+\t\t\t\t\t     sb.len) < 0 ||\n+\t\t\tce_write(&c, newfd, sb.buf, sb.len) < 0;\n+\t\tstrbuf_release(&sb);\n+\t\tif (err)\n+\t\t\treturn -1;\n+\t}\n \n \tif (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))\n \t\treturn -1;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251106","messageId":"1414411846-4450-11-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 10/19] untracked cache: load from UNTR index extension","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:37Z","receivedAt":"2014-10-27T12:10:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c        | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n dir.h        |   3 ++\n read-cache.c |   5 ++\n 3 files changed, 151 insertions(+), 4 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex d696388..c97b0c3 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2085,15 +2085,27 @@ void setup_standard_excludes(struct dir_struct *dir)\n \t}\n \tif (!access_or_warn(path, R_OK, 0)) {\n \t\tstruct sha1_stat *ss = NULL;\n-\t\tif (dir->untracked)\n+\t\tint ss_valid = 0;\n+\t\tif (dir->untracked) {\n \t\t\tss = &dir->ss_info_exclude;\n-\t\tadd_excludes_from_file_1(dir, path, ss, 0);\n+\t\t\tif (dir->untracked->loaded) {\n+\t\t\t\t*ss = dir->untracked->ss_info_exclude;\n+\t\t\t\tss_valid = 1;\n+\t\t\t}\n+\t\t}\n+\t\tadd_excludes_from_file_1(dir, path, ss, ss_valid);\n \t}\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) {\n \t\tstruct sha1_stat *ss = NULL;\n-\t\tif (dir->untracked)\n+\t\tint ss_valid = 0;\n+\t\tif (dir->untracked) {\n \t\t\tss = &dir->ss_excludes_file;\n-\t\tadd_excludes_from_file_1(dir, excludes_file, ss, 0);\n+\t\t\tif (dir->untracked->loaded) {\n+\t\t\t\t*ss = dir->untracked->ss_excludes_file;\n+\t\t\t\tss_valid = 1;\n+\t\t\t}\n+\t\t}\n+\t\tadd_excludes_from_file_1(dir, excludes_file, ss, ss_valid);\n \t}\n }\n \n@@ -2230,3 +2242,130 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra\n \tif (untracked->root)\n \t\twrite_one_dir(out, untracked->root);\n }\n+\n+static void free_untracked(struct untracked_cache_dir *ucd)\n+{\n+\tint i;\n+\tif (!ucd)\n+\t\treturn;\n+\tfor (i = 0; i < ucd->dirs_nr; i++)\n+\t\tfree_untracked(ucd->dirs[i]);\n+\tfor (i = 0; i < ucd->untracked_nr; i++)\n+\t\tfree(ucd->untracked[i]);\n+\tfree(ucd->untracked);\n+\tfree(ucd->dirs);\n+\tfree(ucd);\n+}\n+\n+void free_untracked_cache(struct untracked_cache *uc)\n+{\n+\tif (uc)\n+\t\tfree_untracked(uc->root);\n+\tfree(uc);\n+}\n+\n+static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from)\n+{\n+\tto->sd_ctime.sec  = get_be32(&from->sd_ctime.sec);\n+\tto->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec);\n+\tto->sd_mtime.sec  = get_be32(&from->sd_mtime.sec);\n+\tto->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec);\n+\tto->sd_dev\t  = get_be32(&from->sd_dev);\n+\tto->sd_ino\t  = get_be32(&from->sd_ino);\n+\tto->sd_uid\t  = get_be32(&from->sd_uid);\n+\tto->sd_gid\t  = get_be32(&from->sd_gid);\n+\tto->sd_size\t  = get_be32(&from->sd_size);\n+}\n+\n+static int read_one_dir(struct untracked_cache_dir **untracked_,\n+\t\t\tconst unsigned char *data_, unsigned long sz)\n+{\n+#define NEXT(x) \\\n+\tnext = data + (x); \\\n+\tif (next > data_ + sz) \\\n+\t\treturn -1;\n+\n+\tstruct untracked_cache_dir ud, *untracked;\n+\tconst unsigned char *next, *data = data_;\n+\tunsigned int value;\n+\tint i, len;\n+\n+\tmemset(&ud, 0, sizeof(ud));\n+\n+\tNEXT(sizeof(struct stat_data));\n+\tstat_data_from_disk(&ud.stat_data, (struct stat_data *)data);\n+\tdata = next;\n+\n+\tNEXT(20);\n+\thashcpy(ud.exclude_sha1, data);\n+\tdata = next;\n+\n+\tnext = data;\n+\tvalue = decode_varint(&next);\n+\tif (next > data_ + sz)\n+\t\treturn -1;\n+\tud.recurse = 1;\n+\tud.valid = value & 1;\n+\tud.check_only = (value >> 1) & 1;\n+\tud.untracked_alloc = ud.untracked_nr = value >> 2;\n+\tif (ud.untracked_nr)\n+\t\tud.untracked = xmalloc(sizeof(*ud.untracked) * ud.untracked_nr);\n+\tdata = next;\n+\n+\tnext = data;\n+\tud.dirs_alloc = ud.dirs_nr = decode_varint(&next);\n+\tif (next > data_ + sz)\n+\t\treturn -1;\n+\tud.dirs = xmalloc(sizeof(*ud.dirs) * ud.dirs_nr);\n+\tdata = next;\n+\n+\tlen = strlen((const char *)data);\n+\tNEXT(len + 1);\n+\t*untracked_ = untracked = xmalloc(sizeof(*untracked) + len);\n+\tmemcpy(untracked, &ud, sizeof(ud));\n+\tmemcpy(untracked->name, data, len + 1);\n+\tdata = next;\n+\n+\tfor (i = 0; i < untracked->untracked_nr; i++) {\n+\t\tlen = strlen((const char *)data);\n+\t\tNEXT(len + 1);\n+\t\tuntracked->untracked[i] = xstrdup((const char*)data);\n+\t\tdata = next;\n+\t}\n+\n+\tfor (i = 0; i < untracked->dirs_nr; i++) {\n+\t\tlen = read_one_dir(untracked->dirs + i, data, sz - (data - data_));\n+\t\tif (len < 0)\n+\t\t\treturn -1;\n+\t\tdata += len;\n+\t}\n+\treturn data - data_;\n+}\n+\n+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)\n+{\n+\tconst struct ondisk_untracked_cache *ouc = data;\n+\tstruct untracked_cache *uc;\n+\tint len;\n+\n+\tif (sz < sizeof(*ouc))\n+\t\treturn NULL;\n+\n+\tuc = xcalloc(1, sizeof(*uc));\n+\tstat_data_from_disk(&uc->ss_info_exclude.stat, &ouc->info_exclude_stat);\n+\tstat_data_from_disk(&uc->ss_excludes_file.stat, &ouc->excludes_file_stat);\n+\thashcpy(uc->ss_info_exclude.sha1, ouc->info_exclude_sha1);\n+\thashcpy(uc->ss_excludes_file.sha1, ouc->excludes_file_sha1);\n+\tuc->dir_flags = get_be32(&ouc->dir_flags);\n+\tuc->exclude_per_dir = xstrdup(ouc->exclude_per_dir);\n+\tuc->loaded = 1;\n+\tlen = sizeof(*ouc) + strlen(ouc->exclude_per_dir);\n+\tif (sz == len)\n+\t\treturn uc;\n+\tif (sz > len &&\n+\t    read_one_dir(&uc->root, (const unsigned char *)data + len,\n+\t\t\t sz - len) == sz - len)\n+\t\treturn uc;\n+\tfree_untracked_cache(uc);\n+\treturn NULL;\n+}\ndiff --git a/dir.h b/dir.h\nindex 7ef0f63..014f3ed 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -137,6 +137,7 @@ struct untracked_cache {\n \tint gitignore_invalidated;\n \tint dir_invalidated;\n \tint dir_opened;\n+\tint loaded;\n };\n \n struct dir_struct {\n@@ -297,5 +298,7 @@ static inline int dir_path_match(const struct dir_entry *ent,\n \t\t\t      has_trailing_dir);\n }\n \n+void free_untracked_cache(struct untracked_cache *);\n+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);\n void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);\n #endif\ndiff --git a/read-cache.c b/read-cache.c\nindex efff4e2..60baeaf 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1359,6 +1359,9 @@ static int read_index_extension(struct index_state *istate,\n \t\tif (read_link_extension(istate, data, sz))\n \t\t\treturn -1;\n \t\tbreak;\n+\tcase CACHE_EXT_UNTRACKED:\n+\t\tistate->untracked = read_untracked_extension(data, sz);\n+\t\tbreak;\n \tdefault:\n \t\tif (*ext < 'A' || 'Z' < *ext)\n \t\t\treturn error(\"index uses %.4s extension, which we do not understand\",\n@@ -1650,6 +1653,8 @@ int discard_index(struct index_state *istate)\n \tistate->cache = NULL;\n \tistate->cache_alloc = 0;\n \tdiscard_split_index(istate);\n+\tfree_untracked_cache(istate->untracked);\n+\tistate->untracked = NULL;\n \treturn 0;\n }\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251107","messageId":"1414411846-4450-12-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 11/19] untracked cache: invalidate at index addition or removal","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:38Z","receivedAt":"2014-10-27T12:10:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Ideally we should implement untracked_cache_remove_from_index() and\nuntracked_cache_add_to_index() so that they update untracked cache\nright away instead of invalidating it and wait for read_directory()\nnext time to deal with it. But that may need some more work in\nunpack-trees.c. So stay simple as the first step.\n\nThe new call in add_index_entry_with_check() may look strange because\nnew calls usually stay close to cache_tree_invalidate_path(). We do it\na bit later than c_t_i_p() in this function because if it's about\nreplacing the entry with the same name, we don't care (but cache-tree\ndoes).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c          | 31 +++++++++++++++++++++++++++++++\n dir.h          |  4 ++++\n read-cache.c   |  4 ++++\n unpack-trees.c |  7 +++++--\n 4 files changed, 44 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex c97b0c3..ddc549c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2369,3 +2369,34 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long\n \tfree_untracked_cache(uc);\n \treturn NULL;\n }\n+\n+void untracked_cache_invalidate_path(struct index_state *istate,\n+\t\t\t\t     const char *path)\n+{\n+\tconst char *sep;\n+\tstruct untracked_cache_dir *d;\n+\tif (!istate->untracked || !istate->untracked->root)\n+\t\treturn;\n+\tsep = strrchr(path, '/');\n+\tif (sep)\n+\t\td = lookup_untracked(istate->untracked,\n+\t\t\t\t     istate->untracked->root,\n+\t\t\t\t     path, sep - path);\n+\telse\n+\t\td = istate->untracked->root;\n+\tistate->untracked->dir_invalidated++;\n+\td->valid = 0;\n+\td->untracked_nr = 0;\n+}\n+\n+void untracked_cache_remove_from_index(struct index_state *istate,\n+\t\t\t\t       const char *path)\n+{\n+\tuntracked_cache_invalidate_path(istate, path);\n+}\n+\n+void untracked_cache_add_to_index(struct index_state *istate,\n+\t\t\t\t  const char *path)\n+{\n+\tuntracked_cache_invalidate_path(istate, path);\n+}\ndiff --git a/dir.h b/dir.h\nindex 014f3ed..8c29324 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -298,6 +298,10 @@ static inline int dir_path_match(const struct dir_entry *ent,\n \t\t\t      has_trailing_dir);\n }\n \n+void untracked_cache_invalidate_path(struct index_state *, const char *);\n+void untracked_cache_remove_from_index(struct index_state *, const char *);\n+void untracked_cache_add_to_index(struct index_state *, const char *);\n+\n void free_untracked_cache(struct untracked_cache *);\n struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);\n void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);\ndiff --git a/read-cache.c b/read-cache.c\nindex 60baeaf..feb10b0 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -79,6 +79,7 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n\n \tmemcpy(new->name, new_name, namelen + 1);\n \n \tcache_tree_invalidate_path(istate, old->name);\n+\tuntracked_cache_remove_from_index(istate, old->name);\n \tremove_index_entry_at(istate, nr);\n \tadd_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);\n }\n@@ -538,6 +539,7 @@ int remove_file_from_index(struct index_state *istate, const char *path)\n \tif (pos < 0)\n \t\tpos = -pos-1;\n \tcache_tree_invalidate_path(istate, path);\n+\tuntracked_cache_remove_from_index(istate, path);\n \twhile (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))\n \t\tremove_index_entry_at(istate, pos);\n \treturn 0;\n@@ -969,6 +971,8 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e\n \t}\n \tpos = -pos-1;\n \n+\tuntracked_cache_add_to_index(istate, ce->name);\n+\n \t/*\n \t * Inserting a merged entry (\"stage 0\") into the index\n \t * will always replace all non-merged entries..\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 629c658..e5ddb0c 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -9,6 +9,7 @@\n #include \"refs.h\"\n #include \"attr.h\"\n #include \"split-index.h\"\n+#include \"dir.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n@@ -1255,8 +1256,10 @@ static int verify_uptodate_sparse(const struct cache_entry *ce,\n static void invalidate_ce_path(const struct cache_entry *ce,\n \t\t\t       struct unpack_trees_options *o)\n {\n-\tif (ce)\n-\t\tcache_tree_invalidate_path(o->src_index, ce->name);\n+\tif (!ce)\n+\t\treturn;\n+\tcache_tree_invalidate_path(o->src_index, ce->name);\n+\tuntracked_cache_invalidate_path(o->src_index, ce->name);\n }\n \n /*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251108","messageId":"1414411846-4450-13-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 12/19] read-cache.c: split racy stat test to a separate function","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:39Z","receivedAt":"2014-10-27T12:10:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n read-cache.c | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex feb10b0..a14646b 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -271,20 +271,26 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \treturn changed;\n }\n \n-static int is_racy_timestamp(const struct index_state *istate,\n-\t\t\t     const struct cache_entry *ce)\n+static int is_racy_stat(const struct index_state *istate,\n+\t\t\tconst struct stat_data *sd)\n {\n-\treturn (!S_ISGITLINK(ce->ce_mode) &&\n-\t\tistate->timestamp.sec &&\n+\treturn (istate->timestamp.sec &&\n #ifdef USE_NSEC\n \t\t /* nanosecond timestamped files can also be racy! */\n-\t\t(istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||\n-\t\t (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&\n-\t\t  istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))\n+\t\t(istate->timestamp.sec < sd->sd_mtime.sec ||\n+\t\t (istate->timestamp.sec == sd->sd_mtime.sec &&\n+\t\t  istate->timestamp.nsec <= sd->sd_mtime.nsec))\n #else\n-\t\tistate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec\n+\t\tistate->timestamp.sec <= sd->sd_mtime.sec\n #endif\n-\t\t );\n+\t\t);\n+}\n+\n+static int is_racy_timestamp(const struct index_state *istate,\n+\t\t\t     const struct cache_entry *ce)\n+{\n+\treturn (!S_ISGITLINK(ce->ce_mode) &&\n+\t\tis_racy_stat(istate, &ce->ce_stat_data));\n }\n \n int ie_match_stat(const struct index_state *istate,\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251109","messageId":"1414411846-4450-14-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 13/19] untracked cache: avoid racy timestamps","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:40Z","receivedAt":"2014-10-27T12:10:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"When a directory is updated within the same second that its timestamp\nis last saved, we cannot realize the directory has been updated by\nchecking timestamps. Assume the worst (something is update). See\n29e4d36 (Racy GIT - 2005-12-20) for more information.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h      | 2 ++\n dir.c        | 5 +++--\n read-cache.c | 8 ++++++++\n 3 files changed, 13 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 4f903fa..2b93217 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -562,6 +562,8 @@ extern void fill_stat_data(struct stat_data *sd, struct stat *st);\n  * INODE_CHANGED, and DATA_CHANGED.\n  */\n extern int match_stat_data(const struct stat_data *sd, struct stat *st);\n+extern int match_stat_data_racy(const struct index_state *istate,\n+\t\t\t\tconst struct stat_data *sd, struct stat *st);\n \n extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);\n \ndiff --git a/dir.c b/dir.c\nindex ddc549c..3eb1a12 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -678,7 +678,8 @@ static int add_excludes(const char *fname, const char *base, int baselen,\n \t\tclose(fd);\n \t\tif (ss) {\n \t\t\tint pos;\n-\t\t\tif (ss_valid && !match_stat_data(&ss->stat, &st))\n+\t\t\tif (ss_valid &&\n+\t\t\t    !match_stat_data_racy(&the_index, &ss->stat, &st))\n \t\t\t\t; /* no content change, ss->sha1 still good */\n \t\t\telse if (check_index &&\n \t\t\t\t (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&\n@@ -1533,7 +1534,7 @@ static int valid_cached_dir(struct dir_struct *dir,\n \t\treturn 0;\n \t}\n \tif (!untracked->valid ||\n-\t    match_stat_data(&untracked->stat_data, &st)) {\n+\t    match_stat_data_racy(&the_index, &untracked->stat_data, &st)) {\n \t\tif (untracked->valid)\n \t\t\tinvalidate_directory(dir->untracked, untracked);\n \t\tfill_stat_data(&untracked->stat_data, &st);\ndiff --git a/read-cache.c b/read-cache.c\nindex a14646b..177cbae 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -293,6 +293,14 @@ static int is_racy_timestamp(const struct index_state *istate,\n \t\tis_racy_stat(istate, &ce->ce_stat_data));\n }\n \n+int match_stat_data_racy(const struct index_state *istate,\n+\t\t\t const struct stat_data *sd, struct stat *st)\n+{\n+\tif (is_racy_stat(istate, sd))\n+\t\treturn MTIME_CHANGED;\n+\treturn match_stat_data(sd, st);\n+}\n+\n int ie_match_stat(const struct index_state *istate,\n \t\t  const struct cache_entry *ce, struct stat *st,\n \t\t  unsigned int options)\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251110","messageId":"1414411846-4450-15-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 14/19] untracked cache: print stats with $GIT_TRACE_UNTRACKED_STATS","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:41Z","receivedAt":"2014-10-27T12:10:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This could be used to verify correct behavior in tests\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/dir.c b/dir.c\nindex 3eb1a12..57b49f7 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1918,6 +1918,18 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \tfree_simplify(simplify);\n \tqsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);\n \tqsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);\n+\tif (dir->untracked) {\n+\t\tstatic struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);\n+\t\ttrace_printf_key(&trace_untracked_stats,\n+\t\t\t\t \"node creation: %u\\n\"\n+\t\t\t\t \"gitignore invalidation: %u\\n\"\n+\t\t\t\t \"directory invalidation: %u\\n\"\n+\t\t\t\t \"opendir: %u\\n\",\n+\t\t\t\t dir->untracked->dir_created,\n+\t\t\t\t dir->untracked->gitignore_invalidated,\n+\t\t\t\t dir->untracked->dir_invalidated,\n+\t\t\t\t dir->untracked->dir_opened);\n+\t}\n \treturn dir->nr;\n }\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251111","messageId":"1414411846-4450-16-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 15/19] untracked cache: mark index dirty if untracked cache is updated","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:42Z","receivedAt":"2014-10-27T12:10:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h      | 1 +\n dir.c        | 9 +++++++++\n read-cache.c | 2 +-\n 3 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/cache.h b/cache.h\nindex 2b93217..dcbdc3c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -296,6 +296,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n #define RESOLVE_UNDO_CHANGED\t(1 << 4)\n #define CACHE_TREE_CHANGED\t(1 << 5)\n #define SPLIT_INDEX_ORDERED\t(1 << 6)\n+#define UNTRACKED_CHANGED       (1 << 7)\n \n struct split_index;\n struct untracked_cache;\ndiff --git a/dir.c b/dir.c\nindex 57b49f7..d373d9a 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1929,6 +1929,15 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \t\t\t\t dir->untracked->gitignore_invalidated,\n \t\t\t\t dir->untracked->dir_invalidated,\n \t\t\t\t dir->untracked->dir_opened);\n+\t\tif (dir->untracked == the_index.untracked &&\n+\t\t    (dir->untracked->dir_opened ||\n+\t\t     dir->untracked->gitignore_invalidated ||\n+\t\t     dir->untracked->dir_invalidated))\n+\t\t\tthe_index.cache_changed |= UNTRACKED_CHANGED;\n+\t\tif (dir->untracked != the_index.untracked) {\n+\t\t\tfree(dir->untracked);\n+\t\t\tdir->untracked = NULL;\n+\t\t}\n \t}\n \treturn dir->nr;\n }\ndiff --git a/read-cache.c b/read-cache.c\nindex 177cbae..779c080 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -43,7 +43,7 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,\n /* changes that can be kept in $GIT_DIR/index (basically all extensions) */\n #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \\\n \t\t CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \\\n-\t\t SPLIT_INDEX_ORDERED)\n+\t\t SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)\n \n struct index_state the_index;\n static const char *alternate_index_output;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251115","messageId":"1414411846-4450-17-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 16/19] status: enable untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:43Z","receivedAt":"2014-10-27T12:10:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"update_index_if_able() is moved down so that the updated untracked\ncache could be written out.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/commit.c | 5 +++--\n wt-status.c      | 2 ++\n 2 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 81dc622..08e2964 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1408,13 +1408,14 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \trefresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);\n \n \tfd = hold_locked_index(&index_lock, 0);\n-\tif (0 <= fd)\n-\t\tupdate_index_if_able(&the_index, &index_lock);\n \n \ts.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;\n \ts.ignore_submodule_arg = ignore_submodule_arg;\n \twt_status_collect(&s);\n \n+\tif (0 <= fd)\n+\t\tupdate_index_if_able(&the_index, &index_lock);\n+\n \tif (s.relative_paths)\n \t\ts.prefix = prefix;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 1bf5d72..42aeebe 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -585,6 +585,8 @@ static void wt_status_collect_untracked(struct wt_status *s)\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+\telse\n+\t\tdir.untracked = the_index.untracked;\n \tsetup_standard_excludes(&dir);\n \n \tfill_directory(&dir, &s->pathspec);\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251112","messageId":"1414411846-4450-18-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 17/19] update-index: manually enable or disable untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:44Z","receivedAt":"2014-10-27T12:10:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Overall time saving on \"git status\" is about 40% in the best case\nscenario, removing ..collect_untracked() as the most time consuming\nfunction. read and refresh index operations are now at the top (which\nshould drop when index-helper and/or watchman support is added). More\nnumbers and analysis below.\n\nwebkit.git\n==========\n\n169k files. 6k dirs. Lots of test data (i.e. not touched most of the\ntime)\n\nBase status\n-----------\n\nIndex version 4 in split index mode and cache-tree populated. No\nuntracked cache. It shows how time is consumed by \"git status\". The\nsame settings are used for other repos below.\n\n18:28:10.199679 builtin/commit.c:1394   performance: 0.000000451 s: cmd_status:setup\n18:28:10.474847 read-cache.c:1407       performance: 0.274873831 s: read_index\n18:28:10.475295 read-cache.c:1407       performance: 0.000000656 s: read_index\n18:28:10.728443 preload-index.c:131     performance: 0.253147487 s: read_index_preload\n18:28:10.741422 read-cache.c:1254       performance: 0.012868340 s: refresh_index\n18:28:10.752300 wt-status.c:623         performance: 0.010421357 s: wt_status_collect_changes_worktree\n18:28:10.762069 wt-status.c:629         performance: 0.009644748 s: wt_status_collect_changes_index\n18:28:11.601019 wt-status.c:632         performance: 0.838859547 s: wt_status_collect_untracked\n18:28:11.605939 builtin/commit.c:1421   performance: 0.004835004 s: cmd_status:update_index\n18:28:11.606580 trace.c:415             performance: 1.407878388 s: git command: 'git' 'status'\n\nPopulating status\n-----------------\n\nThis is after enabling untracked cache and the cache is still empty.\nWe see a slight increase in .._collect_untracked() and update_index\n(because new cache has to be written to $GIT_DIR/index).\n\n18:28:18.915213 builtin/commit.c:1394   performance: 0.000000326 s: cmd_status:setup\n18:28:19.197364 read-cache.c:1407       performance: 0.281901416 s: read_index\n18:28:19.197754 read-cache.c:1407       performance: 0.000000546 s: read_index\n18:28:19.451355 preload-index.c:131     performance: 0.253599607 s: read_index_preload\n18:28:19.464400 read-cache.c:1254       performance: 0.012935336 s: refresh_index\n18:28:19.475115 wt-status.c:623         performance: 0.010236920 s: wt_status_collect_changes_worktree\n18:28:19.486022 wt-status.c:629         performance: 0.010801685 s: wt_status_collect_changes_index\n18:28:20.362660 wt-status.c:632         performance: 0.876551366 s: wt_status_collect_untracked\n18:28:20.396199 builtin/commit.c:1421   performance: 0.033447969 s: cmd_status:update_index\n18:28:20.396939 trace.c:415             performance: 1.482695902 s: git command: 'git' 'status'\n\nPopulated status\n----------------\n\nAfter the cache is populated, wt_status_collect_untracked() drops 82%\nfrom 0.838s to 0.144s. Overall time drops 45%. Top offenders are now\nread_index() and read_index_preload().\n\n18:28:20.408605 builtin/commit.c:1394   performance: 0.000000457 s: cmd_status:setup\n18:28:20.692864 read-cache.c:1407       performance: 0.283980458 s: read_index\n18:28:20.693273 read-cache.c:1407       performance: 0.000000661 s: read_index\n18:28:20.958814 preload-index.c:131     performance: 0.265540254 s: read_index_preload\n18:28:20.972375 read-cache.c:1254       performance: 0.013437429 s: refresh_index\n18:28:20.983959 wt-status.c:623         performance: 0.011146646 s: wt_status_collect_changes_worktree\n18:28:20.993948 wt-status.c:629         performance: 0.009879094 s: wt_status_collect_changes_index\n18:28:21.138125 wt-status.c:632         performance: 0.144084737 s: wt_status_collect_untracked\n18:28:21.173678 builtin/commit.c:1421   performance: 0.035463949 s: cmd_status:update_index\n18:28:21.174251 trace.c:415             performance: 0.766707355 s: git command: 'git' 'status'\n\ngentoo-x86.git\n==============\n\nThis repository is a strange one with a balanced, wide and shallow\nworktree (about 100k files and 23k dirs) and no .gitignore in\nworktree. .._collect_untracked() time drops 88%, total time drops 56%.\n\nBase status\n-----------\n18:20:40.828642 builtin/commit.c:1394   performance: 0.000000496 s: cmd_status:setup\n18:20:41.027233 read-cache.c:1407       performance: 0.198130532 s: read_index\n18:20:41.027670 read-cache.c:1407       performance: 0.000000581 s: read_index\n18:20:41.171716 preload-index.c:131     performance: 0.144045594 s: read_index_preload\n18:20:41.179171 read-cache.c:1254       performance: 0.007320424 s: refresh_index\n18:20:41.185785 wt-status.c:623         performance: 0.006144638 s: wt_status_collect_changes_worktree\n18:20:41.192701 wt-status.c:629         performance: 0.006780184 s: wt_status_collect_changes_index\n18:20:41.991723 wt-status.c:632         performance: 0.798927029 s: wt_status_collect_untracked\n18:20:41.994664 builtin/commit.c:1421   performance: 0.002852772 s: cmd_status:update_index\n18:20:41.995458 trace.c:415             performance: 1.168427502 s: git command: 'git' 'status'\nPopulating status\n-----------------\n18:20:48.968848 builtin/commit.c:1394   performance: 0.000000380 s: cmd_status:setup\n18:20:49.172918 read-cache.c:1407       performance: 0.203734214 s: read_index\n18:20:49.173341 read-cache.c:1407       performance: 0.000000562 s: read_index\n18:20:49.320013 preload-index.c:131     performance: 0.146671391 s: read_index_preload\n18:20:49.328039 read-cache.c:1254       performance: 0.007921957 s: refresh_index\n18:20:49.334680 wt-status.c:623         performance: 0.006172020 s: wt_status_collect_changes_worktree\n18:20:49.342526 wt-status.c:629         performance: 0.007731746 s: wt_status_collect_changes_index\n18:20:50.257510 wt-status.c:632         performance: 0.914864222 s: wt_status_collect_untracked\n18:20:50.338371 builtin/commit.c:1421   performance: 0.080776477 s: cmd_status:update_index\n18:20:50.338900 trace.c:415             performance: 1.371462446 s: git command: 'git' 'status'\nPopulated status\n----------------\n18:20:50.351160 builtin/commit.c:1394   performance: 0.000000571 s: cmd_status:setup\n18:20:50.577358 read-cache.c:1407       performance: 0.225917338 s: read_index\n18:20:50.577794 read-cache.c:1407       performance: 0.000000617 s: read_index\n18:20:50.734140 preload-index.c:131     performance: 0.156345564 s: read_index_preload\n18:20:50.745717 read-cache.c:1254       performance: 0.011463075 s: refresh_index\n18:20:50.755176 wt-status.c:623         performance: 0.008877929 s: wt_status_collect_changes_worktree\n18:20:50.763768 wt-status.c:629         performance: 0.008471633 s: wt_status_collect_changes_index\n18:20:50.854885 wt-status.c:632         performance: 0.090988721 s: wt_status_collect_untracked\n18:20:50.857765 builtin/commit.c:1421   performance: 0.002789097 s: cmd_status:update_index\n18:20:50.858411 trace.c:415             performance: 0.508647673 s: git command: 'git' 'status'\n\nlinux-2.6\n=========\n\nReference repo. Not too big. .._collect_status() drops 84%. Total time\ndrops 42%.\n\nBase status\n-----------\n18:34:09.870122 builtin/commit.c:1394   performance: 0.000000385 s: cmd_status:setup\n18:34:09.943218 read-cache.c:1407       performance: 0.072871177 s: read_index\n18:34:09.943614 read-cache.c:1407       performance: 0.000000491 s: read_index\n18:34:10.004364 preload-index.c:131     performance: 0.060748102 s: read_index_preload\n18:34:10.008190 read-cache.c:1254       performance: 0.003714285 s: refresh_index\n18:34:10.012087 wt-status.c:623         performance: 0.002775446 s: wt_status_collect_changes_worktree\n18:34:10.016054 wt-status.c:629         performance: 0.003862140 s: wt_status_collect_changes_index\n18:34:10.214747 wt-status.c:632         performance: 0.198604837 s: wt_status_collect_untracked\n18:34:10.216102 builtin/commit.c:1421   performance: 0.001244166 s: cmd_status:update_index\n18:34:10.216817 trace.c:415             performance: 0.347670735 s: git command: 'git' 'status'\nPopulating status\n-----------------\n18:34:16.595102 builtin/commit.c:1394   performance: 0.000000456 s: cmd_status:setup\n18:34:16.666600 read-cache.c:1407       performance: 0.070992413 s: read_index\n18:34:16.667012 read-cache.c:1407       performance: 0.000000606 s: read_index\n18:34:16.729375 preload-index.c:131     performance: 0.062362492 s: read_index_preload\n18:34:16.732565 read-cache.c:1254       performance: 0.003075517 s: refresh_index\n18:34:16.736148 wt-status.c:623         performance: 0.002422201 s: wt_status_collect_changes_worktree\n18:34:16.739990 wt-status.c:629         performance: 0.003746618 s: wt_status_collect_changes_index\n18:34:16.948505 wt-status.c:632         performance: 0.208426710 s: wt_status_collect_untracked\n18:34:16.961744 builtin/commit.c:1421   performance: 0.013151887 s: cmd_status:update_index\n18:34:16.962233 trace.c:415             performance: 0.368537535 s: git command: 'git' 'status'\nPopulated status\n----------------\n18:34:16.970026 builtin/commit.c:1394   performance: 0.000000631 s: cmd_status:setup\n18:34:17.046235 read-cache.c:1407       performance: 0.075904673 s: read_index\n18:34:17.046644 read-cache.c:1407       performance: 0.000000681 s: read_index\n18:34:17.113564 preload-index.c:131     performance: 0.066920253 s: read_index_preload\n18:34:17.117281 read-cache.c:1254       performance: 0.003604055 s: refresh_index\n18:34:17.121115 wt-status.c:623         performance: 0.002508345 s: wt_status_collect_changes_worktree\n18:34:17.125089 wt-status.c:629         performance: 0.003871636 s: wt_status_collect_changes_index\n18:34:17.156089 wt-status.c:632         performance: 0.030895703 s: wt_status_collect_untracked\n18:34:17.169861 builtin/commit.c:1421   performance: 0.013686404 s: cmd_status:update_index\n18:34:17.170391 trace.c:415             performance: 0.201474531 s: git command: 'git' 'status'\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-update-index.txt |  8 ++++++++\n builtin/update-index.c             | 16 ++++++++++++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt\nindex 82eca6f..16f2686 100644\n--- a/Documentation/git-update-index.txt\n+++ b/Documentation/git-update-index.txt\n@@ -172,6 +172,14 @@ may not support it yet.\n \tthe shared index file. This mode is designed for very large\n \tindexes that take a signficant amount of time to read or write.\n \n+--untracked-cache::\n+--no-untracked-cache::\n+\tEnable or disable untracked cache extension. This could speed\n+\tup for commands that involve determining untracked files such\n+\tas `git status`. The underlying operating system and file\n+\tsystem must change `st_mtime` field of a directory if files\n+\tare added or deleted in that directory.\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex b0e3dc9..e57e2d7 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -741,6 +741,7 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx,\n int cmd_update_index(int argc, const char **argv, const char *prefix)\n {\n \tint newfd, entries, has_errors = 0, line_termination = '\\n';\n+\tint untracked_cache = -1;\n \tint read_from_stdin = 0;\n \tint prefix_length = prefix ? strlen(prefix) : 0;\n \tint preferred_index_format = 0;\n@@ -832,6 +833,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"write index in this format\")),\n \t\tOPT_BOOL(0, \"split-index\", &split_index,\n \t\t\tN_(\"enable or disable split index\")),\n+\t\tOPT_BOOL(0, \"untracked-cache\", &untracked_cache,\n+\t\t\tN_(\"enable/disable untracked cache\")),\n \t\tOPT_END()\n \t};\n \n@@ -938,6 +941,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\tthe_index.split_index = NULL;\n \t\tthe_index.cache_changed |= SOMETHING_CHANGED;\n \t}\n+\tif (untracked_cache > 0 && !the_index.untracked) {\n+\t\tstruct untracked_cache *uc;\n+\n+\t\tuc = xcalloc(1, sizeof(*uc));\n+\t\tuc->exclude_per_dir = \".gitignore\";\n+\t\t/* should be the same flags used by git-status */\n+\t\tuc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n+\t\tthe_index.untracked = uc;\n+\t\tthe_index.cache_changed |= SOMETHING_CHANGED;\n+\t} else if (!untracked_cache && the_index.untracked) {\n+\t\tthe_index.untracked = NULL;\n+\t\tthe_index.cache_changed |= SOMETHING_CHANGED;\n+\t}\n \n \tif (active_cache_changed) {\n \t\tif (newfd < 0) {\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251113","messageId":"1414411846-4450-19-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 18/19] update-index: test the system before enabling untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:45Z","receivedAt":"2014-10-27T12:10:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-update-index.txt |   6 ++\n builtin/update-index.c             | 146 +++++++++++++++++++++++++++++++++++++\n 2 files changed, 152 insertions(+)\n\ndiff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt\nindex 16f2686..fab1fea 100644\n--- a/Documentation/git-update-index.txt\n+++ b/Documentation/git-update-index.txt\n@@ -180,6 +180,12 @@ may not support it yet.\n \tsystem must change `st_mtime` field of a directory if files\n \tare added or deleted in that directory.\n \n+--force-untracked-cache::\n+\tFor safety, `--untracked-cache` performs tests on the working\n+\tdirectory to make sure untracked cache can be used. These\n+\ttests can take a few seconds. `--force-untracked-cache` can be\n+\tused to skip the tests.\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex e57e2d7..471c0b4 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -48,6 +48,145 @@ static void report(const char *fmt, ...)\n \tva_end(vp);\n }\n \n+static void remove_test_directory(void)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstrbuf_addstr(&sb, \"dir-mtime-test\");\n+\tremove_dir_recursively(&sb, 0);\n+\tstrbuf_release(&sb);\n+}\n+\n+static void xmkdir(const char *path)\n+{\n+\tif (mkdir(path, 0700))\n+\t\tdie_errno(_(\"failed to create directory %s\"), path);\n+}\n+\n+static int xstat(const char *path, struct stat *st)\n+{\n+\tif (stat(path, st))\n+\t\tdie_errno(_(\"failed to stat %s\"), path);\n+\treturn 0;\n+}\n+\n+static int create_file(const char *path)\n+{\n+\tint fd = open(path, O_CREAT | O_RDWR, 0644);\n+\tif (fd < 0)\n+\t\tdie_errno(_(\"failed to create file %s\"), path);\n+\treturn fd;\n+}\n+\n+static void xunlink(const char *path)\n+{\n+\tif (unlink(path))\n+\t\tdie_errno(_(\"failed to delete file %s\"), path);\n+}\n+\n+static void xrmdir(const char *path)\n+{\n+\tif (rmdir(path))\n+\t\tdie_errno(_(\"failed to delete directory %s\"), path);\n+}\n+\n+static void avoid_racy(void)\n+{\n+\t/*\n+\t * not use if we could usleep(10) if USE_NSEC is defined. The\n+\t * field nsec could be there, but the OS could choose to\n+\t * ignore it?\n+\t */\n+\tsleep(1);\n+}\n+\n+static int test_if_untracked_cache_is_supported(void)\n+{\n+\tstruct stat st;\n+\tstruct stat_data base;\n+\tint fd;\n+\n+\tfprintf(stderr, _(\"Testing \"));\n+\txmkdir(\"dir-mtime-test\");\n+\tatexit(remove_test_directory);\n+\txstat(\"dir-mtime-test\", &st);\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\tfd = create_file(\"dir-mtime-test/newfile\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr,_(\"directory stat info does not \"\n+\t\t\t\t    \"change after adding a new file\"));\n+\t\treturn 0;\n+\t}\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\txmkdir(\"dir-mtime-test/new-dir\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info does not change \"\n+\t\t\t\t     \"after adding a new directory\"));\n+\t\treturn 0;\n+\t}\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\twrite_or_die(fd, \"data\", 4);\n+\tclose(fd);\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info changes \"\n+\t\t\t\t     \"after updating a file\"));\n+\t\treturn 0;\n+\t}\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\tclose(create_file(\"dir-mtime-test/new-dir/new\"));\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info changes after \"\n+\t\t\t\t     \"adding a file inside subdirectory\"));\n+\t\treturn 0;\n+\t}\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\txunlink(\"dir-mtime-test/newfile\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info does not \"\n+\t\t\t\t     \"change after deleting a file\"));\n+\t\treturn 0;\n+\t}\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\txunlink(\"dir-mtime-test/new-dir/new\");\n+\txrmdir(\"dir-mtime-test/new-dir\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info does not \"\n+\t\t\t\t     \"change after deleting a directory\"));\n+\t\treturn 0;\n+\t}\n+\n+\txrmdir(\"dir-mtime-test\");\n+\tfprintf_ln(stderr, _(\" OK\"));\n+\treturn 1;\n+}\n+\n static int mark_ce_flags(const char *path, int flag, int mark)\n {\n \tint namelen = strlen(path);\n@@ -835,6 +974,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"enable or disable split index\")),\n \t\tOPT_BOOL(0, \"untracked-cache\", &untracked_cache,\n \t\t\tN_(\"enable/disable untracked cache\")),\n+\t\tOPT_SET_INT(0, \"force-untracked-cache\", &untracked_cache,\n+\t\t\t    N_(\"enable untracked cache without testing the filesystem\"), 2),\n \t\tOPT_END()\n \t};\n \n@@ -944,6 +1085,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \tif (untracked_cache > 0 && !the_index.untracked) {\n \t\tstruct untracked_cache *uc;\n \n+\t\tif (untracked_cache < 2) {\n+\t\t\tsetup_work_tree();\n+\t\t\tif (!test_if_untracked_cache_is_supported())\n+\t\t\t\treturn 1;\n+\t\t}\n \t\tuc = xcalloc(1, sizeof(*uc));\n \t\tuc->exclude_per_dir = \".gitignore\";\n \t\t/* should be the same flags used by git-status */\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251114","messageId":"1414411846-4450-20-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 19/19] t7063: tests for untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-27T12:10:46Z","receivedAt":"2014-10-27T12:10:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n .gitignore                                 |   1 +\n Makefile                                   |   1 +\n t/t7063-status-untracked-cache.sh (new +x) | 353 +++++++++++++++++++++++++++++\n test-dump-untracked-cache.c (new)          |  61 +++++\n 4 files changed, 416 insertions(+)\n create mode 100755 t/t7063-status-untracked-cache.sh\n create mode 100644 test-dump-untracked-cache.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 9ec40fa..fde1143 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -183,6 +183,7 @@\n /test-delta\n /test-dump-cache-tree\n /test-dump-split-index\n+/test-dump-untracked-cache\n /test-scrap-cache-tree\n /test-genrandom\n /test-hashmap\ndiff --git a/Makefile b/Makefile\nindex 356feb5..8070e56 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -552,6 +552,7 @@ TEST_PROGRAMS_NEED_X += test-date\n TEST_PROGRAMS_NEED_X += test-delta\n TEST_PROGRAMS_NEED_X += test-dump-cache-tree\n TEST_PROGRAMS_NEED_X += test-dump-split-index\n+TEST_PROGRAMS_NEED_X += test-dump-untracked-cache\n TEST_PROGRAMS_NEED_X += test-genrandom\n TEST_PROGRAMS_NEED_X += test-hashmap\n TEST_PROGRAMS_NEED_X += test-index-version\ndiff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh\nnew file mode 100755\nindex 0000000..2b2ffd7\n--- /dev/null\n+++ b/t/t7063-status-untracked-cache.sh\n@@ -0,0 +1,353 @@\n+#!/bin/sh\n+\n+test_description='test untracked cache'\n+\n+. ./test-lib.sh\n+\n+avoid_racy() {\n+\tsleep 1\n+}\n+\n+git update-index --untracked-cache\n+# It's fine if git update-index returns an error code other than one,\n+# it'll be caught in the first test.\n+if test $? -eq 1; then\n+\tskip_all='This system does not support untracked cache'\n+\ttest_done\n+fi\n+\n+test_expect_success 'setup' '\n+\tgit init worktree &&\n+\tcd worktree &&\n+\tmkdir done dtwo dthree &&\n+\ttouch one two three done/one dtwo/two dthree/three &&\n+\tgit add one two done/one &&\n+\t: >.git/info/exclude &&\n+\tgit update-index --untracked-cache\n+'\n+\n+test_expect_success 'untracked cache is empty' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 0000000000000000000000000000000000000000\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+cat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? dthree/\n+?? dtwo/\n+?? three\n+EOF\n+\n+cat >../dump.expect <<EOF &&\n+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ 0000000000000000000000000000000000000000 recurse valid\n+dthree/\n+dtwo/\n+three\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+three\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\n+test_expect_success 'status first time (empty cache)' '\n+\tavoid_racy &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 3\n+gitignore invalidation: 1\n+directory invalidation: 0\n+opendir: 4\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'untracked cache after first status' '\n+\ttest-dump-untracked-cache >../actual &&\n+\ttest_cmp ../dump.expect ../actual\n+'\n+\n+test_expect_success 'status second time (fully populated cache)' '\n+\tavoid_racy &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 0\n+opendir: 0\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'untracked cache after second status' '\n+\ttest-dump-untracked-cache >../actual &&\n+\ttest_cmp ../dump.expect ../actual\n+'\n+\n+test_expect_success 'modify in root directory, one dir invalidation' '\n+\tavoid_racy &&\n+\t: >four &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? dthree/\n+?? dtwo/\n+?? four\n+?? three\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 1\n+opendir: 1\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ 0000000000000000000000000000000000000000 recurse valid\n+dthree/\n+dtwo/\n+four\n+three\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+three\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'new .gitignore invalidates recursively' '\n+\tavoid_racy &&\n+\techo four >.gitignore &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? .gitignore\n+?? dthree/\n+?? dtwo/\n+?? three\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 1\n+directory invalidation: 1\n+opendir: 4\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dthree/\n+dtwo/\n+three\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+three\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'new info/exclude invalidates everything' '\n+\tavoid_racy &&\n+\techo three >>.git/info/exclude &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? .gitignore\n+?? dtwo/\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 1\n+directory invalidation: 0\n+opendir: 4\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dtwo/\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'move two from tracked to untracked' '\n+\tgit rm --cached two &&\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'status after the move' '\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+?? .gitignore\n+?? dtwo/\n+?? two\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 0\n+opendir: 1\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dtwo/\n+two\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'move two from untracked to tracked' '\n+\tgit add two &&\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'status after the move' '\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? .gitignore\n+?? dtwo/\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 0\n+opendir: 1\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dtwo/\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_done\ndiff --git a/test-dump-untracked-cache.c b/test-dump-untracked-cache.c\nnew file mode 100644\nindex 0000000..710441e\n--- /dev/null\n+++ b/test-dump-untracked-cache.c\n@@ -0,0 +1,61 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+\n+static int compare_untracked(const void *a_, const void *b_)\n+{\n+\tconst char *const *a = a_;\n+\tconst char *const *b = b_;\n+\treturn strcmp(*a, *b);\n+}\n+\n+static int compare_dir(const void *a_, const void *b_)\n+{\n+\tconst struct untracked_cache_dir *const *a = a_;\n+\tconst struct untracked_cache_dir *const *b = b_;\n+\treturn strcmp((*a)->name, (*b)->name);\n+}\n+\n+static void dump(struct untracked_cache_dir *ucd, struct strbuf *base)\n+{\n+\tint i, len;\n+\tqsort(ucd->untracked, ucd->untracked_nr, sizeof(*ucd->untracked),\n+\t      compare_untracked);\n+\tqsort(ucd->dirs, ucd->dirs_nr, sizeof(*ucd->dirs),\n+\t      compare_dir);\n+\tlen = base->len;\n+\tstrbuf_addf(base, \"%s/\", ucd->name);\n+\tprintf(\"%s %s\", base->buf,\n+\t       sha1_to_hex(ucd->exclude_sha1));\n+\tif (ucd->recurse)\n+\t\tfputs(\" recurse\", stdout);\n+\tif (ucd->check_only)\n+\t\tfputs(\" check_only\", stdout);\n+\tif (ucd->valid)\n+\t\tfputs(\" valid\", stdout);\n+\tprintf(\"\\n\");\n+\tfor (i = 0; i < ucd->untracked_nr; i++)\n+\t\tprintf(\"%s\\n\", ucd->untracked[i]);\n+\tfor (i = 0; i < ucd->dirs_nr; i++)\n+\t\tdump(ucd->dirs[i], base);\n+\tstrbuf_setlen(base, len);\n+}\n+\n+int main(int ac, char **av)\n+{\n+\tstruct untracked_cache *uc;\n+\tstruct strbuf base = STRBUF_INIT;\n+\tif (read_cache() < 0)\n+\t\tdie(\"unable to read index file\");\n+\tuc = the_index.untracked;\n+\tif (!uc) {\n+\t\tprintf(\"no untracked cache\\n\");\n+\t\treturn 0;\n+\t}\n+\tprintf(\"info/exclude %s\\n\", sha1_to_hex(uc->ss_info_exclude.sha1));\n+\tprintf(\"core.excludesfile %s\\n\", sha1_to_hex(uc->ss_excludes_file.sha1));\n+\tprintf(\"exclude_per_dir %s\\n\", uc->exclude_per_dir);\n+\tprintf(\"flags %08x\\n\", uc->dir_flags);\n+\tif (uc->root)\n+\t\tdump(uc->root, &base);\n+\treturn 0;\n+}\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251134","messageId":"xmqq7fzl5dxw.fsf@gitster.dls.corp.google.com","threadId":"37820","inReplyTo":"1414411846-4450-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 01/19] dir.c: optionally compute sha-1 of a .gitignore file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-27T22:46:03Z","receivedAt":"2014-10-27T22:46:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> This is not used anywhere yet. But the goal is to compare quickly if a\n> .gitignore file has changed when we have the SHA-1 of both old (cached\n> somewhere) and new (from index or a tree) versions.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  dir.c | 50 +++++++++++++++++++++++++++++++++++++++++++-------\n>  dir.h |  5 +++++\n>  2 files changed, 48 insertions(+), 7 deletions(-)\n>\n> diff --git a/dir.c b/dir.c\n> index bd274a7..33a35c1 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -466,7 +466,8 @@ void add_exclude(const char *string, const char *base,\n>  \tx->el = el;\n>  }\n>  \n> -static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n> +static void *read_skip_worktree_file_from_index(const char *path, size_t *size,\n> +\t\t\t\t\t\tstruct sha1_stat *ss)\n>  {\n>  \tint pos, len;\n>  \tunsigned long sz;\n> @@ -485,6 +486,10 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n>  \t\treturn NULL;\n>  \t}\n>  \t*size = xsize_t(sz);\n> +\tif (ss) {\n> +\t\tmemset(&ss->stat, 0, sizeof(ss->stat));\n> +\t\thashcpy(ss->sha1, active_cache[pos]->sha1);\n> +\t}\n>  \treturn data;\n>  }\n>  \n> @@ -529,11 +534,18 @@ static void trim_trailing_spaces(char *buf)\n>  \t\t*last_space = '\\0';\n>  }\n>  \n> -int add_excludes_from_file_to_list(const char *fname,\n> -\t\t\t\t   const char *base,\n> -\t\t\t\t   int baselen,\n> -\t\t\t\t   struct exclude_list *el,\n> -\t\t\t\t   int check_index)\n> +/*\n> + * Given a file with name \"fname\", read it (either from disk, or from\n> + * the index if \"check_index\" is non-zero), parse it and store the\n> + * exclude rules in \"el\".\n> + *\n> + * If \"ss\" is not NULL, compute SHA-1 of the exclude file and fill\n> + * stat data from disk (only valid if add_excludes returns zero). If\n> + * ss_valid is non-zero, \"ss\" must contain good value as input.\n> + */\n\nHmm, do we want a separate parameter for that?  Wouldn't it be\nsufficient and cleaner to pass is_null_sha1(ss->sha1[]), or\nalternatively have an element ss->valid that is initialized to\nfalse?\n\nThat is...\n\n> @@ -571,6 +587,19 @@ int add_excludes_from_file_to_list(const char *fname,\n>  \t\t}\n>  \t\tbuf[size++] = '\\n';\n>  \t\tclose(fd);\n> +\t\tif (ss) {\n> +\t\t\tint pos;\n> +\t\t\tif (ss_valid && !match_stat_data(&ss->stat, &st))\n\ns/ss_valid/ss->valid/;\n\n> +\t\t\t\t; /* no content change, ss->sha1 still good */\n> +\t\t\telse if (check_index &&\n> +\t\t\t\t (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&\n> +\t\t\t\t !ce_stage(active_cache[pos]) &&\n> +\t\t\t\t ce_uptodate(active_cache[pos]))\n> +\t\t\t\thashcpy(ss->sha1, active_cache[pos]->sha1);\n> +\t\t\telse\n> +\t\t\t\thash_sha1_file(buf, size, \"blob\", ss->sha1);\n> +\t\t\tfill_stat_data(&ss->stat, &st);\n\nAnd toggle \"ss->valid = 1\"; here...\n\n> @@ -589,6 +618,13 @@ int add_excludes_from_file_to_list(const char *fname,\n>  \treturn 0;\n>  }\n>  \n> +int add_excludes_from_file_to_list(const char *fname, const char *base,\n> +\t\t\t\t   int baselen, struct exclude_list *el,\n> +\t\t\t\t   int check_index)\n> +{\n> +\treturn add_excludes(fname, base, baselen, el, check_index, NULL, 0);\n> +}\n> +\n>  struct exclude_list *add_exclude_list(struct dir_struct *dir,\n>  \t\t\t\t      int group_type, const char *src)\n>  {\n> diff --git a/dir.h b/dir.h\n> index 6c45e9d..032d197 100644\n> --- a/dir.h\n> +++ b/dir.h\n> @@ -73,6 +73,11 @@ struct exclude_list_group {\n>  \tstruct exclude_list *el;\n>  };\n>  \n> +struct sha1_stat {\n> +\tstruct stat_data stat;\n> +\tunsigned char sha1[20];\n> +};\n> +\n>  struct dir_struct {\n>  \tint nr, alloc;\n>  \tint ignored_nr, ignored_alloc;\n"},{"id":"251139","messageId":"CACsJy8CgiQhAs2ydKLei9QUZLUXCfCHMCAaZcOdKgMVBGNB=FA@mail.gmail.com","threadId":"37820","inReplyTo":"xmqq7fzl5dxw.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 01/19] dir.c: optionally compute sha-1 of a .gitignore file","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-10-28T00:12:26Z","receivedAt":"2014-10-28T00:12:26Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Oct 28, 2014 at 5:46 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> This is not used anywhere yet. But the goal is to compare quickly if a\n>> .gitignore file has changed when we have the SHA-1 of both old (cached\n>> somewhere) and new (from index or a tree) versions.\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>>  dir.c | 50 +++++++++++++++++++++++++++++++++++++++++++-------\n>>  dir.h |  5 +++++\n>>  2 files changed, 48 insertions(+), 7 deletions(-)\n>>\n>> diff --git a/dir.c b/dir.c\n>> index bd274a7..33a35c1 100644\n>> --- a/dir.c\n>> +++ b/dir.c\n>> @@ -466,7 +466,8 @@ void add_exclude(const char *string, const char *base,\n>>       x->el = el;\n>>  }\n>>\n>> -static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n>> +static void *read_skip_worktree_file_from_index(const char *path, size_t *size,\n>> +                                             struct sha1_stat *ss)\n>>  {\n>>       int pos, len;\n>>       unsigned long sz;\n>> @@ -485,6 +486,10 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n>>               return NULL;\n>>       }\n>>       *size = xsize_t(sz);\n>> +     if (ss) {\n>> +             memset(&ss->stat, 0, sizeof(ss->stat));\n>> +             hashcpy(ss->sha1, active_cache[pos]->sha1);\n>> +     }\n>>       return data;\n>>  }\n>>\n>> @@ -529,11 +534,18 @@ static void trim_trailing_spaces(char *buf)\n>>               *last_space = '\\0';\n>>  }\n>>\n>> -int add_excludes_from_file_to_list(const char *fname,\n>> -                                const char *base,\n>> -                                int baselen,\n>> -                                struct exclude_list *el,\n>> -                                int check_index)\n>> +/*\n>> + * Given a file with name \"fname\", read it (either from disk, or from\n>> + * the index if \"check_index\" is non-zero), parse it and store the\n>> + * exclude rules in \"el\".\n>> + *\n>> + * If \"ss\" is not NULL, compute SHA-1 of the exclude file and fill\n>> + * stat data from disk (only valid if add_excludes returns zero). If\n>> + * ss_valid is non-zero, \"ss\" must contain good value as input.\n>> + */\n>\n> Hmm, do we want a separate parameter for that?  Wouldn't it be\n> sufficient and cleaner to pass is_null_sha1(ss->sha1[]),\n\nHm.. no. If both ss->sha1 and ss->stat are valid, then we could try to\nmatch stat on disk and reuse ss->sha1, so we can't use a special value\nof ss->sha1[] to mark the validity.\n\n> or alternatively have an element ss->valid that is initialized to false?\n\nYeah..\n-- \nDuy\n"},{"id":"251147","messageId":"544FD44D.4000101@web.de","threadId":"37820","inReplyTo":"1414411846-4450-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 01/19] dir.c: optionally compute sha-1 of a .gitignore file","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-10-28T17:37:17Z","receivedAt":"2014-10-28T17:37:17Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"\nOn 2014-10-27 13.10, Nguyễn Thái Ngọc Duy wrote:\n[]\nNice serious, I can imagine to test & benchmark it (so I assume there is a branch \non github or so ?)\nAnother thing:\nCan we switch the feature off?\n\nIt could be nice to benchmark with and without the cache on the command line,\nand besides that we may want to switch it on or off, depending on the file system.\nI think this can be easily done when reading and writing the index file.\n(But may cost a config variable, core.dirmtime ??)\n\nTo my knowledge there is support for the mtime in SAMBA (and probably NFS),\nbut I can help to find out more.\n\n\n\n> diff --git a/dir.c b/dir.c\n> +static int add_excludes(const char *fname, const char *base, int baselen,\n> +\t\t\tstruct exclude_list *el, int check_index,\n> +\t\t\tstruct sha1_stat *ss, int ss_valid)\nCosmetic question: does it make sense to write\n\nstruct sha1_stat *sha1_stat\nor \nstruct sha1_stat *s_stat\n"},{"id":"251148","messageId":"544FD465.2040700@web.de","threadId":"37820","inReplyTo":"1414411846-4450-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 02/19] untracked cache: record .gitignore information and dir hierarchy","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-10-28T17:37:41Z","receivedAt":"2014-10-28T17:37:41Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-10-27 13.10, Nguyễn Thái Ngọc Duy wrote:\n\n>  dir.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------\n>  dir.h |  60 +++++++++++++++++++++++++++\n\n>  2 files changed, 189 insertions(+), 21 deletions(-)\n\n[]\n\n> +struct untracked_cache_dir {\n> +\tstruct untracked_cache_dir **dirs;\n> +\tchar **untracked;\n> +\t/* null SHA-1 means this directory does not have .gitignore */\n> +\tunsigned char exclude_sha1[20];\n> +\tstruct stat_data stat_data;\n> +\tunsigned int check_only : 1;\n> +\tunsigned int untracked_nr : 29;\n> +\tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n> +\tchar name[1];\n> +};\nAre we utilizing the CPU and the L2 cache in a good way ?\nI would consider to re-arrange, according to the following rule:\n- 64 bit ints first (we do not have any)\n- pointers (may be 64 or 32 bits)\n- structs\n- ints\n- chars\n\nAnd then we have the question why untracked_nr gets 29 bits,\nand check_only one bit, which means we have 2 bits spare ?\n\nFrom what I know from CPUs and compilers it could be slighty better to give\n32 bits to untracked_nr and either 1 bit to check_only,\nor simply make check_only a char.\nOr do I miss something ?\n\n> +\tstruct untracked_cache_dir **dirs;\n> +\tchar **untracked;\n> +\t/* null SHA-1 means this directory does not have .gitignore */\n> +\tstruct stat_data stat_data;\n> +\tunsigned char exclude_sha1[20];\n> +\tunsigned int check_only : 1;\n> +\tunsigned int untracked_nr : 29;\n> +\tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n> +\tchar name[1];\n"},{"id":"251149","messageId":"544FD474.3050805@web.de","threadId":"37820","inReplyTo":"1414411846-4450-19-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 18/19] update-index: test the system before enabling untracked cache","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-10-28T17:37:56Z","receivedAt":"2014-10-28T17:37:56Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-10-27 13.10, Nguyễn Thái Ngọc Duy wrote:\n[]\n\n> +static void xmkdir(const char *path)\n> +{\n> +\tif (mkdir(path, 0700))\n> +\t\tdie_errno(_(\"failed to create directory %s\"), path);\n> +}\n\nDoes it makes sense to ignore EINTR and do a \"retry\" ?\nAnother question is if the function could be called mkdir_or_die() instead?\n \nI realized that there are 2 families of xfunc() in wrapper.c, some die, some retry.\n"},{"id":"251171","messageId":"CAPig+cS9C9m-wqMO3up+BRHBN1a0FB-eDO7UUPv7NLx1JCC3PQ@mail.gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-19-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 18/19] update-index: test the system before enabling untracked cache","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-10-28T23:25:19Z","receivedAt":"2014-10-28T23:25:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Oct 27, 2014 at 8:10 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/update-index.c b/builtin/update-index.c\n> index e57e2d7..471c0b4 100644\n> --- a/builtin/update-index.c\n> +++ b/builtin/update-index.c\n> @@ -48,6 +48,145 @@ static void report(const char *fmt, ...)\n>         va_end(vp);\n>  }\n>\n> +static int test_if_untracked_cache_is_supported(void)\n> +{\n> +       struct stat st;\n> +       struct stat_data base;\n> +       int fd;\n> +\n> +       fprintf(stderr, _(\"Testing \"));\n> +       xmkdir(\"dir-mtime-test\");\n> +       atexit(remove_test_directory);\n> +       xstat(\"dir-mtime-test\", &st);\n> +       fill_stat_data(&base, &st);\n> +       fputc('.', stderr);\n> +\n> +       avoid_racy();\n> +       fd = create_file(\"dir-mtime-test/newfile\");\n> +       xstat(\"dir-mtime-test\", &st);\n> +       if (!match_stat_data(&base, &st)) {\n\nclose(fd);\n\n> +               fputc('\\n', stderr);\n> +               fprintf_ln(stderr,_(\"directory stat info does not \"\n> +                                   \"change after adding a new file\"));\n> +               return 0;\n> +       }\n> +       fill_stat_data(&base, &st);\n> +       fputc('.', stderr);\n> +\n> +       avoid_racy();\n> +       xmkdir(\"dir-mtime-test/new-dir\");\n> +       xstat(\"dir-mtime-test\", &st);\n> +       if (!match_stat_data(&base, &st)) {\n\nclose(fd);\n\n> +               fputc('\\n', stderr);\n> +               fprintf_ln(stderr, _(\"directory stat info does not change \"\n> +                                    \"after adding a new directory\"));\n> +               return 0;\n> +       }\n> +       fill_stat_data(&base, &st);\n> +       fputc('.', stderr);\n> +\n> +       avoid_racy();\n> +       write_or_die(fd, \"data\", 4);\n> +       close(fd);\n> +       xstat(\"dir-mtime-test\", &st);\n> +       if (match_stat_data(&base, &st)) {\n> +               fputc('\\n', stderr);\n> +               fprintf_ln(stderr, _(\"directory stat info changes \"\n> +                                    \"after updating a file\"));\n> +               return 0;\n> +       }\n> +       fputc('.', stderr);\n> +\n> +       avoid_racy();\n> +       close(create_file(\"dir-mtime-test/new-dir/new\"));\n> +       xstat(\"dir-mtime-test\", &st);\n> +       if (match_stat_data(&base, &st)) {\n> +               fputc('\\n', stderr);\n> +               fprintf_ln(stderr, _(\"directory stat info changes after \"\n> +                                    \"adding a file inside subdirectory\"));\n> +               return 0;\n> +       }\n> +       fputc('.', stderr);\n> +\n> +       avoid_racy();\n> +       xunlink(\"dir-mtime-test/newfile\");\n> +       xstat(\"dir-mtime-test\", &st);\n> +       if (!match_stat_data(&base, &st)) {\n> +               fputc('\\n', stderr);\n> +               fprintf_ln(stderr, _(\"directory stat info does not \"\n> +                                    \"change after deleting a file\"));\n> +               return 0;\n> +       }\n> +       fill_stat_data(&base, &st);\n> +       fputc('.', stderr);\n> +\n> +       avoid_racy();\n> +       xunlink(\"dir-mtime-test/new-dir/new\");\n> +       xrmdir(\"dir-mtime-test/new-dir\");\n> +       xstat(\"dir-mtime-test\", &st);\n> +       if (!match_stat_data(&base, &st)) {\n> +               fputc('\\n', stderr);\n> +               fprintf_ln(stderr, _(\"directory stat info does not \"\n> +                                    \"change after deleting a directory\"));\n> +               return 0;\n> +       }\n> +\n> +       xrmdir(\"dir-mtime-test\");\n> +       fprintf_ln(stderr, _(\" OK\"));\n> +       return 1;\n> +}\n"},{"id":"251220","messageId":"CAPig+cRQPWRjCeZ6LD0pn3DRX_rZcG-modk5OGXxqoyb=kE6iw@mail.gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-7-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 06/19] untracked cache: record/validate dir mtime and reuse cached output","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-10-30T16:19:12Z","receivedAt":"2014-10-30T16:19:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Oct 27, 2014 at 8:10 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> diff --git a/dir.c b/dir.c\n> index 2793e57..55780a7 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -37,7 +37,12 @@ enum path_treatment {\n> +static enum path_treatment treat_path_fast(struct dir_struct *dir,\n> +                                          struct untracked_cache_dir *untracked,\n> +                                          struct cached_dir *cdir,\n> +                                          struct strbuf *path,\n> +                                          int baselen,\n> +                                          const struct path_simplify *simplify)\n> +{\n> +       if (!cdir->ucd) {\n> +               strbuf_setlen(path, baselen);\n> +               strbuf_addstr(path, cdir->file);\n> +               return path_untracked;\n> +       }\n> +       strbuf_setlen(path, baseline);\n\nWould it make sense to move the strbuf_setlen(path,baseline) above the\nconditional since it is common to both cases, or are they conceptually\ndistinct enough that it is clearer to duplicate the function call for\neach case?\n\n> +       strbuf_addstr(path, cdir->ucd->name);\n> +       /* treat_one_path() does this before it calls treat_directory() */\n> +       if (path->buf[path->len - 1] != '/')\n> +               strbuf_addch(path, '/');\n> +       if (cdir->ucd->check_only)\n> +               /*\n> +                * check_only is set as a result of treat_directory() getting\n> +                * to its bottom. Verify again the same set of directories\n> +                * with check_only set.\n> +                */\n> +               return read_directory_recursive(dir, path->buf, path->len,\n> +\n> +                                               cdir->ucd, 1, simplify);\n\nUnusual blank line placement.\n\n> +       /*\n> +        * We get path_recurse in the first run when\n> +        * directory_exists_in_index() returns index_nonexistent. We\n> +        * are sure that new changes in the index does not impact the\n> +        * outcome. Return now.\n> +        */\n> +       return path_recurse;\n> +}\n> +\n> @@ -1477,6 +1590,12 @@ static void close_cached_dir(struct cached_dir *cdir)\n>  {\n>         if (cdir->fdir)\n>                 closedir(cdir->fdir);\n> +       /*\n> +        * We have gone through this directory and found no untracked\n> +        * entries. Mark it valid.\n> +        */\n> +       if (cdir->untracked && !cdir->untracked->valid)\n> +               cdir->untracked->valid = 1;\n\nOr, stated more simply:\n\n   if (cdir->untracked)\n        cdir->untracked->valid = 1;\n\n>  }\n>\n>  /*\n"},{"id":"251288","messageId":"CACsJy8COjFxFUoUn7YY-0ciGmmRnih+ijPoF7PAd=89ejAUfiA@mail.gmail.com","threadId":"37820","inReplyTo":"544FD44D.4000101@web.de","subject":"Re: [PATCH 01/19] dir.c: optionally compute sha-1 of a .gitignore file","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-02T01:25:12Z","receivedAt":"2014-11-02T01:25:12Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Oct 29, 2014 at 12:37 AM, Torsten Bögershausen <tboegi@web.de> wrote:\n>\n> On 2014-10-27 13.10, Nguyễn Thái Ngọc Duy wrote:\n> []\n> Nice serious, I can imagine to test & benchmark it (so I assume there is a branch\n> on github or so ?)\n\nIt's on 'pu' now. There's a branch on my github repo, but it has some\nextra debugging on top, so 'pu' is probably the best option.\n\n> Another thing:\n> Can we switch the feature off?\n>\n> It could be nice to benchmark with and without the cache on the command line,\n> and besides that we may want to switch it on or off, depending on the file system.\n> I think this can be easily done when reading and writing the index file.\n> (But may cost a config variable, core.dirmtime ??)\n\nYou can permanently switch it off with \"git update-index\n--no-untracked-cache\". An option to temporarily disable it is not\navailable. I'll add an environment variable for that.\n\nFor a normal case, \"update-index --untracked-cache\" would test if the\nOS/FS supports this before enabling it. If the repo is moved to\nanother fs, or being used by a different OS, then the user has to\nmanually disable it first. I don't know what we can do here, maybe\nrecord uname and filesystem in the index as well..\n\n>> diff --git a/dir.c b/dir.c\n>> +static int add_excludes(const char *fname, const char *base, int baselen,\n>> +                     struct exclude_list *el, int check_index,\n>> +                     struct sha1_stat *ss, int ss_valid)\n> Cosmetic question: does it make sense to write\n>\n> struct sha1_stat *sha1_stat\n> or\n> struct sha1_stat *s_stat\n\nNoted.\n\n\n-- \nDuy\n"},{"id":"251298","messageId":"CACsJy8ASZX6rVMgUrScFh-RWWhuhQ+P_H1bwb9OvwmC+DRp9zg@mail.gmail.com","threadId":"37820","inReplyTo":"544FD474.3050805@web.de","subject":"Re: [PATCH 18/19] update-index: test the system before enabling untracked cache","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-03T12:16:46Z","receivedAt":"2014-11-03T12:16:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Oct 29, 2014 at 12:37 AM, Torsten Bögershausen <tboegi@web.de> wrote:\n> On 2014-10-27 13.10, Nguyễn Thái Ngọc Duy wrote:\n> []\n>\n>> +static void xmkdir(const char *path)\n>> +{\n>> +     if (mkdir(path, 0700))\n>> +             die_errno(_(\"failed to create directory %s\"), path);\n>> +}\n>\n> Does it makes sense to ignore EINTR and do a \"retry\" ?\n> Another question is if the function could be called mkdir_or_die() instead?\n>\n> I realized that there are 2 families of xfunc() in wrapper.c, some die, some retry.\n\nThis is only used interactively, I think it's ok to ignore EINTR as\nlong as we report clearly the case (and hope the user to re-enter the\ncommand)\n-- \nDuy\n"},{"id":"251320","messageId":"xmqqr3xkrw9f.fsf@gitster.dls.corp.google.com","threadId":"37820","inReplyTo":"544FD474.3050805@web.de","subject":"Re: [PATCH 18/19] update-index: test the system before enabling untracked cache","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-11-03T18:09:48Z","receivedAt":"2014-11-03T18:09:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Torsten Bögershausen <tboegi@web.de> writes:\n\n> On 2014-10-27 13.10, Nguyễn Thái Ngọc Duy wrote:\n> []\n>\n>> +static void xmkdir(const char *path)\n>> +{\n>> +\tif (mkdir(path, 0700))\n>> +\t\tdie_errno(_(\"failed to create directory %s\"), path);\n>> +}\n>\n> Does it makes sense to ignore EINTR and do a \"retry\" ?\n> Another question is if the function could be called mkdir_or_die() instead?\n\nProbably.  It is in the same league as xread() and xwrite().\n\n> I realized that there are 2 families of xfunc() in wrapper.c, some die, some retry.\n\nThe general idea of wrapper.c is to free callers from coding\nsomething they have only one sensible choice for.  The ones that\nretry such as xread() and xwrite() are to free callers from\ndiagnosing and retrying upon EAGAIN/EINTR because they want to read\nor write and there is no other thing they want to do.  The ones that\ndie such as xmkstemp() are for the failure modes that are likely\nwithout any other choice.  xcalloc() and other allocators started as\n(and still are) the latter, but instead of immediately dying they\nknow a last-ditch effort that is common to all Git operations.\n"},{"id":"251525","messageId":"1415439595-469-1-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1414411846-4450-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 00/22] untracked cache updates","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:33Z","receivedAt":"2014-11-08T09:39:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Updated based on comments from the list, including three new patches:\n\n - 16/22 allows to ignore untracked cache without destroying it (for\n   comparison and verification)\n - 21/22 and 22/22 add some protection against filesystem or operating\n   system changes\n\nAlso fix 'update-index --untracked-cache' essentially merging the\nsplit index back because I set wrong update flag.\n\nThis series is also available on github [1] but you will have to\nignore the few top debugging patches first. Diff against the version\non 'pu' below.\n\n[1] https://github.com/pclouds/git.git untracked-cache\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex c1c18db..f23ec83 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -115,6 +115,7 @@ static int test_if_untracked_cache_is_supported(void)\n \tfd = create_file(\"dir-mtime-test/newfile\");\n \txstat(\"dir-mtime-test\", &st);\n \tif (!match_stat_data(&base, &st)) {\n+\t\tclose(fd);\n \t\tfputc('\\n', stderr);\n \t\tfprintf_ln(stderr,_(\"directory stat info does not \"\n \t\t\t\t    \"change after adding a new file\"));\n@@ -127,6 +128,7 @@ static int test_if_untracked_cache_is_supported(void)\n \txmkdir(\"dir-mtime-test/new-dir\");\n \txstat(\"dir-mtime-test\", &st);\n \tif (!match_stat_data(&base, &st)) {\n+\t\tclose(fd);\n \t\tfputc('\\n', stderr);\n \t\tfprintf_ln(stderr, _(\"directory stat info does not change \"\n \t\t\t\t     \"after adding a new directory\"));\n@@ -1094,10 +1096,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\t/* should be the same flags used by git-status */\n \t\tuc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n \t\tthe_index.untracked = uc;\n-\t\tthe_index.cache_changed |= SOMETHING_CHANGED;\n+\t\tthe_index.cache_changed |= UNTRACKED_CHANGED;\n \t} else if (!untracked_cache && the_index.untracked) {\n \t\tthe_index.untracked = NULL;\n-\t\tthe_index.cache_changed |= SOMETHING_CHANGED;\n+\t\tthe_index.cache_changed |= UNTRACKED_CHANGED;\n \t}\n \n \tif (active_cache_changed) {\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex c5c37e5..b817678 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2128,3 +2128,14 @@ void mingw_startup()\n \t/* initialize Unicode console */\n \twinansi_init();\n }\n+\n+int uname(struct utsname *buf)\n+{\n+\tDWORD v = GetVersion();\n+\tmemset(buf, 0, sizeof(*buf));\n+\tsprintf(buf->sysname, \"Windows\");\n+\tsprintf(buf->release, \"%u.%u\", v & 0xff, (v >> 8) & 0xff);\n+\t/* assuming NT variants only.. */\n+\tsprintf(buf->version, \"%u\", (v >> 16) & 0x7fff);\n+\treturn 0;\n+}\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex df0e320..d00ba7a 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -77,6 +77,14 @@ struct itimerval {\n };\n #define ITIMER_REAL 0\n \n+struct utsname {\n+\tchar sysname[16];\n+\tchar nodename[1];\n+\tchar release[16];\n+\tchar version[16];\n+\tchar machine[1];\n+};\n+\n /*\n  * sanitize preprocessor namespace polluted by Windows headers defining\n  * macros which collide with git local versions\n@@ -166,6 +174,7 @@ struct passwd *getpwuid(uid_t uid);\n int setitimer(int type, struct itimerval *in, struct itimerval *out);\n int sigaction(int sig, struct sigaction *in, struct sigaction *out);\n int link(const char *oldpath, const char *newpath);\n+int uname(struct utsname *buf);\n \n /*\n  * replacements of existing functions\ndiff --git a/dir.c b/dir.c\nindex 0ae2188..2324c52 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -482,7 +482,7 @@ void add_exclude(const char *string, const char *base,\n }\n \n static void *read_skip_worktree_file_from_index(const char *path, size_t *size,\n-\t\t\t\t\t\tstruct sha1_stat *ss)\n+\t\t\t\t\t\tstruct sha1_stat *sha1_stat)\n {\n \tint pos, len;\n \tunsigned long sz;\n@@ -501,9 +501,9 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size,\n \t\treturn NULL;\n \t}\n \t*size = xsize_t(sz);\n-\tif (ss) {\n-\t\tmemset(&ss->stat, 0, sizeof(ss->stat));\n-\t\thashcpy(ss->sha1, active_cache[pos]->sha1);\n+\tif (sha1_stat) {\n+\t\tmemset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));\n+\t\thashcpy(sha1_stat->sha1, active_cache[pos]->sha1);\n \t}\n \treturn data;\n }\n@@ -634,7 +634,7 @@ static void invalidate_directory(struct untracked_cache *uc,\n  */\n static int add_excludes(const char *fname, const char *base, int baselen,\n \t\t\tstruct exclude_list *el, int check_index,\n-\t\t\tstruct sha1_stat *ss, int ss_valid)\n+\t\t\tstruct sha1_stat *sha1_stat)\n {\n \tstruct stat st;\n \tint fd, i, lineno = 1;\n@@ -648,7 +648,7 @@ static int add_excludes(const char *fname, const char *base, int baselen,\n \t\tif (0 <= fd)\n \t\t\tclose(fd);\n \t\tif (!check_index ||\n-\t\t    (buf = read_skip_worktree_file_from_index(fname, &size, ss)) == NULL)\n+\t\t    (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)\n \t\t\treturn -1;\n \t\tif (size == 0) {\n \t\t\tfree(buf);\n@@ -661,9 +661,10 @@ static int add_excludes(const char *fname, const char *base, int baselen,\n \t} else {\n \t\tsize = xsize_t(st.st_size);\n \t\tif (size == 0) {\n-\t\t\tif (ss) {\n-\t\t\t\tfill_stat_data(&ss->stat, &st);\n-\t\t\t\thashcpy(ss->sha1, EMPTY_BLOB_SHA1_BIN);\n+\t\t\tif (sha1_stat) {\n+\t\t\t\tfill_stat_data(&sha1_stat->stat, &st);\n+\t\t\t\thashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);\n+\t\t\t\tsha1_stat->valid = 1;\n \t\t\t}\n \t\t\tclose(fd);\n \t\t\treturn 0;\n@@ -676,19 +677,20 @@ static int add_excludes(const char *fname, const char *base, int baselen,\n \t\t}\n \t\tbuf[size++] = '\\n';\n \t\tclose(fd);\n-\t\tif (ss) {\n+\t\tif (sha1_stat) {\n \t\t\tint pos;\n-\t\t\tif (ss_valid &&\n-\t\t\t    !match_stat_data_racy(&the_index, &ss->stat, &st))\n+\t\t\tif (sha1_stat->valid &&\n+\t\t\t    !match_stat_data_racy(&the_index, &sha1_stat->stat, &st))\n \t\t\t\t; /* no content change, ss->sha1 still good */\n \t\t\telse if (check_index &&\n \t\t\t\t (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&\n \t\t\t\t !ce_stage(active_cache[pos]) &&\n \t\t\t\t ce_uptodate(active_cache[pos]))\n-\t\t\t\thashcpy(ss->sha1, active_cache[pos]->sha1);\n+\t\t\t\thashcpy(sha1_stat->sha1, active_cache[pos]->sha1);\n \t\t\telse\n-\t\t\t\thash_sha1_file(buf, size, \"blob\", ss->sha1);\n-\t\t\tfill_stat_data(&ss->stat, &st);\n+\t\t\t\thash_sha1_file(buf, size, \"blob\", sha1_stat->sha1);\n+\t\t\tfill_stat_data(&sha1_stat->stat, &st);\n+\t\t\tsha1_stat->valid = 1;\n \t\t}\n \t}\n \n@@ -712,7 +714,7 @@ int add_excludes_from_file_to_list(const char *fname, const char *base,\n \t\t\t\t   int baselen, struct exclude_list *el,\n \t\t\t\t   int check_index)\n {\n-\treturn add_excludes(fname, base, baselen, el, check_index, NULL, 0);\n+\treturn add_excludes(fname, base, baselen, el, check_index, NULL);\n }\n \n struct exclude_list *add_exclude_list(struct dir_struct *dir,\n@@ -733,7 +735,7 @@ struct exclude_list *add_exclude_list(struct dir_struct *dir,\n  * Used to set up core.excludesfile and .git/info/exclude lists.\n  */\n static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n-\t\t\t\t     struct sha1_stat *ss, int ss_valid)\n+\t\t\t\t     struct sha1_stat *sha1_stat)\n {\n \tstruct exclude_list *el;\n \t/*\n@@ -744,14 +746,14 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n \tif (!dir->untracked)\n \t\tdir->unmanaged_exclude_files++;\n \tel = add_exclude_list(dir, EXC_FILE, fname);\n-\tif (add_excludes(fname, \"\", 0, el, 0, ss, ss_valid) < 0)\n+\tif (add_excludes(fname, \"\", 0, el, 0, sha1_stat) < 0)\n \t\tdie(\"cannot use %s as an exclude file\", fname);\n }\n \n void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n {\n \tdir->unmanaged_exclude_files++; /* see validate_untracked_cache() */\n-\tadd_excludes_from_file_1(dir, fname, NULL, 0);\n+\tadd_excludes_from_file_1(dir, fname, NULL);\n }\n \n int match_basename(const char *basename, int basenamelen,\n@@ -974,7 +976,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \twhile (current < baselen) {\n \t\tstruct exclude_stack *stk = xcalloc(1, sizeof(*stk));\n \t\tconst char *cp;\n-\t\tstruct sha1_stat ss;\n+\t\tstruct sha1_stat sha1_stat;\n \n \t\tif (current < 0) {\n \t\t\tcp = base;\n@@ -1015,7 +1017,8 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t}\n \n \t\t/* Try to read per-directory file */\n-\t\thashclr(ss.sha1);\n+\t\thashclr(sha1_stat.sha1);\n+\t\tsha1_stat.valid = 0;\n \t\tif (dir->exclude_per_dir &&\n \t\t    /*\n \t\t     * If we know that no files have been added in\n@@ -1044,7 +1047,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tstrbuf_addstr(&sb, dir->exclude_per_dir);\n \t\t\tel->src = strbuf_detach(&sb, NULL);\n \t\t\tadd_excludes(el->src, el->src, stk->baselen, el, 1,\n-\t\t\t\t     untracked ? &ss : NULL, 0);\n+\t\t\t\t     untracked ? &sha1_stat : NULL);\n \t\t}\n \t\t/*\n \t\t * NEEDSWORK: when untracked cache is enabled, prep_exclude()\n@@ -1060,9 +1063,10 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t * last_exclude_matching(). Be careful about ignore rule\n \t\t * order, though, if you do that.\n \t\t */\n-\t\tif (untracked && hashcmp(ss.sha1, untracked->exclude_sha1)) {\n+\t\tif (untracked &&\n+\t\t    hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) {\n \t\t\tinvalidate_gitignore(dir->untracked, untracked);\n-\t\t\thashcpy(untracked->exclude_sha1, ss.sha1);\n+\t\t\thashcpy(untracked->exclude_sha1, sha1_stat.sha1);\n \t\t}\n \t\tdir->exclude_stack = stk;\n \t\tcurrent = stk->baselen;\n@@ -1457,12 +1461,11 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,\n \t\t\t\t\t   int baselen,\n \t\t\t\t\t   const struct path_simplify *simplify)\n {\n+\tstrbuf_setlen(path, baselen);\n \tif (!cdir->ucd) {\n-\t\tstrbuf_setlen(path, baselen);\n \t\tstrbuf_addstr(path, cdir->file);\n \t\treturn path_untracked;\n \t}\n-\tstrbuf_setlen(path, baselen);\n \tstrbuf_addstr(path, cdir->ucd->name);\n \t/* treat_one_path() does this before it calls treat_directory() */\n \tif (path->buf[path->len - 1] != '/')\n@@ -1474,7 +1477,6 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,\n \t\t * with check_only set.\n \t\t */\n \t\treturn read_directory_recursive(dir, path->buf, path->len,\n-\n \t\t\t\t\t\tcdir->ucd, 1, simplify);\n \t/*\n \t * We get path_recurse in the first run when\n@@ -1582,7 +1584,7 @@ static int open_cached_dir(struct cached_dir *cdir,\n \treturn 0;\n }\n \n-int read_cached_dir(struct cached_dir *cdir)\n+static int read_cached_dir(struct cached_dir *cdir)\n {\n \tif (cdir->fdir) {\n \t\tcdir->de = readdir(cdir->fdir);\n@@ -1796,7 +1798,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \tstruct untracked_cache_dir *root;\n \tint i;\n \n-\tif (!dir->untracked)\n+\tif (!dir->untracked || getenv(\"GIT_DISABLE_UNTRACKED_CACHE\"))\n \t\treturn NULL;\n \n \t/*\n@@ -2101,30 +2103,12 @@ void setup_standard_excludes(struct dir_struct *dir)\n \t\thome_config_paths(NULL, &xdg_path, \"ignore\");\n \t\texcludes_file = xdg_path;\n \t}\n-\tif (!access_or_warn(path, R_OK, 0)) {\n-\t\tstruct sha1_stat *ss = NULL;\n-\t\tint ss_valid = 0;\n-\t\tif (dir->untracked) {\n-\t\t\tss = &dir->ss_info_exclude;\n-\t\t\tif (dir->untracked->loaded) {\n-\t\t\t\t*ss = dir->untracked->ss_info_exclude;\n-\t\t\t\tss_valid = 1;\n-\t\t\t}\n-\t\t}\n-\t\tadd_excludes_from_file_1(dir, path, ss, ss_valid);\n-\t}\n-\tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) {\n-\t\tstruct sha1_stat *ss = NULL;\n-\t\tint ss_valid = 0;\n-\t\tif (dir->untracked) {\n-\t\t\tss = &dir->ss_excludes_file;\n-\t\t\tif (dir->untracked->loaded) {\n-\t\t\t\t*ss = dir->untracked->ss_excludes_file;\n-\t\t\t\tss_valid = 1;\n-\t\t\t}\n-\t\t}\n-\t\tadd_excludes_from_file_1(dir, excludes_file, ss, ss_valid);\n-\t}\n+\tif (!access_or_warn(path, R_OK, 0))\n+\t\tadd_excludes_from_file_1(dir, path,\n+\t\t\t\t\t dir->untracked ? &dir->ss_info_exclude : NULL);\n+\tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n+\t\tadd_excludes_from_file_1(dir, excludes_file,\n+\t\t\t\t\t dir->untracked ? &dir->ss_excludes_file : NULL);\n }\n \n int remove_path(const char *name)\n@@ -2243,10 +2227,22 @@ static void write_one_dir(struct strbuf *out, struct untracked_cache_dir *untrac\n \t\t\twrite_one_dir(out, untracked->dirs[i]);\n }\n \n+static void get_ident_string(struct strbuf *sb)\n+{\n+\tstruct utsname uts;\n+\n+\tif (uname(&uts))\n+\t\tdie_errno(_(\"failed to get kernel name and information\"));\n+\tstrbuf_addf(sb, \"Location %s, system %s %s %s\", get_git_work_tree(),\n+\t\t    uts.sysname, uts.release, uts.version);\n+}\n+\n void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)\n {\n \tstruct ondisk_untracked_cache *ouc;\n-\tint len = 0;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tunsigned char varbuf[16];\n+\tint len = 0, varint_len;\n \tif (untracked->exclude_per_dir)\n \t\tlen = strlen(untracked->exclude_per_dir);\n \touc = xmalloc(sizeof(*ouc) + len);\n@@ -2256,6 +2252,13 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra\n \thashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);\n \touc->dir_flags = htonl(untracked->dir_flags);\n \tmemcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);\n+\n+\tget_ident_string(&sb);\n+\tvarint_len = encode_varint(sb.len + 1, varbuf);\n+\tstrbuf_add(out, varbuf, varint_len);\n+\tstrbuf_add(out, sb.buf, sb.len + 1);\n+\tstrbuf_release(&sb);\n+\n \tstrbuf_add(out, ouc, sizeof(*ouc) + len);\n \tif (untracked->root)\n \t\twrite_one_dir(out, untracked->root);\n@@ -2360,28 +2363,55 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,\n \treturn data - data_;\n }\n \n+static void load_sha1_stat(struct sha1_stat *sha1_stat,\n+\t\t\t   const struct stat_data *stat,\n+\t\t\t   const unsigned char *sha1)\n+{\n+\tstat_data_from_disk(&sha1_stat->stat, stat);\n+\thashcpy(sha1_stat->sha1, sha1);\n+\tsha1_stat->valid = 1;\n+}\n+\n struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)\n {\n-\tconst struct ondisk_untracked_cache *ouc = data;\n+\tconst struct ondisk_untracked_cache *ouc;\n \tstruct untracked_cache *uc;\n+\tconst unsigned char *next = data;\n+\tstruct strbuf sb = STRBUF_INIT;\n \tint len;\n \n+\tlen = decode_varint(&next);\n+\tif (sz <= (next - (const unsigned char *)data) + len ||\n+\t    next[len - 1] != '\\0')\n+\t\treturn NULL;\n+\n+\tget_ident_string(&sb);\n+\tif (strcmp(sb.buf, (const char *)next)) {\n+\t\twarning(_(\"system identification does not match, untracked cache disabled.\\n\"\n+\t\t\t  \"Stored: %s\\nCurrent: %s\\n\"),\n+\t\t\tnext, sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn NULL;\n+\t}\n+\tstrbuf_release(&sb);\n+\touc = (const struct ondisk_untracked_cache *)(next + len);\n+\tsz -= (const char *)ouc - (const char *)data;\n+\n \tif (sz < sizeof(*ouc))\n \t\treturn NULL;\n \n \tuc = xcalloc(1, sizeof(*uc));\n-\tstat_data_from_disk(&uc->ss_info_exclude.stat, &ouc->info_exclude_stat);\n-\tstat_data_from_disk(&uc->ss_excludes_file.stat, &ouc->excludes_file_stat);\n-\thashcpy(uc->ss_info_exclude.sha1, ouc->info_exclude_sha1);\n-\thashcpy(uc->ss_excludes_file.sha1, ouc->excludes_file_sha1);\n+\tload_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat,\n+\t\t       ouc->info_exclude_sha1);\n+\tload_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat,\n+\t\t       ouc->excludes_file_sha1);\n \tuc->dir_flags = get_be32(&ouc->dir_flags);\n \tuc->exclude_per_dir = xstrdup(ouc->exclude_per_dir);\n-\tuc->loaded = 1;\n \tlen = sizeof(*ouc) + strlen(ouc->exclude_per_dir);\n \tif (sz == len)\n \t\treturn uc;\n \tif (sz > len &&\n-\t    read_one_dir(&uc->root, (const unsigned char *)data + len,\n+\t    read_one_dir(&uc->root, (const unsigned char *)ouc + len,\n \t\t\t sz - len) == sz - len)\n \t\treturn uc;\n \tfree_untracked_cache(uc);\ndiff --git a/dir.h b/dir.h\nindex 8c29324..708cdd5 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -77,6 +77,7 @@ struct exclude_list_group {\n struct sha1_stat {\n \tstruct stat_data stat;\n \tunsigned char sha1[20];\n+\tint valid;\n };\n \n /*\n@@ -110,15 +111,15 @@ struct sha1_stat {\n struct untracked_cache_dir {\n \tstruct untracked_cache_dir **dirs;\n \tchar **untracked;\n-\t/* null SHA-1 means this directory does not have .gitignore */\n-\tunsigned char exclude_sha1[20];\n \tstruct stat_data stat_data;\n-\tunsigned int recurse : 1;\n+\tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n+\tunsigned int untracked_nr;\n \tunsigned int check_only : 1;\n \t/* all data except 'dirs' in this struct are good */\n \tunsigned int valid : 1;\n-\tunsigned int untracked_nr : 29;\n-\tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n+\tunsigned int recurse : 1;\n+\t/* null SHA-1 means this directory does not have .gitignore */\n+\tunsigned char exclude_sha1[20];\n \tchar name[1];\n };\n \n@@ -137,7 +138,6 @@ struct untracked_cache {\n \tint gitignore_invalidated;\n \tint dir_invalidated;\n \tint dir_opened;\n-\tint loaded;\n };\n \n struct dir_struct {\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex f587749..6b1f259 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -125,6 +125,7 @@\n #else\n #include <poll.h>\n #endif\n+#include <sys/utsname.h>\n \n #if defined(__MINGW32__)\n /* pull in Windows compatibility stuff */\ndiff --git a/test-dump-untracked-cache.c b/test-dump-untracked-cache.c\nindex 710441e..25d855d 100644\n--- a/test-dump-untracked-cache.c\n+++ b/test-dump-untracked-cache.c\n@@ -44,6 +44,7 @@ int main(int ac, char **av)\n {\n \tstruct untracked_cache *uc;\n \tstruct strbuf base = STRBUF_INIT;\n+\tsetup_git_directory();\n \tif (read_cache() < 0)\n \t\tdie(\"unable to read index file\");\n \tuc = the_index.untracked;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251526","messageId":"1415439595-469-2-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 01/22] dir.c: optionally compute sha-1 of a .gitignore file","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:34Z","receivedAt":"2014-11-08T09:39:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is not used anywhere yet. But the goal is to compare quickly if a\n.gitignore file has changed when we have the SHA-1 of both old (cached\nsomewhere) and new (from index or a tree) versions.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Torsten Bögershausen <tboegi@web.de>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++-------\n dir.h |  6 ++++++\n 2 files changed, 52 insertions(+), 7 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex fcb6872..4cc936b 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -466,7 +466,8 @@ void add_exclude(const char *string, const char *base,\n \tx->el = el;\n }\n \n-static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n+static void *read_skip_worktree_file_from_index(const char *path, size_t *size,\n+\t\t\t\t\t\tstruct sha1_stat *sha1_stat)\n {\n \tint pos, len;\n \tunsigned long sz;\n@@ -485,6 +486,10 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)\n \t\treturn NULL;\n \t}\n \t*size = xsize_t(sz);\n+\tif (sha1_stat) {\n+\t\tmemset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));\n+\t\thashcpy(sha1_stat->sha1, active_cache[pos]->sha1);\n+\t}\n \treturn data;\n }\n \n@@ -529,11 +534,18 @@ static void trim_trailing_spaces(char *buf)\n \t\t*last_space = '\\0';\n }\n \n-int add_excludes_from_file_to_list(const char *fname,\n-\t\t\t\t   const char *base,\n-\t\t\t\t   int baselen,\n-\t\t\t\t   struct exclude_list *el,\n-\t\t\t\t   int check_index)\n+/*\n+ * Given a file with name \"fname\", read it (either from disk, or from\n+ * the index if \"check_index\" is non-zero), parse it and store the\n+ * exclude rules in \"el\".\n+ *\n+ * If \"ss\" is not NULL, compute SHA-1 of the exclude file and fill\n+ * stat data from disk (only valid if add_excludes returns zero). If\n+ * ss_valid is non-zero, \"ss\" must contain good value as input.\n+ */\n+static int add_excludes(const char *fname, const char *base, int baselen,\n+\t\t\tstruct exclude_list *el, int check_index,\n+\t\t\tstruct sha1_stat *sha1_stat)\n {\n \tstruct stat st;\n \tint fd, i, lineno = 1;\n@@ -547,7 +559,7 @@ int add_excludes_from_file_to_list(const char *fname,\n \t\tif (0 <= fd)\n \t\t\tclose(fd);\n \t\tif (!check_index ||\n-\t\t    (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)\n+\t\t    (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)\n \t\t\treturn -1;\n \t\tif (size == 0) {\n \t\t\tfree(buf);\n@@ -560,6 +572,11 @@ int add_excludes_from_file_to_list(const char *fname,\n \t} else {\n \t\tsize = xsize_t(st.st_size);\n \t\tif (size == 0) {\n+\t\t\tif (sha1_stat) {\n+\t\t\t\tfill_stat_data(&sha1_stat->stat, &st);\n+\t\t\t\thashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);\n+\t\t\t\tsha1_stat->valid = 1;\n+\t\t\t}\n \t\t\tclose(fd);\n \t\t\treturn 0;\n \t\t}\n@@ -571,6 +588,21 @@ int add_excludes_from_file_to_list(const char *fname,\n \t\t}\n \t\tbuf[size++] = '\\n';\n \t\tclose(fd);\n+\t\tif (sha1_stat) {\n+\t\t\tint pos;\n+\t\t\tif (sha1_stat->valid &&\n+\t\t\t    !match_stat_data(&sha1_stat->stat, &st))\n+\t\t\t\t; /* no content change, ss->sha1 still good */\n+\t\t\telse if (check_index &&\n+\t\t\t\t (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&\n+\t\t\t\t !ce_stage(active_cache[pos]) &&\n+\t\t\t\t ce_uptodate(active_cache[pos]))\n+\t\t\t\thashcpy(sha1_stat->sha1, active_cache[pos]->sha1);\n+\t\t\telse\n+\t\t\t\thash_sha1_file(buf, size, \"blob\", sha1_stat->sha1);\n+\t\t\tfill_stat_data(&sha1_stat->stat, &st);\n+\t\t\tsha1_stat->valid = 1;\n+\t\t}\n \t}\n \n \tel->filebuf = buf;\n@@ -589,6 +621,13 @@ int add_excludes_from_file_to_list(const char *fname,\n \treturn 0;\n }\n \n+int add_excludes_from_file_to_list(const char *fname, const char *base,\n+\t\t\t\t   int baselen, struct exclude_list *el,\n+\t\t\t\t   int check_index)\n+{\n+\treturn add_excludes(fname, base, baselen, el, check_index, NULL);\n+}\n+\n struct exclude_list *add_exclude_list(struct dir_struct *dir,\n \t\t\t\t      int group_type, const char *src)\n {\ndiff --git a/dir.h b/dir.h\nindex 6c45e9d..cdca71b 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -73,6 +73,12 @@ struct exclude_list_group {\n \tstruct exclude_list *el;\n };\n \n+struct sha1_stat {\n+\tstruct stat_data stat;\n+\tunsigned char sha1[20];\n+\tint valid;\n+};\n+\n struct dir_struct {\n \tint nr, alloc;\n \tint ignored_nr, ignored_alloc;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251528","messageId":"1415439595-469-3-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 02/22] untracked cache: record .gitignore information and dir hierarchy","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:35Z","receivedAt":"2014-11-08T09:39:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The idea is if we can capture all input and (non-rescursive) output of\nread_directory_recursive(), and can verify later that all the input is\nthe same, then the second r_d_r() should produce the same output as in\nthe first run.\n\nThe requirement for this to work is stat info of a directory MUST\nchange if an entry is added to or removed from that directory (and\nshould not change often otherwise). If your OS and filesytem do not\nmeet this requirement, untracked cache is not for you. Most file\nsystems on *nix should be fine. On Windows, NTFS is fine while FAT may\nbe not [1] even though FAT on Linux seems to be fine.\n\nThe list of input of r_d_r() is in the big comment block in dir.h. In\nshort, the output of a directory (not counting subdirs) mainly depends\non stat info of the directory in question, all .gitignore leading to\nit and the check_only flag when r_d_r() is called recursively. This\npatch records all this info (and the output) as r_d_r() runs.\n\nTwo hash_sha1_file() are required for $GIT_DIR/info/exclude and\ncore.excludesfile unless their stat data matches. hash_sha1_file() is\nonly needed when .gitignore files in the worktree are modified,\notherwise their SHA-1 in index is used (see the previous patch).\n\nWe could store stat data for .gitignore files so we don't have to\nrehash them if their content is different from index, but I think\n.gitignore files are rarely modified, so not worth extra cache data\n(and hashing penalty read-cache.c:verify_hdr(), as we will be storing\nthis as an index extension).\n\nThe implication is, if you change .gitignore, you better add it to the\nindex soon or you lose all the benefit of untracked cache because a\nmodified .gitignore invalidates all subdirs recursively. This is\nespecially bad for .gitignore at root.\n\nThis cached output is about untracked files only, not ignored files\nbecause the number of tracked files is usually small, so small cache\noverhead, while the number of ignored files could go really high\n(e.g. *.o files mixing with source code).\n\n[1] \"Description of NTFS date and time stamps for files and folders\"\n    http://support.microsoft.com/kb/299648\n\nHelped-by: Torsten Bögershausen <tboegi@web.de>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------\n dir.h |  60 ++++++++++++++++++++++++++++\n 2 files changed, 182 insertions(+), 19 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 4cc936b..19f4b1e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -32,7 +32,7 @@ enum path_treatment {\n };\n \n static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n-\tconst char *path, int len,\n+\tconst char *path, int len, struct untracked_cache_dir *untracked,\n \tint check_only, const struct path_simplify *simplify);\n static int get_dtype(struct dirent *de, const char *path, int len);\n \n@@ -535,6 +535,53 @@ static void trim_trailing_spaces(char *buf)\n }\n \n /*\n+ * Given a subdirectory name and \"dir\" of the current directory,\n+ * search the subdir in \"dir\" and return it, or create a new one if it\n+ * does not exist in \"dir\".\n+ *\n+ * If \"name\" has the trailing slash, it'll be excluded in the search.\n+ */\n+static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,\n+\t\t\t\t\t\t    struct untracked_cache_dir *dir,\n+\t\t\t\t\t\t    const char *name, int len)\n+{\n+\tint first, last;\n+\tstruct untracked_cache_dir *d;\n+\tif (!dir)\n+\t\treturn NULL;\n+\tif (len && name[len - 1] == '/')\n+\t\tlen--;\n+\tfirst = 0;\n+\tlast = dir->dirs_nr;\n+\twhile (last > first) {\n+\t\tint cmp, next = (last + first) >> 1;\n+\t\td = dir->dirs[next];\n+\t\tcmp = strncmp(name, d->name, len);\n+\t\tif (!cmp && strlen(d->name) > len)\n+\t\t\tcmp = -1;\n+\t\tif (!cmp)\n+\t\t\treturn d;\n+\t\tif (cmp < 0) {\n+\t\t\tlast = next;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tfirst = next+1;\n+\t}\n+\n+\tuc->dir_created++;\n+\td = xmalloc(sizeof(*d) + len);\n+\tmemset(d, 0, sizeof(*d) + len);\n+\tmemcpy(d->name, name, len);\n+\n+\tALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);\n+\tmemmove(dir->dirs + first + 1, dir->dirs + first,\n+\t\t(dir->dirs_nr - first) * sizeof(*dir->dirs));\n+\tdir->dirs_nr++;\n+\tdir->dirs[first] = d;\n+\treturn d;\n+}\n+\n+/*\n  * Given a file with name \"fname\", read it (either from disk, or from\n  * the index if \"check_index\" is non-zero), parse it and store the\n  * exclude rules in \"el\".\n@@ -645,14 +692,20 @@ struct exclude_list *add_exclude_list(struct dir_struct *dir,\n /*\n  * Used to set up core.excludesfile and .git/info/exclude lists.\n  */\n-void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n+static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n+\t\t\t\t     struct sha1_stat *sha1_stat)\n {\n \tstruct exclude_list *el;\n \tel = add_exclude_list(dir, EXC_FILE, fname);\n-\tif (add_excludes_from_file_to_list(fname, \"\", 0, el, 0) < 0)\n+\tif (add_excludes(fname, \"\", 0, el, 0, sha1_stat) < 0)\n \t\tdie(\"cannot use %s as an exclude file\", fname);\n }\n \n+void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n+{\n+\tadd_excludes_from_file_1(dir, fname, NULL);\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@@ -827,6 +880,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \tstruct exclude_list_group *group;\n \tstruct exclude_list *el;\n \tstruct exclude_stack *stk = NULL;\n+\tstruct untracked_cache_dir *untracked;\n \tint current;\n \n \tgroup = &dir->exclude_list_group[EXC_DIRS];\n@@ -864,9 +918,15 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t/* Read from the parent directories and push them down. */\n \tcurrent = stk ? stk->baselen : -1;\n \tstrbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);\n+\tif (dir->untracked)\n+\t\tuntracked = stk ? stk->ucd : dir->untracked->root;\n+\telse\n+\t\tuntracked = NULL;\n+\n \twhile (current < baselen) {\n \t\tstruct exclude_stack *stk = xcalloc(1, sizeof(*stk));\n \t\tconst char *cp;\n+\t\tstruct sha1_stat sha1_stat;\n \n \t\tif (current < 0) {\n \t\t\tcp = base;\n@@ -876,10 +936,15 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tif (!cp)\n \t\t\t\tdie(\"oops in prep_exclude\");\n \t\t\tcp++;\n+\t\t\tuntracked =\n+\t\t\t\tlookup_untracked(dir->untracked, untracked,\n+\t\t\t\t\t\t base + current,\n+\t\t\t\t\t\t cp - base - current);\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\tstk->ucd = untracked;\n \t\tel = add_exclude_list(dir, EXC_DIRS, NULL);\n \t\tstrbuf_add(&dir->basebuf, base + current, stk->baselen - current);\n \t\tassert(stk->baselen == dir->basebuf.len);\n@@ -902,6 +967,8 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t}\n \n \t\t/* Try to read per-directory file */\n+\t\thashclr(sha1_stat.sha1);\n+\t\tsha1_stat.valid = 0;\n \t\tif (dir->exclude_per_dir) {\n \t\t\t/*\n \t\t\t * dir->basebuf gets reused by the traversal, but we\n@@ -915,8 +982,11 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tstrbuf_addbuf(&sb, &dir->basebuf);\n \t\t\tstrbuf_addstr(&sb, dir->exclude_per_dir);\n \t\t\tel->src = strbuf_detach(&sb, NULL);\n-\t\t\tadd_excludes_from_file_to_list(el->src, el->src,\n-\t\t\t\t\t\t       stk->baselen, el, 1);\n+\t\t\tadd_excludes(el->src, el->src, stk->baselen, el, 1,\n+\t\t\t\t     untracked ? &sha1_stat : NULL);\n+\t\t}\n+\t\tif (untracked) {\n+\t\t\thashcpy(untracked->exclude_sha1, sha1_stat.sha1);\n \t\t}\n \t\tdir->exclude_stack = stk;\n \t\tcurrent = stk->baselen;\n@@ -1097,6 +1167,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)\n  *  (c) otherwise, we recurse into it.\n  */\n static enum path_treatment treat_directory(struct dir_struct *dir,\n+\tstruct untracked_cache_dir *untracked,\n \tconst char *dirname, int len, int exclude,\n \tconst struct path_simplify *simplify)\n {\n@@ -1124,7 +1195,9 @@ static enum path_treatment treat_directory(struct dir_struct *dir,\n \tif (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))\n \t\treturn exclude ? path_excluded : path_untracked;\n \n-\treturn read_directory_recursive(dir, dirname, len, 1, simplify);\n+\tuntracked = lookup_untracked(dir->untracked, untracked, dirname, len);\n+\treturn read_directory_recursive(dir, dirname, len,\n+\t\t\t\t\tuntracked, 1, simplify);\n }\n \n /*\n@@ -1240,6 +1313,7 @@ static int get_dtype(struct dirent *de, const char *path, int len)\n }\n \n static enum path_treatment treat_one_path(struct dir_struct *dir,\n+\t\t\t\t\t  struct untracked_cache_dir *untracked,\n \t\t\t\t\t  struct strbuf *path,\n \t\t\t\t\t  const struct path_simplify *simplify,\n \t\t\t\t\t  int dtype, struct dirent *de)\n@@ -1292,7 +1366,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t\treturn path_none;\n \tcase DT_DIR:\n \t\tstrbuf_addch(path, '/');\n-\t\treturn treat_directory(dir, path->buf, path->len, exclude,\n+\t\treturn treat_directory(dir, untracked, path->buf, path->len, exclude,\n \t\t\tsimplify);\n \tcase DT_REG:\n \tcase DT_LNK:\n@@ -1301,6 +1375,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n }\n \n static enum path_treatment treat_path(struct dir_struct *dir,\n+\t\t\t\t      struct untracked_cache_dir *untracked,\n \t\t\t\t      struct dirent *de,\n \t\t\t\t      struct strbuf *path,\n \t\t\t\t      int baselen,\n@@ -1316,7 +1391,16 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n \t\treturn path_none;\n \n \tdtype = DTYPE(de);\n-\treturn treat_one_path(dir, path, simplify, dtype, de);\n+\treturn treat_one_path(dir, untracked, path, simplify, dtype, de);\n+}\n+\n+static void add_untracked(struct untracked_cache_dir *dir, const char *name)\n+{\n+\tif (!dir)\n+\t\treturn;\n+\tALLOC_GROW(dir->untracked, dir->untracked_nr + 1,\n+\t\t   dir->untracked_alloc);\n+\tdir->untracked[dir->untracked_nr++] = xstrdup(name);\n }\n \n /*\n@@ -1332,7 +1416,7 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n  */\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    struct untracked_cache_dir *untracked, int check_only,\n \t\t\t\t    const struct path_simplify *simplify)\n {\n \tDIR *fdir;\n@@ -1346,24 +1430,36 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \tif (!fdir)\n \t\tgoto out;\n \n+\tif (untracked)\n+\t\tuntracked->check_only = !!check_only;\n+\n \twhile ((de = readdir(fdir)) != NULL) {\n \t\t/* check how the file or directory should be treated */\n-\t\tstate = treat_path(dir, de, &path, baselen, simplify);\n+\t\tstate = treat_path(dir, untracked, de, &path, baselen, simplify);\n+\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\tstruct untracked_cache_dir *ud;\n+\t\t\tud = lookup_untracked(dir->untracked, untracked,\n+\t\t\t\t\t      path.buf + baselen,\n+\t\t\t\t\t      path.len - baselen);\n+\t\t\tsubdir_state =\n+\t\t\t\tread_directory_recursive(dir, path.buf, path.len,\n+\t\t\t\t\t\t\t ud, 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\tif (dir_state == path_untracked) {\n+\t\t\t\tif (untracked)\n+\t\t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\t\tbreak;\n+\t\t\t}\n \t\t\t/* skip the dir_add_* part */\n \t\t\tcontinue;\n \t\t}\n@@ -1381,8 +1477,11 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\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\tif (dir->flags & DIR_SHOW_IGNORED)\n+\t\t\t\tbreak;\n+\t\t\tdir_add_name(dir, path.buf, path.len);\n+\t\t\tif (untracked)\n+\t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\tbreak;\n \n \t\tdefault:\n@@ -1459,7 +1558,7 @@ static int treat_leading_path(struct dir_struct *dir,\n \t\t\tbreak;\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\tif (treat_one_path(dir, NULL, &sb, simplify,\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@@ -1499,7 +1598,9 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \t */\n \tsimplify = create_simplify(pathspec ? pathspec->_raw : NULL);\n \tif (!len || treat_leading_path(dir, path, len, simplify))\n-\t\tread_directory_recursive(dir, path, len, 0, simplify);\n+\t\tread_directory_recursive(dir, path, len,\n+\t\t\t\t\t dir->untracked ? dir->untracked->root : NULL,\n+\t\t\t\t\t 0, simplify);\n \tfree_simplify(simplify);\n \tqsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);\n \tqsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);\n@@ -1666,9 +1767,11 @@ void setup_standard_excludes(struct dir_struct *dir)\n \t\texcludes_file = xdg_path;\n \t}\n \tif (!access_or_warn(path, R_OK, 0))\n-\t\tadd_excludes_from_file(dir, path);\n+\t\tadd_excludes_from_file_1(dir, path,\n+\t\t\t\t\t dir->untracked ? &dir->ss_info_exclude : NULL);\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n-\t\tadd_excludes_from_file(dir, excludes_file);\n+\t\tadd_excludes_from_file_1(dir, excludes_file,\n+\t\t\t\t\t dir->untracked ? &dir->ss_excludes_file : NULL);\n }\n \n int remove_path(const char *name)\ndiff --git a/dir.h b/dir.h\nindex cdca71b..278c464 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -66,6 +66,7 @@ struct exclude_stack {\n \tstruct exclude_stack *prev; /* the struct exclude_stack for the parent directory */\n \tint baselen;\n \tint exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */\n+\tstruct untracked_cache_dir *ucd;\n };\n \n struct exclude_list_group {\n@@ -79,6 +80,60 @@ struct sha1_stat {\n \tint valid;\n };\n \n+/*\n+ *  Untracked cache\n+ *\n+ *  The following inputs are sufficient to determine what files in a\n+ *  directory are excluded:\n+ *\n+ *   - The list of files and directories of the direction in question\n+ *   - The $GIT_DIR/index\n+ *   - dir_struct flags\n+ *   - The content of $GIT_DIR/info/exclude\n+ *   - The content of core.excludesfile\n+ *   - The content (or the lack) of .gitignore of all parent directories\n+ *     from $GIT_WORK_TREE\n+ *   - The check_only flag in read_directory_recursive (for\n+ *     DIR_HIDE_EMPTY_DIRECTORIES)\n+ *\n+ *  The first input can be checked using directory mtime. In many\n+ *  filesystems, directory mtime (stat_data field) is updated when its\n+ *  files or direct subdirs are added or removed.\n+ *\n+ *  The second one can be hooked from cache_tree_invalidate_path().\n+ *  Whenever a file (or a submodule) is added or removed from a\n+ *  directory, we invalidate that directory.\n+ *\n+ *  The remaining inputs are easy, their SHA-1 could be used to verify\n+ *  their contents (exclude_sha1[], info_exclude_sha1[] and\n+ *  excludes_file_sha1[])\n+ */\n+struct untracked_cache_dir {\n+\tstruct untracked_cache_dir **dirs;\n+\tchar **untracked;\n+\tstruct stat_data stat_data;\n+\tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n+\tunsigned int untracked_nr;\n+\tunsigned int check_only : 1;\n+\t/* null SHA-1 means this directory does not have .gitignore */\n+\tunsigned char exclude_sha1[20];\n+\tchar name[1];\n+};\n+\n+struct untracked_cache {\n+\tstruct sha1_stat ss_info_exclude;\n+\tstruct sha1_stat ss_excludes_file;\n+\tconst char *exclude_per_dir;\n+\t/*\n+\t * dir_struct#flags must match dir_flags or the untracked\n+\t * cache is ignored.\n+\t */\n+\tunsigned dir_flags;\n+\tstruct untracked_cache_dir *root;\n+\t/* Statistics */\n+\tint dir_created;\n+};\n+\n struct dir_struct {\n \tint nr, alloc;\n \tint ignored_nr, ignored_alloc;\n@@ -126,6 +181,11 @@ struct dir_struct {\n \tstruct exclude_stack *exclude_stack;\n \tstruct exclude *exclude;\n \tstruct strbuf basebuf;\n+\n+\t/* Enable untracked file cache if set */\n+\tstruct untracked_cache *untracked;\n+\tstruct sha1_stat ss_info_exclude;\n+\tstruct sha1_stat ss_excludes_file;\n };\n \n /*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251527","messageId":"1415439595-469-4-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 03/22] untracked cache: initial untracked cache validation","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:36Z","receivedAt":"2014-11-08T09:39:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Make sure the starting conditions and all global exclude files are\ngood to go. If not, either disable untracked cache completely, or wipe\nout the cache and start fresh.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n dir.h |   4 +++\n 2 files changed, 114 insertions(+), 3 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 19f4b1e..4f4f302 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -581,6 +581,22 @@ static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,\n \treturn d;\n }\n \n+static void do_invalidate_gitignore(struct untracked_cache_dir *dir)\n+{\n+\tint i;\n+\tdir->valid = 0;\n+\tdir->untracked_nr = 0;\n+\tfor (i = 0; i < dir->dirs_nr; i++)\n+\t\tdo_invalidate_gitignore(dir->dirs[i]);\n+}\n+\n+static void invalidate_gitignore(struct untracked_cache *uc,\n+\t\t\t\t struct untracked_cache_dir *dir)\n+{\n+\tuc->gitignore_invalidated++;\n+\tdo_invalidate_gitignore(dir);\n+}\n+\n /*\n  * Given a file with name \"fname\", read it (either from disk, or from\n  * the index if \"check_index\" is non-zero), parse it and store the\n@@ -696,6 +712,13 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n \t\t\t\t     struct sha1_stat *sha1_stat)\n {\n \tstruct exclude_list *el;\n+\t/*\n+\t * catch setup_standard_excludes() that's called before\n+\t * dir->untracked is assigned. That function behaves\n+\t * differently when dir->untracked is non-NULL.\n+\t */\n+\tif (!dir->untracked)\n+\t\tdir->unmanaged_exclude_files++;\n \tel = add_exclude_list(dir, EXC_FILE, fname);\n \tif (add_excludes(fname, \"\", 0, el, 0, sha1_stat) < 0)\n \t\tdie(\"cannot use %s as an exclude file\", fname);\n@@ -703,6 +726,7 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,\n \n void add_excludes_from_file(struct dir_struct *dir, const char *fname)\n {\n+\tdir->unmanaged_exclude_files++; /* see validate_untracked_cache() */\n \tadd_excludes_from_file_1(dir, fname, NULL);\n }\n \n@@ -1571,9 +1595,87 @@ static int treat_leading_path(struct dir_struct *dir,\n \treturn rc;\n }\n \n+static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,\n+\t\t\t\t\t\t      int base_len,\n+\t\t\t\t\t\t      const struct pathspec *pathspec)\n+{\n+\tstruct untracked_cache_dir *root;\n+\n+\tif (!dir->untracked)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * We only support $GIT_DIR/info/exclude and core.excludesfile\n+\t * as the global ignore rule files. Any other additions\n+\t * (e.g. from command line) invalidate the cache. This\n+\t * condition also catches running setup_standard_excludes()\n+\t * before setting dir->untracked!\n+\t */\n+\tif (dir->unmanaged_exclude_files)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * Optimize for the main use case only: whole-tree git\n+\t * status. More work involved in treat_leading_path() if we\n+\t * use cache on just a subset of the worktree. pathspec\n+\t * support could make the matter even worse.\n+\t */\n+\tif (base_len || (pathspec && pathspec->nr))\n+\t\treturn NULL;\n+\n+\t/* Different set of flags may produce different results */\n+\tif (dir->flags != dir->untracked->dir_flags ||\n+\t    /*\n+\t     * See treat_directory(), case index_nonexistent. Without\n+\t     * this flag, we may need to also cache .git file content\n+\t     * for the resolve_gitlink_ref() call, which we don't.\n+\t     */\n+\t    !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||\n+\t    /* We don't support collecting ignore files */\n+\t    (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |\n+\t\t\t   DIR_COLLECT_IGNORED)))\n+\t\treturn NULL;\n+\n+\t/*\n+\t * If we use .gitignore in the cache and now you change it to\n+\t * .gitexclude, everything will go wrong.\n+\t */\n+\tif (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&\n+\t    strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))\n+\t\treturn NULL;\n+\n+\t/*\n+\t * EXC_CMDL is not considered in the cache. If people set it,\n+\t * skip the cache.\n+\t */\n+\tif (dir->exclude_list_group[EXC_CMDL].nr)\n+\t\treturn NULL;\n+\n+\tif (!dir->untracked->root) {\n+\t\tconst int len = sizeof(*dir->untracked->root);\n+\t\tdir->untracked->root = xmalloc(len);\n+\t\tmemset(dir->untracked->root, 0, len);\n+\t}\n+\n+\t/* Validate $GIT_DIR/info/exclude and core.excludesfile */\n+\troot = dir->untracked->root;\n+\tif (hashcmp(dir->ss_info_exclude.sha1,\n+\t\t    dir->untracked->ss_info_exclude.sha1)) {\n+\t\tinvalidate_gitignore(dir->untracked, root);\n+\t\tdir->untracked->ss_info_exclude = dir->ss_info_exclude;\n+\t}\n+\tif (hashcmp(dir->ss_excludes_file.sha1,\n+\t\t    dir->untracked->ss_excludes_file.sha1)) {\n+\t\tinvalidate_gitignore(dir->untracked, root);\n+\t\tdir->untracked->ss_excludes_file = dir->ss_excludes_file;\n+\t}\n+\treturn root;\n+}\n+\n int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)\n {\n \tstruct path_simplify *simplify;\n+\tstruct untracked_cache_dir *untracked;\n \n \t/*\n \t * Check out create_simplify()\n@@ -1597,10 +1699,15 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \t * create_simplify().\n \t */\n \tsimplify = create_simplify(pathspec ? pathspec->_raw : NULL);\n+\tuntracked = validate_untracked_cache(dir, len, pathspec);\n+\tif (!untracked)\n+\t\t/*\n+\t\t * make sure untracked cache code path is disabled,\n+\t\t * e.g. prep_exclude()\n+\t\t */\n+\t\tdir->untracked = NULL;\n \tif (!len || treat_leading_path(dir, path, len, simplify))\n-\t\tread_directory_recursive(dir, path, len,\n-\t\t\t\t\t dir->untracked ? dir->untracked->root : NULL,\n-\t\t\t\t\t 0, simplify);\n+\t\tread_directory_recursive(dir, path, len, untracked, 0, simplify);\n \tfree_simplify(simplify);\n \tqsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);\n \tqsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);\ndiff --git a/dir.h b/dir.h\nindex 278c464..bd51948 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -115,6 +115,8 @@ struct untracked_cache_dir {\n \tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n \tunsigned int untracked_nr;\n \tunsigned int check_only : 1;\n+\t/* all data in this struct are good */\n+\tunsigned int valid : 1;\n \t/* null SHA-1 means this directory does not have .gitignore */\n \tunsigned char exclude_sha1[20];\n \tchar name[1];\n@@ -132,6 +134,7 @@ struct untracked_cache {\n \tstruct untracked_cache_dir *root;\n \t/* Statistics */\n \tint dir_created;\n+\tint gitignore_invalidated;\n };\n \n struct dir_struct {\n@@ -186,6 +189,7 @@ struct dir_struct {\n \tstruct untracked_cache *untracked;\n \tstruct sha1_stat ss_info_exclude;\n \tstruct sha1_stat ss_excludes_file;\n+\tunsigned unmanaged_exclude_files;\n };\n \n /*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251529","messageId":"1415439595-469-5-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 04/22] untracked cache: invalidate dirs recursively if .gitignore changes","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:37Z","receivedAt":"2014-11-08T09:39:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"It's easy to see that if an existing .gitignore changes, its SHA-1\nwould be different and invalidate_gitignore() is called.\n\nIf .gitignore is removed, add_excludes() will treat it like an empty\n.gitignore, which again should invalidate the cached directory data.\n\nif .gitignore is added, lookup_untracked() already fills initial\n.gitignore SHA-1 as \"empty file\", so again invalidate_gitignore() is\ncalled.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 18 +++++++++++++++++-\n 1 file changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/dir.c b/dir.c\nindex 4f4f302..d5e35ea 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1009,7 +1009,23 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tadd_excludes(el->src, el->src, stk->baselen, el, 1,\n \t\t\t\t     untracked ? &sha1_stat : NULL);\n \t\t}\n-\t\tif (untracked) {\n+\t\t/*\n+\t\t * NEEDSWORK: when untracked cache is enabled, prep_exclude()\n+\t\t * will first be called in valid_cached_dir() then maybe many\n+\t\t * times more in last_exclude_matching(). When the cache is\n+\t\t * used, last_exclude_matching() will not be called and\n+\t\t * reading .gitignore content will be a waste.\n+\t\t *\n+\t\t * So when it's called by valid_cached_dir() and we can get\n+\t\t * .gitignore SHA-1 from the index (i.e. .gitignore is not\n+\t\t * modified on work tree), we could delay reading the\n+\t\t * .gitignore content until we absolutely need it in\n+\t\t * last_exclude_matching(). Be careful about ignore rule\n+\t\t * order, though, if you do that.\n+\t\t */\n+\t\tif (untracked &&\n+\t\t    hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) {\n+\t\t\tinvalidate_gitignore(dir->untracked, untracked);\n \t\t\thashcpy(untracked->exclude_sha1, sha1_stat.sha1);\n \t\t}\n \t\tdir->exclude_stack = stk;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251530","messageId":"1415439595-469-6-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 05/22] untracked cache: make a wrapper around {open,read,close}dir()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:38Z","receivedAt":"2014-11-08T09:39:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows us to feed different info to read_directory_recursive()\nbased on untracked cache in the next patch.\n\nHelped-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++--------\n 1 file changed, 47 insertions(+), 8 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex d5e35ea..65ba98e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -31,6 +31,15 @@ enum path_treatment {\n \tpath_untracked\n };\n \n+/*\n+ * Support data structure for our opendir/readdir/closedir wrappers\n+ */\n+struct cached_dir {\n+\tDIR *fdir;\n+\tstruct untracked_cache_dir *untracked;\n+\tstruct dirent *de;\n+};\n+\n static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \tconst char *path, int len, struct untracked_cache_dir *untracked,\n \tint check_only, const struct path_simplify *simplify);\n@@ -1416,12 +1425,13 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \n static enum path_treatment treat_path(struct dir_struct *dir,\n \t\t\t\t      struct untracked_cache_dir *untracked,\n-\t\t\t\t      struct dirent *de,\n+\t\t\t\t      struct cached_dir *cdir,\n \t\t\t\t      struct strbuf *path,\n \t\t\t\t      int baselen,\n \t\t\t\t      const struct path_simplify *simplify)\n {\n \tint dtype;\n+\tstruct dirent *de = cdir->de;\n \n \tif (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, \".git\"))\n \t\treturn path_none;\n@@ -1443,6 +1453,37 @@ static void add_untracked(struct untracked_cache_dir *dir, const char *name)\n \tdir->untracked[dir->untracked_nr++] = xstrdup(name);\n }\n \n+static int open_cached_dir(struct cached_dir *cdir,\n+\t\t\t   struct dir_struct *dir,\n+\t\t\t   struct untracked_cache_dir *untracked,\n+\t\t\t   struct strbuf *path,\n+\t\t\t   int check_only)\n+{\n+\tmemset(cdir, 0, sizeof(*cdir));\n+\tcdir->untracked = untracked;\n+\tcdir->fdir = opendir(path->len ? path->buf : \".\");\n+\tif (!cdir->fdir)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static int read_cached_dir(struct cached_dir *cdir)\n+{\n+\tif (cdir->fdir) {\n+\t\tcdir->de = readdir(cdir->fdir);\n+\t\tif (!cdir->de)\n+\t\t\treturn -1;\n+\t\treturn 0;\n+\t}\n+\treturn -1;\n+}\n+\n+static void close_cached_dir(struct cached_dir *cdir)\n+{\n+\tif (cdir->fdir)\n+\t\tclosedir(cdir->fdir);\n+}\n+\n /*\n  * Read a directory tree. We currently ignore anything but\n  * directories, regular files and symlinks. That's because git\n@@ -1459,23 +1500,21 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\t\t\t    struct untracked_cache_dir *untracked, int check_only,\n \t\t\t\t    const struct path_simplify *simplify)\n {\n-\tDIR *fdir;\n+\tstruct cached_dir cdir;\n \tenum path_treatment state, subdir_state, dir_state = path_none;\n-\tstruct dirent *de;\n \tstruct strbuf path = STRBUF_INIT;\n \n \tstrbuf_add(&path, base, baselen);\n \n-\tfdir = opendir(path.len ? path.buf : \".\");\n-\tif (!fdir)\n+\tif (open_cached_dir(&cdir, dir, untracked, &path, check_only))\n \t\tgoto out;\n \n \tif (untracked)\n \t\tuntracked->check_only = !!check_only;\n \n-\twhile ((de = readdir(fdir)) != NULL) {\n+\twhile (!read_cached_dir(&cdir)) {\n \t\t/* check how the file or directory should be treated */\n-\t\tstate = treat_path(dir, untracked, de, &path, baselen, simplify);\n+\t\tstate = treat_path(dir, untracked, &cdir, &path, baselen, simplify);\n \n \t\tif (state > dir_state)\n \t\t\tdir_state = state;\n@@ -1528,7 +1567,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\t\tbreak;\n \t\t}\n \t}\n-\tclosedir(fdir);\n+\tclose_cached_dir(&cdir);\n  out:\n \tstrbuf_release(&path);\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251531","messageId":"1415439595-469-7-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 06/22] untracked cache: record/validate dir mtime and reuse cached output","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:39Z","receivedAt":"2014-11-08T09:39:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The main readdir loop in read_directory_recursive() is replaced with a\nnew one that checks if cached results of a directory is still valid.\n\nIf a file is added or removed from the index, the containing directory\nis invalidated (but not its subdirs). If directory's mtime is changed,\nthe same happens. If a .gitignore is updated, the containing directory\nand all subdirs are invalidated recursively. If dir_struct#flags or\nother conditions change, the cache is ignored.\n\nIf a directory is invalidated, we opendir/readdir/closedir and run the\nexclude machinery on that directory listing as usual. If untracked\ncache is also enabled, we'll update the cache along the way. If a\ndirectory is validated, we simply pull the untracked listing out from\nthe cache. The cache also records the list of direct subdirs that we\nhave to recurse in. Fully excluded directories are seen as \"untracked\nfiles\".\n\nIn the best case when no dirs are invalidated, read_directory()\nbecomes a series of\n\n  stat(dir), open(.gitignore), fstat(), read(), close() and optionally\n  hash_sha1_file()\n\nFor comparison, standard read_directory() is a sequence of\n\n  opendir(), readdir(), open(.gitignore), fstat(), read(), close(), the\n  expensive last_exclude_matching() and closedir().\n\nWe already try not to open(.gitignore) if we know it does not exist,\nso open/fstat/read/close sequence does not apply to every\ndirectory. The sequence could be reduced further, as noted in\nprep_exclude() in another patch. So in theory, the entire best-case\nread_directory sequence could be reduced to a series of stat() and\nnothing else.\n\nThis is not a silver bullet approach. When you compile a C file, for\nexample, the old .o file is removed and a new one with the same name\ncreated, effectively invalidating the containing directory's cache\n(but not its subdirectories). If your build process touches every\ndirectory, this cache adds extra overhead for nothing, so it's a good\nidea to separate generated files from tracked files.. Editors may use\nthe same strategy for saving files. And of course you're out of luck\nrunning your repo on an unsupported filesytem and/or operating system.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n dir.h |   2 ++\n 2 files changed, 121 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 65ba98e..f590761 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -37,7 +37,12 @@ enum path_treatment {\n struct cached_dir {\n \tDIR *fdir;\n \tstruct untracked_cache_dir *untracked;\n+\tint nr_files;\n+\tint nr_dirs;\n+\n \tstruct dirent *de;\n+\tconst char *file;\n+\tstruct untracked_cache_dir *ucd;\n };\n \n static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n@@ -606,6 +611,14 @@ static void invalidate_gitignore(struct untracked_cache *uc,\n \tdo_invalidate_gitignore(dir);\n }\n \n+static void invalidate_directory(struct untracked_cache *uc,\n+\t\t\t\t struct untracked_cache_dir *dir)\n+{\n+\tuc->dir_invalidated++;\n+\tdir->valid = 0;\n+\tdir->untracked_nr = 0;\n+}\n+\n /*\n  * Given a file with name \"fname\", read it (either from disk, or from\n  * the index if \"check_index\" is non-zero), parse it and store the\n@@ -1423,6 +1436,39 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,\n \t}\n }\n \n+static enum path_treatment treat_path_fast(struct dir_struct *dir,\n+\t\t\t\t\t   struct untracked_cache_dir *untracked,\n+\t\t\t\t\t   struct cached_dir *cdir,\n+\t\t\t\t\t   struct strbuf *path,\n+\t\t\t\t\t   int baselen,\n+\t\t\t\t\t   const struct path_simplify *simplify)\n+{\n+\tstrbuf_setlen(path, baselen);\n+\tif (!cdir->ucd) {\n+\t\tstrbuf_addstr(path, cdir->file);\n+\t\treturn path_untracked;\n+\t}\n+\tstrbuf_addstr(path, cdir->ucd->name);\n+\t/* treat_one_path() does this before it calls treat_directory() */\n+\tif (path->buf[path->len - 1] != '/')\n+\t\tstrbuf_addch(path, '/');\n+\tif (cdir->ucd->check_only)\n+\t\t/*\n+\t\t * check_only is set as a result of treat_directory() getting\n+\t\t * to its bottom. Verify again the same set of directories\n+\t\t * with check_only set.\n+\t\t */\n+\t\treturn read_directory_recursive(dir, path->buf, path->len,\n+\t\t\t\t\t\tcdir->ucd, 1, simplify);\n+\t/*\n+\t * We get path_recurse in the first run when\n+\t * directory_exists_in_index() returns index_nonexistent. We\n+\t * are sure that new changes in the index does not impact the\n+\t * outcome. Return now.\n+\t */\n+\treturn path_recurse;\n+}\n+\n static enum path_treatment treat_path(struct dir_struct *dir,\n \t\t\t\t      struct untracked_cache_dir *untracked,\n \t\t\t\t      struct cached_dir *cdir,\n@@ -1433,6 +1479,9 @@ static enum path_treatment treat_path(struct dir_struct *dir,\n \tint dtype;\n \tstruct dirent *de = cdir->de;\n \n+\tif (!de)\n+\t\treturn treat_path_fast(dir, untracked, cdir, path,\n+\t\t\t\t       baselen, simplify);\n \tif (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, \".git\"))\n \t\treturn path_none;\n \tstrbuf_setlen(path, baselen);\n@@ -1453,6 +1502,52 @@ static void add_untracked(struct untracked_cache_dir *dir, const char *name)\n \tdir->untracked[dir->untracked_nr++] = xstrdup(name);\n }\n \n+static int valid_cached_dir(struct dir_struct *dir,\n+\t\t\t    struct untracked_cache_dir *untracked,\n+\t\t\t    struct strbuf *path,\n+\t\t\t    int check_only)\n+{\n+\tstruct stat st;\n+\n+\tif (!untracked)\n+\t\treturn 0;\n+\n+\tif (stat(path->len ? path->buf : \".\", &st)) {\n+\t\tinvalidate_directory(dir->untracked, untracked);\n+\t\tmemset(&untracked->stat_data, 0, sizeof(untracked->stat_data));\n+\t\treturn 0;\n+\t}\n+\tif (!untracked->valid ||\n+\t    match_stat_data(&untracked->stat_data, &st)) {\n+\t\tif (untracked->valid)\n+\t\t\tinvalidate_directory(dir->untracked, untracked);\n+\t\tfill_stat_data(&untracked->stat_data, &st);\n+\t\treturn 0;\n+\t}\n+\n+\tif (untracked->check_only != !!check_only) {\n+\t\tinvalidate_directory(dir->untracked, untracked);\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * prep_exclude will be called eventually on this directory,\n+\t * but it's called much later in last_exclude_matching(). We\n+\t * need it now to determine the validity of the cache for this\n+\t * path. The next calls will be nearly no-op, the way\n+\t * prep_exclude() is designed.\n+\t */\n+\tif (path->len && path->buf[path->len - 1] != '/') {\n+\t\tstrbuf_addch(path, '/');\n+\t\tprep_exclude(dir, path->buf, path->len);\n+\t\tstrbuf_setlen(path, path->len - 1);\n+\t} else\n+\t\tprep_exclude(dir, path->buf, path->len);\n+\n+\t/* hopefully prep_exclude() haven't invalidated this entry... */\n+\treturn untracked->valid;\n+}\n+\n static int open_cached_dir(struct cached_dir *cdir,\n \t\t\t   struct dir_struct *dir,\n \t\t\t   struct untracked_cache_dir *untracked,\n@@ -1461,7 +1556,11 @@ static int open_cached_dir(struct cached_dir *cdir,\n {\n \tmemset(cdir, 0, sizeof(*cdir));\n \tcdir->untracked = untracked;\n+\tif (valid_cached_dir(dir, untracked, path, check_only))\n+\t\treturn 0;\n \tcdir->fdir = opendir(path->len ? path->buf : \".\");\n+\tif (dir->untracked)\n+\t\tdir->untracked->dir_opened++;\n \tif (!cdir->fdir)\n \t\treturn -1;\n \treturn 0;\n@@ -1475,6 +1574,18 @@ static int read_cached_dir(struct cached_dir *cdir)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n+\twhile (cdir->nr_dirs < cdir->untracked->dirs_nr) {\n+\t\tstruct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];\n+\t\tcdir->ucd = d;\n+\t\tcdir->nr_dirs++;\n+\t\treturn 0;\n+\t}\n+\tcdir->ucd = NULL;\n+\tif (cdir->nr_files < cdir->untracked->untracked_nr) {\n+\t\tstruct untracked_cache_dir *d = cdir->untracked;\n+\t\tcdir->file = d->untracked[cdir->nr_files++];\n+\t\treturn 0;\n+\t}\n \treturn -1;\n }\n \n@@ -1482,6 +1593,12 @@ static void close_cached_dir(struct cached_dir *cdir)\n {\n \tif (cdir->fdir)\n \t\tclosedir(cdir->fdir);\n+\t/*\n+\t * We have gone through this directory and found no untracked\n+\t * entries. Mark it valid.\n+\t */\n+\tif (cdir->untracked)\n+\t\tcdir->untracked->valid = 1;\n }\n \n /*\n@@ -1535,7 +1652,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\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\tif (untracked)\n+\t\t\t\tif (cdir.fdir)\n \t\t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1559,7 +1676,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \t\t\tif (dir->flags & DIR_SHOW_IGNORED)\n \t\t\t\tbreak;\n \t\t\tdir_add_name(dir, path.buf, path.len);\n-\t\t\tif (untracked)\n+\t\t\tif (cdir.fdir)\n \t\t\t\tadd_untracked(untracked, path.buf + baselen);\n \t\t\tbreak;\n \ndiff --git a/dir.h b/dir.h\nindex bd51948..a755e49 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -135,6 +135,8 @@ struct untracked_cache {\n \t/* Statistics */\n \tint dir_created;\n \tint gitignore_invalidated;\n+\tint dir_invalidated;\n+\tint dir_opened;\n };\n \n struct dir_struct {\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251532","messageId":"1415439595-469-8-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 07/22] untracked cache: mark what dirs should be recursed/saved","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:40Z","receivedAt":"2014-11-08T09:39:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If we redo this thing in a functional style, we would have one struct\nuntracked_dir as input tree and another as output. The input is used\nfor verification. The output is a brand new tree, reflecting current\nworktree.\n\nBut that means recreate a lot of dir nodes even if a lot could be\nshared between input and output trees in good cases. So we go with the\nmessy but efficient way, combining both input and output trees into\none. We need a way to know which node in this combined tree belongs to\nthe output. This is the purpose of this \"recurse\" flag.\n\n\"valid\" bit can't be used for this because it's about data of the node\nexcept the subdirs. When we invalidate a directory, we want to keep\ncached data of the subdirs intact even though we don't really know\nwhat subdir still exists (yet). Then we check worktree to see what\nactual subdir remains on disk. Those will have 'recurse' bit set\nagain. If cached data for those are still valid, we may be able to\navoid computing exclude files for them. Those subdirs that are deleted\nwill have 'recurse' remained clear and their 'valid' bits do not\nmatter.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 14 +++++++++++++-\n dir.h |  3 ++-\n 2 files changed, 15 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex f590761..32406bf 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -614,9 +614,12 @@ static void invalidate_gitignore(struct untracked_cache *uc,\n static void invalidate_directory(struct untracked_cache *uc,\n \t\t\t\t struct untracked_cache_dir *dir)\n {\n+\tint i;\n \tuc->dir_invalidated++;\n \tdir->valid = 0;\n \tdir->untracked_nr = 0;\n+\tfor (i = 0; i < dir->dirs_nr; i++)\n+\t\tdir->dirs[i]->recurse = 0;\n }\n \n /*\n@@ -1576,6 +1579,10 @@ static int read_cached_dir(struct cached_dir *cdir)\n \t}\n \twhile (cdir->nr_dirs < cdir->untracked->dirs_nr) {\n \t\tstruct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];\n+\t\tif (!d->recurse) {\n+\t\t\tcdir->nr_dirs++;\n+\t\t\tcontinue;\n+\t\t}\n \t\tcdir->ucd = d;\n \t\tcdir->nr_dirs++;\n \t\treturn 0;\n@@ -1597,8 +1604,10 @@ static void close_cached_dir(struct cached_dir *cdir)\n \t * We have gone through this directory and found no untracked\n \t * entries. Mark it valid.\n \t */\n-\tif (cdir->untracked)\n+\tif (cdir->untracked) {\n \t\tcdir->untracked->valid = 1;\n+\t\tcdir->untracked->recurse = 1;\n+\t}\n }\n \n /*\n@@ -1841,6 +1850,9 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \t\tinvalidate_gitignore(dir->untracked, root);\n \t\tdir->untracked->ss_excludes_file = dir->ss_excludes_file;\n \t}\n+\n+\t/* Make sure this directory is not dropped out at saving phase */\n+\troot->recurse = 1;\n \treturn root;\n }\n \ndiff --git a/dir.h b/dir.h\nindex a755e49..5c688da 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -115,8 +115,9 @@ struct untracked_cache_dir {\n \tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n \tunsigned int untracked_nr;\n \tunsigned int check_only : 1;\n-\t/* all data in this struct are good */\n+\t/* all data except 'dirs' in this struct are good */\n \tunsigned int valid : 1;\n+\tunsigned int recurse : 1;\n \t/* null SHA-1 means this directory does not have .gitignore */\n \tunsigned char exclude_sha1[20];\n \tchar name[1];\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251533","messageId":"1415439595-469-9-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 08/22] untracked cache: don't open non-existent .gitignore","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:41Z","receivedAt":"2014-11-08T09:39:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This cuts down a signficant number of open(.gitignore) because most\ndirectories usually don't have .gitignore files.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 26 +++++++++++++++++++++++++-\n 1 file changed, 25 insertions(+), 1 deletion(-)\n\ndiff --git a/dir.c b/dir.c\nindex 32406bf..5936c84 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1018,7 +1018,21 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t/* Try to read per-directory file */\n \t\thashclr(sha1_stat.sha1);\n \t\tsha1_stat.valid = 0;\n-\t\tif (dir->exclude_per_dir) {\n+\t\tif (dir->exclude_per_dir &&\n+\t\t    /*\n+\t\t     * If we know that no files have been added in\n+\t\t     * this directory (i.e. valid_cached_dir() has\n+\t\t     * been executed and set untracked->valid) ..\n+\t\t     */\n+\t\t    (!untracked || !untracked->valid ||\n+\t\t     /*\n+\t\t      * .. and .gitignore does not exist before\n+\t\t      * (i.e. null exclude_sha1 and skip_worktree is\n+\t\t      * not set). Then we can skip loading .gitignore,\n+\t\t      * which would result in ENOENT anyway.\n+\t\t      * skip_worktree is taken care in read_directory()\n+\t\t      */\n+\t\t     !is_null_sha1(untracked->exclude_sha1))) {\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@@ -1781,6 +1795,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \t\t\t\t\t\t      const struct pathspec *pathspec)\n {\n \tstruct untracked_cache_dir *root;\n+\tint i;\n \n \tif (!dir->untracked)\n \t\treturn NULL;\n@@ -1832,6 +1847,15 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \tif (dir->exclude_list_group[EXC_CMDL].nr)\n \t\treturn NULL;\n \n+\t/*\n+\t * An optimization in prep_exclude() does not play well with\n+\t * CE_SKIP_WORKTREE. It's a rare case anyway, if a single\n+\t * entry has that bit set, disable the whole untracked cache.\n+\t */\n+\tfor (i = 0; i < active_nr; i++)\n+\t\tif (ce_skip_worktree(active_cache[i]))\n+\t\t\treturn NULL;\n+\n \tif (!dir->untracked->root) {\n \t\tconst int len = sizeof(*dir->untracked->root);\n \t\tdir->untracked->root = xmalloc(len);\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251534","messageId":"1415439595-469-10-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 09/22] untracked cache: save to an index extension","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:42Z","receivedAt":"2014-11-08T09:39:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h      |  3 +++\n dir.c        | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n dir.h        |  1 +\n read-cache.c | 12 +++++++++\n 4 files changed, 101 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex dcf3a2a..b14d6e2 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -297,6 +297,8 @@ static inline unsigned int canon_mode(unsigned int mode)\n #define SPLIT_INDEX_ORDERED\t(1 << 6)\n \n struct split_index;\n+struct untracked_cache;\n+\n struct index_state {\n \tstruct cache_entry **cache;\n \tunsigned int version;\n@@ -310,6 +312,7 @@ struct index_state {\n \tstruct hashmap name_hash;\n \tstruct hashmap dir_hash;\n \tunsigned char sha1[20];\n+\tstruct untracked_cache *untracked;\n };\n \n extern struct index_state the_index;\ndiff --git a/dir.c b/dir.c\nindex 5936c84..4e15f20 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -12,6 +12,7 @@\n #include \"refs.h\"\n #include \"wildmatch.h\"\n #include \"pathspec.h\"\n+#include \"varint.h\"\n \n struct path_simplify {\n \tint len;\n@@ -2138,3 +2139,87 @@ void clear_directory(struct dir_struct *dir)\n \t}\n \tstrbuf_release(&dir->basebuf);\n }\n+\n+struct ondisk_untracked_cache {\n+\tstruct stat_data info_exclude_stat;\n+\tstruct stat_data excludes_file_stat;\n+\tuint32_t dir_flags;\n+\tunsigned char info_exclude_sha1[20];\n+\tunsigned char excludes_file_sha1[20];\n+\tchar exclude_per_dir[1];\n+};\n+\n+static void stat_data_to_disk(struct stat_data *to, const struct stat_data *from)\n+{\n+\tto->sd_ctime.sec  = htonl(from->sd_ctime.sec);\n+\tto->sd_ctime.nsec = htonl(from->sd_ctime.nsec);\n+\tto->sd_mtime.sec  = htonl(from->sd_mtime.sec);\n+\tto->sd_mtime.nsec = htonl(from->sd_mtime.nsec);\n+\tto->sd_dev\t  = htonl(from->sd_dev);\n+\tto->sd_ino\t  = htonl(from->sd_ino);\n+\tto->sd_uid\t  = htonl(from->sd_uid);\n+\tto->sd_gid\t  = htonl(from->sd_gid);\n+\tto->sd_size\t  = htonl(from->sd_size);\n+}\n+\n+static void write_one_dir(struct strbuf *out, struct untracked_cache_dir *untracked)\n+{\n+\tstruct stat_data stat_data;\n+\tunsigned char intbuf[16];\n+\tunsigned int intlen, value;\n+\tint i;\n+\n+\tstat_data_to_disk(&stat_data, &untracked->stat_data);\n+\tstrbuf_add(out, &stat_data, sizeof(stat_data));\n+\tstrbuf_add(out, untracked->exclude_sha1, 20);\n+\n+\t/*\n+\t * untracked_nr should be reset whenever valid is clear, but\n+\t * for safety..\n+\t */\n+\tif (!untracked->valid) {\n+\t\tuntracked->untracked_nr = 0;\n+\t\tuntracked->check_only = 0;\n+\t}\n+\n+\tvalue  = untracked->valid;\n+\tvalue |= untracked->check_only   << 1;\n+\tvalue |= untracked->untracked_nr << 2;\n+\tintlen = encode_varint(value, intbuf);\n+\tstrbuf_add(out, intbuf, intlen);\n+\n+\t/* skip non-recurse directories */\n+\tfor (i = 0, value = 0; i < untracked->dirs_nr; i++)\n+\t\tif (untracked->dirs[i]->recurse)\n+\t\t\tvalue++;\n+\tintlen = encode_varint(value, intbuf);\n+\tstrbuf_add(out, intbuf, intlen);\n+\n+\tstrbuf_add(out, untracked->name, strlen(untracked->name) + 1);\n+\n+\tfor (i = 0; i < untracked->untracked_nr; i++)\n+\t\tstrbuf_add(out, untracked->untracked[i],\n+\t\t\t   strlen(untracked->untracked[i]) + 1);\n+\n+\tfor (i = 0; i < untracked->dirs_nr; i++)\n+\t\tif (untracked->dirs[i]->recurse)\n+\t\t\twrite_one_dir(out, untracked->dirs[i]);\n+}\n+\n+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)\n+{\n+\tstruct ondisk_untracked_cache *ouc;\n+\tint len = 0;\n+\tif (untracked->exclude_per_dir)\n+\t\tlen = strlen(untracked->exclude_per_dir);\n+\touc = xmalloc(sizeof(*ouc) + len);\n+\tstat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);\n+\tstat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);\n+\thashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);\n+\thashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);\n+\touc->dir_flags = htonl(untracked->dir_flags);\n+\tmemcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);\n+\tstrbuf_add(out, ouc, sizeof(*ouc) + len);\n+\tif (untracked->root)\n+\t\twrite_one_dir(out, untracked->root);\n+}\ndiff --git a/dir.h b/dir.h\nindex 5c688da..c771057 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -298,4 +298,5 @@ static inline int dir_path_match(const struct dir_entry *ent,\n \t\t\t      has_trailing_dir);\n }\n \n+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);\n #endif\ndiff --git a/read-cache.c b/read-cache.c\nindex 6f0057f..26e938d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -37,6 +37,7 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,\n #define CACHE_EXT_TREE 0x54524545\t/* \"TREE\" */\n #define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* \"REUC\" */\n #define CACHE_EXT_LINK 0x6c696e6b\t  /* \"link\" */\n+#define CACHE_EXT_UNTRACKED 0x554E5452\t  /* \"UNTR\" */\n \n /* changes that can be kept in $GIT_DIR/index (basically all extensions) */\n #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \\\n@@ -2016,6 +2017,17 @@ static int do_write_index(struct index_state *istate, int newfd,\n \t\tif (err)\n \t\t\treturn -1;\n \t}\n+\tif (istate->untracked) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\twrite_untracked_extension(&sb, istate->untracked);\n+\t\terr = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED,\n+\t\t\t\t\t     sb.len) < 0 ||\n+\t\t\tce_write(&c, newfd, sb.buf, sb.len) < 0;\n+\t\tstrbuf_release(&sb);\n+\t\tif (err)\n+\t\t\treturn -1;\n+\t}\n \n \tif (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))\n \t\treturn -1;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251535","messageId":"1415439595-469-11-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 10/22] untracked cache: load from UNTR index extension","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:43Z","receivedAt":"2014-11-08T09:39:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c        | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n dir.h        |   2 +\n read-cache.c |   5 +++\n 3 files changed, 142 insertions(+)\n\ndiff --git a/dir.c b/dir.c\nindex 4e15f20..bc196b3 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2223,3 +2223,138 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra\n \tif (untracked->root)\n \t\twrite_one_dir(out, untracked->root);\n }\n+\n+static void free_untracked(struct untracked_cache_dir *ucd)\n+{\n+\tint i;\n+\tif (!ucd)\n+\t\treturn;\n+\tfor (i = 0; i < ucd->dirs_nr; i++)\n+\t\tfree_untracked(ucd->dirs[i]);\n+\tfor (i = 0; i < ucd->untracked_nr; i++)\n+\t\tfree(ucd->untracked[i]);\n+\tfree(ucd->untracked);\n+\tfree(ucd->dirs);\n+\tfree(ucd);\n+}\n+\n+void free_untracked_cache(struct untracked_cache *uc)\n+{\n+\tif (uc)\n+\t\tfree_untracked(uc->root);\n+\tfree(uc);\n+}\n+\n+static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from)\n+{\n+\tto->sd_ctime.sec  = get_be32(&from->sd_ctime.sec);\n+\tto->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec);\n+\tto->sd_mtime.sec  = get_be32(&from->sd_mtime.sec);\n+\tto->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec);\n+\tto->sd_dev\t  = get_be32(&from->sd_dev);\n+\tto->sd_ino\t  = get_be32(&from->sd_ino);\n+\tto->sd_uid\t  = get_be32(&from->sd_uid);\n+\tto->sd_gid\t  = get_be32(&from->sd_gid);\n+\tto->sd_size\t  = get_be32(&from->sd_size);\n+}\n+\n+static int read_one_dir(struct untracked_cache_dir **untracked_,\n+\t\t\tconst unsigned char *data_, unsigned long sz)\n+{\n+#define NEXT(x) \\\n+\tnext = data + (x); \\\n+\tif (next > data_ + sz) \\\n+\t\treturn -1;\n+\n+\tstruct untracked_cache_dir ud, *untracked;\n+\tconst unsigned char *next, *data = data_;\n+\tunsigned int value;\n+\tint i, len;\n+\n+\tmemset(&ud, 0, sizeof(ud));\n+\n+\tNEXT(sizeof(struct stat_data));\n+\tstat_data_from_disk(&ud.stat_data, (struct stat_data *)data);\n+\tdata = next;\n+\n+\tNEXT(20);\n+\thashcpy(ud.exclude_sha1, data);\n+\tdata = next;\n+\n+\tnext = data;\n+\tvalue = decode_varint(&next);\n+\tif (next > data_ + sz)\n+\t\treturn -1;\n+\tud.recurse = 1;\n+\tud.valid = value & 1;\n+\tud.check_only = (value >> 1) & 1;\n+\tud.untracked_alloc = ud.untracked_nr = value >> 2;\n+\tif (ud.untracked_nr)\n+\t\tud.untracked = xmalloc(sizeof(*ud.untracked) * ud.untracked_nr);\n+\tdata = next;\n+\n+\tnext = data;\n+\tud.dirs_alloc = ud.dirs_nr = decode_varint(&next);\n+\tif (next > data_ + sz)\n+\t\treturn -1;\n+\tud.dirs = xmalloc(sizeof(*ud.dirs) * ud.dirs_nr);\n+\tdata = next;\n+\n+\tlen = strlen((const char *)data);\n+\tNEXT(len + 1);\n+\t*untracked_ = untracked = xmalloc(sizeof(*untracked) + len);\n+\tmemcpy(untracked, &ud, sizeof(ud));\n+\tmemcpy(untracked->name, data, len + 1);\n+\tdata = next;\n+\n+\tfor (i = 0; i < untracked->untracked_nr; i++) {\n+\t\tlen = strlen((const char *)data);\n+\t\tNEXT(len + 1);\n+\t\tuntracked->untracked[i] = xstrdup((const char*)data);\n+\t\tdata = next;\n+\t}\n+\n+\tfor (i = 0; i < untracked->dirs_nr; i++) {\n+\t\tlen = read_one_dir(untracked->dirs + i, data, sz - (data - data_));\n+\t\tif (len < 0)\n+\t\t\treturn -1;\n+\t\tdata += len;\n+\t}\n+\treturn data - data_;\n+}\n+\n+static void load_sha1_stat(struct sha1_stat *sha1_stat,\n+\t\t\t   const struct stat_data *stat,\n+\t\t\t   const unsigned char *sha1)\n+{\n+\tstat_data_from_disk(&sha1_stat->stat, stat);\n+\thashcpy(sha1_stat->sha1, sha1);\n+\tsha1_stat->valid = 1;\n+}\n+\n+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)\n+{\n+\tconst struct ondisk_untracked_cache *ouc = data;\n+\tstruct untracked_cache *uc;\n+\tint len;\n+\n+\tif (sz < sizeof(*ouc))\n+\t\treturn NULL;\n+\n+\tuc = xcalloc(1, sizeof(*uc));\n+\tload_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat,\n+\t\t       ouc->info_exclude_sha1);\n+\tload_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat,\n+\t\t       ouc->excludes_file_sha1);\n+\tuc->dir_flags = get_be32(&ouc->dir_flags);\n+\tuc->exclude_per_dir = xstrdup(ouc->exclude_per_dir);\n+\tlen = sizeof(*ouc) + strlen(ouc->exclude_per_dir);\n+\tif (sz == len)\n+\t\treturn uc;\n+\tif (sz > len &&\n+\t    read_one_dir(&uc->root, (const unsigned char *)data + len,\n+\t\t\t sz - len) == sz - len)\n+\t\treturn uc;\n+\tfree_untracked_cache(uc);\n+\treturn NULL;\n+}\ndiff --git a/dir.h b/dir.h\nindex c771057..14ee4fd 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -298,5 +298,7 @@ static inline int dir_path_match(const struct dir_entry *ent,\n \t\t\t      has_trailing_dir);\n }\n \n+void free_untracked_cache(struct untracked_cache *);\n+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);\n void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);\n #endif\ndiff --git a/read-cache.c b/read-cache.c\nindex 26e938d..f5bd174 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1366,6 +1366,9 @@ static int read_index_extension(struct index_state *istate,\n \t\tif (read_link_extension(istate, data, sz))\n \t\t\treturn -1;\n \t\tbreak;\n+\tcase CACHE_EXT_UNTRACKED:\n+\t\tistate->untracked = read_untracked_extension(data, sz);\n+\t\tbreak;\n \tdefault:\n \t\tif (*ext < 'A' || 'Z' < *ext)\n \t\t\treturn error(\"index uses %.4s extension, which we do not understand\",\n@@ -1631,6 +1634,8 @@ int discard_index(struct index_state *istate)\n \tistate->cache = NULL;\n \tistate->cache_alloc = 0;\n \tdiscard_split_index(istate);\n+\tfree_untracked_cache(istate->untracked);\n+\tistate->untracked = NULL;\n \treturn 0;\n }\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251536","messageId":"1415439595-469-12-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 11/22] untracked cache: invalidate at index addition or removal","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:44Z","receivedAt":"2014-11-08T09:39:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Ideally we should implement untracked_cache_remove_from_index() and\nuntracked_cache_add_to_index() so that they update untracked cache\nright away instead of invalidating it and wait for read_directory()\nnext time to deal with it. But that may need some more work in\nunpack-trees.c. So stay simple as the first step.\n\nThe new call in add_index_entry_with_check() may look strange because\nnew calls usually stay close to cache_tree_invalidate_path(). We do it\na bit later than c_t_i_p() in this function because if it's about\nreplacing the entry with the same name, we don't care (but cache-tree\ndoes).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c          | 31 +++++++++++++++++++++++++++++++\n dir.h          |  4 ++++\n read-cache.c   |  4 ++++\n unpack-trees.c |  7 +++++--\n 4 files changed, 44 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex bc196b3..b3ba278 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2358,3 +2358,34 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long\n \tfree_untracked_cache(uc);\n \treturn NULL;\n }\n+\n+void untracked_cache_invalidate_path(struct index_state *istate,\n+\t\t\t\t     const char *path)\n+{\n+\tconst char *sep;\n+\tstruct untracked_cache_dir *d;\n+\tif (!istate->untracked || !istate->untracked->root)\n+\t\treturn;\n+\tsep = strrchr(path, '/');\n+\tif (sep)\n+\t\td = lookup_untracked(istate->untracked,\n+\t\t\t\t     istate->untracked->root,\n+\t\t\t\t     path, sep - path);\n+\telse\n+\t\td = istate->untracked->root;\n+\tistate->untracked->dir_invalidated++;\n+\td->valid = 0;\n+\td->untracked_nr = 0;\n+}\n+\n+void untracked_cache_remove_from_index(struct index_state *istate,\n+\t\t\t\t       const char *path)\n+{\n+\tuntracked_cache_invalidate_path(istate, path);\n+}\n+\n+void untracked_cache_add_to_index(struct index_state *istate,\n+\t\t\t\t  const char *path)\n+{\n+\tuntracked_cache_invalidate_path(istate, path);\n+}\ndiff --git a/dir.h b/dir.h\nindex 14ee4fd..708cdd5 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -298,6 +298,10 @@ static inline int dir_path_match(const struct dir_entry *ent,\n \t\t\t      has_trailing_dir);\n }\n \n+void untracked_cache_invalidate_path(struct index_state *, const char *);\n+void untracked_cache_remove_from_index(struct index_state *, const char *);\n+void untracked_cache_add_to_index(struct index_state *, const char *);\n+\n void free_untracked_cache(struct untracked_cache *);\n struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);\n void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);\ndiff --git a/read-cache.c b/read-cache.c\nindex f5bd174..7870d4a 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -78,6 +78,7 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n\n \tmemcpy(new->name, new_name, namelen + 1);\n \n \tcache_tree_invalidate_path(istate, old->name);\n+\tuntracked_cache_remove_from_index(istate, old->name);\n \tremove_index_entry_at(istate, nr);\n \tadd_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);\n }\n@@ -537,6 +538,7 @@ int remove_file_from_index(struct index_state *istate, const char *path)\n \tif (pos < 0)\n \t\tpos = -pos-1;\n \tcache_tree_invalidate_path(istate, path);\n+\tuntracked_cache_remove_from_index(istate, path);\n \twhile (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))\n \t\tremove_index_entry_at(istate, pos);\n \treturn 0;\n@@ -968,6 +970,8 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e\n \t}\n \tpos = -pos-1;\n \n+\tuntracked_cache_add_to_index(istate, ce->name);\n+\n \t/*\n \t * Inserting a merged entry (\"stage 0\") into the index\n \t * will always replace all non-merged entries..\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 629c658..e5ddb0c 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -9,6 +9,7 @@\n #include \"refs.h\"\n #include \"attr.h\"\n #include \"split-index.h\"\n+#include \"dir.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n@@ -1255,8 +1256,10 @@ static int verify_uptodate_sparse(const struct cache_entry *ce,\n static void invalidate_ce_path(const struct cache_entry *ce,\n \t\t\t       struct unpack_trees_options *o)\n {\n-\tif (ce)\n-\t\tcache_tree_invalidate_path(o->src_index, ce->name);\n+\tif (!ce)\n+\t\treturn;\n+\tcache_tree_invalidate_path(o->src_index, ce->name);\n+\tuntracked_cache_invalidate_path(o->src_index, ce->name);\n }\n \n /*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251537","messageId":"1415439595-469-13-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 12/22] read-cache.c: split racy stat test to a separate function","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:45Z","receivedAt":"2014-11-08T09:39:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n read-cache.c | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 7870d4a..794176f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -270,20 +270,26 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \treturn changed;\n }\n \n-static int is_racy_timestamp(const struct index_state *istate,\n-\t\t\t     const struct cache_entry *ce)\n+static int is_racy_stat(const struct index_state *istate,\n+\t\t\tconst struct stat_data *sd)\n {\n-\treturn (!S_ISGITLINK(ce->ce_mode) &&\n-\t\tistate->timestamp.sec &&\n+\treturn (istate->timestamp.sec &&\n #ifdef USE_NSEC\n \t\t /* nanosecond timestamped files can also be racy! */\n-\t\t(istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||\n-\t\t (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&\n-\t\t  istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))\n+\t\t(istate->timestamp.sec < sd->sd_mtime.sec ||\n+\t\t (istate->timestamp.sec == sd->sd_mtime.sec &&\n+\t\t  istate->timestamp.nsec <= sd->sd_mtime.nsec))\n #else\n-\t\tistate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec\n+\t\tistate->timestamp.sec <= sd->sd_mtime.sec\n #endif\n-\t\t );\n+\t\t);\n+}\n+\n+static int is_racy_timestamp(const struct index_state *istate,\n+\t\t\t     const struct cache_entry *ce)\n+{\n+\treturn (!S_ISGITLINK(ce->ce_mode) &&\n+\t\tis_racy_stat(istate, &ce->ce_stat_data));\n }\n \n int ie_match_stat(const struct index_state *istate,\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251541","messageId":"1415439595-469-14-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 13/22] untracked cache: avoid racy timestamps","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:46Z","receivedAt":"2014-11-08T09:39:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"When a directory is updated within the same second that its timestamp\nis last saved, we cannot realize the directory has been updated by\nchecking timestamps. Assume the worst (something is update). See\n29e4d36 (Racy GIT - 2005-12-20) for more information.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h      | 2 ++\n dir.c        | 4 ++--\n read-cache.c | 8 ++++++++\n 3 files changed, 12 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex b14d6e2..f8b3dc5 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -561,6 +561,8 @@ extern void fill_stat_data(struct stat_data *sd, struct stat *st);\n  * INODE_CHANGED, and DATA_CHANGED.\n  */\n extern int match_stat_data(const struct stat_data *sd, struct stat *st);\n+extern int match_stat_data_racy(const struct index_state *istate,\n+\t\t\t\tconst struct stat_data *sd, struct stat *st);\n \n extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);\n \ndiff --git a/dir.c b/dir.c\nindex b3ba278..52daaf7 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -680,7 +680,7 @@ static int add_excludes(const char *fname, const char *base, int baselen,\n \t\tif (sha1_stat) {\n \t\t\tint pos;\n \t\t\tif (sha1_stat->valid &&\n-\t\t\t    !match_stat_data(&sha1_stat->stat, &st))\n+\t\t\t    !match_stat_data_racy(&the_index, &sha1_stat->stat, &st))\n \t\t\t\t; /* no content change, ss->sha1 still good */\n \t\t\telse if (check_index &&\n \t\t\t\t (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&\n@@ -1536,7 +1536,7 @@ static int valid_cached_dir(struct dir_struct *dir,\n \t\treturn 0;\n \t}\n \tif (!untracked->valid ||\n-\t    match_stat_data(&untracked->stat_data, &st)) {\n+\t    match_stat_data_racy(&the_index, &untracked->stat_data, &st)) {\n \t\tif (untracked->valid)\n \t\t\tinvalidate_directory(dir->untracked, untracked);\n \t\tfill_stat_data(&untracked->stat_data, &st);\ndiff --git a/read-cache.c b/read-cache.c\nindex 794176f..103c294 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -292,6 +292,14 @@ static int is_racy_timestamp(const struct index_state *istate,\n \t\tis_racy_stat(istate, &ce->ce_stat_data));\n }\n \n+int match_stat_data_racy(const struct index_state *istate,\n+\t\t\t const struct stat_data *sd, struct stat *st)\n+{\n+\tif (is_racy_stat(istate, sd))\n+\t\treturn MTIME_CHANGED;\n+\treturn match_stat_data(sd, st);\n+}\n+\n int ie_match_stat(const struct index_state *istate,\n \t\t  const struct cache_entry *ce, struct stat *st,\n \t\t  unsigned int options)\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251538","messageId":"1415439595-469-15-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 14/22] untracked cache: print stats with $GIT_TRACE_UNTRACKED_STATS","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:47Z","receivedAt":"2014-11-08T09:39:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This could be used to verify correct behavior in tests\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/dir.c b/dir.c\nindex 52daaf7..27bd40a 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1920,6 +1920,18 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \tfree_simplify(simplify);\n \tqsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);\n \tqsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);\n+\tif (dir->untracked) {\n+\t\tstatic struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);\n+\t\ttrace_printf_key(&trace_untracked_stats,\n+\t\t\t\t \"node creation: %u\\n\"\n+\t\t\t\t \"gitignore invalidation: %u\\n\"\n+\t\t\t\t \"directory invalidation: %u\\n\"\n+\t\t\t\t \"opendir: %u\\n\",\n+\t\t\t\t dir->untracked->dir_created,\n+\t\t\t\t dir->untracked->gitignore_invalidated,\n+\t\t\t\t dir->untracked->dir_invalidated,\n+\t\t\t\t dir->untracked->dir_opened);\n+\t}\n \treturn dir->nr;\n }\n \n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251539","messageId":"1415439595-469-16-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 15/22] untracked cache: mark index dirty if untracked cache is updated","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:48Z","receivedAt":"2014-11-08T09:39:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h      | 1 +\n dir.c        | 9 +++++++++\n read-cache.c | 2 +-\n 3 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/cache.h b/cache.h\nindex f8b3dc5..201b22e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -295,6 +295,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n #define RESOLVE_UNDO_CHANGED\t(1 << 4)\n #define CACHE_TREE_CHANGED\t(1 << 5)\n #define SPLIT_INDEX_ORDERED\t(1 << 6)\n+#define UNTRACKED_CHANGED       (1 << 7)\n \n struct split_index;\n struct untracked_cache;\ndiff --git a/dir.c b/dir.c\nindex 27bd40a..b1f2be5 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1931,6 +1931,15 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru\n \t\t\t\t dir->untracked->gitignore_invalidated,\n \t\t\t\t dir->untracked->dir_invalidated,\n \t\t\t\t dir->untracked->dir_opened);\n+\t\tif (dir->untracked == the_index.untracked &&\n+\t\t    (dir->untracked->dir_opened ||\n+\t\t     dir->untracked->gitignore_invalidated ||\n+\t\t     dir->untracked->dir_invalidated))\n+\t\t\tthe_index.cache_changed |= UNTRACKED_CHANGED;\n+\t\tif (dir->untracked != the_index.untracked) {\n+\t\t\tfree(dir->untracked);\n+\t\t\tdir->untracked = NULL;\n+\t\t}\n \t}\n \treturn dir->nr;\n }\ndiff --git a/read-cache.c b/read-cache.c\nindex 103c294..21ae963 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -42,7 +42,7 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,\n /* changes that can be kept in $GIT_DIR/index (basically all extensions) */\n #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \\\n \t\t CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \\\n-\t\t SPLIT_INDEX_ORDERED)\n+\t\t SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)\n \n struct index_state the_index;\n static const char *alternate_index_output;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251540","messageId":"1415439595-469-17-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 16/22] untracked-cache: temporarily disable with $GIT_DISABLE_UNTRACKED_CACHE","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:49Z","receivedAt":"2014-11-08T09:39:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This can be used to double check if results with untracked cache are\ncorrectly, compared to vanilla version. Untracked cache remains in\nindex, but not used.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/dir.c b/dir.c\nindex b1f2be5..a99bd63 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1798,7 +1798,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \tstruct untracked_cache_dir *root;\n \tint i;\n \n-\tif (!dir->untracked)\n+\tif (!dir->untracked || getenv(\"GIT_DISABLE_UNTRACKED_CACHE\"))\n \t\treturn NULL;\n \n \t/*\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251542","messageId":"1415439595-469-18-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 17/22] status: enable untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:50Z","receivedAt":"2014-11-08T09:39:50Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"update_index_if_able() is moved down so that the updated untracked\ncache could be written out.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/commit.c | 5 +++--\n wt-status.c      | 2 ++\n 2 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 5ed6036..bdcfa61 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1372,13 +1372,14 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \trefresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);\n \n \tfd = hold_locked_index(&index_lock, 0);\n-\tif (0 <= fd)\n-\t\tupdate_index_if_able(&the_index, &index_lock);\n \n \ts.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;\n \ts.ignore_submodule_arg = ignore_submodule_arg;\n \twt_status_collect(&s);\n \n+\tif (0 <= fd)\n+\t\tupdate_index_if_able(&the_index, &index_lock);\n+\n \tif (s.relative_paths)\n \t\ts.prefix = prefix;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 27da529..8880c3b 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -585,6 +585,8 @@ static void wt_status_collect_untracked(struct wt_status *s)\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+\telse\n+\t\tdir.untracked = the_index.untracked;\n \tsetup_standard_excludes(&dir);\n \n \tfill_directory(&dir, &s->pathspec);\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251543","messageId":"1415439595-469-19-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 18/22] update-index: manually enable or disable untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:51Z","receivedAt":"2014-11-08T09:39:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Overall time saving on \"git status\" is about 40% in the best case\nscenario, removing ..collect_untracked() as the most time consuming\nfunction. read and refresh index operations are now at the top (which\nshould drop when index-helper and/or watchman support is added). More\nnumbers and analysis below.\n\nwebkit.git\n==========\n\n169k files. 6k dirs. Lots of test data (i.e. not touched most of the\ntime)\n\nBase status\n-----------\n\nIndex version 4 in split index mode and cache-tree populated. No\nuntracked cache. It shows how time is consumed by \"git status\". The\nsame settings are used for other repos below.\n\n18:28:10.199679 builtin/commit.c:1394   performance: 0.000000451 s: cmd_status:setup\n18:28:10.474847 read-cache.c:1407       performance: 0.274873831 s: read_index\n18:28:10.475295 read-cache.c:1407       performance: 0.000000656 s: read_index\n18:28:10.728443 preload-index.c:131     performance: 0.253147487 s: read_index_preload\n18:28:10.741422 read-cache.c:1254       performance: 0.012868340 s: refresh_index\n18:28:10.752300 wt-status.c:623         performance: 0.010421357 s: wt_status_collect_changes_worktree\n18:28:10.762069 wt-status.c:629         performance: 0.009644748 s: wt_status_collect_changes_index\n18:28:11.601019 wt-status.c:632         performance: 0.838859547 s: wt_status_collect_untracked\n18:28:11.605939 builtin/commit.c:1421   performance: 0.004835004 s: cmd_status:update_index\n18:28:11.606580 trace.c:415             performance: 1.407878388 s: git command: 'git' 'status'\n\nPopulating status\n-----------------\n\nThis is after enabling untracked cache and the cache is still empty.\nWe see a slight increase in .._collect_untracked() and update_index\n(because new cache has to be written to $GIT_DIR/index).\n\n18:28:18.915213 builtin/commit.c:1394   performance: 0.000000326 s: cmd_status:setup\n18:28:19.197364 read-cache.c:1407       performance: 0.281901416 s: read_index\n18:28:19.197754 read-cache.c:1407       performance: 0.000000546 s: read_index\n18:28:19.451355 preload-index.c:131     performance: 0.253599607 s: read_index_preload\n18:28:19.464400 read-cache.c:1254       performance: 0.012935336 s: refresh_index\n18:28:19.475115 wt-status.c:623         performance: 0.010236920 s: wt_status_collect_changes_worktree\n18:28:19.486022 wt-status.c:629         performance: 0.010801685 s: wt_status_collect_changes_index\n18:28:20.362660 wt-status.c:632         performance: 0.876551366 s: wt_status_collect_untracked\n18:28:20.396199 builtin/commit.c:1421   performance: 0.033447969 s: cmd_status:update_index\n18:28:20.396939 trace.c:415             performance: 1.482695902 s: git command: 'git' 'status'\n\nPopulated status\n----------------\n\nAfter the cache is populated, wt_status_collect_untracked() drops 82%\nfrom 0.838s to 0.144s. Overall time drops 45%. Top offenders are now\nread_index() and read_index_preload().\n\n18:28:20.408605 builtin/commit.c:1394   performance: 0.000000457 s: cmd_status:setup\n18:28:20.692864 read-cache.c:1407       performance: 0.283980458 s: read_index\n18:28:20.693273 read-cache.c:1407       performance: 0.000000661 s: read_index\n18:28:20.958814 preload-index.c:131     performance: 0.265540254 s: read_index_preload\n18:28:20.972375 read-cache.c:1254       performance: 0.013437429 s: refresh_index\n18:28:20.983959 wt-status.c:623         performance: 0.011146646 s: wt_status_collect_changes_worktree\n18:28:20.993948 wt-status.c:629         performance: 0.009879094 s: wt_status_collect_changes_index\n18:28:21.138125 wt-status.c:632         performance: 0.144084737 s: wt_status_collect_untracked\n18:28:21.173678 builtin/commit.c:1421   performance: 0.035463949 s: cmd_status:update_index\n18:28:21.174251 trace.c:415             performance: 0.766707355 s: git command: 'git' 'status'\n\ngentoo-x86.git\n==============\n\nThis repository is a strange one with a balanced, wide and shallow\nworktree (about 100k files and 23k dirs) and no .gitignore in\nworktree. .._collect_untracked() time drops 88%, total time drops 56%.\n\nBase status\n-----------\n18:20:40.828642 builtin/commit.c:1394   performance: 0.000000496 s: cmd_status:setup\n18:20:41.027233 read-cache.c:1407       performance: 0.198130532 s: read_index\n18:20:41.027670 read-cache.c:1407       performance: 0.000000581 s: read_index\n18:20:41.171716 preload-index.c:131     performance: 0.144045594 s: read_index_preload\n18:20:41.179171 read-cache.c:1254       performance: 0.007320424 s: refresh_index\n18:20:41.185785 wt-status.c:623         performance: 0.006144638 s: wt_status_collect_changes_worktree\n18:20:41.192701 wt-status.c:629         performance: 0.006780184 s: wt_status_collect_changes_index\n18:20:41.991723 wt-status.c:632         performance: 0.798927029 s: wt_status_collect_untracked\n18:20:41.994664 builtin/commit.c:1421   performance: 0.002852772 s: cmd_status:update_index\n18:20:41.995458 trace.c:415             performance: 1.168427502 s: git command: 'git' 'status'\nPopulating status\n-----------------\n18:20:48.968848 builtin/commit.c:1394   performance: 0.000000380 s: cmd_status:setup\n18:20:49.172918 read-cache.c:1407       performance: 0.203734214 s: read_index\n18:20:49.173341 read-cache.c:1407       performance: 0.000000562 s: read_index\n18:20:49.320013 preload-index.c:131     performance: 0.146671391 s: read_index_preload\n18:20:49.328039 read-cache.c:1254       performance: 0.007921957 s: refresh_index\n18:20:49.334680 wt-status.c:623         performance: 0.006172020 s: wt_status_collect_changes_worktree\n18:20:49.342526 wt-status.c:629         performance: 0.007731746 s: wt_status_collect_changes_index\n18:20:50.257510 wt-status.c:632         performance: 0.914864222 s: wt_status_collect_untracked\n18:20:50.338371 builtin/commit.c:1421   performance: 0.080776477 s: cmd_status:update_index\n18:20:50.338900 trace.c:415             performance: 1.371462446 s: git command: 'git' 'status'\nPopulated status\n----------------\n18:20:50.351160 builtin/commit.c:1394   performance: 0.000000571 s: cmd_status:setup\n18:20:50.577358 read-cache.c:1407       performance: 0.225917338 s: read_index\n18:20:50.577794 read-cache.c:1407       performance: 0.000000617 s: read_index\n18:20:50.734140 preload-index.c:131     performance: 0.156345564 s: read_index_preload\n18:20:50.745717 read-cache.c:1254       performance: 0.011463075 s: refresh_index\n18:20:50.755176 wt-status.c:623         performance: 0.008877929 s: wt_status_collect_changes_worktree\n18:20:50.763768 wt-status.c:629         performance: 0.008471633 s: wt_status_collect_changes_index\n18:20:50.854885 wt-status.c:632         performance: 0.090988721 s: wt_status_collect_untracked\n18:20:50.857765 builtin/commit.c:1421   performance: 0.002789097 s: cmd_status:update_index\n18:20:50.858411 trace.c:415             performance: 0.508647673 s: git command: 'git' 'status'\n\nlinux-2.6\n=========\n\nReference repo. Not too big. .._collect_status() drops 84%. Total time\ndrops 42%.\n\nBase status\n-----------\n18:34:09.870122 builtin/commit.c:1394   performance: 0.000000385 s: cmd_status:setup\n18:34:09.943218 read-cache.c:1407       performance: 0.072871177 s: read_index\n18:34:09.943614 read-cache.c:1407       performance: 0.000000491 s: read_index\n18:34:10.004364 preload-index.c:131     performance: 0.060748102 s: read_index_preload\n18:34:10.008190 read-cache.c:1254       performance: 0.003714285 s: refresh_index\n18:34:10.012087 wt-status.c:623         performance: 0.002775446 s: wt_status_collect_changes_worktree\n18:34:10.016054 wt-status.c:629         performance: 0.003862140 s: wt_status_collect_changes_index\n18:34:10.214747 wt-status.c:632         performance: 0.198604837 s: wt_status_collect_untracked\n18:34:10.216102 builtin/commit.c:1421   performance: 0.001244166 s: cmd_status:update_index\n18:34:10.216817 trace.c:415             performance: 0.347670735 s: git command: 'git' 'status'\nPopulating status\n-----------------\n18:34:16.595102 builtin/commit.c:1394   performance: 0.000000456 s: cmd_status:setup\n18:34:16.666600 read-cache.c:1407       performance: 0.070992413 s: read_index\n18:34:16.667012 read-cache.c:1407       performance: 0.000000606 s: read_index\n18:34:16.729375 preload-index.c:131     performance: 0.062362492 s: read_index_preload\n18:34:16.732565 read-cache.c:1254       performance: 0.003075517 s: refresh_index\n18:34:16.736148 wt-status.c:623         performance: 0.002422201 s: wt_status_collect_changes_worktree\n18:34:16.739990 wt-status.c:629         performance: 0.003746618 s: wt_status_collect_changes_index\n18:34:16.948505 wt-status.c:632         performance: 0.208426710 s: wt_status_collect_untracked\n18:34:16.961744 builtin/commit.c:1421   performance: 0.013151887 s: cmd_status:update_index\n18:34:16.962233 trace.c:415             performance: 0.368537535 s: git command: 'git' 'status'\nPopulated status\n----------------\n18:34:16.970026 builtin/commit.c:1394   performance: 0.000000631 s: cmd_status:setup\n18:34:17.046235 read-cache.c:1407       performance: 0.075904673 s: read_index\n18:34:17.046644 read-cache.c:1407       performance: 0.000000681 s: read_index\n18:34:17.113564 preload-index.c:131     performance: 0.066920253 s: read_index_preload\n18:34:17.117281 read-cache.c:1254       performance: 0.003604055 s: refresh_index\n18:34:17.121115 wt-status.c:623         performance: 0.002508345 s: wt_status_collect_changes_worktree\n18:34:17.125089 wt-status.c:629         performance: 0.003871636 s: wt_status_collect_changes_index\n18:34:17.156089 wt-status.c:632         performance: 0.030895703 s: wt_status_collect_untracked\n18:34:17.169861 builtin/commit.c:1421   performance: 0.013686404 s: cmd_status:update_index\n18:34:17.170391 trace.c:415             performance: 0.201474531 s: git command: 'git' 'status'\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-update-index.txt |  8 ++++++++\n builtin/update-index.c             | 16 ++++++++++++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt\nindex dfc09d9..f9a35cd 100644\n--- a/Documentation/git-update-index.txt\n+++ b/Documentation/git-update-index.txt\n@@ -172,6 +172,14 @@ may not support it yet.\n \tthe shared index file. This mode is designed for very large\n \tindexes that take a signficant amount of time to read or write.\n \n+--untracked-cache::\n+--no-untracked-cache::\n+\tEnable or disable untracked cache extension. This could speed\n+\tup for commands that involve determining untracked files such\n+\tas `git status`. The underlying operating system and file\n+\tsystem must change `st_mtime` field of a directory if files\n+\tare added or deleted in that directory.\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex e8c7fd4..3d2dedd 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -740,6 +740,7 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx,\n int cmd_update_index(int argc, const char **argv, const char *prefix)\n {\n \tint newfd, entries, has_errors = 0, line_termination = '\\n';\n+\tint untracked_cache = -1;\n \tint read_from_stdin = 0;\n \tint prefix_length = prefix ? strlen(prefix) : 0;\n \tint preferred_index_format = 0;\n@@ -831,6 +832,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"write index in this format\")),\n \t\tOPT_BOOL(0, \"split-index\", &split_index,\n \t\t\tN_(\"enable or disable split index\")),\n+\t\tOPT_BOOL(0, \"untracked-cache\", &untracked_cache,\n+\t\t\tN_(\"enable/disable untracked cache\")),\n \t\tOPT_END()\n \t};\n \n@@ -937,6 +940,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\tthe_index.split_index = NULL;\n \t\tthe_index.cache_changed |= SOMETHING_CHANGED;\n \t}\n+\tif (untracked_cache > 0 && !the_index.untracked) {\n+\t\tstruct untracked_cache *uc;\n+\n+\t\tuc = xcalloc(1, sizeof(*uc));\n+\t\tuc->exclude_per_dir = \".gitignore\";\n+\t\t/* should be the same flags used by git-status */\n+\t\tuc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;\n+\t\tthe_index.untracked = uc;\n+\t\tthe_index.cache_changed |= UNTRACKED_CHANGED;\n+\t} else if (!untracked_cache && the_index.untracked) {\n+\t\tthe_index.untracked = NULL;\n+\t\tthe_index.cache_changed |= UNTRACKED_CHANGED;\n+\t}\n \n \tif (active_cache_changed) {\n \t\tif (newfd < 0) {\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251544","messageId":"1415439595-469-20-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 19/22] update-index: test the system before enabling untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:52Z","receivedAt":"2014-11-08T09:39:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Helped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-update-index.txt |   6 ++\n builtin/update-index.c             | 148 +++++++++++++++++++++++++++++++++++++\n 2 files changed, 154 insertions(+)\n\ndiff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt\nindex f9a35cd..ed32bae 100644\n--- a/Documentation/git-update-index.txt\n+++ b/Documentation/git-update-index.txt\n@@ -180,6 +180,12 @@ may not support it yet.\n \tsystem must change `st_mtime` field of a directory if files\n \tare added or deleted in that directory.\n \n+--force-untracked-cache::\n+\tFor safety, `--untracked-cache` performs tests on the working\n+\tdirectory to make sure untracked cache can be used. These\n+\ttests can take a few seconds. `--force-untracked-cache` can be\n+\tused to skip the tests.\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 3d2dedd..f23ec83 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -47,6 +47,147 @@ static void report(const char *fmt, ...)\n \tva_end(vp);\n }\n \n+static void remove_test_directory(void)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstrbuf_addstr(&sb, \"dir-mtime-test\");\n+\tremove_dir_recursively(&sb, 0);\n+\tstrbuf_release(&sb);\n+}\n+\n+static void xmkdir(const char *path)\n+{\n+\tif (mkdir(path, 0700))\n+\t\tdie_errno(_(\"failed to create directory %s\"), path);\n+}\n+\n+static int xstat(const char *path, struct stat *st)\n+{\n+\tif (stat(path, st))\n+\t\tdie_errno(_(\"failed to stat %s\"), path);\n+\treturn 0;\n+}\n+\n+static int create_file(const char *path)\n+{\n+\tint fd = open(path, O_CREAT | O_RDWR, 0644);\n+\tif (fd < 0)\n+\t\tdie_errno(_(\"failed to create file %s\"), path);\n+\treturn fd;\n+}\n+\n+static void xunlink(const char *path)\n+{\n+\tif (unlink(path))\n+\t\tdie_errno(_(\"failed to delete file %s\"), path);\n+}\n+\n+static void xrmdir(const char *path)\n+{\n+\tif (rmdir(path))\n+\t\tdie_errno(_(\"failed to delete directory %s\"), path);\n+}\n+\n+static void avoid_racy(void)\n+{\n+\t/*\n+\t * not use if we could usleep(10) if USE_NSEC is defined. The\n+\t * field nsec could be there, but the OS could choose to\n+\t * ignore it?\n+\t */\n+\tsleep(1);\n+}\n+\n+static int test_if_untracked_cache_is_supported(void)\n+{\n+\tstruct stat st;\n+\tstruct stat_data base;\n+\tint fd;\n+\n+\tfprintf(stderr, _(\"Testing \"));\n+\txmkdir(\"dir-mtime-test\");\n+\tatexit(remove_test_directory);\n+\txstat(\"dir-mtime-test\", &st);\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\tfd = create_file(\"dir-mtime-test/newfile\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tclose(fd);\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr,_(\"directory stat info does not \"\n+\t\t\t\t    \"change after adding a new file\"));\n+\t\treturn 0;\n+\t}\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\txmkdir(\"dir-mtime-test/new-dir\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tclose(fd);\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info does not change \"\n+\t\t\t\t     \"after adding a new directory\"));\n+\t\treturn 0;\n+\t}\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\twrite_or_die(fd, \"data\", 4);\n+\tclose(fd);\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info changes \"\n+\t\t\t\t     \"after updating a file\"));\n+\t\treturn 0;\n+\t}\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\tclose(create_file(\"dir-mtime-test/new-dir/new\"));\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info changes after \"\n+\t\t\t\t     \"adding a file inside subdirectory\"));\n+\t\treturn 0;\n+\t}\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\txunlink(\"dir-mtime-test/newfile\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info does not \"\n+\t\t\t\t     \"change after deleting a file\"));\n+\t\treturn 0;\n+\t}\n+\tfill_stat_data(&base, &st);\n+\tfputc('.', stderr);\n+\n+\tavoid_racy();\n+\txunlink(\"dir-mtime-test/new-dir/new\");\n+\txrmdir(\"dir-mtime-test/new-dir\");\n+\txstat(\"dir-mtime-test\", &st);\n+\tif (!match_stat_data(&base, &st)) {\n+\t\tfputc('\\n', stderr);\n+\t\tfprintf_ln(stderr, _(\"directory stat info does not \"\n+\t\t\t\t     \"change after deleting a directory\"));\n+\t\treturn 0;\n+\t}\n+\n+\txrmdir(\"dir-mtime-test\");\n+\tfprintf_ln(stderr, _(\" OK\"));\n+\treturn 1;\n+}\n+\n static int mark_ce_flags(const char *path, int flag, int mark)\n {\n \tint namelen = strlen(path);\n@@ -834,6 +975,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"enable or disable split index\")),\n \t\tOPT_BOOL(0, \"untracked-cache\", &untracked_cache,\n \t\t\tN_(\"enable/disable untracked cache\")),\n+\t\tOPT_SET_INT(0, \"force-untracked-cache\", &untracked_cache,\n+\t\t\t    N_(\"enable untracked cache without testing the filesystem\"), 2),\n \t\tOPT_END()\n \t};\n \n@@ -943,6 +1086,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \tif (untracked_cache > 0 && !the_index.untracked) {\n \t\tstruct untracked_cache *uc;\n \n+\t\tif (untracked_cache < 2) {\n+\t\t\tsetup_work_tree();\n+\t\t\tif (!test_if_untracked_cache_is_supported())\n+\t\t\t\treturn 1;\n+\t\t}\n \t\tuc = xcalloc(1, sizeof(*uc));\n \t\tuc->exclude_per_dir = \".gitignore\";\n \t\t/* should be the same flags used by git-status */\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251545","messageId":"1415439595-469-21-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 20/22] t7063: tests for untracked cache","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:53Z","receivedAt":"2014-11-08T09:39:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n .gitignore                                 |   1 +\n Makefile                                   |   1 +\n t/t7063-status-untracked-cache.sh (new +x) | 353 +++++++++++++++++++++++++++++\n test-dump-untracked-cache.c (new)          |  61 +++++\n 4 files changed, 416 insertions(+)\n create mode 100755 t/t7063-status-untracked-cache.sh\n create mode 100644 test-dump-untracked-cache.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 81e12c0..e2bb375 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -182,6 +182,7 @@\n /test-delta\n /test-dump-cache-tree\n /test-dump-split-index\n+/test-dump-untracked-cache\n /test-scrap-cache-tree\n /test-genrandom\n /test-hashmap\ndiff --git a/Makefile b/Makefile\nindex 9f984a9..fa58a53 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -555,6 +555,7 @@ TEST_PROGRAMS_NEED_X += test-date\n TEST_PROGRAMS_NEED_X += test-delta\n TEST_PROGRAMS_NEED_X += test-dump-cache-tree\n TEST_PROGRAMS_NEED_X += test-dump-split-index\n+TEST_PROGRAMS_NEED_X += test-dump-untracked-cache\n TEST_PROGRAMS_NEED_X += test-genrandom\n TEST_PROGRAMS_NEED_X += test-hashmap\n TEST_PROGRAMS_NEED_X += test-index-version\ndiff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh\nnew file mode 100755\nindex 0000000..2b2ffd7\n--- /dev/null\n+++ b/t/t7063-status-untracked-cache.sh\n@@ -0,0 +1,353 @@\n+#!/bin/sh\n+\n+test_description='test untracked cache'\n+\n+. ./test-lib.sh\n+\n+avoid_racy() {\n+\tsleep 1\n+}\n+\n+git update-index --untracked-cache\n+# It's fine if git update-index returns an error code other than one,\n+# it'll be caught in the first test.\n+if test $? -eq 1; then\n+\tskip_all='This system does not support untracked cache'\n+\ttest_done\n+fi\n+\n+test_expect_success 'setup' '\n+\tgit init worktree &&\n+\tcd worktree &&\n+\tmkdir done dtwo dthree &&\n+\ttouch one two three done/one dtwo/two dthree/three &&\n+\tgit add one two done/one &&\n+\t: >.git/info/exclude &&\n+\tgit update-index --untracked-cache\n+'\n+\n+test_expect_success 'untracked cache is empty' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 0000000000000000000000000000000000000000\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+cat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? dthree/\n+?? dtwo/\n+?? three\n+EOF\n+\n+cat >../dump.expect <<EOF &&\n+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ 0000000000000000000000000000000000000000 recurse valid\n+dthree/\n+dtwo/\n+three\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+three\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\n+test_expect_success 'status first time (empty cache)' '\n+\tavoid_racy &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 3\n+gitignore invalidation: 1\n+directory invalidation: 0\n+opendir: 4\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'untracked cache after first status' '\n+\ttest-dump-untracked-cache >../actual &&\n+\ttest_cmp ../dump.expect ../actual\n+'\n+\n+test_expect_success 'status second time (fully populated cache)' '\n+\tavoid_racy &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 0\n+opendir: 0\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'untracked cache after second status' '\n+\ttest-dump-untracked-cache >../actual &&\n+\ttest_cmp ../dump.expect ../actual\n+'\n+\n+test_expect_success 'modify in root directory, one dir invalidation' '\n+\tavoid_racy &&\n+\t: >four &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? dthree/\n+?? dtwo/\n+?? four\n+?? three\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 1\n+opendir: 1\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ 0000000000000000000000000000000000000000 recurse valid\n+dthree/\n+dtwo/\n+four\n+three\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+three\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'new .gitignore invalidates recursively' '\n+\tavoid_racy &&\n+\techo four >.gitignore &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? .gitignore\n+?? dthree/\n+?? dtwo/\n+?? three\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 1\n+directory invalidation: 1\n+opendir: 4\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dthree/\n+dtwo/\n+three\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+three\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'new info/exclude invalidates everything' '\n+\tavoid_racy &&\n+\techo three >>.git/info/exclude &&\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? .gitignore\n+?? dtwo/\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 1\n+directory invalidation: 0\n+opendir: 4\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dtwo/\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'move two from tracked to untracked' '\n+\tgit rm --cached two &&\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'status after the move' '\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+?? .gitignore\n+?? dtwo/\n+?? two\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 0\n+opendir: 1\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dtwo/\n+two\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'move two from untracked to tracked' '\n+\tgit add two &&\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_expect_success 'status after the move' '\n+\t: >../trace &&\n+\tGIT_TRACE_UNTRACKED_STATS=\"$TRASH_DIRECTORY/trace\" \\\n+\tgit status --porcelain >../actual &&\n+\tcat >../status.expect <<EOF &&\n+A  done/one\n+A  one\n+A  two\n+?? .gitignore\n+?? dtwo/\n+EOF\n+\ttest_cmp ../status.expect ../actual &&\n+\tcat >../trace.expect <<EOF &&\n+node creation: 0\n+gitignore invalidation: 0\n+directory invalidation: 0\n+opendir: 1\n+EOF\n+\ttest_cmp ../trace.expect ../trace\n+'\n+\n+test_expect_success 'verify untracked cache dump' '\n+\ttest-dump-untracked-cache >../actual &&\n+\tcat >../expect <<EOF &&\n+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0\n+core.excludesfile 0000000000000000000000000000000000000000\n+exclude_per_dir .gitignore\n+flags 00000006\n+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid\n+.gitignore\n+dtwo/\n+/done/ 0000000000000000000000000000000000000000 recurse valid\n+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid\n+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid\n+two\n+EOF\n+\ttest_cmp ../expect ../actual\n+'\n+\n+test_done\ndiff --git a/test-dump-untracked-cache.c b/test-dump-untracked-cache.c\nnew file mode 100644\nindex 0000000..710441e\n--- /dev/null\n+++ b/test-dump-untracked-cache.c\n@@ -0,0 +1,61 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+\n+static int compare_untracked(const void *a_, const void *b_)\n+{\n+\tconst char *const *a = a_;\n+\tconst char *const *b = b_;\n+\treturn strcmp(*a, *b);\n+}\n+\n+static int compare_dir(const void *a_, const void *b_)\n+{\n+\tconst struct untracked_cache_dir *const *a = a_;\n+\tconst struct untracked_cache_dir *const *b = b_;\n+\treturn strcmp((*a)->name, (*b)->name);\n+}\n+\n+static void dump(struct untracked_cache_dir *ucd, struct strbuf *base)\n+{\n+\tint i, len;\n+\tqsort(ucd->untracked, ucd->untracked_nr, sizeof(*ucd->untracked),\n+\t      compare_untracked);\n+\tqsort(ucd->dirs, ucd->dirs_nr, sizeof(*ucd->dirs),\n+\t      compare_dir);\n+\tlen = base->len;\n+\tstrbuf_addf(base, \"%s/\", ucd->name);\n+\tprintf(\"%s %s\", base->buf,\n+\t       sha1_to_hex(ucd->exclude_sha1));\n+\tif (ucd->recurse)\n+\t\tfputs(\" recurse\", stdout);\n+\tif (ucd->check_only)\n+\t\tfputs(\" check_only\", stdout);\n+\tif (ucd->valid)\n+\t\tfputs(\" valid\", stdout);\n+\tprintf(\"\\n\");\n+\tfor (i = 0; i < ucd->untracked_nr; i++)\n+\t\tprintf(\"%s\\n\", ucd->untracked[i]);\n+\tfor (i = 0; i < ucd->dirs_nr; i++)\n+\t\tdump(ucd->dirs[i], base);\n+\tstrbuf_setlen(base, len);\n+}\n+\n+int main(int ac, char **av)\n+{\n+\tstruct untracked_cache *uc;\n+\tstruct strbuf base = STRBUF_INIT;\n+\tif (read_cache() < 0)\n+\t\tdie(\"unable to read index file\");\n+\tuc = the_index.untracked;\n+\tif (!uc) {\n+\t\tprintf(\"no untracked cache\\n\");\n+\t\treturn 0;\n+\t}\n+\tprintf(\"info/exclude %s\\n\", sha1_to_hex(uc->ss_info_exclude.sha1));\n+\tprintf(\"core.excludesfile %s\\n\", sha1_to_hex(uc->ss_excludes_file.sha1));\n+\tprintf(\"exclude_per_dir %s\\n\", uc->exclude_per_dir);\n+\tprintf(\"flags %08x\\n\", uc->dir_flags);\n+\tif (uc->root)\n+\t\tdump(uc->root, &base);\n+\treturn 0;\n+}\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251546","messageId":"1415439595-469-22-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 21/22] mingw32: add uname()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:54Z","receivedAt":"2014-11-08T09:39:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n compat/mingw.c | 11 +++++++++++\n compat/mingw.h |  9 +++++++++\n 2 files changed, 20 insertions(+)\n\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex c5c37e5..b817678 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2128,3 +2128,14 @@ void mingw_startup()\n \t/* initialize Unicode console */\n \twinansi_init();\n }\n+\n+int uname(struct utsname *buf)\n+{\n+\tDWORD v = GetVersion();\n+\tmemset(buf, 0, sizeof(*buf));\n+\tsprintf(buf->sysname, \"Windows\");\n+\tsprintf(buf->release, \"%u.%u\", v & 0xff, (v >> 8) & 0xff);\n+\t/* assuming NT variants only.. */\n+\tsprintf(buf->version, \"%u\", (v >> 16) & 0x7fff);\n+\treturn 0;\n+}\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex df0e320..d00ba7a 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -77,6 +77,14 @@ struct itimerval {\n };\n #define ITIMER_REAL 0\n \n+struct utsname {\n+\tchar sysname[16];\n+\tchar nodename[1];\n+\tchar release[16];\n+\tchar version[16];\n+\tchar machine[1];\n+};\n+\n /*\n  * sanitize preprocessor namespace polluted by Windows headers defining\n  * macros which collide with git local versions\n@@ -166,6 +174,7 @@ struct passwd *getpwuid(uid_t uid);\n int setitimer(int type, struct itimerval *in, struct itimerval *out);\n int sigaction(int sig, struct sigaction *in, struct sigaction *out);\n int link(const char *oldpath, const char *newpath);\n+int uname(struct utsname *buf);\n \n /*\n  * replacements of existing functions\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251547","messageId":"1415439595-469-23-git-send-email-pclouds@gmail.com","threadId":"37820","inReplyTo":"1415439595-469-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 22/22] untracked cache: guard and disable on system changes","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-08T09:39:55Z","receivedAt":"2014-11-08T09:39:55Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"If the user enables untracked cache, then\n\n - move worktree to an unsupported filesystem\n - or simply upgrade OS\n - or move the whole (portable) disk from one machine to another\n - or access a shared fs from another machine\n\nthere's no guarantee that untracked cache can still function properly.\nRecord the worktree location and OS footprint in the cache. If it\nchanges, err on the safe side and disable the cache. The user can\n'update-index --untracked-cache' again to make sure all conditions are\nmet.\n\nThis change does not cover all bases, you can fool it if you try\nhard. The point is to stop accidents.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c                       | 44 +++++++++++++++++++++++++++++++++++++++++---\n git-compat-util.h           |  1 +\n test-dump-untracked-cache.c |  1 +\n 3 files changed, 43 insertions(+), 3 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex a99bd63..2324c52 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2227,10 +2227,22 @@ static void write_one_dir(struct strbuf *out, struct untracked_cache_dir *untrac\n \t\t\twrite_one_dir(out, untracked->dirs[i]);\n }\n \n+static void get_ident_string(struct strbuf *sb)\n+{\n+\tstruct utsname uts;\n+\n+\tif (uname(&uts))\n+\t\tdie_errno(_(\"failed to get kernel name and information\"));\n+\tstrbuf_addf(sb, \"Location %s, system %s %s %s\", get_git_work_tree(),\n+\t\t    uts.sysname, uts.release, uts.version);\n+}\n+\n void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)\n {\n \tstruct ondisk_untracked_cache *ouc;\n-\tint len = 0;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tunsigned char varbuf[16];\n+\tint len = 0, varint_len;\n \tif (untracked->exclude_per_dir)\n \t\tlen = strlen(untracked->exclude_per_dir);\n \touc = xmalloc(sizeof(*ouc) + len);\n@@ -2240,6 +2252,13 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra\n \thashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);\n \touc->dir_flags = htonl(untracked->dir_flags);\n \tmemcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);\n+\n+\tget_ident_string(&sb);\n+\tvarint_len = encode_varint(sb.len + 1, varbuf);\n+\tstrbuf_add(out, varbuf, varint_len);\n+\tstrbuf_add(out, sb.buf, sb.len + 1);\n+\tstrbuf_release(&sb);\n+\n \tstrbuf_add(out, ouc, sizeof(*ouc) + len);\n \tif (untracked->root)\n \t\twrite_one_dir(out, untracked->root);\n@@ -2355,10 +2374,29 @@ static void load_sha1_stat(struct sha1_stat *sha1_stat,\n \n struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)\n {\n-\tconst struct ondisk_untracked_cache *ouc = data;\n+\tconst struct ondisk_untracked_cache *ouc;\n \tstruct untracked_cache *uc;\n+\tconst unsigned char *next = data;\n+\tstruct strbuf sb = STRBUF_INIT;\n \tint len;\n \n+\tlen = decode_varint(&next);\n+\tif (sz <= (next - (const unsigned char *)data) + len ||\n+\t    next[len - 1] != '\\0')\n+\t\treturn NULL;\n+\n+\tget_ident_string(&sb);\n+\tif (strcmp(sb.buf, (const char *)next)) {\n+\t\twarning(_(\"system identification does not match, untracked cache disabled.\\n\"\n+\t\t\t  \"Stored: %s\\nCurrent: %s\\n\"),\n+\t\t\tnext, sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn NULL;\n+\t}\n+\tstrbuf_release(&sb);\n+\touc = (const struct ondisk_untracked_cache *)(next + len);\n+\tsz -= (const char *)ouc - (const char *)data;\n+\n \tif (sz < sizeof(*ouc))\n \t\treturn NULL;\n \n@@ -2373,7 +2411,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long\n \tif (sz == len)\n \t\treturn uc;\n \tif (sz > len &&\n-\t    read_one_dir(&uc->root, (const unsigned char *)data + len,\n+\t    read_one_dir(&uc->root, (const unsigned char *)ouc + len,\n \t\t\t sz - len) == sz - len)\n \t\treturn uc;\n \tfree_untracked_cache(uc);\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex f587749..6b1f259 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -125,6 +125,7 @@\n #else\n #include <poll.h>\n #endif\n+#include <sys/utsname.h>\n \n #if defined(__MINGW32__)\n /* pull in Windows compatibility stuff */\ndiff --git a/test-dump-untracked-cache.c b/test-dump-untracked-cache.c\nindex 710441e..25d855d 100644\n--- a/test-dump-untracked-cache.c\n+++ b/test-dump-untracked-cache.c\n@@ -44,6 +44,7 @@ int main(int ac, char **av)\n {\n \tstruct untracked_cache *uc;\n \tstruct strbuf base = STRBUF_INIT;\n+\tsetup_git_directory();\n \tif (read_cache() < 0)\n \t\tdie(\"unable to read index file\");\n \tuc = the_index.untracked;\n-- \n2.1.0.rc0.78.gc0d8480\n"},{"id":"251563","messageId":"20141108170828.GA162918@vauxhall.crustytoothpaste.net","threadId":"37820","inReplyTo":"1415439595-469-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 02/22] untracked cache: record .gitignore information and dir hierarchy","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2014-11-08T17:08:28Z","receivedAt":"2014-11-08T17:08:28Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On Sat, Nov 08, 2014 at 04:39:35PM +0700, Nguyễn Thái Ngọc Duy wrote:\n> The requirement for this to work is stat info of a directory MUST\n> change if an entry is added to or removed from that directory (and\n> should not change often otherwise). If your OS and filesytem do not\n\nShould be \"filesystem\" (or \"file system\").\n\n> meet this requirement, untracked cache is not for you. Most file\n> systems on *nix should be fine. On Windows, NTFS is fine while FAT may\n> be not [1] even though FAT on Linux seems to be fine.\n\nTiny nit: \"may be not\" should probably be \"may not be\".\n-- \nbrian m. carlson / brian with sandals: Houston, Texas, US\n+1 832 623 2791 | http://www.crustytoothpaste.net/~bmc | My opinion only\nOpenPGP: RSA v4 4096b: 88AC E9B2 9196 305B A994 7552 F1BA 225C 0223 B187\n"},{"id":"251571","messageId":"CAPig+cS2FzL6kXTvoG6cTX1yUjmGUkEm5mAHNxmtU5-=ngcoFg@mail.gmail.com","threadId":"37820","inReplyTo":"1415439595-469-22-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 21/22] mingw32: add uname()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-11-09T03:32:08Z","receivedAt":"2014-11-09T03:32:08Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Nov 8, 2014 at 4:39 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/compat/mingw.c b/compat/mingw.c\n> index c5c37e5..b817678 100644\n> --- a/compat/mingw.c\n> +++ b/compat/mingw.c\n> @@ -2128,3 +2128,14 @@ void mingw_startup()\n>         /* initialize Unicode console */\n>         winansi_init();\n>  }\n> +\n> +int uname(struct utsname *buf)\n> +{\n> +       DWORD v = GetVersion();\n> +       memset(buf, 0, sizeof(*buf));\n> +       sprintf(buf->sysname, \"Windows\");\n\nstrcpy() maybe?\n\n> +       sprintf(buf->release, \"%u.%u\", v & 0xff, (v >> 8) & 0xff);\n> +       /* assuming NT variants only.. */\n> +       sprintf(buf->version, \"%u\", (v >> 16) & 0x7fff);\n> +       return 0;\n> +}\n> diff --git a/compat/mingw.h b/compat/mingw.h\n> index df0e320..d00ba7a 100644\n> --- a/compat/mingw.h\n> +++ b/compat/mingw.h\n> @@ -77,6 +77,14 @@ struct itimerval {\n>  };\n>  #define ITIMER_REAL 0\n>\n> +struct utsname {\n> +       char sysname[16];\n> +       char nodename[1];\n> +       char release[16];\n> +       char version[16];\n> +       char machine[1];\n> +};\n> +\n>  /*\n>   * sanitize preprocessor namespace polluted by Windows headers defining\n>   * macros which collide with git local versions\n> @@ -166,6 +174,7 @@ struct passwd *getpwuid(uid_t uid);\n>  int setitimer(int type, struct itimerval *in, struct itimerval *out);\n>  int sigaction(int sig, struct sigaction *in, struct sigaction *out);\n>  int link(const char *oldpath, const char *newpath);\n> +int uname(struct utsname *buf);\n>\n>  /*\n>   * replacements of existing functions\n> --\n> 2.1.0.rc0.78.gc0d8480\n"},{"id":"251572","messageId":"CAPig+cRNpgBNr0cT6P-tsMc3McyC4aC7czcQYz0P+9_uSpkZSw@mail.gmail.com","threadId":"37820","inReplyTo":"1415439595-469-23-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 22/22] untracked cache: guard and disable on system changes","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-11-09T03:39:01Z","receivedAt":"2014-11-09T03:39:01Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Nov 8, 2014 at 4:39 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> If the user enables untracked cache, then\n>\n>  - move worktree to an unsupported filesystem\n>  - or simply upgrade OS\n>  - or move the whole (portable) disk from one machine to another\n>  - or access a shared fs from another machine\n>\n> there's no guarantee that untracked cache can still function properly.\n> Record the worktree location and OS footprint in the cache. If it\n> changes, err on the safe side and disable the cache. The user can\n> 'update-index --untracked-cache' again to make sure all conditions are\n> met.\n>\n> This change does not cover all bases, you can fool it if you try\n> hard. The point is to stop accidents.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/test-dump-untracked-cache.c b/test-dump-untracked-cache.c\n> index 710441e..25d855d 100644\n> --- a/test-dump-untracked-cache.c\n> +++ b/test-dump-untracked-cache.c\n> @@ -44,6 +44,7 @@ int main(int ac, char **av)\n>  {\n>         struct untracked_cache *uc;\n>         struct strbuf base = STRBUF_INIT;\n> +       setup_git_directory();\n\nWhat is this change about? Is it related to the rest of this patch?\n\n>         if (read_cache() < 0)\n>                 die(\"unable to read index file\");\n>         uc = the_index.untracked;\n> --\n> 2.1.0.rc0.78.gc0d8480\n"},{"id":"251579","messageId":"CACsJy8Am8+XcKzd5vqODLkCVAerMW_619c2XQsL35R6-k0F7-g@mail.gmail.com","threadId":"37820","inReplyTo":"CAPig+cRNpgBNr0cT6P-tsMc3McyC4aC7czcQYz0P+9_uSpkZSw@mail.gmail.com","subject":"Re: [PATCH v2 22/22] untracked cache: guard and disable on system changes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-09T08:34:17Z","receivedAt":"2014-11-09T08:34:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Nov 9, 2014 at 10:39 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> --- a/test-dump-untracked-cache.c\n>> +++ b/test-dump-untracked-cache.c\n>> @@ -44,6 +44,7 @@ int main(int ac, char **av)\n>>  {\n>>         struct untracked_cache *uc;\n>>         struct strbuf base = STRBUF_INIT;\n>> +       setup_git_directory();\n>\n> What is this change about? Is it related to the rest of this patch?\n\nYes. This patch makes use of get_git_work_tree() from read_cache()\nbelow. Without setup_git_... worktree is not set up,\nread_untracked_extension() thinks the repo is moved and returns no\ncache. I'll make a note about this.\n\n>\n>>         if (read_cache() < 0)\n>>                 die(\"unable to read index file\");\n>>         uc = the_index.untracked;\n\n\n\n\n-- \nDuy\n"},{"id":"251580","messageId":"CACsJy8CRzngC5U3VY9Bi1uRmdRfEWQWOCokRLLWNKRNUjyJrrw@mail.gmail.com","threadId":"37820","inReplyTo":"CAPig+cS2FzL6kXTvoG6cTX1yUjmGUkEm5mAHNxmtU5-=ngcoFg@mail.gmail.com","subject":"Re: [PATCH v2 21/22] mingw32: add uname()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-09T08:36:25Z","receivedAt":"2014-11-09T08:36:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Nov 9, 2014 at 10:32 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sat, Nov 8, 2014 at 4:39 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>> diff --git a/compat/mingw.c b/compat/mingw.c\n>> index c5c37e5..b817678 100644\n>> --- a/compat/mingw.c\n>> +++ b/compat/mingw.c\n>> @@ -2128,3 +2128,14 @@ void mingw_startup()\n>>         /* initialize Unicode console */\n>>         winansi_init();\n>>  }\n>> +\n>> +int uname(struct utsname *buf)\n>> +{\n>> +       DWORD v = GetVersion();\n>> +       memset(buf, 0, sizeof(*buf));\n>> +       sprintf(buf->sysname, \"Windows\");\n>\n> strcpy() maybe?\n\nIt was originally strcpy, then I wanted to get fancy and show Win3.1,\nWin95... but it got complicated (couldn't just do it based on the last\nbit of 'v'). Will revert.\n-- \nDuy\n"},{"id":"251594","messageId":"545F540D.8010207@web.de","threadId":"37820","inReplyTo":"CACsJy8CRzngC5U3VY9Bi1uRmdRfEWQWOCokRLLWNKRNUjyJrrw@mail.gmail.com","subject":"Re: [PATCH v2 21/22] mingw32: add uname()","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-11-09T11:46:21Z","receivedAt":"2014-11-09T11:46:21Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-11-09 09.36, Duy Nguyen wrote:\n> On Sun, Nov 9, 2014 at 10:32 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> On Sat, Nov 8, 2014 at 4:39 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>>> ---\n>>> diff --git a/compat/mingw.c b/compat/mingw.c\n>>> index c5c37e5..b817678 100644\n>>> --- a/compat/mingw.c\n>>> +++ b/compat/mingw.c\n>>> @@ -2128,3 +2128,14 @@ void mingw_startup()\n>>>         /* initialize Unicode console */\n>>>         winansi_init();\n>>>  }\n>>> +\n>>> +int uname(struct utsname *buf)\n>>> +{\n>>> +       DWORD v = GetVersion();\n>>> +       memset(buf, 0, sizeof(*buf));\n>>> +       sprintf(buf->sysname, \"Windows\");\n>>\n>> strcpy() maybe?\n> \n> It was originally strcpy, then I wanted to get fancy and show Win3.1,\n> Win95... but it got complicated (couldn't just do it based on the last\n> bit of 'v'). Will revert.\n> \nWhy not use strlcpy() ?\n(This feels little like an overkill, but on the other hand it is safe to use regardless\nhow long the buf is, and it is a good example how to avoid \"overrunning\" code)\n"},{"id":"251633","messageId":"xmqqd28wb48h.fsf@gitster.dls.corp.google.com","threadId":"37820","inReplyTo":"545F540D.8010207@web.de","subject":"Re: [PATCH v2 21/22] mingw32: add uname()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-11-09T18:47:42Z","receivedAt":"2014-11-09T18:47:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Torsten Bögershausen <tboegi@web.de> writes:\n\n>>> strcpy() maybe?\n>> \n>> It was originally strcpy, then I wanted to get fancy and show Win3.1,\n>> Win95... but it got complicated (couldn't just do it based on the last\n>> bit of 'v'). Will revert.\n>> \n> Why not use strlcpy() ?\n> (This feels little like an overkill, but on the other hand it is safe to use regardless\n> how long the buf is, and it is a good example how to avoid \"overrunning\" code)\n\nI actually think such a blind belief in strlcpy() is a disease.\n\nSurely it will help you avoid overwriting other unrelated data after\nthe string buffer you are writing into, but it does not help you\nvery much to make sure what you wanted to place in the string buffer\nall got recorded in that buffer, unless you remember to check its\nreturn value to make sure it did not get truncated.  Otherwise, you\nmay be valuing other people's data, but you are not valuing your own\ndata sufficiently enough.\n\nAnd if you (1) know how big your own buffer is, and (2) are checking\nif your call to strlcpy() copied everything you wanted to copy, it\nwould not be more work to do it without strlcpy()---you need to know\nhow big a string you are copying into anyway if you care about your\nown data to the same degree you care about other people's data ;-)\n"},{"id":"251637","messageId":"545FDF22.2020404@web.de","threadId":"37820","inReplyTo":"1415439595-469-23-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 22/22] untracked cache: guard and disable on system changes","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-11-09T21:39:46Z","receivedAt":"2014-11-09T21:39:46Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 2014-11-08 10.39, Nguyễn Thái Ngọc Duy wrote:\n> If the user enables untracked cache, then\n> \n>  - move worktree to an unsupported filesystem\n\nHow do we detect this move ?\nShouldn't we be able to detect an unsupported file system \n(by probing if stat(root_dir_of_repo) == stat(what_we_have_in_index_file))\n>  - or simply upgrade OS\n>  - or move the whole (portable) disk from one machine to another\nHow does this effect Git ?\nI would rather expect an update of Git to be an issue,\nbut knowing that Git strongly tends to be backward compatible, there\nshouldn't be a issue.\n\n>  - or access a shared fs from another machine\nThis is interesting.\nI have done some basic test on git.git using a medium fast laptop\ntalking to a medium fast server using a medium normal WLAN.\ngit status was is in a range of 2-3 seconds, with your patch 1-1.5 seconds.\n\n(That all depends on the network load, some caching here or there)\n\nBut roughly twice the speed, very nice!\n\nI will do some tests with networking file systems, like \nLinux+ext4 -- SMB -- Windows (cygwin/Git for Windows)\nLinux+ext4 -- SMB -- Mac OS X\nLinux+ext4 -- NFS -- Linux\nWindows    -- SMB -- Linux\nWindows    -- SMB -- Mac OS X\n(and then we have some cases where a virtual machine runs a \"shared\" FS with a host file system,\nwhere the untracked cache looks promising)\n\nI am not really sure when we need this protection.\nWhat I understand is that stat(dir).mtime must be reliable.\nAnother problem may be mixing old Git with new Git, but the old Git\nshould write an index file without UNTR, and we should be safe ?\nThe new Git will write an index file with UNTR, which the old Git will ignore.\n\nWhat do I miss ?\n"},{"id":"251644","messageId":"CACsJy8AM2pADQy8+eAsAdikW3gGKwtTjvqBzojPRCzuD1jQ+Jw@mail.gmail.com","threadId":"37820","inReplyTo":"545FDF22.2020404@web.de","subject":"Re: [PATCH v2 22/22] untracked cache: guard and disable on system changes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-11-09T23:47:29Z","receivedAt":"2014-11-09T23:47:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Nov 10, 2014 at 4:39 AM, Torsten Bögershausen <tboegi@web.de> wrote:\n> On 2014-11-08 10.39, Nguyễn Thái Ngọc Duy wrote:\n>> If the user enables untracked cache, then\n>>\n>>  - move worktree to an unsupported filesystem\n>\n> How do we detect this move ?\n> Shouldn't we be able to detect an unsupported file system\n> (by probing if stat(root_dir_of_repo) == stat(what_we_have_in_index_file))\n\nI don't see any generic way of detecting this. So I just save $(cwd)\nand check if the repo is moved. False positive if you move your repo\nwithin the same filesystem. If you move your stuff to a new filesystem\nand mount it to the same place as before, my test fails.\n\n>>  - or simply upgrade OS\n>>  - or move the whole (portable) disk from one machine to another\n> How does this effect Git ?\n> I would rather expect an update of Git to be an issue,\n> but knowing that Git strongly tends to be backward compatible, there\n> shouldn't be a issue.\n\nIf this link [1] is true and you use vfat on Linux, then we should\ndisable the cache when moving it to Windows.\n\n[1] http://support.microsoft.com/kb/299648\n\n>>  - or access a shared fs from another machine\n> This is interesting.\n> I have done some basic test on git.git using a medium fast laptop\n> talking to a medium fast server using a medium normal WLAN.\n> git status was is in a range of 2-3 seconds, with your patch 1-1.5 seconds.\n>\n> (That all depends on the network load, some caching here or there)\n>\n> But roughly twice the speed, very nice!\n\nFor network fs, that's probably about it. For local fs, we still have\nwatchman option to speed it up a little more. Still not sure if I can\nbeat \".. made Mercurial's status command more than 5x faster than\nGit's status command.\"\n\n> I am not really sure when we need this protection.\n> What I understand is that stat(dir).mtime must be reliable.\n\nYes and [1] shows that mtime is not reliable, at least on Windows+vfat.\n\n> Another problem may be mixing old Git with new Git, but the old Git\n> should write an index file without UNTR, and we should be safe ?\n> The new Git will write an index file with UNTR, which the old Git will ignore.\n\nOld git should ignore (and discard) untr extension and go with the slow old way.\n-- \nDuy\n"},{"id":"251692","messageId":"54612486.7070709@web.de","threadId":"37820","inReplyTo":"CACsJy8AM2pADQy8+eAsAdikW3gGKwtTjvqBzojPRCzuD1jQ+Jw@mail.gmail.com","subject":"Re: [PATCH v2 22/22] untracked cache: guard and disable on system changes","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2014-11-10T20:48:06Z","receivedAt":"2014-11-10T20:48:06Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"On 11/10/2014 12:47 AM, Duy Nguyen wrote:\n\nSome updates from the test lab, Windows 7\n\"Working\" means git update-index --untracked-cache reports Testing...OK\n\"Rejected\" means \"..does not change..\"\ncygwin + NTFS: Working\ncygwin + VFAT: Rejected\n\nThe same good news for Msysgit, running your github branch\n(I needed to create an empty compat/win32/sys/utsname.h to get it compiled)\n\nAnd I'm still not sure if we need to store the OS in the index, or if it \nis enough\nto store the $pwd.\n"},{"id":"251999","messageId":"1416252664.9065.3.camel@leckie","threadId":"37820","inReplyTo":"1415439595-469-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 01/22] dir.c: optionally compute sha-1 of a .gitignore file","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2014-11-17T19:31:04Z","receivedAt":"2014-11-17T19:31:04Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Sat, 2014-11-08 at 16:39 +0700, Nguyễn Thái Ngọc Duy wrote:\n> + * If \"ss\" is not NULL, compute SHA-1 of the exclude file and fill\n> + * stat data from disk (only valid if add_excludes returns zero). If\n> + * ss_valid is non-zero, \"ss\" must contain good value as input.\n\nss and ss_valid should be sha1_stat and sha1_stat.valid\n\n> +struct sha1_stat {\n> +\tstruct stat_data stat;\n> +\tunsigned char sha1[20];\n> +\tint valid;\n> +};\n\nIt might be good to document what \"valid\" means here e.g. \"a sha1_stat\nis valid if both sha1 and stat_data match the working tree's version of\nthe file\" or whatever.\n"},{"id":"252000","messageId":"1416256528.9065.14.camel@leckie","threadId":"37820","inReplyTo":"1415439595-469-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 02/22] untracked cache: record .gitignore information and dir hierarchy","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2014-11-17T20:35:28Z","receivedAt":"2014-11-17T20:35:28Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Sat, 2014-11-08 at 16:39 +0700, Nguyễn Thái Ngọc Duy wrote:\n> +\td = xmalloc(sizeof(*d) + len);\n> +\tmemset(d, 0, sizeof(*d) + len);\n>+\tmemcpy(d->name, name, len);\n\ncalloc instead of malloc+memset?  But do we really need this memset to\ninclude name if we're about to use a memcpy? Couldn't we just add a\ntrailing zero?\n\n> + *   - The list of files and directories of the direction in question\n\ns/direction/directory/\n\n> +struct untracked_cache_dir {\n> +\tstruct untracked_cache_dir **dirs;\n> +\tchar **untracked;\n> +\tstruct stat_data stat_data;\n> +\tunsigned int untracked_alloc, dirs_nr, dirs_alloc;\n> +\tunsigned int untracked_nr;\n> +\tunsigned int check_only : 1;\n> +\t/* null SHA-1 means this directory does not have .gitignore */\n> +\tunsigned char exclude_sha1[20];\n> +\tchar name[1];\n\nFor consistency, should this be char name[FLEX_ARRAY]? (this will entail\nsome changes when allocating these, of course)\n"}]}