Skip to content

Commit 34b53e9

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 <shaoxuan.yuan02@gmail.com>
1 parent 044cd40 commit 34b53e9

4 files changed

Lines changed: 62 additions & 11 deletions

File tree

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

178179
git_config(git_default_config, NULL);
179180

@@ -404,6 +405,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
404405
const char *src = source[i], *dst = destination[i];
405406
enum update_mode mode = modes[i];
406407
int pos;
408+
int up_to_date = 0;
407409
struct checkout state = CHECKOUT_INIT;
408410
state.istate = &the_index;
409411

@@ -414,6 +416,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
414416
if (show_only)
415417
continue;
416418
if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
419+
!(dst_mode & SKIP_WORKTREE_DIR) &&
417420
rename(src, dst) < 0) {
418421
if (ignore_errors)
419422
continue;
@@ -433,20 +436,52 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
433436

434437
pos = cache_name_pos(src, strlen(src));
435438
assert(pos >= 0);
439+
if (!(mode & SPARSE) && !lstat(src, &st))
440+
up_to_date = !ce_modified(active_cache[pos], &st, 0);
436441
rename_cache_entry_at(pos, dst);
437442

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

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

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

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

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

457492
string_list_clear(&src_for_dst, 0);
493+
string_list_clear(&dirty_paths, 0);
458494
UNLEAK(source);
459495
UNLEAK(dest_path);
460496
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)