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:
commit
48eee46d6a
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
'
|
||||
|
@ -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 &&
|
||||
|
@ -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
|
||||
|
257
unpack-trees.c
257
unpack-trees.c
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user