diff options
Diffstat (limited to 'builtin/mv.c')
| -rw-r--r-- | builtin/mv.c | 336 |
1 files changed, 174 insertions, 162 deletions
diff --git a/builtin/mv.c b/builtin/mv.c index 22e64fc290..6c69033c5f 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -3,7 +3,7 @@ * * Copyright (C) 2006 Johannes Schindelin */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "abspath.h" #include "advice.h" @@ -20,6 +20,7 @@ #include "read-cache-ll.h" #include "repository.h" #include "setup.h" +#include "strvec.h" #include "submodule.h" #include "entry.h" @@ -38,45 +39,35 @@ enum update_mode { #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 -static const char **internal_prefix_pathspec(const char *prefix, - const char **pathspec, - int count, unsigned flags) +static void internal_prefix_pathspec(struct strvec *out, + const char *prefix, + const char **pathspec, + int count, unsigned flags) { - int i; - const char **result; int prefixlen = prefix ? strlen(prefix) : 0; - ALLOC_ARRAY(result, count + 1); /* Create an intermediate copy of the pathspec based on the flags */ - for (i = 0; i < count; i++) { - int length = strlen(pathspec[i]); - int to_copy = length; - char *it; + for (int i = 0; i < count; i++) { + size_t length = strlen(pathspec[i]); + size_t to_copy = length; + const char *maybe_basename; + char *trimmed, *prefixed_path; + while (!(flags & KEEP_TRAILING_SLASH) && to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1])) to_copy--; - it = xmemdupz(pathspec[i], to_copy); - if (flags & DUP_BASENAME) { - result[i] = xstrdup(basename(it)); - free(it); - } else { - result[i] = it; - } - } - result[count] = NULL; + trimmed = xmemdupz(pathspec[i], to_copy); + maybe_basename = (flags & DUP_BASENAME) ? basename(trimmed) : trimmed; + prefixed_path = prefix_path(prefix, prefixlen, maybe_basename); + strvec_push(out, prefixed_path); - /* Prefix the pathspec and free the old intermediate strings */ - for (i = 0; i < count; i++) { - const char *match = prefix_path(prefix, prefixlen, result[i]); - free((char *) result[i]); - result[i] = match; + free(prefixed_path); + free(trimmed); } - - return result; } -static const char *add_slash(const char *path) +static char *add_slash(const char *path) { size_t len = strlen(path); if (len && path[len - 1] != '/') { @@ -86,46 +77,48 @@ static const char *add_slash(const char *path) with_slash[len] = 0; return with_slash; } - return path; + return xstrdup(path); } #define SUBMODULE_WITH_GITDIR ((const char *)1) -static void prepare_move_submodule(const char *src, int first, - const char **submodule_gitfile) +static const char *submodule_gitfile_path(const char *src, int first) { struct strbuf submodule_dotgit = STRBUF_INIT; - if (!S_ISGITLINK(the_index.cache[first]->ce_mode)) + const char *path; + + if (!S_ISGITLINK(the_repository->index->cache[first]->ce_mode)) die(_("Directory %s is in index and no submodule?"), src); - if (!is_staging_gitmodules_ok(&the_index)) + if (!is_staging_gitmodules_ok(the_repository->index)) die(_("Please stage your changes to .gitmodules or stash them to proceed")); + strbuf_addf(&submodule_dotgit, "%s/.git", src); - *submodule_gitfile = read_gitfile(submodule_dotgit.buf); - if (*submodule_gitfile) - *submodule_gitfile = xstrdup(*submodule_gitfile); - else - *submodule_gitfile = SUBMODULE_WITH_GITDIR; + + path = read_gitfile(submodule_dotgit.buf); strbuf_release(&submodule_dotgit); + if (path) + return path; + return SUBMODULE_WITH_GITDIR; } static int index_range_of_same_dir(const char *src, int length, int *first_p, int *last_p) { - const char *src_w_slash = add_slash(src); + char *src_w_slash = add_slash(src); int first, last, len_w_slash = length + 1; - first = index_name_pos(&the_index, src_w_slash, len_w_slash); + first = index_name_pos(the_repository->index, src_w_slash, len_w_slash); if (first >= 0) die(_("%.*s is in index"), len_w_slash, src_w_slash); first = -1 - first; - for (last = first; last < the_index.cache_nr; last++) { - const char *path = the_index.cache[last]->name; + for (last = first; last < the_repository->index->cache_nr; last++) { + const char *path = the_repository->index->cache[last]->name; if (strncmp(path, src_w_slash, len_w_slash)) break; } - if (src_w_slash != src) - free((char *)src_w_slash); + + free(src_w_slash); *first_p = first; *last_p = last; return last - first; @@ -141,17 +134,17 @@ static int index_range_of_same_dir(const char *src, int length, static int empty_dir_has_sparse_contents(const char *name) { int ret = 0; - const char *with_slash = add_slash(name); + char *with_slash = add_slash(name); int length = strlen(with_slash); - int pos = index_name_pos(&the_index, with_slash, length); + int pos = index_name_pos(the_repository->index, with_slash, length); const struct cache_entry *ce; if (pos < 0) { pos = -pos - 1; - if (pos >= the_index.cache_nr) + if (pos >= the_repository->index->cache_nr) goto free_return; - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; if (strncmp(with_slash, ce->name, length)) goto free_return; if (ce_skip_worktree(ce)) @@ -159,11 +152,32 @@ static int empty_dir_has_sparse_contents(const char *name) } free_return: - if (with_slash != name) - free((char *)with_slash); + free(with_slash); return ret; } +static void remove_empty_src_dirs(const char **src_dir, size_t src_dir_nr) +{ + size_t i; + struct strbuf a_src_dir = STRBUF_INIT; + + for (i = 0; i < src_dir_nr; i++) { + int dummy; + strbuf_addstr(&a_src_dir, src_dir[i]); + /* + * if entries under a_src_dir are all moved away, + * recursively remove a_src_dir to cleanup + */ + if (index_range_of_same_dir(a_src_dir.buf, a_src_dir.len, + &dummy, &dummy) < 1) { + remove_dir_recursively(&a_src_dir, 0); + } + strbuf_reset(&a_src_dir); + } + + strbuf_release(&a_src_dir); +} + int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; @@ -177,18 +191,21 @@ int cmd_mv(int argc, const char **argv, const char *prefix) 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; - const char *dst_w_slash; - const char **src_dir = NULL; - int src_dir_nr = 0, src_dir_alloc = 0; - struct strbuf a_src_dir = STRBUF_INIT; + struct strvec sources = STRVEC_INIT; + struct strvec dest_paths = STRVEC_INIT; + struct strvec destinations = STRVEC_INIT; + struct strvec submodule_gitfiles_to_free = STRVEC_INIT; + const char **submodule_gitfiles; + char *dst_w_slash = NULL; + struct strvec src_dir = STRVEC_INIT; enum update_mode *modes, dst_mode = 0; struct stat st, dest_st; - struct string_list src_for_dst = STRING_LIST_INIT_NODUP; + struct string_list src_for_dst = STRING_LIST_INIT_DUP; struct lock_file lock_file = LOCK_INIT; struct cache_entry *ce; - struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; - struct string_list dirty_paths = STRING_LIST_INIT_NODUP; + struct string_list only_match_skip_worktree = STRING_LIST_INIT_DUP; + struct string_list dirty_paths = STRING_LIST_INIT_DUP; + int ret; git_config(git_default_config, NULL); @@ -201,7 +218,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (repo_read_index(the_repository) < 0) die(_("index file corrupt")); - source = internal_prefix_pathspec(prefix, argv, argc, 0); + internal_prefix_pathspec(&sources, prefix, argv, argc, 0); CALLOC_ARRAY(modes, argc); /* @@ -212,45 +229,39 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags = KEEP_TRAILING_SLASH; if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) flags = 0; - dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags); - dst_w_slash = add_slash(dest_path[0]); - submodule_gitfile = xcalloc(argc, sizeof(char *)); + internal_prefix_pathspec(&dest_paths, prefix, argv + argc, 1, flags); + dst_w_slash = add_slash(dest_paths.v[0]); + submodule_gitfiles = xcalloc(argc, sizeof(char *)); - if (dest_path[0][0] == '\0') + if (dest_paths.v[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); - else if (!lstat(dest_path[0], &st) && - S_ISDIR(st.st_mode)) { - destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); + internal_prefix_pathspec(&destinations, dest_paths.v[0], argv, argc, DUP_BASENAME); + else if (!lstat(dest_paths.v[0], &st) && S_ISDIR(st.st_mode)) { + internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); + } else if (!path_in_sparse_checkout(dst_w_slash, the_repository->index) && + empty_dir_has_sparse_contents(dst_w_slash)) { + internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); + dst_mode = SKIP_WORKTREE_DIR; + } else if (argc != 1) { + die(_("destination '%s' is not a directory"), dest_paths.v[0]); } else { - if (!path_in_sparse_checkout(dst_w_slash, &the_index) && - empty_dir_has_sparse_contents(dst_w_slash)) { - destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); - dst_mode = SKIP_WORKTREE_DIR; - } else if (argc != 1) { - die(_("destination '%s' is not a directory"), dest_path[0]); - } else { - destination = dest_path; - /* - * <destination> is a file outside of sparse-checkout - * cone. Insist on cone mode here for backward - * compatibility. We don't want dst_mode to be assigned - * for a file when the repo is using no-cone mode (which - * is deprecated at this point) sparse-checkout. As - * SPARSE here is only considering cone-mode situation. - */ - if (!path_in_cone_mode_sparse_checkout(destination[0], &the_index)) - dst_mode = SPARSE; - } - } - if (dst_w_slash != dest_path[0]) { - free((char *)dst_w_slash); - dst_w_slash = NULL; + strvec_pushv(&destinations, dest_paths.v); + + /* + * <destination> is a file outside of sparse-checkout + * cone. Insist on cone mode here for backward + * compatibility. We don't want dst_mode to be assigned + * for a file when the repo is using no-cone mode (which + * is deprecated at this point) sparse-checkout. As + * SPARSE here is only considering cone-mode situation. + */ + if (!path_in_cone_mode_sparse_checkout(destinations.v[0], the_repository->index)) + dst_mode = SPARSE; } /* Checking */ for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; + const char *src = sources.v[i], *dst = destinations.v[i]; int length; const char *bad = NULL; int skip_sparse = 0; @@ -263,20 +274,22 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int pos; const struct cache_entry *ce; - pos = index_name_pos(&the_index, src, length); + pos = index_name_pos(the_repository->index, src, length); if (pos < 0) { - const char *src_w_slash = add_slash(src); - if (!path_in_sparse_checkout(src_w_slash, &the_index) && + char *src_w_slash = add_slash(src); + if (!path_in_sparse_checkout(src_w_slash, the_repository->index) && empty_dir_has_sparse_contents(src)) { + free(src_w_slash); modes[i] |= SKIP_WORKTREE_DIR; goto dir_check; } + free(src_w_slash); /* only error if existence is expected. */ if (!(modes[i] & SPARSE)) bad = _("bad source"); goto act_on_entry; } - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; if (!ce_skip_worktree(ce)) { bad = _("bad source"); goto act_on_entry; @@ -286,7 +299,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) goto act_on_entry; } /* Check if dst exists in index */ - if (index_name_pos(&the_index, dst, strlen(dst)) < 0) { + if (index_name_pos(the_repository->index, dst, strlen(dst)) < 0) { modes[i] |= SPARSE; goto act_on_entry; } @@ -310,12 +323,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dir_check: if (S_ISDIR(st.st_mode)) { - int j, dst_len, n; - int first = index_name_pos(&the_index, src, length), last; + char *dst_with_slash; + size_t dst_with_slash_len; + int j, n; + int first = index_name_pos(the_repository->index, src, length), last; if (first >= 0) { - prepare_move_submodule(src, first, - submodule_gitfile + i); + const char *path = submodule_gitfile_path(src, first); + if (path != SUBMODULE_WITH_GITDIR) + path = strvec_push(&submodule_gitfiles_to_free, path); + submodule_gitfiles[i] = path; goto act_on_entry; } else if (index_range_of_same_dir(src, length, &first, &last) < 1) { @@ -326,32 +343,35 @@ dir_check: /* last - first >= 1 */ modes[i] |= WORKING_DIRECTORY; - ALLOC_GROW(src_dir, src_dir_nr + 1, src_dir_alloc); - src_dir[src_dir_nr++] = src; + strvec_push(&src_dir, src); n = argc + last - first; - REALLOC_ARRAY(source, n); - REALLOC_ARRAY(destination, n); REALLOC_ARRAY(modes, n); - REALLOC_ARRAY(submodule_gitfile, n); + REALLOC_ARRAY(submodule_gitfiles, n); - dst = add_slash(dst); - dst_len = strlen(dst); + dst_with_slash = add_slash(dst); + dst_with_slash_len = strlen(dst_with_slash); for (j = 0; j < last - first; j++) { - const struct cache_entry *ce = the_index.cache[first + j]; + const struct cache_entry *ce = the_repository->index->cache[first + j]; const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, path + length + 1); + char *prefixed_path = prefix_path(dst_with_slash, dst_with_slash_len, path + length + 1); + + strvec_push(&sources, path); + strvec_push(&destinations, prefixed_path); + memset(modes + argc + j, 0, sizeof(enum update_mode)); modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; - submodule_gitfile[argc + j] = NULL; + submodule_gitfiles[argc + j] = NULL; + + free(prefixed_path); } + + free(dst_with_slash); argc += last - first; goto act_on_entry; } - if (!(ce = index_file_exists(&the_index, src, length, 0))) { + if (!(ce = index_file_exists(the_repository->index, src, length, 0))) { bad = _("not under version control"); goto act_on_entry; } @@ -387,7 +407,7 @@ dir_check: if (ignore_sparse && (dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && - index_entry_exists(&the_index, dst, strlen(dst))) { + index_entry_exists(the_repository->index, dst, strlen(dst))) { bad = _("destination exists in the index"); if (force) { if (verbose) @@ -404,12 +424,12 @@ dir_check: * option as a way to have a successful run. */ if (!ignore_sparse && - !path_in_sparse_checkout(src, &the_index)) { + !path_in_sparse_checkout(src, the_repository->index)) { string_list_append(&only_match_skip_worktree, src); skip_sparse = 1; } if (!ignore_sparse && - !path_in_sparse_checkout(dst, &the_index)) { + !path_in_sparse_checkout(dst, the_repository->index)) { string_list_append(&only_match_skip_worktree, dst); skip_sparse = 1; } @@ -428,28 +448,30 @@ act_on_entry: remove_entry: if (--argc > 0) { int n = argc - i; - MOVE_ARRAY(source + i, source + i + 1, n); - MOVE_ARRAY(destination + i, destination + i + 1, n); + strvec_remove(&sources, i); + strvec_remove(&destinations, i); MOVE_ARRAY(modes + i, modes + i + 1, n); - MOVE_ARRAY(submodule_gitfile + i, - submodule_gitfile + i + 1, n); + MOVE_ARRAY(submodule_gitfiles + i, + submodule_gitfiles + i + 1, n); i--; } } if (only_match_skip_worktree.nr) { advise_on_updating_sparse_paths(&only_match_skip_worktree); - if (!ignore_errors) - return 1; + if (!ignore_errors) { + ret = 1; + goto out; + } } for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; + const char *src = sources.v[i], *dst = destinations.v[i]; enum update_mode mode = modes[i]; int pos; int sparse_and_dirty = 0; struct checkout state = CHECKOUT_INIT; - state.istate = &the_index; + state.istate = the_repository->index; if (force) state.force = 1; @@ -464,26 +486,26 @@ remove_entry: continue; die_errno(_("renaming '%s' failed"), src); } - if (submodule_gitfile[i]) { + if (submodule_gitfiles[i]) { if (!update_path_in_gitmodules(src, dst)) gitmodules_modified = 1; - if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) + if (submodule_gitfiles[i] != SUBMODULE_WITH_GITDIR) connect_work_tree_and_git_dir(dst, - submodule_gitfile[i], + submodule_gitfiles[i], 1); } if (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR)) continue; - pos = index_name_pos(&the_index, src, strlen(src)); + pos = index_name_pos(the_repository->index, src, strlen(src)); assert(pos >= 0); if (!(mode & SPARSE) && !lstat(src, &st)) - sparse_and_dirty = ie_modified(&the_index, - the_index.cache[pos], + sparse_and_dirty = ie_modified(the_repository->index, + the_repository->index->cache[pos], &st, 0); - rename_index_entry_at(&the_index, pos, dst); + rename_index_entry_at(the_repository->index, pos, dst); if (ignore_sparse && core_apply_sparse_checkout && @@ -495,11 +517,11 @@ remove_entry: * should be added in a future patch. */ if ((mode & SPARSE) && - path_in_sparse_checkout(dst, &the_index)) { + path_in_sparse_checkout(dst, the_repository->index)) { /* from out-of-cone to in-cone */ - int dst_pos = index_name_pos(&the_index, dst, + int dst_pos = index_name_pos(the_repository->index, dst, strlen(dst)); - struct cache_entry *dst_ce = the_index.cache[dst_pos]; + struct cache_entry *dst_ce = the_repository->index->cache[dst_pos]; dst_ce->ce_flags &= ~CE_SKIP_WORKTREE; @@ -507,11 +529,11 @@ remove_entry: die(_("cannot checkout %s"), dst_ce->name); } else if ((dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && !(mode & SPARSE) && - !path_in_sparse_checkout(dst, &the_index)) { + !path_in_sparse_checkout(dst, the_repository->index)) { /* from in-cone to out-of-cone */ - int dst_pos = index_name_pos(&the_index, dst, + int dst_pos = index_name_pos(the_repository->index, dst, strlen(dst)); - struct cache_entry *dst_ce = the_index.cache[dst_pos]; + struct cache_entry *dst_ce = the_repository->index->cache[dst_pos]; /* * if src is clean, it will suffice to remove it @@ -535,41 +557,31 @@ remove_entry: } } - /* - * cleanup the empty src_dirs - */ - for (i = 0; i < src_dir_nr; i++) { - int dummy; - strbuf_addstr(&a_src_dir, src_dir[i]); - /* - * if entries under a_src_dir are all moved away, - * recursively remove a_src_dir to cleanup - */ - if (index_range_of_same_dir(a_src_dir.buf, a_src_dir.len, - &dummy, &dummy) < 1) { - remove_dir_recursively(&a_src_dir, 0); - } - strbuf_reset(&a_src_dir); - } - - strbuf_release(&a_src_dir); - free(src_dir); + remove_empty_src_dirs(src_dir.v, src_dir.nr); if (dirty_paths.nr) advise_on_moving_dirty_path(&dirty_paths); if (gitmodules_modified) - stage_updated_gitmodules(&the_index); + stage_updated_gitmodules(the_repository->index); - if (write_locked_index(&the_index, &lock_file, + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + ret = 0; + +out: + strvec_clear(&src_dir); + free(dst_w_slash); string_list_clear(&src_for_dst, 0); string_list_clear(&dirty_paths, 0); - UNLEAK(source); - UNLEAK(dest_path); - free(submodule_gitfile); + string_list_clear(&only_match_skip_worktree, 0); + strvec_clear(&sources); + strvec_clear(&dest_paths); + strvec_clear(&destinations); + strvec_clear(&submodule_gitfiles_to_free); + free(submodule_gitfiles); free(modes); - return 0; + return ret; } |
