Merge branch 'en/sparse-checkout'

"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
This commit is contained in:
Junio C Hamano 2020-04-29 16:15:30 -07:00
commit 48eee46d6a
7 changed files with 375 additions and 111 deletions

View File

@ -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

View File

@ -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
};
@ -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);
setup_unpack_trees_porcelain(&o, "sparse-checkout");
result = update_sparsity(&o);
clear_unpack_trees_porcelain(&o);
if (!result) {
prime_cache_tree(r, r->index, tree);
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;
@ -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,
@ -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,
@ -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);
@ -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);
}

View File

@ -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
'

View File

@ -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' '
@ -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 &&

View File

@ -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

View File

@ -24,7 +24,7 @@
* situation better. See how "git checkout" and "git merge" replaces
* them using setup_unpack_trees_porcelain(), for example.
*/
static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
static const char *unpack_plumbing_errors[NB_UNPACK_TREES_WARNING_TYPES] = {
/* ERROR_WOULD_OVERWRITE */
"Entry '%s' would be overwritten by merge. Cannot merge.",
@ -43,17 +43,20 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
/* ERROR_BIND_OVERLAP */
"Entry '%s' overlaps with '%s'. Cannot bind.",
/* ERROR_SPARSE_NOT_UPTODATE_FILE */
"Entry '%s' not uptodate. Cannot update sparse checkout.",
/* ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN */
"Working tree file '%s' would be overwritten by sparse checkout update.",
/* ERROR_WOULD_LOSE_ORPHANED_REMOVED */
"Working tree file '%s' would be removed by sparse checkout update.",
/* ERROR_WOULD_LOSE_SUBMODULE */
"Submodule '%s' cannot checkout new HEAD.",
/* NB_UNPACK_TREES_ERROR_TYPES; just a meta value */
"",
/* WARNING_SPARSE_NOT_UPTODATE_FILE */
"Path '%s' not uptodate; will not remove from working tree.",
/* WARNING_SPARSE_UNMERGED_FILE */
"Path '%s' unmerged; will not remove from working tree.",
/* WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN */
"Path '%s' already present; will not overwrite with sparse update.",
};
#define ERRORMSG(o,type) \
@ -168,15 +171,16 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
*/
msgs[ERROR_BIND_OVERLAP] = _("Entry '%s' overlaps with '%s'. Cannot bind.");
msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
_("Cannot update sparse checkout: the following entries are not up to date:\n%s");
msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
_("The following working tree files would be overwritten by sparse checkout update:\n%s");
msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
_("The following working tree files would be removed by sparse checkout update:\n%s");
msgs[ERROR_WOULD_LOSE_SUBMODULE] =
_("Cannot update submodule:\n%s");
msgs[WARNING_SPARSE_NOT_UPTODATE_FILE] =
_("The following paths are not up to date and were left despite sparse patterns:\n%s");
msgs[WARNING_SPARSE_UNMERGED_FILE] =
_("The following paths are unmerged and were left despite sparse patterns:\n%s");
msgs[WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN] =
_("The following paths were already present and thus not updated despite sparse patterns:\n%s");
opts->show_all_errors = 1;
/* rejected paths may not have a static buffer */
for (i = 0; i < ARRAY_SIZE(opts->unpack_rejects); i++)
@ -226,7 +230,7 @@ static int add_rejected_path(struct unpack_trees_options *o,
/*
* Otherwise, insert in a list for future display by
* display_error_msgs()
* display_(error|warning)_msgs()
*/
string_list_append(&o->unpack_rejects[e], path);
return -1;
@ -237,13 +241,16 @@ static int add_rejected_path(struct unpack_trees_options *o,
*/
static void display_error_msgs(struct unpack_trees_options *o)
{
int e, i;
int something_displayed = 0;
int e;
unsigned error_displayed = 0;
for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
struct string_list *rejects = &o->unpack_rejects[e];
if (rejects->nr > 0) {
int i;
struct strbuf path = STRBUF_INIT;
something_displayed = 1;
error_displayed = 1;
for (i = 0; i < rejects->nr; i++)
strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
error(ERRORMSG(o, e), super_prefixed(path.buf));
@ -251,10 +258,36 @@ static void display_error_msgs(struct unpack_trees_options *o)
}
string_list_clear(rejects, 0);
}
if (something_displayed)
if (error_displayed)
fprintf(stderr, _("Aborting\n"));
}
/*
* display all the warning messages stored in a nice way
*/
static void display_warning_msgs(struct unpack_trees_options *o)
{
int e;
unsigned warning_displayed = 0;
for (e = NB_UNPACK_TREES_ERROR_TYPES + 1;
e < NB_UNPACK_TREES_WARNING_TYPES; e++) {
struct string_list *rejects = &o->unpack_rejects[e];
if (rejects->nr > 0) {
int i;
struct strbuf path = STRBUF_INIT;
warning_displayed = 1;
for (i = 0; i < rejects->nr; i++)
strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
warning(ERRORMSG(o, e), super_prefixed(path.buf));
strbuf_release(&path);
}
string_list_clear(rejects, 0);
}
if (warning_displayed)
fprintf(stderr, _("After fixing the above paths, you may want to run `git sparse-checkout reapply`.\n"));
}
static int check_submodule_move_head(const struct cache_entry *ce,
const char *old_id,
const char *new_id,
@ -357,12 +390,12 @@ static void report_collided_checkout(struct index_state *index)
string_list_clear(&list, 0);
}
static int check_updates(struct unpack_trees_options *o)
static int check_updates(struct unpack_trees_options *o,
struct index_state *index)
{
unsigned cnt = 0;
int errs = 0;
struct progress *progress;
struct index_state *index = &o->result;
struct checkout state = CHECKOUT_INIT;
int i;
@ -504,19 +537,39 @@ static int apply_sparse_checkout(struct index_state *istate,
* also stat info may have lost after merged_entry() so calling
* verify_uptodate() again may fail
*/
if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
if (!(ce->ce_flags & CE_UPDATE) &&
verify_uptodate_sparse(ce, o)) {
ce->ce_flags &= ~CE_SKIP_WORKTREE;
return -1;
}
ce->ce_flags |= CE_WT_REMOVE;
ce->ce_flags &= ~CE_UPDATE;
}
if (was_skip_worktree && !ce_skip_worktree(ce)) {
if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
if (verify_absent_sparse(ce, WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN, o))
return -1;
ce->ce_flags |= CE_UPDATE;
}
return 0;
}
static int warn_conflicted_path(struct index_state *istate,
int i,
struct unpack_trees_options *o)
{
char *conflicting_path = istate->cache[i]->name;
int count = 0;
add_rejected_path(o, WARNING_SPARSE_UNMERGED_FILE, conflicting_path);
/* Find out how many higher stage entries at same path */
while (++count < istate->cache_nr &&
!strcmp(conflicting_path,
istate->cache[i+count]->name))
/* do nothing */;
return count;
}
static inline int call_unpack_fn(const struct cache_entry * const *src,
struct unpack_trees_options *o)
{
@ -1493,6 +1546,20 @@ static void mark_new_skip_worktree(struct pattern_list *pl,
clear_ce_flags(istate, select_flag, skip_wt_flag, pl, show_progress);
}
static void populate_from_existing_patterns(struct unpack_trees_options *o,
struct pattern_list *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)
o->skip_sparse_checkout = 1;
else
o->pl = pl;
free(sparse);
}
static int verify_absent(const struct cache_entry *,
enum unpack_trees_error_types,
struct unpack_trees_options *);
@ -1507,22 +1574,18 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
int i, ret;
static struct cache_entry *dfc;
struct pattern_list pl;
int free_pattern_list = 0;
if (len > MAX_UNPACK_TREES)
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
trace_performance_enter();
memset(&pl, 0, sizeof(pl));
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
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)
o->skip_sparse_checkout = 1;
else
o->pl = &pl;
free(sparse);
memset(&pl, 0, sizeof(pl));
free_pattern_list = 1;
populate_from_existing_patterns(o, &pl);
}
memset(&o->result, 0, sizeof(o->result));
@ -1618,7 +1681,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
/*
* Sparse checkout loop #2: set NEW_SKIP_WORKTREE on entries not in loop #1
* If the will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
* If they will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
* so apply_sparse_checkout() won't attempt to remove it from worktree
*/
mark_new_skip_worktree(o->pl, &o->result,
@ -1638,23 +1701,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
* correct CE_NEW_SKIP_WORKTREE
*/
if (ce->ce_flags & CE_ADDED &&
verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
if (!o->show_all_errors)
goto return_failed;
ret = -1;
}
verify_absent(ce, WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN, o))
ret = 1;
if (apply_sparse_checkout(&o->result, ce, o))
ret = 1;
if (apply_sparse_checkout(&o->result, ce, o)) {
if (!o->show_all_errors)
goto return_failed;
ret = -1;
}
if (!ce_skip_worktree(ce))
empty_worktree = 0;
}
if (ret < 0)
goto return_failed;
/*
* Sparse checkout is meant to narrow down checkout area
* but it does not make sense to narrow down to empty working
@ -1665,9 +1720,18 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
goto done;
}
if (ret == 1) {
/*
* Inability to sparsify or de-sparsify individual
* paths is not an error, but just a warning.
*/
if (o->show_all_errors)
display_warning_msgs(o);
ret = 0;
}
}
ret = check_updates(o) ? (-2) : 0;
ret = check_updates(o, &o->result) ? (-2) : 0;
if (o->dst_index) {
move_index_extensions(&o->result, o->src_index);
if (!ret) {
@ -1690,9 +1754,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->src_index = NULL;
done:
trace_performance_leave("unpack_trees");
if (!o->keep_pattern_list)
if (free_pattern_list)
clear_pattern_list(&pl);
trace_performance_leave("unpack_trees");
return ret;
return_failed:
@ -1705,6 +1769,91 @@ return_failed:
goto done;
}
/*
* Update SKIP_WORKTREE bits according to sparsity patterns, and update
* working directory to match.
*
* CE_NEW_SKIP_WORKTREE is used internally.
*/
enum update_sparsity_result update_sparsity(struct unpack_trees_options *o)
{
enum update_sparsity_result ret = UPDATE_SPARSITY_SUCCESS;
struct pattern_list pl;
int i, empty_worktree;
unsigned old_show_all_errors;
int free_pattern_list = 0;
old_show_all_errors = o->show_all_errors;
o->show_all_errors = 1;
/* Sanity checks */
if (!o->update || o->index_only || o->skip_sparse_checkout)
BUG("update_sparsity() is for reflecting sparsity patterns in working directory");
if (o->src_index != o->dst_index || o->fn)
BUG("update_sparsity() called wrong");
trace_performance_enter();
/* If we weren't given patterns, use the recorded ones */
if (!o->pl) {
memset(&pl, 0, sizeof(pl));
free_pattern_list = 1;
populate_from_existing_patterns(o, &pl);
if (o->skip_sparse_checkout)
goto skip_sparse_checkout;
}
/* Set NEW_SKIP_WORKTREE on existing entries. */
mark_all_ce_unused(o->src_index);
mark_new_skip_worktree(o->pl, o->src_index, 0,
CE_NEW_SKIP_WORKTREE, o->verbose_update);
/* Then loop over entries and update/remove as needed */
ret = UPDATE_SPARSITY_SUCCESS;
empty_worktree = 1;
for (i = 0; i < o->src_index->cache_nr; i++) {
struct cache_entry *ce = o->src_index->cache[i];
if (ce_stage(ce)) {
/* -1 because for loop will increment by 1 */
i += warn_conflicted_path(o->src_index, i, o) - 1;
ret = UPDATE_SPARSITY_WARNINGS;
continue;
}
if (apply_sparse_checkout(o->src_index, ce, o))
ret = UPDATE_SPARSITY_WARNINGS;
if (!ce_skip_worktree(ce))
empty_worktree = 0;
}
/*
* Sparse checkout is meant to narrow down checkout area
* but it does not make sense to narrow down to empty working
* tree. This is usually a mistake in sparse checkout rules.
* Do not allow users to do that.
*/
if (o->src_index->cache_nr && empty_worktree) {
unpack_failed(o, "Sparse checkout leaves no entry on working directory");
ret = UPDATE_SPARSITY_INDEX_UPDATE_FAILURES;
goto done;
}
skip_sparse_checkout:
if (check_updates(o, o->src_index))
ret = UPDATE_SPARSITY_WORKTREE_UPDATE_FAILURES;
done:
display_warning_msgs(o);
o->show_all_errors = old_show_all_errors;
if (free_pattern_list)
clear_pattern_list(&pl);
trace_performance_leave("update_sparsity");
return ret;
}
/* Here come the merge functions */
static int reject_merge(const struct cache_entry *ce,
@ -1789,7 +1938,7 @@ int verify_uptodate(const struct cache_entry *ce,
static int verify_uptodate_sparse(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
return verify_uptodate_1(ce, o, WARNING_SPARSE_NOT_UPTODATE_FILE);
}
/*
@ -2027,11 +2176,7 @@ static int verify_absent_sparse(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
enum unpack_trees_error_types orphaned_error = error_type;
if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN;
return verify_absent_1(ce, orphaned_error, o);
return verify_absent_1(ce, error_type, o);
}
static int merged_entry(const struct cache_entry *ce,

View File

@ -22,11 +22,15 @@ enum unpack_trees_error_types {
ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN,
ERROR_WOULD_LOSE_UNTRACKED_REMOVED,
ERROR_BIND_OVERLAP,
ERROR_SPARSE_NOT_UPTODATE_FILE,
ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN,
ERROR_WOULD_LOSE_ORPHANED_REMOVED,
ERROR_WOULD_LOSE_SUBMODULE,
NB_UNPACK_TREES_ERROR_TYPES
NB_UNPACK_TREES_ERROR_TYPES,
WARNING_SPARSE_NOT_UPTODATE_FILE,
WARNING_SPARSE_UNMERGED_FILE,
WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN,
NB_UNPACK_TREES_WARNING_TYPES,
};
/*
@ -59,20 +63,19 @@ struct unpack_trees_options {
quiet,
exiting_early,
show_all_errors,
dry_run,
keep_pattern_list;
dry_run;
const char *prefix;
int cache_bottom;
struct dir_struct *dir;
struct pathspec *pathspec;
merge_fn_t fn;
const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
const char *msgs[NB_UNPACK_TREES_WARNING_TYPES];
struct argv_array msgs_to_free;
/*
* Store error messages in an array, each case
* corresponding to a error message type
*/
struct string_list unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES];
struct string_list unpack_rejects[NB_UNPACK_TREES_WARNING_TYPES];
int head_idx;
int merge_size;
@ -91,6 +94,15 @@ struct unpack_trees_options {
int unpack_trees(unsigned n, struct tree_desc *t,
struct unpack_trees_options *options);
enum update_sparsity_result {
UPDATE_SPARSITY_SUCCESS = 0,
UPDATE_SPARSITY_WARNINGS = 1,
UPDATE_SPARSITY_INDEX_UPDATE_FAILURES = -1,
UPDATE_SPARSITY_WORKTREE_UPDATE_FAILURES = -2
};
enum update_sparsity_result update_sparsity(struct unpack_trees_options *options);
int verify_uptodate(const struct cache_entry *ce,
struct unpack_trees_options *o);