From: Derrick Stolee <dstolee@xxxxxxxxxxxxx> Users can get into strange situations if 'git mv' allows moving files into, out of, or around the sparse-checkout cone. However, some users may still want to do it. Allow knowledgeable users to do so via a new '--sparse' option. There are some special cases that occur in this change, such as the case of a directory that doesn't match the sparse-checkout cone, but exists in the working tree because a subset of its contents do match. We need to communicate that index entries with the SKIP_WORKTREE bit are not expected to be in the working directory and hence are not needed when moving the contents. This is only a check for the existence of the source file. The call to rename_cache_entry_at() still changes the index appropriately in these cases. Signed-off-by: Derrick Stolee <dstolee@xxxxxxxxxxxxx> --- builtin/mv.c | 24 ++++++++++++++---------- t/t7002-mv-sparse-checkout.sh | 34 ++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/builtin/mv.c b/builtin/mv.c index b58fd4ce5ba..92ea9f0ca37 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -118,17 +118,18 @@ static int index_range_of_same_dir(const char *src, int length, int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; - int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; + int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), OPT__DRY_RUN(&show_only, N_("dry run")), OPT__FORCE(&force, N_("force move/rename even if target exists"), PARSE_OPT_NOCOMPLETE), OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")), + OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; + enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; struct lock_file lock_file = LOCK_INIT; @@ -182,11 +183,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (show_only) printf(_("Checking rename of '%s' to '%s'\n"), src, dst); - if (!path_in_sparse_checkout(src, &the_index)) { + if (!ignore_sparse && !path_in_sparse_checkout(src, &the_index)) { string_list_append(&only_match_skip_worktree, src); skip_sparse = 1; } - if (!path_in_sparse_checkout(dst, &the_index)) { + if (!ignore_sparse && !path_in_sparse_checkout(dst, &the_index)) { string_list_append(&only_match_skip_worktree, dst); skip_sparse = 1; } @@ -194,9 +195,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix) continue; length = strlen(src); - if (lstat(src, &st) < 0) - bad = _("bad source"); - else if (!strncmp(src, dst, length) && + if (lstat(src, &st) < 0) { + /* only error if existence is expected. */ + if (modes[i] != SPARSE) + bad = _("bad source"); + } else if (!strncmp(src, dst, length) && (dst[length] == 0 || dst[length] == '/')) { bad = _("can not move directory into itself"); } else if ((src_is_dir = S_ISDIR(st.st_mode)) @@ -225,11 +228,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dst_len = strlen(dst); for (j = 0; j < last - first; j++) { - const char *path = active_cache[first + j]->name; + const struct cache_entry *ce = active_cache[first + j]; + const char *path = ce->name; source[argc + j] = path; destination[argc + j] = prefix_path(dst, dst_len, path + length + 1); - modes[argc + j] = INDEX; + modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX; submodule_gitfile[argc + j] = NULL; } argc += last - first; @@ -293,7 +297,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) printf(_("Renaming %s to %s\n"), src, dst); if (show_only) continue; - if (mode != INDEX && rename(src, dst) < 0) { + if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) { if (ignore_errors) continue; die_errno(_("renaming '%s' failed"), src); diff --git a/t/t7002-mv-sparse-checkout.sh b/t/t7002-mv-sparse-checkout.sh index 5397c6d07bd..517fd587fa8 100755 --- a/t/t7002-mv-sparse-checkout.sh +++ b/t/t7002-mv-sparse-checkout.sh @@ -31,7 +31,9 @@ test_expect_success 'mv refuses to move sparse-to-sparse' ' echo b >>expect && echo e >>expect && cat sparse_hint >>expect && - test_cmp expect stderr + test_cmp expect stderr && + git mv --sparse b e 2>stderr && + test_must_be_empty stderr ' test_expect_success 'mv refuses to move sparse-to-sparse, ignores failure' ' @@ -44,7 +46,9 @@ test_expect_success 'mv refuses to move sparse-to-sparse, ignores failure' ' echo b >>expect && echo e >>expect && cat sparse_hint >>expect && - test_cmp expect stderr + test_cmp expect stderr && + git mv --sparse -k b e 2>stderr && + test_must_be_empty stderr ' test_expect_success 'mv refuses to move non-sparse-to-sparse' ' @@ -55,7 +59,9 @@ test_expect_success 'mv refuses to move non-sparse-to-sparse' ' cat sparse_error_header >expect && echo e >>expect && cat sparse_hint >>expect && - test_cmp expect stderr + test_cmp expect stderr && + git mv --sparse a e 2>stderr && + test_must_be_empty stderr ' test_expect_success 'mv refuses to move sparse-to-non-sparse' ' @@ -67,7 +73,9 @@ test_expect_success 'mv refuses to move sparse-to-non-sparse' ' cat sparse_error_header >expect && echo b >>expect && cat sparse_hint >>expect && - test_cmp expect stderr + test_cmp expect stderr && + git mv --sparse b e 2>stderr && + test_must_be_empty stderr ' test_expect_success 'recursive mv refuses to move (possible) sparse' ' @@ -80,7 +88,14 @@ test_expect_success 'recursive mv refuses to move (possible) sparse' ' echo sub >>expect && echo sub2 >>expect && cat sparse_hint >>expect && - test_cmp expect stderr + test_cmp expect stderr && + git mv --sparse sub sub2 2>stderr && + test_must_be_empty stderr && + git commit -m "moved sub to sub2" && + git rev-parse HEAD~1:sub >expect && + git rev-parse HEAD:sub2 >actual && + test_cmp expect actual && + git reset --hard HEAD~1 ' test_expect_success 'recursive mv refuses to move sparse' ' @@ -93,7 +108,14 @@ test_expect_success 'recursive mv refuses to move sparse' ' echo sub/dir2/e >>expect && echo sub2/dir2/e >>expect && cat sparse_hint >>expect && - test_cmp expect stderr + test_cmp expect stderr && + git mv --sparse sub sub2 2>stderr && + test_must_be_empty stderr && + git commit -m "moved sub to sub2" && + git rev-parse HEAD~1:sub >expect && + git rev-parse HEAD:sub2 >actual && + test_cmp expect actual && + git reset --hard HEAD~1 ' test_done -- gitgitgadget