Skip to content

Commit 1ed1b28

Browse files
committed
mv: from in-cone to out-of-cone
Originally, moving an in-cone <source> to an out-of-cone <destination> was not possible, mainly because such <destination> is a directory that is not present in the working tree. Change the behavior so that we can move an in-cone <source> to out-of-cone <destination> when --sparse is supplied. Such <source> can be either clean or dirty, and moving it results in different behaviors: A clean move should move the <source> to the <destination>, both in the working tree and the index, then remove the resulted path from the working tree, and turn on its CE_SKIP_WORKTREE bit. A dirty move should move the <source> to the <destination>, both in the working tree and the index, but should *not* remove the resulted path from the working tree and should *not* turn on its CE_SKIP_WORKTREE bit. Instead advise user to "git add" this path and run "git sparse-checkout reapply" to re-sparsify that path. Helped-by: Victoria Dye <[email protected]> Signed-off-by: Shaoxuan Yuan <[email protected]>
1 parent ee0300c commit 1ed1b28

File tree

4 files changed

+67
-11
lines changed

4 files changed

+67
-11
lines changed

advice.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,22 @@ void detach_advice(const char *new_name)
261261

262262
fprintf(stderr, fmt, new_name);
263263
}
264+
265+
void advise_on_moving_dirty_path(struct string_list *pathspec_list)
266+
{
267+
struct string_list_item *item;
268+
269+
if (!pathspec_list->nr)
270+
return;
271+
272+
fprintf(stderr, _("The following paths have been moved outside the\n"
273+
"sparse-checkout definition but are not sparse due to local\n"
274+
"modifications.\n"));
275+
for_each_string_list_item(item, pathspec_list)
276+
fprintf(stderr, "%s\n", item->string);
277+
278+
advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
279+
_("To correct the sparsity of these paths, do the following:\n"
280+
"* Use \"git add --sparse <paths>\" to update the index\n"
281+
"* Use \"git sparse-checkout reapply\" to apply the sparsity rules"));
282+
}

advice.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,6 @@ void NORETURN die_conclude_merge(void);
7474
void NORETURN die_ff_impossible(void);
7575
void advise_on_updating_sparse_paths(struct string_list *pathspec_list);
7676
void detach_advice(const char *new_name);
77+
void advise_on_moving_dirty_path(struct string_list *pathspec_list);
7778

7879
#endif /* ADVICE_H */

builtin/mv.c

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
183183
struct lock_file lock_file = LOCK_INIT;
184184
struct cache_entry *ce;
185185
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
186+
struct string_list dirty_paths = STRING_LIST_INIT_NODUP;
186187

187188
git_config(git_default_config, NULL);
188189

@@ -424,6 +425,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
424425
const char *src = source[i], *dst = destination[i];
425426
enum update_mode mode = modes[i];
426427
int pos;
428+
int up_to_date = 0;
427429
struct checkout state = CHECKOUT_INIT;
428430
state.istate = &the_index;
429431

