On 6/28/2021 10:04 PM, Derrick Stolee via GitGitGadget wrote: ... > Update in V7 (relative to v5) > ============================= > > APOLOGIES: As I was working on this cover letter, I was still organizing my > big list of patches, including reordering some into this series. I forgot to > actually include them in my v6 submission, so here is a re-submission. > Please ignore v6. Since v6 was a mistake, here is the full range-diff of v5 versus v7: 1: 5a2ed3d1d70 = 1: 2a4a7256304 sparse-index: skip indexes with unmerged entries 2: 8aa41e74947 ! 2: f5bae86014d sparse-index: include EXTENDED flag when expanding @@ Commit message When creating a full index from a sparse one, we create cache entries for every blob within a given sparse directory entry. These are - correctly marked with the CE_SKIP_WORKTREE flag, but they must also be - marked with the CE_EXTENDED flag to ensure that the skip-worktree bit is - correctly written to disk in the case that the index is not converted - back down to a sparse-index. + correctly marked with the CE_SKIP_WORKTREE flag, but the CE_EXTENDED + flag is not included. The CE_EXTENDED flag would exist if we loaded a + full index from disk with these entries marked with CE_SKIP_WORKTREE, so + we can add the flag here to be consistent. This allows us to directly + compare the flags present in cache entries when testing the sparse-index + feature, but has no significance to its correctness in the user-facing + functionality. Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> 3: b99371c7dd6 ! 3: d965669c766 t1092: replace incorrect 'echo' with 'cat' @@ Commit message t1092: replace incorrect 'echo' with 'cat' This fixes the test data shape to be as expected, allowing rename - detection to work properly now that the 'larger-conent' file actually + detection to work properly now that the 'larger-content' file actually has meaningful lines. Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> 4: f4dddac1859 ! 4: e10fa11cfdb t1092: expand repository data shape @@ Commit message one entry and are the first entry of a directory with multiple entries. + * Add filenames adjacent to a sparse directory entry that sort before + and after the trailing slash. + Later tests will take advantage of these shapes, but they also deepen the tests that already exist. @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'setup' ' + mkdir deep/deeper1/0/0 && + touch deep/deeper1/0/1 && + touch deep/deeper1/0/0/0 && ++ >folder1- && ++ >folder1.x && ++ >folder10 && + cp -r deep/deeper1/0 folder1 && + cp -r deep/deeper1/0 folder2 && + echo >>folder1/0/0/0 && 5: 856346b72f7 = 5: e94ffa07d46 t1092: add tests for status/add and sparse files 6: f3f6223e955 = 6: a8dda933567 unpack-trees: preserve cache_bottom 7: 45ae96adf28 ! 7: e52166f6e4c unpack-trees: compare sparse directories correctly @@ Commit message Within compare_entry(), it first calls do_compare_entry() to check the leading portion of the name. When the input path is a directory name, we could match exactly already. Thus, we should return 0 if we have an - exact string match on a sparse directory entry. + exact string match on a sparse directory entry. The final check is a + length comparison between the strings. Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> @@ unpack-trees.c: static int compare_entry(const struct cache_entry *ce, const str + * works when the input name is a directory, since ce->name + * ends in a directory separator. + */ -+ if (S_ISSPARSEDIR(ce->ce_mode)) ++ if (S_ISSPARSEDIR(ce->ce_mode) && ++ ce->ce_namelen == traverse_path_len(info, tree_entry_len(n)) + 1) + return 0; + /* -: ----------- > 8: d04b62381b8 unpack-trees: rename unpack_nondirectories() 8: 724194eef9f ! 9: 237ccf4e43d unpack-trees: unpack sparse directory entries @@ Commit message might want to perform a merge operation on the entry, such as during 'git checkout <commit>' which wants to replace a sparse tree entry with the tree for that path at the target commit. We extend the logic within - unpack_nondirectories() to create a sparse-directory entry in this case, + unpack_single_entry() to create a sparse-directory entry in this case, and then that is sent to call_unpack_fn(). There are some subtleties in this process. For instance, we need to update find_cache_entry() to allow finding a sparse-directory entry that - exactly matches a given path. + exactly matches a given path. Use the new helper method + sparse_dir_matches_path() for this. We also need to ignore conflict + markers in the case that the entries correspond to directories and we + already have a sparse directory entry. Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> @@ unpack-trees.c: static struct cache_entry *create_ce_entry(const struct traverse + size_t alloc_len = is_sparse_directory ? len + 1 : len; struct cache_entry *ce = is_transient ? -- make_empty_transient_cache_entry(len) : +- make_empty_transient_cache_entry(len, NULL) : - make_empty_cache_entry(istate, len); -+ make_empty_transient_cache_entry(alloc_len) : ++ make_empty_transient_cache_entry(alloc_len, NULL) : + make_empty_cache_entry(istate, alloc_len); ce->ce_mode = create_ce_mode(n->mode); @@ unpack-trees.c: static struct cache_entry *create_ce_entry(const struct traverse + if (is_sparse_directory) { + ce->name[len] = '/'; -+ ce->name[len + 1] = 0; ++ ce->name[len + 1] = '\0'; + ce->ce_namelen++; + ce->ce_flags |= CE_SKIP_WORKTREE; + } @@ unpack-trees.c: static struct cache_entry *create_ce_entry(const struct traverse return ce; } -@@ unpack-trees.c: static int unpack_nondirectories(int n, unsigned long mask, - unsigned long dirmask, - struct cache_entry **src, - const struct name_entry *names, -- const struct traverse_info *info) -+ const struct traverse_info *info, -+ int sparse_directory) - { - int i; +@@ unpack-trees.c: static int unpack_single_entry(int n, unsigned long mask, struct unpack_trees_options *o = info->data; unsigned long conflicts = info->df_conflicts | dirmask; @@ unpack-trees.c: static int unpack_nondirectories(int n, unsigned long mask, if (mask == dirmask && !src[0]) return 0; -+ /* no-op if our cache entry doesn't match the expectations. */ -+ if (sparse_directory) { -+ if (src[0] && !S_ISSPARSEDIR(src[0]->ce_mode)) -+ BUG("expected sparse directory entry"); -+ } else if (src[0] && S_ISSPARSEDIR(src[0]->ce_mode)) { -+ return 0; -+ } ++ /* ++ * When we have a sparse directory entry for src[0], ++ * then this isn't necessarily a directory-file conflict. ++ */ ++ if (mask == dirmask && src[0] && ++ S_ISSPARSEDIR(src[0]->ce_mode)) ++ conflicts = 0; + /* * Ok, we've filled in up to any potential index entry in src[0], * now do the rest. -@@ unpack-trees.c: static int unpack_nondirectories(int n, unsigned long mask, +@@ unpack-trees.c: static int unpack_single_entry(int n, unsigned long mask, * not stored in the index. otherwise construct the * cache entry from the index aware logic. */ - src[i + o->merge] = create_ce_entry(info, names + i, stage, &o->result, o->merge); + src[i + o->merge] = create_ce_entry(info, names + i, stage, + &o->result, o->merge, -+ sparse_directory); ++ bit & dirmask); } if (o->merge) { @@ unpack-trees.c: static int find_cache_pos(struct traverse_info *info, + return -1; + } + ++/* ++ * Given a sparse directory entry 'ce', compare ce->name to ++ * info->name + '/' + p->path + '/' if info->name is non-empty. ++ * Compare ce->name to p->path + '/' otherwise. Note that ++ * ce->name must end in a trailing '/' because it is a sparse ++ * directory entry. ++ */ ++static int sparse_dir_matches_path(const struct cache_entry *ce, ++ struct traverse_info *info, ++ const struct name_entry *p) ++{ ++ assert(S_ISSPARSEDIR(ce->ce_mode)); ++ assert(ce->name[ce->ce_namelen - 1] == '/'); ++ ++ if (info->namelen) ++ return ce->ce_namelen == info->namelen + p->pathlen + 2 && ++ ce->name[info->namelen] == '/' && ++ !strncmp(ce->name, info->name, info->namelen) && ++ !strncmp(ce->name + info->namelen + 1, p->path, p->pathlen); ++ return ce->ce_namelen == p->pathlen + 1 && ++ !strncmp(ce->name, p->path, p->pathlen); ++} ++ static struct cache_entry *find_cache_entry(struct traverse_info *info, const struct name_entry *p) { @@ unpack-trees.c: static int find_cache_pos(struct traverse_info *info, + * Check for a sparse-directory entry named "path/". + * Due to the input p->path not having a trailing + * slash, the negative 'pos' value overshoots the -+ * expected position by one, hence "-2" here. ++ * expected position by at least one, hence "-2" here. + */ + pos = -pos - 2; + + if (pos < 0 || pos >= o->src_index->cache_nr) -+ return NULL; -+ -+ ce = o->src_index->cache[pos]; -+ -+ if (!S_ISSPARSEDIR(ce->ce_mode)) return NULL; + + /* -+ * Compare ce->name to info->name + '/' + p->path + '/' -+ * if info->name is non-empty. Compare ce->name to -+ * p-.path + '/' otherwise. ++ * We might have multiple entries between 'pos' and ++ * the actual sparse-directory entry, so start walking ++ * back until finding it or passing where it would be. + */ -+ if (info->namelen) { -+ if (ce->ce_namelen == info->namelen + p->pathlen + 2 && -+ ce->name[info->namelen] == '/' && -+ !strncmp(ce->name, info->name, info->namelen) && -+ !strncmp(ce->name + info->namelen + 1, p->path, p->pathlen)) ++ while (pos >= 0) { ++ ce = o->src_index->cache[pos]; ++ ++ if (strncmp(ce->name, p->path, p->pathlen)) ++ return NULL; ++ ++ if (S_ISSPARSEDIR(ce->ce_mode) && ++ sparse_dir_matches_path(ce, info, p)) + return ce; -+ } else if (ce->ce_namelen == p->pathlen + 1 && -+ !strncmp(ce->name, p->path, p->pathlen)) -+ return ce; ++ ++ pos--; ++ } ++ + return NULL; } @@ unpack-trees.c: static void debug_unpack_callback(int n, + * sparse-directory entry that matches the given name_entry + * from the tree walk at the given traverse_info. + */ -+static int is_sparse_directory_entry(struct cache_entry *ce, struct name_entry *name, struct traverse_info *info) ++static int is_sparse_directory_entry(struct cache_entry *ce, ++ struct name_entry *name, ++ struct traverse_info *info) +{ -+ size_t expected_len, name_start; -+ + if (!ce || !name || !S_ISSPARSEDIR(ce->ce_mode)) + return 0; + -+ if (info->namelen) -+ name_start = info->namelen + 1; -+ else -+ name_start = 0; -+ expected_len = name->pathlen + 1 + name_start; -+ -+ if (ce->ce_namelen != expected_len || -+ strncmp(ce->name, info->name, info->namelen) || -+ strncmp(ce->name + name_start, name->path, name->pathlen)) -+ return 0; -+ -+ return 1; ++ return sparse_dir_matches_path(ce, info, name); +} + /* * Note that traverse_by_cache_tree() duplicates some logic in this function * without actually calling it. If you change the logic here you may need to -@@ unpack-trees.c: static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str - } - } - -- if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0) -+ if (unpack_nondirectories(n, mask, dirmask, src, names, info, 0) < 0) - return -1; - - if (o->merge && src[0]) { @@ unpack-trees.c: static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str } } - if (traverse_trees_recursive(n, dirmask, mask & ~dirmask, - names, info) < 0) -+ if (is_sparse_directory_entry(src[0], names, info)) { -+ if (unpack_nondirectories(n, dirmask, mask & ~dirmask, src, names, info, 1) < 0) -+ return -1; -+ } else if (traverse_trees_recursive(n, dirmask, mask & ~dirmask, ++ if (!is_sparse_directory_entry(src[0], names, info) && ++ traverse_trees_recursive(n, dirmask, mask & ~dirmask, + names, info) < 0) { return -1; + } -: ----------- > 10: 9f31c691af6 unpack-trees: handle dir/file conflict of sparse entries 9: b8ff179f43e ! 11: 2a43287c47e dir.c: accept a directory as part of cone-mode patterns @@ Commit message Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> ## dir.c ## +@@ dir.c: enum pattern_match_result path_matches_pattern_list( + struct path_pattern *pattern; + struct strbuf parent_pathname = STRBUF_INIT; + int result = NOT_MATCHED; +- const char *slash_pos; ++ size_t slash_pos; + + if (!pl->use_cone_patterns) { + pattern = last_matching_pattern_from_list(pathname, pathlen, basename, @@ dir.c: enum pattern_match_result path_matches_pattern_list( strbuf_addch(&parent_pathname, '/'); strbuf_add(&parent_pathname, pathname, pathlen); @@ dir.c: enum pattern_match_result path_matches_pattern_list( + * use the file-base matching logic in an equivalent way. + */ + if (parent_pathname.len > 0 && -+ parent_pathname.buf[parent_pathname.len - 1] == '/') ++ parent_pathname.buf[parent_pathname.len - 1] == '/') { ++ slash_pos = parent_pathname.len - 1; + strbuf_add(&parent_pathname, "-", 1); ++ } else { ++ const char *slash_ptr = strrchr(parent_pathname.buf, '/'); ++ slash_pos = slash_ptr ? slash_ptr - parent_pathname.buf : 0; ++ } + if (hashmap_contains_path(&pl->recursive_hashmap, &parent_pathname)) { result = MATCHED_RECURSIVE; + goto done; + } + +- slash_pos = strrchr(parent_pathname.buf, '/'); +- +- if (slash_pos == parent_pathname.buf) { ++ if (!slash_pos) { + /* include every file in root */ + result = MATCHED; + goto done; + } + +- strbuf_setlen(&parent_pathname, slash_pos - parent_pathname.buf); ++ strbuf_setlen(&parent_pathname, slash_pos); + + if (hashmap_contains_path(&pl->parent_hashmap, &parent_pathname)) { + result = MATCHED; 10: b9b97e01129 ! 12: f83aa08ff6b diff-lib: handle index diffs with sparse dirs @@ Commit message identical to before: the lack of a cache entry is the same with a sparse index. - In the case where a tree is modified, we need to expand the tree - recursively, and start comparing each contained entry as either an - addition, deletion, or modification. This causes an interesting - recursion that did not exist before. + Use diff_tree_oid() appropriately to compute the diff. Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> ## diff-lib.c ## -@@ diff-lib.c: static int get_stat_data(const struct cache_entry *ce, - return 0; - } - -+struct show_new_tree_context { -+ struct rev_info *revs; -+ unsigned added:1; -+}; -+ -+static int show_new_file_from_tree(const struct object_id *oid, -+ struct strbuf *base, const char *path, -+ unsigned int mode, void *context) -+{ -+ struct show_new_tree_context *ctx = context; -+ struct cache_entry *new_file = make_transient_cache_entry(mode, oid, path, /* stage */ 0); -+ -+ diff_index_show_file(ctx->revs, ctx->added ? "+" : "-", new_file, oid, !is_null_oid(oid), mode, 0); -+ discard_cache_entry(new_file); -+ return 0; -+} -+ -+static void show_directory(struct rev_info *revs, -+ const struct cache_entry *new_dir, -+ int added) -+{ -+ /* -+ * new_dir is a sparse directory entry, so we want to collect all -+ * of the new files within the tree. This requires recursively -+ * expanding the trees. -+ */ -+ struct show_new_tree_context ctx = { revs, added }; -+ struct repository *r = revs->repo; -+ struct strbuf base = STRBUF_INIT; -+ struct pathspec ps; -+ struct tree *tree = lookup_tree(r, &new_dir->oid); -+ -+ memset(&ps, 0, sizeof(ps)); -+ ps.recursive = 1; -+ ps.has_wildcard = 1; -+ ps.max_depth = -1; -+ -+ strbuf_add(&base, new_dir->name, new_dir->ce_namelen); -+ read_tree_at(r, tree, &base, &ps, -+ show_new_file_from_tree, &ctx); -+} -+ - static void show_new_file(struct rev_info *revs, - const struct cache_entry *new_file, - int cached, int match_missing) @@ diff-lib.c: static void show_new_file(struct rev_info *revs, - unsigned int mode; unsigned dirty_submodule = 0; + struct index_state *istate = revs->diffopt.repo->index; + if (new_file && S_ISSPARSEDIR(new_file->ce_mode)) { -+ show_directory(revs, new_file, /*added */ 1); ++ diff_tree_oid(NULL, &new_file->oid, new_file->name, &revs->diffopt); + return; + } + /* * New file in the index: it might actually be different in * the working tree. -@@ diff-lib.c: static void show_new_file(struct rev_info *revs, - diff_index_show_file(revs, "+", new_file, oid, !is_null_oid(oid), mode, dirty_submodule); - } - -+static int show_modified(struct rev_info *revs, -+ const struct cache_entry *old_entry, -+ const struct cache_entry *new_entry, -+ int report_missing, -+ int cached, int match_missing); -+ -+static int compare_within_sparse_dir(int n, unsigned long mask, -+ unsigned long dirmask, struct name_entry *entry, -+ struct traverse_info *info) -+{ -+ struct rev_info *revs = info->data; -+ struct object_id *oid0 = &entry[0].oid; -+ struct object_id *oid1 = &entry[1].oid; -+ -+ if (oideq(oid0, oid1)) -+ return mask; -+ -+ /* Directory/file conflicts are handled earlier. */ -+ if (S_ISDIR(entry[0].mode) && S_ISDIR(entry[1].mode)) { -+ struct tree_desc t[2]; -+ void *buf[2]; -+ struct traverse_info info_r = { NULL, }; -+ -+ info_r.name = xstrfmt("%s%s", info->traverse_path, entry[0].path); -+ info_r.namelen = strlen(info_r.name); -+ info_r.traverse_path = xstrfmt("%s/", info_r.name); -+ info_r.fn = compare_within_sparse_dir; -+ info_r.prev = info; -+ info_r.mode = entry[0].mode; -+ info_r.pathlen = entry[0].pathlen; -+ info_r.df_conflicts = 0; -+ info_r.data = revs; -+ -+ buf[0] = fill_tree_descriptor(revs->repo, &t[0], oid0); -+ buf[1] = fill_tree_descriptor(revs->repo, &t[1], oid1); -+ -+ traverse_trees(NULL, 2, t, &info_r); -+ -+ free((char *)info_r.name); -+ free((char *)info_r.traverse_path); -+ free(buf[0]); -+ free(buf[1]); -+ } else { -+ char *old_path = NULL, *new_path = NULL; -+ struct cache_entry *old_entry = NULL, *new_entry = NULL; -+ -+ if (entry[0].path) { -+ old_path = xstrfmt("%s%s", info->traverse_path, entry[0].path); -+ old_entry = make_transient_cache_entry( -+ entry[0].mode, &entry[0].oid, -+ old_path, /* stage */ 0); -+ old_entry->ce_flags |= CE_SKIP_WORKTREE; -+ } -+ if (entry[1].path) { -+ new_path = xstrfmt("%s%s", info->traverse_path, entry[1].path); -+ new_entry = make_transient_cache_entry( -+ entry[1].mode, &entry[1].oid, -+ new_path, /* stage */ 0); -+ new_entry->ce_flags |= CE_SKIP_WORKTREE; -+ } -+ -+ if (entry[0].path && entry[1].path) -+ show_modified(revs, old_entry, new_entry, 0, 1, 0); -+ else if (entry[0].path) -+ diff_index_show_file(revs, revs->prefix, -+ old_entry, &entry[0].oid, -+ 0, entry[0].mode, 0); -+ else if (entry[1].path) -+ show_new_file(revs, new_entry, 1, 0); -+ -+ discard_cache_entry(old_entry); -+ discard_cache_entry(new_entry); -+ free(old_path); -+ free(new_path); -+ } -+ -+ return mask; -+} -+ -+static void show_modified_sparse_directory(struct rev_info *revs, -+ const struct cache_entry *old_entry, -+ const struct cache_entry *new_entry, -+ int report_missing, -+ int cached, int match_missing) -+{ -+ struct tree_desc t[2]; -+ void *buf[2]; -+ struct traverse_info info = { NULL }; -+ struct strbuf name = STRBUF_INIT; -+ struct strbuf parent_path = STRBUF_INIT; -+ char *last_dir_sep; -+ -+ if (oideq(&old_entry->oid, &new_entry->oid)) -+ return; -+ -+ info.fn = compare_within_sparse_dir; -+ info.prev = &info; -+ -+ strbuf_add(&name, new_entry->name, new_entry->ce_namelen - 1); -+ info.name = name.buf; -+ info.namelen = name.len; -+ -+ strbuf_add(&parent_path, new_entry->name, new_entry->ce_namelen - 1); -+ if ((last_dir_sep = find_last_dir_sep(parent_path.buf)) > parent_path.buf) -+ strbuf_setlen(&parent_path, (last_dir_sep - parent_path.buf) - 1); -+ else -+ strbuf_setlen(&parent_path, 0); -+ -+ info.pathlen = parent_path.len; -+ -+ if (parent_path.len) -+ info.traverse_path = parent_path.buf; -+ else -+ info.traverse_path = ""; -+ -+ info.mode = new_entry->ce_mode; -+ info.df_conflicts = 0; -+ info.data = revs; -+ -+ buf[0] = fill_tree_descriptor(revs->repo, &t[0], &old_entry->oid); -+ buf[1] = fill_tree_descriptor(revs->repo, &t[1], &new_entry->oid); -+ -+ traverse_trees(NULL, 2, t, &info); -+ -+ free(buf[0]); -+ free(buf[1]); -+ strbuf_release(&name); -+ strbuf_release(&parent_path); -+} -+ - static int show_modified(struct rev_info *revs, - const struct cache_entry *old_entry, - const struct cache_entry *new_entry, @@ diff-lib.c: static int show_modified(struct rev_info *revs, - const struct object_id *oid; unsigned dirty_submodule = 0; + struct index_state *istate = revs->diffopt.repo->index; + /* + * If both are sparse directory entries, then expand the @@ diff-lib.c: static int show_modified(struct rev_info *revs, + if (old_entry && new_entry && + S_ISSPARSEDIR(old_entry->ce_mode) && + S_ISSPARSEDIR(new_entry->ce_mode)) { -+ show_modified_sparse_directory(revs, old_entry, new_entry, report_missing, cached, match_missing); ++ diff_tree_oid(&old_entry->oid, &new_entry->oid, new_entry->name, &revs->diffopt); + return 0; + } + - if (get_stat_data(new_entry, &oid, &mode, cached, match_missing, + if (get_stat_data(istate, new_entry, &oid, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) { if (report_missing) 11: 611b9f61fb2 = 13: 35063ffb8ed status: skip sparse-checkout percentage with sparse-index 12: 0c0a765dde8 = 14: b4033a9bf36 status: use sparse-index throughout 13: 02f2c7b6398 ! 15: 717a3f49f97 wt-status: expand added sparse directory entries @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse-index is n + test_sparse_match git reset --mixed HEAD~1 && + test_sparse_match test-tool read-cache --table --expand && + test_sparse_match git status --porcelain=v2 && -+ test_sparse_match git status --porcelain=v2 && + + # At this point, sparse-checkouts behave differently + # from the full-checkout. + test_sparse_match git checkout --orphan new-branch && + test_sparse_match test-tool read-cache --table --expand && -+ test_sparse_match git status --porcelain=v2 && + test_sparse_match git status --porcelain=v2 +' + @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse-index is n + + run_on_all touch deep/deeper1/x && + test_all_match git add . && -+ test_all_match git status --porcelain=v2 && + test_all_match git status --porcelain=v2 +' + @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse-index is n ## wt-status.c ## @@ wt-status.c: static void wt_status_collect_changes_index(struct wt_status *s) - run_diff_index(&rev, 1); + clear_pathspec(&rev.prune_data); } +static int add_file_to_list(const struct object_id *oid, @@ wt-status.c: static void wt_status_collect_changes_index(struct wt_status *s) + struct string_list_item *it; + struct wt_status_change_data *d; + struct wt_status *s = context; -+ char *full_name; ++ struct strbuf full_name = STRBUF_INIT; + + if (S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + -+ full_name = xstrfmt("%s%s", base->buf, path); -+ it = string_list_insert(&s->change, full_name); ++ strbuf_add(&full_name, base->buf, base->len); ++ strbuf_addstr(&full_name, path); ++ it = string_list_insert(&s->change, full_name.buf); + d = it->util; + if (!d) { + CALLOC_ARRAY(d, 1); @@ wt-status.c: static void wt_status_collect_changes_index(struct wt_status *s) + d->mode_index = mode; + oidcpy(&d->oid_index, oid); + s->committable = 1; ++ strbuf_release(&full_name); + return 0; +} + @@ wt-status.c: static void wt_status_collect_changes_initial(struct wt_status *s) + * tree and marking them with DIFF_STATUS_ADDED. + */ + struct strbuf base = STRBUF_INIT; -+ struct pathspec ps; ++ struct pathspec ps = { 0 }; + struct tree *tree = lookup_tree(istate->repo, &ce->oid); + -+ memset(&ps, 0, sizeof(ps)); + ps.recursive = 1; + ps.has_wildcard = 1; + ps.max_depth = -1; 14: 46ca150c354 ! 16: 1d744848ee6 fsmonitor: integrate with sparse index @@ t/t7519-status-fsmonitor.sh: test_expect_success 'status succeeds after staging/ + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ + git status --porcelain=v2 >actual && + test_region $1 index ensure_full_index trace2.txt && ++ test_region fsm_hook query trace2.txt && + test_cmp expect actual && + rm trace2.txt && + git sparse-checkout disable