Git fork
at reftables-rust 653 lines 19 kB view raw
1/* 2 * "git mv" builtin command 3 * 4 * Copyright (C) 2006 Johannes Schindelin 5 */ 6 7#define USE_THE_REPOSITORY_VARIABLE 8#define DISABLE_SIGN_COMPARE_WARNINGS 9 10#include "builtin.h" 11#include "abspath.h" 12#include "advice.h" 13#include "config.h" 14#include "environment.h" 15#include "gettext.h" 16#include "name-hash.h" 17#include "object-file.h" 18#include "path.h" 19#include "pathspec.h" 20#include "lockfile.h" 21#include "dir.h" 22#include "string-list.h" 23#include "parse-options.h" 24#include "read-cache-ll.h" 25 26#include "setup.h" 27#include "strvec.h" 28#include "submodule.h" 29#include "entry.h" 30 31static const char * const builtin_mv_usage[] = { 32 N_("git mv [-v] [-f] [-n] [-k] <source> <destination>"), 33 N_("git mv [-v] [-f] [-n] [-k] <source>... <destination-directory>"), 34 NULL 35}; 36 37enum update_mode { 38 WORKING_DIRECTORY = (1 << 1), 39 INDEX = (1 << 2), 40 SPARSE = (1 << 3), 41 SKIP_WORKTREE_DIR = (1 << 4), 42 /* 43 * A file gets moved implicitly via a move of one of its parent 44 * directories. This flag causes us to skip the check that we don't try 45 * to move a file and any of its parent directories at the same point 46 * in time. 47 */ 48 MOVE_VIA_PARENT_DIR = (1 << 5), 49}; 50 51#define DUP_BASENAME 1 52#define KEEP_TRAILING_SLASH 2 53 54static void internal_prefix_pathspec(struct strvec *out, 55 const char *prefix, 56 const char **pathspec, 57 int count, unsigned flags) 58{ 59 int prefixlen = prefix ? strlen(prefix) : 0; 60 61 /* Create an intermediate copy of the pathspec based on the flags */ 62 for (int i = 0; i < count; i++) { 63 size_t length = strlen(pathspec[i]); 64 size_t to_copy = length; 65 const char *maybe_basename; 66 char *trimmed, *prefixed_path; 67 68 while (!(flags & KEEP_TRAILING_SLASH) && 69 to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1])) 70 to_copy--; 71 72 trimmed = xmemdupz(pathspec[i], to_copy); 73 maybe_basename = (flags & DUP_BASENAME) ? basename(trimmed) : trimmed; 74 prefixed_path = prefix_path(prefix, prefixlen, maybe_basename); 75 strvec_push(out, prefixed_path); 76 77 free(prefixed_path); 78 free(trimmed); 79 } 80} 81 82static char *add_slash(const char *path) 83{ 84 size_t len = strlen(path); 85 if (len && path[len - 1] != '/') { 86 char *with_slash = xmalloc(st_add(len, 2)); 87 memcpy(with_slash, path, len); 88 with_slash[len++] = '/'; 89 with_slash[len] = 0; 90 return with_slash; 91 } 92 return xstrdup(path); 93} 94 95#define SUBMODULE_WITH_GITDIR ((const char *)1) 96 97static const char *submodule_gitfile_path(const char *src, int first) 98{ 99 struct strbuf submodule_dotgit = STRBUF_INIT; 100 const char *path; 101 102 if (!S_ISGITLINK(the_repository->index->cache[first]->ce_mode)) 103 die(_("Directory %s is in index and no submodule?"), src); 104 if (!is_staging_gitmodules_ok(the_repository->index)) 105 die(_("Please stage your changes to .gitmodules or stash them to proceed")); 106 107 strbuf_addf(&submodule_dotgit, "%s/.git", src); 108 109 path = read_gitfile(submodule_dotgit.buf); 110 strbuf_release(&submodule_dotgit); 111 if (path) 112 return path; 113 return SUBMODULE_WITH_GITDIR; 114} 115 116static int index_range_of_same_dir(const char *src, int length, 117 int *first_p, int *last_p) 118{ 119 char *src_w_slash = add_slash(src); 120 int first, last, len_w_slash = length + 1; 121 122 first = index_name_pos(the_repository->index, src_w_slash, len_w_slash); 123 if (first >= 0) 124 die(_("%.*s is in index"), len_w_slash, src_w_slash); 125 126 first = -1 - first; 127 for (last = first; last < the_repository->index->cache_nr; last++) { 128 const char *path = the_repository->index->cache[last]->name; 129 if (strncmp(path, src_w_slash, len_w_slash)) 130 break; 131 } 132 133 free(src_w_slash); 134 *first_p = first; 135 *last_p = last; 136 return last - first; 137} 138 139/* 140 * Given the path of a directory that does not exist on-disk, check whether the 141 * directory contains any entries in the index with the SKIP_WORKTREE flag 142 * enabled. 143 * Return 1 if such index entries exist. 144 * Return 0 otherwise. 145 */ 146static int empty_dir_has_sparse_contents(const char *name) 147{ 148 int ret = 0; 149 char *with_slash = add_slash(name); 150 int length = strlen(with_slash); 151 152 int pos = index_name_pos(the_repository->index, with_slash, length); 153 const struct cache_entry *ce; 154 155 if (pos < 0) { 156 pos = -pos - 1; 157 if (pos >= the_repository->index->cache_nr) 158 goto free_return; 159 ce = the_repository->index->cache[pos]; 160 if (strncmp(with_slash, ce->name, length)) 161 goto free_return; 162 if (ce_skip_worktree(ce)) 163 ret = 1; 164 } 165 166free_return: 167 free(with_slash); 168 return ret; 169} 170 171static void remove_empty_src_dirs(const char **src_dir, size_t src_dir_nr) 172{ 173 size_t i; 174 struct strbuf a_src_dir = STRBUF_INIT; 175 176 for (i = 0; i < src_dir_nr; i++) { 177 int dummy; 178 strbuf_addstr(&a_src_dir, src_dir[i]); 179 /* 180 * if entries under a_src_dir are all moved away, 181 * recursively remove a_src_dir to cleanup 182 */ 183 if (index_range_of_same_dir(a_src_dir.buf, a_src_dir.len, 184 &dummy, &dummy) < 1) { 185 remove_dir_recursively(&a_src_dir, 0); 186 } 187 strbuf_reset(&a_src_dir); 188 } 189 190 strbuf_release(&a_src_dir); 191} 192 193struct pathmap_entry { 194 struct hashmap_entry ent; 195 const char *path; 196}; 197 198static int pathmap_cmp(const void *cmp_data UNUSED, 199 const struct hashmap_entry *a, 200 const struct hashmap_entry *b, 201 const void *key UNUSED) 202{ 203 const struct pathmap_entry *e1 = container_of(a, struct pathmap_entry, ent); 204 const struct pathmap_entry *e2 = container_of(b, struct pathmap_entry, ent); 205 return fspathcmp(e1->path, e2->path); 206} 207 208int cmd_mv(int argc, 209 const char **argv, 210 const char *prefix, 211 struct repository *repo UNUSED) 212{ 213 int i, flags, gitmodules_modified = 0; 214 int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0; 215 struct option builtin_mv_options[] = { 216 OPT__VERBOSE(&verbose, N_("be verbose")), 217 OPT__DRY_RUN(&show_only, N_("dry run")), 218 OPT__FORCE(&force, N_("force move/rename even if target exists"), 219 PARSE_OPT_NOCOMPLETE), 220 OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")), 221 OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")), 222 OPT_END(), 223 }; 224 struct strvec sources = STRVEC_INIT; 225 struct strvec dest_paths = STRVEC_INIT; 226 struct strvec destinations = STRVEC_INIT; 227 struct strvec submodule_gitfiles_to_free = STRVEC_INIT; 228 const char **submodule_gitfiles; 229 char *dst_w_slash = NULL; 230 struct strvec src_dir = STRVEC_INIT; 231 enum update_mode *modes, dst_mode = 0; 232 struct stat st, dest_st; 233 struct string_list src_for_dst = STRING_LIST_INIT_DUP; 234 struct lock_file lock_file = LOCK_INIT; 235 struct cache_entry *ce; 236 struct string_list only_match_skip_worktree = STRING_LIST_INIT_DUP; 237 struct string_list dirty_paths = STRING_LIST_INIT_DUP; 238 struct hashmap moved_dirs = HASHMAP_INIT(pathmap_cmp, NULL); 239 struct strbuf pathbuf = STRBUF_INIT; 240 int ret; 241 242 repo_config(the_repository, git_default_config, NULL); 243 244 argc = parse_options(argc, argv, prefix, builtin_mv_options, 245 builtin_mv_usage, 0); 246 if (--argc < 1) 247 usage_with_options(builtin_mv_usage, builtin_mv_options); 248 249 repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); 250 if (repo_read_index(the_repository) < 0) 251 die(_("index file corrupt")); 252 253 internal_prefix_pathspec(&sources, prefix, argv, argc, 0); 254 CALLOC_ARRAY(modes, argc); 255 256 /* 257 * Keep trailing slash, needed to let 258 * "git mv file no-such-dir/" error out, except in the case 259 * "git mv directory no-such-dir/". 260 */ 261 flags = KEEP_TRAILING_SLASH; 262 if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) 263 flags = 0; 264 internal_prefix_pathspec(&dest_paths, prefix, argv + argc, 1, flags); 265 dst_w_slash = add_slash(dest_paths.v[0]); 266 submodule_gitfiles = xcalloc(argc, sizeof(char *)); 267 268 if (dest_paths.v[0][0] == '\0') 269 /* special case: "." was normalized to "" */ 270 internal_prefix_pathspec(&destinations, dest_paths.v[0], argv, argc, DUP_BASENAME); 271 else if (!lstat(dest_paths.v[0], &st) && S_ISDIR(st.st_mode)) { 272 internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); 273 } else if (!path_in_sparse_checkout(dst_w_slash, the_repository->index) && 274 empty_dir_has_sparse_contents(dst_w_slash)) { 275 internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); 276 dst_mode = SKIP_WORKTREE_DIR; 277 } else if (argc != 1) { 278 die(_("destination '%s' is not a directory"), dest_paths.v[0]); 279 } else { 280 strvec_pushv(&destinations, dest_paths.v); 281 282 /* 283 * <destination> is a file outside of sparse-checkout 284 * cone. Insist on cone mode here for backward 285 * compatibility. We don't want dst_mode to be assigned 286 * for a file when the repo is using no-cone mode (which 287 * is deprecated at this point) sparse-checkout. As 288 * SPARSE here is only considering cone-mode situation. 289 */ 290 if (!path_in_cone_mode_sparse_checkout(destinations.v[0], the_repository->index)) 291 dst_mode = SPARSE; 292 } 293 294 /* Checking */ 295 for (i = 0; i < argc; i++) { 296 const char *src = sources.v[i], *dst = destinations.v[i]; 297 int length; 298 const char *bad = NULL; 299 int skip_sparse = 0; 300 301 if (show_only) 302 printf(_("Checking rename of '%s' to '%s'\n"), src, dst); 303 304 length = strlen(src); 305 if (lstat(src, &st) < 0) { 306 int pos; 307 const struct cache_entry *ce; 308 309 pos = index_name_pos(the_repository->index, src, length); 310 if (pos < 0) { 311 char *src_w_slash = add_slash(src); 312 if (!path_in_sparse_checkout(src_w_slash, the_repository->index) && 313 empty_dir_has_sparse_contents(src)) { 314 free(src_w_slash); 315 modes[i] |= SKIP_WORKTREE_DIR; 316 goto dir_check; 317 } 318 free(src_w_slash); 319 /* only error if existence is expected. */ 320 if (!(modes[i] & SPARSE)) 321 bad = _("bad source"); 322 goto act_on_entry; 323 } 324 ce = the_repository->index->cache[pos]; 325 if (!ce_skip_worktree(ce)) { 326 bad = _("bad source"); 327 goto act_on_entry; 328 } 329 if (!ignore_sparse) { 330 string_list_append(&only_match_skip_worktree, src); 331 goto act_on_entry; 332 } 333 /* Check if dst exists in index */ 334 if (index_name_pos(the_repository->index, dst, strlen(dst)) < 0) { 335 modes[i] |= SPARSE; 336 goto act_on_entry; 337 } 338 if (!force) { 339 bad = _("destination exists"); 340 goto act_on_entry; 341 } 342 modes[i] |= SPARSE; 343 goto act_on_entry; 344 } 345 if (!strncmp(src, dst, length) && 346 (dst[length] == 0 || dst[length] == '/')) { 347 bad = _("can not move directory into itself"); 348 goto act_on_entry; 349 } 350 if (S_ISDIR(st.st_mode) 351 && lstat(dst, &dest_st) == 0) { 352 bad = _("destination already exists"); 353 goto act_on_entry; 354 } 355 356dir_check: 357 if (S_ISDIR(st.st_mode)) { 358 struct pathmap_entry *entry; 359 char *dst_with_slash; 360 size_t dst_with_slash_len; 361 int j, n; 362 int first = index_name_pos(the_repository->index, src, length), last; 363 364 if (first >= 0) { 365 const char *path = submodule_gitfile_path(src, first); 366 if (path != SUBMODULE_WITH_GITDIR) 367 path = strvec_push(&submodule_gitfiles_to_free, path); 368 submodule_gitfiles[i] = path; 369 goto act_on_entry; 370 } else if (index_range_of_same_dir(src, length, 371 &first, &last) < 1) { 372 bad = _("source directory is empty"); 373 goto act_on_entry; 374 } 375 376 entry = xmalloc(sizeof(*entry)); 377 entry->path = src; 378 hashmap_entry_init(&entry->ent, fspathhash(src)); 379 hashmap_add(&moved_dirs, &entry->ent); 380 381 /* last - first >= 1 */ 382 modes[i] |= WORKING_DIRECTORY; 383 384 strvec_push(&src_dir, src); 385 386 n = argc + last - first; 387 REALLOC_ARRAY(modes, n); 388 REALLOC_ARRAY(submodule_gitfiles, n); 389 390 dst_with_slash = add_slash(dst); 391 dst_with_slash_len = strlen(dst_with_slash); 392 393 for (j = 0; j < last - first; j++) { 394 const struct cache_entry *ce = the_repository->index->cache[first + j]; 395 const char *path = ce->name; 396 char *prefixed_path = prefix_path(dst_with_slash, dst_with_slash_len, path + length + 1); 397 398 strvec_push(&sources, path); 399 strvec_push(&destinations, prefixed_path); 400 401 modes[argc + j] = MOVE_VIA_PARENT_DIR | (ce_skip_worktree(ce) ? SPARSE : INDEX); 402 submodule_gitfiles[argc + j] = NULL; 403 404 free(prefixed_path); 405 } 406 407 free(dst_with_slash); 408 argc += last - first; 409 goto act_on_entry; 410 } 411 if (!(ce = index_file_exists(the_repository->index, src, length, 0))) { 412 bad = _("not under version control"); 413 goto act_on_entry; 414 } 415 if (ce_stage(ce)) { 416 bad = _("conflicted"); 417 goto act_on_entry; 418 } 419 if (lstat(dst, &st) == 0 && 420 (!ignore_case || strcasecmp(src, dst))) { 421 bad = _("destination exists"); 422 if (force) { 423 /* 424 * only files can overwrite each other: 425 * check both source and destination 426 */ 427 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { 428 if (verbose) 429 warning(_("overwriting '%s'"), dst); 430 bad = NULL; 431 } else 432 bad = _("Cannot overwrite"); 433 } 434 goto act_on_entry; 435 } 436 if (string_list_has_string(&src_for_dst, dst)) { 437 bad = _("multiple sources for the same target"); 438 goto act_on_entry; 439 } 440 if (is_dir_sep(dst[strlen(dst) - 1])) { 441 bad = _("destination directory does not exist"); 442 goto act_on_entry; 443 } 444 445 if (ignore_sparse && 446 (dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && 447 index_entry_exists(the_repository->index, dst, strlen(dst))) { 448 bad = _("destination exists in the index"); 449 if (force) { 450 if (verbose) 451 warning(_("overwriting '%s'"), dst); 452 bad = NULL; 453 } else { 454 goto act_on_entry; 455 } 456 } 457 /* 458 * We check if the paths are in the sparse-checkout 459 * definition as a very final check, since that 460 * allows us to point the user to the --sparse 461 * option as a way to have a successful run. 462 */ 463 if (!ignore_sparse && 464 !path_in_sparse_checkout(src, the_repository->index)) { 465 string_list_append(&only_match_skip_worktree, src); 466 skip_sparse = 1; 467 } 468 if (!ignore_sparse && 469 !path_in_sparse_checkout(dst, the_repository->index)) { 470 string_list_append(&only_match_skip_worktree, dst); 471 skip_sparse = 1; 472 } 473 474 if (skip_sparse) 475 goto remove_entry; 476 477 string_list_insert(&src_for_dst, dst); 478 479act_on_entry: 480 if (!bad) 481 continue; 482 if (!ignore_errors) 483 die(_("%s, source=%s, destination=%s"), 484 bad, src, dst); 485remove_entry: 486 if (--argc > 0) { 487 int n = argc - i; 488 strvec_remove(&sources, i); 489 strvec_remove(&destinations, i); 490 MOVE_ARRAY(modes + i, modes + i + 1, n); 491 MOVE_ARRAY(submodule_gitfiles + i, 492 submodule_gitfiles + i + 1, n); 493 i--; 494 } 495 } 496 497 for (i = 0; i < argc; i++) { 498 const char *slash_pos; 499 500 if (modes[i] & MOVE_VIA_PARENT_DIR) 501 continue; 502 503 strbuf_reset(&pathbuf); 504 strbuf_addstr(&pathbuf, sources.v[i]); 505 506 slash_pos = strrchr(pathbuf.buf, '/'); 507 while (slash_pos > pathbuf.buf) { 508 struct pathmap_entry needle; 509 510 strbuf_setlen(&pathbuf, slash_pos - pathbuf.buf); 511 512 needle.path = pathbuf.buf; 513 hashmap_entry_init(&needle.ent, fspathhash(pathbuf.buf)); 514 515 if (hashmap_get_entry(&moved_dirs, &needle, ent, NULL)) 516 die(_("cannot move both '%s' and its parent directory '%s'"), 517 sources.v[i], pathbuf.buf); 518 519 slash_pos = strrchr(pathbuf.buf, '/'); 520 } 521 } 522 523 if (only_match_skip_worktree.nr) { 524 advise_on_updating_sparse_paths(&only_match_skip_worktree); 525 if (!ignore_errors) { 526 ret = 1; 527 goto out; 528 } 529 } 530 531 for (i = 0; i < argc; i++) { 532 const char *src = sources.v[i], *dst = destinations.v[i]; 533 enum update_mode mode = modes[i]; 534 int pos; 535 int sparse_and_dirty = 0; 536 struct checkout state = CHECKOUT_INIT; 537 state.istate = the_repository->index; 538 539 if (force) 540 state.force = 1; 541 if (show_only || verbose) 542 printf(_("Renaming %s to %s\n"), src, dst); 543 if (show_only) 544 continue; 545 if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) && 546 !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && 547 rename(src, dst) < 0) { 548 if (ignore_errors) 549 continue; 550 die_errno(_("renaming '%s' failed"), src); 551 } 552 if (submodule_gitfiles[i]) { 553 if (!update_path_in_gitmodules(src, dst)) 554 gitmodules_modified = 1; 555 if (submodule_gitfiles[i] != SUBMODULE_WITH_GITDIR) 556 connect_work_tree_and_git_dir(dst, 557 submodule_gitfiles[i], 558 1); 559 } 560 561 if (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR)) 562 continue; 563 564 pos = index_name_pos(the_repository->index, src, strlen(src)); 565 if (pos < 0) 566 BUG("could not find source in index: '%s'", src); 567 if (!(mode & SPARSE) && !lstat(src, &st)) 568 sparse_and_dirty = ie_modified(the_repository->index, 569 the_repository->index->cache[pos], 570 &st, 571 0); 572 rename_index_entry_at(the_repository->index, pos, dst); 573 574 if (ignore_sparse && 575 core_apply_sparse_checkout && 576 core_sparse_checkout_cone) { 577 /* 578 * NEEDSWORK: we are *not* paying attention to 579 * "out-to-out" move (<source> is out-of-cone and 580 * <destination> is out-of-cone) at this point. It 581 * should be added in a future patch. 582 */ 583 if ((mode & SPARSE) && 584 path_in_sparse_checkout(dst, the_repository->index)) { 585 /* from out-of-cone to in-cone */ 586 int dst_pos = index_name_pos(the_repository->index, dst, 587 strlen(dst)); 588 struct cache_entry *dst_ce = the_repository->index->cache[dst_pos]; 589 590 dst_ce->ce_flags &= ~CE_SKIP_WORKTREE; 591 592 if (checkout_entry(dst_ce, &state, NULL, NULL)) 593 die(_("cannot checkout %s"), dst_ce->name); 594 } else if ((dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && 595 !(mode & SPARSE) && 596 !path_in_sparse_checkout(dst, the_repository->index)) { 597 /* from in-cone to out-of-cone */ 598 int dst_pos = index_name_pos(the_repository->index, dst, 599 strlen(dst)); 600 struct cache_entry *dst_ce = the_repository->index->cache[dst_pos]; 601 602 /* 603 * if src is clean, it will suffice to remove it 604 */ 605 if (!sparse_and_dirty) { 606 dst_ce->ce_flags |= CE_SKIP_WORKTREE; 607 unlink_or_warn(src); 608 } else { 609 /* 610 * if src is dirty, move it to the 611 * destination and create leading 612 * dirs if necessary 613 */ 614 char *dst_dup = xstrdup(dst); 615 string_list_append(&dirty_paths, dst); 616 safe_create_leading_directories(the_repository, dst_dup); 617 FREE_AND_NULL(dst_dup); 618 rename(src, dst); 619 } 620 } 621 } 622 } 623 624 remove_empty_src_dirs(src_dir.v, src_dir.nr); 625 626 if (dirty_paths.nr) 627 advise_on_moving_dirty_path(&dirty_paths); 628 629 if (gitmodules_modified) 630 stage_updated_gitmodules(the_repository->index); 631 632 if (write_locked_index(the_repository->index, &lock_file, 633 COMMIT_LOCK | SKIP_IF_UNCHANGED)) 634 die(_("Unable to write new index file")); 635 636 ret = 0; 637 638out: 639 strvec_clear(&src_dir); 640 free(dst_w_slash); 641 string_list_clear(&src_for_dst, 0); 642 string_list_clear(&dirty_paths, 0); 643 string_list_clear(&only_match_skip_worktree, 0); 644 strvec_clear(&sources); 645 strvec_clear(&dest_paths); 646 strvec_clear(&destinations); 647 strvec_clear(&submodule_gitfiles_to_free); 648 hashmap_clear_and_free(&moved_dirs, struct pathmap_entry, ent); 649 strbuf_release(&pathbuf); 650 free(submodule_gitfiles); 651 free(modes); 652 return ret; 653}