@@ -434,6 +436,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
434436
if (show_only)
435437
continue;
436438
if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
439+
!(dst_mode & SKIP_WORKTREE_DIR) &&
437440
rename(src, dst) < 0) {
438441
if (ignore_errors)
439442
continue;
@@ -453,20 +456,52 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
453456

454457
pos = cache_name_pos(src, strlen(src));
455458
assert(pos >= 0);
459+
if (!(mode & SPARSE) && !lstat(src, &st))
460+
up_to_date = !ce_modified(active_cache[pos], &st, 0);
456461
rename_cache_entry_at(pos, dst);
457462

458-
if ((mode & SPARSE) &&
459-
(path_in_sparse_checkout(dst, &the_index))) {
460-
int dst_pos;
463+
if (ignore_sparse &&
464+
core_apply_sparse_checkout &&
465+
core_sparse_checkout_cone) {
461466

462-
dst_pos = cache_name_pos(dst, strlen(dst));
463-
active_cache[dst_pos]->ce_flags &= ~CE_SKIP_WORKTREE;
467+
/* from out-of-cone to in-cone */
468+
if ((mode & SPARSE) &&
469+
path_in_sparse_checkout(dst, &the_index)) {
470+
int dst_pos = cache_name_pos(dst, strlen(dst));
471+
struct cache_entry *dst_ce = active_cache[dst_pos];
464472

465-
if (checkout_entry(active_cache[dst_pos], &state, NULL, NULL))
466-
die(_("cannot checkout %s"), active_cache[dst_pos]->name);
473+
dst_ce->ce_flags &= ~CE_SKIP_WORKTREE;
474+
475+
if (checkout_entry(dst_ce, &state, NULL, NULL))
476+
die(_("cannot checkout %s"), dst_ce->name);
477+
continue;
478+
}
479+
480+
/* from in-cone to out-of-cone */
481+
if ((dst_mode & SKIP_WORKTREE_DIR) &&
482+
!(mode & SPARSE) &&
483+
!path_in_sparse_checkout(dst, &the_index)) {
484+
int dst_pos = cache_name_pos(dst, strlen(dst));
485+
struct cache_entry *dst_ce = active_cache[dst_pos];
486+
char *src_dir = dirname(xstrdup(src));
487+
488+
if (up_to_date) {
489+
dst_ce->ce_flags |= CE_SKIP_WORKTREE;
490+
unlink_or_warn(src);
491+
} else {
492+
string_list_append(&dirty_paths, dst);
493+
safe_create_leading_directories(xstrdup(dst));
494+
rename(src, dst);
495+
}
496+
if ((mode & INDEX) && is_empty_dir(src_dir))
497+
rmdir_or_warn(src_dir);
498+
}
467499
}
468500
}
469501

502+
if (dirty_paths.nr)
503+
advise_on_moving_dirty_path(&dirty_paths);
504+
470505
if (gitmodules_modified)
471506
stage_updated_gitmodules(&the_index);
472507

@@ -475,6 +510,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
475510
die(_("Unable to write new index file"));
476511

477512
string_list_clear(&src_for_dst, 0);
513+
string_list_clear(&dirty_paths, 0);
478514
UNLEAK(source);
479515
UNLEAK(dest_path);
480516
free(submodule_gitfile);

t/t7002-mv-sparse-checkout.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ test_expect_success 'move sparse file to existing destination with --force and -
303303
test_cmp expect sub/file1
304304
'
305305

306-
test_expect_failure 'move clean path from in-cone to out-of-cone' '
306+
test_expect_success 'move clean path from in-cone to out-of-cone' '
307307
test_when_finished "cleanup_sparse_checkout" &&
308308
setup_sparse_checkout &&
309309
@@ -323,7 +323,7 @@ test_expect_failure 'move clean path from in-cone to out-of-cone' '
323323
grep -x "S folder1/d" actual
324324
'
325325

326-
test_expect_failure 'move dirty path from in-cone to out-of-cone' '
326+
test_expect_success 'move dirty path from in-cone to out-of-cone' '
327327
test_when_finished "cleanup_sparse_checkout" &&
328328
setup_sparse_checkout &&
329329
echo "modified" >>sub/d &&
@@ -347,7 +347,7 @@ test_expect_failure 'move dirty path from in-cone to out-of-cone' '
347347
grep -x "H folder1/d" actual
348348
'
349349

350-
test_expect_failure 'move dir from in-cone to out-of-cone' '
350+
test_expect_success 'move dir from in-cone to out-of-cone' '
351351
test_when_finished "cleanup_sparse_checkout" &&
352352
setup_sparse_checkout &&
353353
@@ -367,7 +367,7 @@ test_expect_failure 'move dir from in-cone to out-of-cone' '
367367
grep -x "S folder1/dir/e" actual
368368
'
369369

370-
test_expect_failure 'move partially-dirty dir from in-cone to out-of-cone' '
370+
test_expect_success 'move partially-dirty dir from in-cone to out-of-cone' '
371371
test_when_finished "cleanup_sparse_checkout" &&
372372
setup_sparse_checkout &&
373373
touch sub/dir/e2 sub/dir/e3 &&

0 commit comments

Comments
 (0)