Skip to content

Commit 4b11e55

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. Signed-off-by: Shaoxuan Yuan <[email protected]>
1 parent fd35dab commit 4b11e55

File tree

4 files changed

+62
-11
lines changed

4 files changed

+62
-11
lines changed

advice.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,17 @@ 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 dirty paths and/or pathspecs are moved\n"
273+
"but not sparsified. Use \"git add\" to stage them then\n"
274+
"use \"git sparse-checkout reapply\" to sparsify them.\n"));
275+
for_each_string_list_item(item, pathspec_list)
276+
fprintf(stderr, "%s\n", item->string);
277+
}

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
@@ -175,6 +175,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
175175
struct lock_file lock_file = LOCK_INIT;
176176
struct cache_entry *ce;
177177
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
178+
struct string_list dirty_paths = STRING_LIST_INIT_NODUP;
178179

179180
git_config(git_default_config, NULL);
180181

@@ -405,6 +406,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
405406
const char *src = source[i], *dst = destination[i];
406407
enum update_mode mode = !modes[i] ? REGULAR : modes[i];
407408
int pos;
409+
int up_to_date = 0;
408410
struct checkout state = CHECKOUT_INIT;
409411
state.istate = &the_index;
410412

@@ -415,6 +417,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
415417
if (show_only)
416418
continue;
417419
if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
420+
!(dst_mode & SKIP_WORKTREE_DIR) &&
418421
rename(src, dst) < 0) {
419422
if (ignore_errors)
420423
continue;
@@ -434,20 +437,52 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
434437

435438
pos = cache_name_pos(src, strlen(src));
436439
assert(pos >= 0);
440+
if ((mode & (REGULAR | INDEX)) && !lstat(src, &st))
441+
up_to_date = !ce_modified(active_cache[pos], &st, 0);
437442
rename_cache_entry_at(pos, dst);
438443

439-
if ((mode & SPARSE) &&
440-
(path_in_sparse_checkout(dst, &the_index))) {
441-
int dst_pos;
444+
if (ignore_sparse &&
445+
!init_sparse_checkout_patterns(&the_index) &&
446+
the_index.sparse_checkout_patterns->use_cone_patterns) {
442447

443-
dst_pos = cache_name_pos(dst, strlen(dst));
444-
active_cache[dst_pos]->ce_flags &= ~CE_SKIP_WORKTREE;
448+
/* from out-of-cone to in-cone */
449+
if ((mode & SPARSE) &&
450+
(path_in_sparse_checkout(dst, &the_index))) {
451+
int dst_pos = cache_name_pos(dst, strlen(dst));
452+
struct cache_entry *dst_ce = active_cache[dst_pos];
445453

446-
if (checkout_entry(active_cache[dst_pos], &state, NULL, NULL))
447-
die(_("cannot checkout %s"), active_cache[dst_pos]->name);
454+
dst_ce->ce_flags &= ~CE_SKIP_WORKTREE;
455+
456+
if (checkout_entry(dst_ce, &state, NULL, NULL))
457+
die(_("cannot checkout %s"), dst_ce->name);
458+
continue;
459+
}
460+
461+
/* from in-cone to out-of-cone */
462+
if ((dst_mode & SKIP_WORKTREE_DIR) &&
463+
(mode & (REGULAR | INDEX)) &&
464+
(!path_in_sparse_checkout(dst, &the_index))) {
465+
int dst_pos = cache_name_pos(dst, strlen(dst));
466+
struct cache_entry *dst_ce = active_cache[dst_pos];
467+
char *src_dir = dirname(xstrdup(src));
468+
469+
if (up_to_date) {
470+
dst_ce->ce_flags |= CE_SKIP_WORKTREE;
471+
unlink_or_warn(src);
472+
} else {
473+
string_list_append(&dirty_paths, dst);
474+
safe_create_leading_directories(xstrdup(dst));
475+
rename(src, dst);
476+
}
477+
if ((mode & INDEX) && is_empty_dir(src_dir))
478+
rmdir_or_warn(src_dir);
479+
}
448480
}
449481
}
450482

483+
if (dirty_paths.nr)
484+
advise_on_moving_dirty_path(&dirty_paths);
485+
451486
if (gitmodules_modified)
452487
stage_updated_gitmodules(&the_index);
453488

@@ -456,6 +491,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
456491
die(_("Unable to write new index file"));
457492

458493
string_list_clear(&src_for_dst, 0);
494+
string_list_clear(&dirty_paths, 0);
459495
UNLEAK(source);
460496
UNLEAK(dest_path);
461497
free(submodule_gitfile);

t/t7002-mv-sparse-checkout.sh

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

293-
test_expect_failure 'move clean path from in-cone to out-of-cone' '
293+
test_expect_success 'move clean path from in-cone to out-of-cone' '
294294
test_when_finished "cleanup_sparse_checkout" &&
295295
setup_sparse_checkout &&
296296
@@ -304,7 +304,7 @@ test_expect_failure 'move clean path from in-cone to out-of-cone' '
304304
grep -x "S folder1/d" actual
305305
'
306306

307-
test_expect_failure 'move dirty path from in-cone to out-of-cone' '
307+
test_expect_success 'move dirty path from in-cone to out-of-cone' '
308308
test_when_finished "cleanup_sparse_checkout" &&
309309
setup_sparse_checkout &&
310310
echo "modified" >>sub/d &&
@@ -325,7 +325,7 @@ test_expect_failure 'move dirty path from in-cone to out-of-cone' '
325325
grep -x "H folder1/d" actual
326326
'
327327

328-
test_expect_failure 'move dir from in-cone to out-of-cone' '
328+
test_expect_success 'move dir from in-cone to out-of-cone' '
329329
test_when_finished "cleanup_sparse_checkout" &&
330330
setup_sparse_checkout &&
331331
@@ -339,7 +339,7 @@ test_expect_failure 'move dir from in-cone to out-of-cone' '
339339
grep -x "S folder1/dir/e" actual
340340
'
341341

342-
test_expect_failure 'move partially-dirty dir from in-cone to out-of-cone' '
342+
test_expect_success 'move partially-dirty dir from in-cone to out-of-cone' '
343343
test_when_finished "cleanup_sparse_checkout" &&
344344
setup_sparse_checkout &&
345345
touch sub/dir/e2 sub/dir/e3 &&

0 commit comments

Comments
 (0)