On 10/12/2019 6:57 PM, Elijah Newren wrote: > On Mon, Oct 7, 2019 at 1:08 PM Derrick Stolee via GitGitGadget > <gitgitgadget@xxxxxxxxx> wrote: >> >> From: Derrick Stolee <dstolee@xxxxxxxxxxxxx> >> >> The sparse-checkout builtin used 'git read-tree -mu HEAD' to update the >> skip-worktree bits in the index and to update the working directory. >> This extra process is overly complex, and prone to failure. It also >> requires that we write our changes to the sparse-checkout file before >> trying to update the index. >> >> Remove this extra process call by creating a direct call to >> unpack_trees() in the same way 'git read-tree -mu HEAD' does. In >> adition, provide an in-memory list of patterns so we can avoid > > s/adition/addition/ > >> reading from the sparse-checkout file. This allows us to test a >> proposed change to the file before writing to it. >> >> Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> >> --- >> builtin/read-tree.c | 2 +- >> builtin/sparse-checkout.c | 85 +++++++++++++++++++++++++----- >> t/t1091-sparse-checkout-builtin.sh | 17 ++++++ >> unpack-trees.c | 5 +- >> unpack-trees.h | 3 +- >> 5 files changed, 95 insertions(+), 17 deletions(-) >> >> diff --git a/builtin/read-tree.c b/builtin/read-tree.c >> index 69963d83dc..d7eeaa26ec 100644 >> --- a/builtin/read-tree.c >> +++ b/builtin/read-tree.c >> @@ -186,7 +186,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) >> >> if (opts.reset || opts.merge || opts.prefix) { >> if (read_cache_unmerged() && (opts.prefix || opts.merge)) >> - die("You need to resolve your current index first"); >> + die(_("You need to resolve your current index first")); > > A good change, but isn't this unrelated to the current commit? It's related because I'm repeating the error in the sparse-checkout builtin, but it should be localized in both places. >> stage = opts.merge = 1; >> } >> resolve_undo_clear(); >> diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c >> index 25786f8bb0..542d57fac6 100644 >> --- a/builtin/sparse-checkout.c >> +++ b/builtin/sparse-checkout.c >> @@ -7,6 +7,11 @@ >> #include "run-command.h" >> #include "strbuf.h" >> #include "string-list.h" >> +#include "cache.h" >> +#include "cache-tree.h" >> +#include "lockfile.h" >> +#include "resolve-undo.h" >> +#include "unpack-trees.h" >> >> static char const * const builtin_sparse_checkout_usage[] = { >> N_("git sparse-checkout [init|list|set|disable] <options>"), >> @@ -60,18 +65,53 @@ static int sparse_checkout_list(int argc, const char **argv) >> return 0; >> } >> >> -static int update_working_directory(void) >> +static int update_working_directory(struct pattern_list *pl) >> { >> - struct argv_array argv = ARGV_ARRAY_INIT; >> int result = 0; >> - argv_array_pushl(&argv, "read-tree", "-m", "-u", "HEAD", NULL); >> + struct unpack_trees_options o; >> + struct lock_file lock_file = LOCK_INIT; >> + struct object_id oid; >> + struct tree *tree; >> + struct tree_desc t; >> + struct repository *r = the_repository; >> >> - if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { >> - error(_("failed to update index with new sparse-checkout paths")); >> - result = 1; >> + if (repo_read_index_unmerged(r)) >> + die(_("You need to resolve your current index first")); > > Well, at least that ensures that the user gets a good error message. > I'm not sure I like the error, because e.g. if a user hits a conflict > while merging in a sparse checkout and wants to return to a non-sparse > checkout because they think other files might help them resolve the > conflicts, then they ought to be able to do it. Basically, unless > they are trying use sparsification to remove entries from the working > directory that differ from the index (and conflicted entries always > differ), then it seems like we should be able to support > sparsification despite the presence of conflicts. > > Your series is long enough, doesn't make this problem any worse (and > appears to make it slightly better), and so you really don't need to > tackle that problem in this series. I'm just stating a gripe with > sparse checkouts again. :-) Absolutely, we should revisit the entire feature and how it handles these conflicts in the best possible ways. As far as I can see, the only way these conflicts arise is if the user creates conflicting files _outside_ their sparse cone and then expand their cone. Finding all the strange cases will require experimentation. > [...] > >> static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path) >> { >> - struct pattern_entry *e = xmalloc(sizeof(struct pattern_entry)); >> + struct pattern_entry *e = xmalloc(sizeof(*e)); > > This is a good fix, but shouldn't it be squashed into the > "sparse-checkout: init and set in cone mode" commit from earlier in > your series? Yeah, I think I mis-applied a few fixups to this commit instead of an earlier one. >> @@ -262,12 +308,21 @@ static int write_patterns_and_update(struct pattern_list *pl) >> { >> char *sparse_filename; >> FILE *fp; >> - >> + int result; >> + > > Trailing whitespace that should be cleaned up. Thanks. Will do. > >> if (!core_apply_sparse_checkout) { >> warning(_("core.sparseCheckout is disabled, so changes to the sparse-checkout file will have no effect")); >> warning(_("run 'git sparse-checkout init' to enable the sparse-checkout feature")); >> } >> >> + result = update_working_directory(pl); >> + >> + if (result) { >> + clear_pattern_list(pl); >> + update_working_directory(NULL); >> + return result; >> + } >> + >> sparse_filename = get_sparse_checkout_filename(); >> fp = fopen(sparse_filename, "w"); >> >> @@ -277,9 +332,11 @@ static int write_patterns_and_update(struct pattern_list *pl) >> write_patterns_to_file(fp, pl); >> >> fclose(fp); >> + >> free(sparse_filename); >> + clear_pattern_list(pl); >> >> - return update_working_directory(); >> + return 0; >> } >> >> static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) >> @@ -330,6 +387,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) >> struct strbuf line = STRBUF_INIT; >> hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); >> hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0); >> + pl.use_cone_patterns = 1; >> >> if (set_opts.use_stdin) { >> while (!strbuf_getline(&line, stdin)) >> @@ -375,7 +433,8 @@ static int sparse_checkout_disable(int argc, const char **argv) >> fprintf(fp, "/*\n"); >> fclose(fp); >> >> - if (update_working_directory()) >> + core_apply_sparse_checkout = 1; >> + if (update_working_directory(NULL)) >> die(_("error while refreshing working directory")); >> >> unlink(sparse_filename); >> diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh >> index ee4d361787..82eb5fb2f8 100755 >> --- a/t/t1091-sparse-checkout-builtin.sh >> +++ b/t/t1091-sparse-checkout-builtin.sh >> @@ -199,11 +199,13 @@ test_expect_success 'cone mode: init and set' ' >> a >> deep >> EOF >> + test_cmp dir expect && >> ls repo/deep >dir && >> cat >expect <<-EOF && >> a >> deeper1 >> EOF >> + test_cmp dir expect && >> ls repo/deep/deeper1 >dir && >> cat >expect <<-EOF && >> a >> @@ -245,4 +247,19 @@ test_expect_success 'cone mode: set with nested folders' ' >> test_cmp repo/.git/info/sparse-checkout expect >> ' >> >> +test_expect_success 'revert to old sparse-checkout on bad update' ' >> + echo update >repo/deep/deeper2/a && >> + cp repo/.git/info/sparse-checkout expect && >> + test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err && >> + test_i18ngrep "Cannot update sparse checkout" err && >> + test_cmp repo/.git/info/sparse-checkout expect && >> + ls repo/deep >dir && >> + cat >expect <<-EOF && >> + a >> + deeper1 >> + deeper2 >> + EOF >> + test_cmp dir expect >> +' >> + >> test_done >> diff --git a/unpack-trees.c b/unpack-trees.c >> index edf0fb4673..f0fee5adf2 100644 >> --- a/unpack-trees.c >> +++ b/unpack-trees.c >> @@ -1508,7 +1508,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options >> memset(&pl, 0, sizeof(pl)); >> if (!core_apply_sparse_checkout || !o->update) >> o->skip_sparse_checkout = 1; >> - if (!o->skip_sparse_checkout) { >> + if (!o->skip_sparse_checkout && !o->pl) { >> char *sparse = git_pathdup("info/sparse-checkout"); >> pl.use_cone_patterns = core_sparse_checkout_cone; >> if (add_patterns_from_file_to_list(sparse, "", 0, &pl, NULL) < 0) >> @@ -1681,7 +1681,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options >> >> done: >> trace_performance_leave("unpack_trees"); >> - clear_pattern_list(&pl); >> + if (!o->keep_pattern_list) >> + clear_pattern_list(&pl); >> return ret; >> >> return_failed: >> diff --git a/unpack-trees.h b/unpack-trees.h >> index f2eee0c7c5..ca94a421a5 100644 >> --- a/unpack-trees.h >> +++ b/unpack-trees.h >> @@ -59,7 +59,8 @@ struct unpack_trees_options { >> quiet, >> exiting_early, >> show_all_errors, >> - dry_run; >> + dry_run, >> + keep_pattern_list; >> const char *prefix; >> int cache_bottom; >> struct dir_struct *dir; >> -- > > The rest looks reasonable. >