Skip to content

Commit

Permalink
Merge branch 'en/sparse-checkout'
Browse files Browse the repository at this point in the history
"sparse-checkout" UI improvements.

* en/sparse-checkout:
  sparse-checkout: provide a new reapply subcommand
  unpack-trees: failure to set SKIP_WORKTREE bits always just a warning
  unpack-trees: provide warnings on sparse updates for unmerged paths too
  unpack-trees: make sparse path messages sound like warnings
  unpack-trees: split display_error_msgs() into two
  unpack-trees: rename ERROR_* fields meant for warnings to WARNING_*
  unpack-trees: move ERROR_WOULD_LOSE_SUBMODULE earlier
  sparse-checkout: use improved unpack_trees porcelain messages
  sparse-checkout: use new update_sparsity() function
  unpack-trees: add a new update_sparsity() function
  unpack-trees: pull sparse-checkout pattern reading into a new function
  unpack-trees: do not mark a dirty path with SKIP_WORKTREE
  unpack-trees: allow check_updates() to work on a different index
  t1091: make some tests a little more defensive against failures
  unpack-trees: simplify pattern_list freeing
  unpack-trees: simplify verify_absent_sparse()
  unpack-trees: remove unused error type
  unpack-trees: fix minor typo in comment
  • Loading branch information
gitster committed Apr 29, 2020
2 parents 8cb514d + 5644ca2 commit 48eee46
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 111 deletions.
10 changes: 10 additions & 0 deletions Documentation/git-sparse-checkout.txt
Expand Up @@ -70,6 +70,16 @@ C-style quoted strings.
`core.sparseCheckoutCone` is enabled, the given patterns are interpreted
as directory names as in the 'set' subcommand.

'reapply::
Reapply the sparsity pattern rules to paths in the working tree.
Commands like merge or rebase can materialize paths to do their
work (e.g. in order to show you a conflict), and other
sparse-checkout commands might fail to sparsify an individual file
(e.g. because it has unstaged changes or conflicts). In such
cases, it can make sense to run `git sparse-checkout reapply` later
after cleaning up affected paths (e.g. resolving conflicts, undoing
or committing changes, etc.).

'disable'::
Disable the `core.sparseCheckout` config setting, and restore the
working directory to include all files. Leaves the sparse-checkout
Expand Down
55 changes: 22 additions & 33 deletions builtin/sparse-checkout.c
Expand Up @@ -18,7 +18,7 @@
static const char *empty_base = "";

static char const * const builtin_sparse_checkout_usage[] = {
N_("git sparse-checkout (init|list|set|add|disable) <options>"),
N_("git sparse-checkout (init|list|set|add|reapply|disable) <options>"),
NULL
};

Expand Down Expand Up @@ -94,50 +94,37 @@ static int sparse_checkout_list(int argc, const char **argv)

static int update_working_directory(struct pattern_list *pl)
{
int result = 0;
enum update_sparsity_result result;
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 (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);
setup_unpack_trees_porcelain(&o, "sparse-checkout");
result = update_sparsity(&o);
clear_unpack_trees_porcelain(&o);

if (result == UPDATE_SPARSITY_WARNINGS)
/*
* We don't do any special handling of warnings from untracked
* files in the way or dirty entries that can't be removed.
*/
result = UPDATE_SPARSITY_SUCCESS;
if (result == UPDATE_SPARSITY_SUCCESS)
write_locked_index(r->index, &lock_file, COMMIT_LOCK);
} else
else
rollback_lock_file(&lock_file);

return result;
Expand Down Expand Up @@ -304,8 +291,6 @@ static int sparse_checkout_init(int argc, const char **argv)
};

repo_read_index(the_repository);
require_clean_work_tree(the_repository,
N_("initialize sparse-checkout"), NULL, 1, 0);

argc = parse_options(argc, argv, NULL,
builtin_sparse_checkout_init_options,
Expand Down Expand Up @@ -560,8 +545,6 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
};

repo_read_index(the_repository);
require_clean_work_tree(the_repository,
N_("set sparse-checkout patterns"), NULL, 1, 0);

argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_set_options,
Expand All @@ -571,14 +554,18 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
return modify_pattern_list(argc, argv, m);
}

static int sparse_checkout_reapply(int argc, const char **argv)
{
repo_read_index(the_repository);
return update_working_directory(NULL);
}

static int sparse_checkout_disable(int argc, const char **argv)
{
struct pattern_list pl;
struct strbuf match_all = STRBUF_INIT;

repo_read_index(the_repository);
require_clean_work_tree(the_repository,
N_("disable sparse-checkout"), NULL, 1, 0);

memset(&pl, 0, sizeof(pl));
hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
Expand Down Expand Up @@ -622,6 +609,8 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
return sparse_checkout_set(argc, argv, prefix, REPLACE);
if (!strcmp(argv[0], "add"))
return sparse_checkout_set(argc, argv, prefix, ADD);
if (!strcmp(argv[0], "reapply"))
return sparse_checkout_reapply(argc, argv);
if (!strcmp(argv[0], "disable"))
return sparse_checkout_disable(argc, argv);
}
Expand Down
11 changes: 6 additions & 5 deletions t/t1011-read-tree-sparse-checkout.sh
Expand Up @@ -233,18 +233,19 @@ test_expect_success 'read-tree --reset removes outside worktree' '
test_must_be_empty result
'

test_expect_success 'print errors when failed to update worktree' '
test_expect_success 'print warnings when some worktree updates disabled' '
echo sub >.git/info/sparse-checkout &&
git checkout -f init &&
mkdir sub &&
touch sub/added sub/addedtoo &&
test_must_fail git checkout top 2>actual &&
# Use -q to suppress "Previous HEAD position" and "Head is now at" msgs
git checkout -q top 2>actual &&
cat >expected <<\EOF &&
error: The following untracked working tree files would be overwritten by checkout:
warning: The following paths were already present and thus not updated despite sparse patterns:
sub/added
sub/addedtoo
Please move or remove them before you switch branches.
Aborting
After fixing the above paths, you may want to run `git sparse-checkout reapply`.
EOF
test_i18ncmp expected actual
'
Expand Down
105 changes: 95 additions & 10 deletions t/t1091-sparse-checkout-builtin.sh
Expand Up @@ -277,15 +277,23 @@ test_expect_success 'cone mode: add parent path' '
check_files repo a deep folder1
'

test_expect_success 'revert to old sparse-checkout on bad update' '
test_expect_success 'not-up-to-date does not block rest of sparsification' '
test_when_finished git -C repo sparse-checkout disable &&
test_when_finished git -C repo reset --hard &&
git -C repo sparse-checkout set deep &&
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 set sparse-checkout patterns" err &&
test_cmp repo/.git/info/sparse-checkout expect &&
check_files repo/deep a deeper1 deeper2
test_write_lines "!/deep/*/" "/deep/deeper1/" >>expect &&
git -C repo sparse-checkout set deep/deeper1 2>err &&
test_i18ngrep "The following paths are not up to date" err &&
test_cmp expect repo/.git/info/sparse-checkout &&
check_files repo/deep a deeper1 deeper2 &&
check_files repo/deep/deeper1 a deepest &&
check_files repo/deep/deeper1/deepest a &&
check_files repo/deep/deeper2 a
'

test_expect_success 'revert to old sparse-checkout on empty update' '
Expand Down Expand Up @@ -315,19 +323,96 @@ test_expect_success '.gitignore should not warn about cone mode' '
test_i18ngrep ! "disabling cone patterns" err
'

test_expect_success 'sparse-checkout (init|set|disable) fails with dirty status' '
test_expect_success 'sparse-checkout (init|set|disable) warns with dirty status' '
git clone repo dirty &&
echo dirty >dirty/folder1/a &&
test_must_fail git -C dirty sparse-checkout init &&
test_must_fail git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
test_must_fail git -C dirty sparse-checkout disable &&
git -C dirty sparse-checkout init 2>err &&
test_i18ngrep "warning.*The following paths are not up to date" err &&
git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
test_i18ngrep "warning.*The following paths are not up to date" err &&
test_path_is_file dirty/folder1/a &&
git -C dirty sparse-checkout disable 2>err &&
test_must_be_empty err &&
git -C dirty reset --hard &&
git -C dirty sparse-checkout init &&
git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
git -C dirty sparse-checkout disable
test_path_is_missing dirty/folder1/a &&
git -C dirty sparse-checkout disable &&
test_path_is_file dirty/folder1/a
'

