Skip to content

Commit

Permalink
mv: add '--sparse' option to ignore sparse-checkout
Browse files Browse the repository at this point in the history
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@microsoft.com>
  • Loading branch information
derrickstolee committed Aug 30, 2021
1 parent d8fa04e commit c5be4b3
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 16 deletions.
24 changes: 14 additions & 10 deletions builtin/mv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -182,21 +183,23 @@ 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;
}
if (skip_sparse)
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))
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
34 changes: 28 additions & 6 deletions t/t7002-mv-sparse-checkout.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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' '
Expand All @@ -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' '
Expand All @@ -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' '
Expand All @@ -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' '
Expand All @@ -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' '
Expand All @@ -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

0 comments on commit c5be4b3

Please sign in to comment.