Skip to content

Commit

Permalink
sparse-checkout: update working directory in-process
Browse files Browse the repository at this point in the history
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
addition, provide an in-memory list of patterns so we can avoid
reading from the sparse-checkout file. This allows us to test a
proposed change to the file before writing to it.

An earlier version of this patch included a bug when the 'set' command
failed due to the "Sparse checkout leaves no entry on working directory"
error. It would not rollback the index.lock file, so the replay of the
old sparse-checkout specification would fail. A test in t1091 now
covers that scenario.

Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
derrickstolee authored and gitster committed Nov 22, 2019
1 parent e9de487 commit e091228
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 16 deletions.
2 changes: 1 addition & 1 deletion builtin/read-tree.c
Expand Up @@ -185,7 +185,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"));
stage = opts.merge = 1;
}
resolve_undo_clear();
Expand Down
83 changes: 71 additions & 12 deletions builtin/sparse-checkout.c
Expand Up @@ -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>"),
Expand Down Expand Up @@ -60,18 +65,54 @@ 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 patterns"));
result = 1;
}
if (repo_read_index_unmerged(r))
die(_("you need to resolve your current index first"));

if (get_oid("HEAD", &oid))
return 0;

tree = parse_tree_indirect(&oid);
parse_tree(tree);
init_tree_desc(&t, tree->buffer, tree->size);

memset(&o, 0, sizeof(o));
o.verbose_update = isatty(2);
o.merge = 1;
o.update = 1;
o.fn = oneway_merge;
o.head_idx = -1;
o.src_index = r->index;
o.dst_index = r->index;
o.skip_sparse_checkout = 0;
o.pl = pl;
o.keep_pattern_list = !!pl;

resolve_undo_clear_index(r->index);
setup_work_tree();

cache_tree_free(&r->index->cache_tree);

repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR);

core_apply_sparse_checkout = 1;
result = unpack_trees(1, &t, &o);

if (!result) {
prime_cache_tree(r, r->index, tree);
write_locked_index(r->index, &lock_file, COMMIT_LOCK);
} else
rollback_lock_file(&lock_file);

argv_array_clear(&argv);
return result;
}

Expand Down Expand Up @@ -129,6 +170,15 @@ static int write_patterns_and_update(struct pattern_list *pl)
{
char *sparse_filename;
FILE *fp;
int result;

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");
Expand All @@ -139,9 +189,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;
}

enum sparse_checkout_mode {
Expand Down Expand Up @@ -199,7 +251,11 @@ static int sparse_checkout_init(int argc, const char **argv)
builtin_sparse_checkout_init_options,
builtin_sparse_checkout_init_usage, 0);

mode = init_opts.cone_mode ? MODE_CONE_PATTERNS : MODE_ALL_PATTERNS;
if (init_opts.cone_mode) {
mode = MODE_CONE_PATTERNS;
core_sparse_checkout_cone = 1;
} else
mode = MODE_ALL_PATTERNS;

if (set_config(mode))
return 1;
Expand Down Expand Up @@ -230,7 +286,8 @@ static int sparse_checkout_init(int argc, const char **argv)
}

reset_dir:
return update_working_directory();
core_apply_sparse_checkout = 1;
return update_working_directory(NULL);
}

static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
Expand Down Expand Up @@ -311,6 +368,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)

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))
Expand Down Expand Up @@ -365,7 +423,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);
Expand Down
28 changes: 28 additions & 0 deletions t/t1091-sparse-checkout-builtin.sh
Expand Up @@ -248,4 +248,32 @@ 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_expect_success 'revert to old sparse-checkout on empty update' '
git init empty-test &&
(
echo >file &&
git add file &&
git commit -m "test" &&
test_must_fail git sparse-checkout set nothing 2>err &&
test_i18ngrep "Sparse checkout leaves no entry on working directory" err &&
test_i18ngrep ! ".git/index.lock" err &&
git sparse-checkout set file
)
'

test_done
5 changes: 3 additions & 2 deletions unpack-trees.c
Expand Up @@ -1511,7 +1511,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)
Expand Down Expand Up @@ -1684,7 +1684,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:
Expand Down
3 changes: 2 additions & 1 deletion unpack-trees.h
Expand Up @@ -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;
Expand Down

0 comments on commit e091228

Please sign in to comment.