test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged status' '
git clone repo unmerged &&
cat >input <<-EOF &&
0 0000000000000000000000000000000000000000 folder1/a
100644 $(git -C unmerged rev-parse HEAD:folder1/a) 1 folder1/a
EOF
git -C unmerged update-index --index-info <input &&
git -C unmerged sparse-checkout init 2>err &&
test_i18ngrep "warning.*The following paths are unmerged" err &&
git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
test_i18ngrep "warning.*The following paths are unmerged" err &&
test_path_is_file dirty/folder1/a &&
git -C unmerged sparse-checkout disable 2>err &&
test_i18ngrep "warning.*The following paths are unmerged" err &&
git -C unmerged reset --hard &&
git -C unmerged sparse-checkout init &&
git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* &&
git -C unmerged sparse-checkout disable
'

test_expect_success 'sparse-checkout reapply' '
git clone repo tweak &&
echo dirty >tweak/deep/deeper2/a &&
cat >input <<-EOF &&
0 0000000000000000000000000000000000000000 folder1/a
100644 $(git -C tweak rev-parse HEAD:folder1/a) 1 folder1/a
EOF
git -C tweak update-index --index-info <input &&
git -C tweak sparse-checkout init --cone 2>err &&
test_i18ngrep "warning.*The following paths are not up to date" err &&
test_i18ngrep "warning.*The following paths are unmerged" err &&
git -C tweak sparse-checkout set folder2 deep/deeper1 2>err &&
test_i18ngrep "warning.*The following paths are not up to date" err &&
test_i18ngrep "warning.*The following paths are unmerged" err &&
git -C tweak sparse-checkout reapply 2>err &&
test_i18ngrep "warning.*The following paths are not up to date" err &&
test_path_is_file tweak/deep/deeper2/a &&
test_i18ngrep "warning.*The following paths are unmerged" err &&
test_path_is_file tweak/folder1/a &&
git -C tweak checkout HEAD deep/deeper2/a &&
git -C tweak sparse-checkout reapply 2>err &&
test_i18ngrep ! "warning.*The following paths are not up to date" err &&
test_path_is_missing tweak/deep/deeper2/a &&
test_i18ngrep "warning.*The following paths are unmerged" err &&
test_path_is_file tweak/folder1/a &&
git -C tweak add folder1/a &&
git -C tweak sparse-checkout reapply 2>err &&
test_must_be_empty err &&
test_path_is_missing tweak/deep/deeper2/a &&
test_path_is_missing tweak/folder1/a &&
git -C tweak sparse-checkout disable
'

test_expect_success 'cone mode: set with core.ignoreCase=true' '
rm repo/.git/info/sparse-checkout &&
git -C repo sparse-checkout init --cone &&
git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
cat >expect <<-\EOF &&
Expand Down
22 changes: 22 additions & 0 deletions t/t2018-checkout-branch.sh
Expand Up @@ -238,4 +238,26 @@ test_expect_success 'checkout -b after clone --no-checkout does a checkout of HE
test_path_is_file dest/a.t
'

test_expect_success 'checkout -b to a new branch preserves mergeable changes despite sparse-checkout' '
test_when_finished "
git reset --hard &&
git checkout branch1-scratch &&
test_might_fail git branch -D branch3 &&
git config core.sparseCheckout false &&
rm .git/info/sparse-checkout" &&
test_commit file2 &&
echo stuff >>file1 &&
echo file2 >.git/info/sparse-checkout &&
git config core.sparseCheckout true &&
CURHEAD=$(git rev-parse HEAD) &&
do_checkout branch3 $CURHEAD &&
echo file1 >expect &&
git diff --name-only >actual &&
test_cmp expect actual
'

test_done

0 comments on commit 48eee46

Please sign in to comment.