1/*2* "git mv" builtin command3*4* Copyright (C) 2006 Johannes Schindelin5*/6#include "cache.h"7#include "builtin.h"8#include "dir.h"9#include "cache-tree.h"10#include "path-list.h"1112static const char builtin_mv_usage[] =13"git-mv [-n] [-f] (<source> <destination> | [-k] <source>... <destination>)";1415static const char **copy_pathspec(const char *prefix, const char **pathspec,16int count, int base_name)17{18int i;19const char **result = xmalloc((count + 1) * sizeof(const char *));20memcpy(result, pathspec, count * sizeof(const char *));21result[count] = NULL;22for (i = 0; i < count; i++) {23int length = strlen(result[i]);24if (length > 0 && result[i][length - 1] == '/') {25char *without_slash = xmalloc(length);26memcpy(without_slash, result[i], length - 1);27without_slash[length - 1] = '\0';28result[i] = without_slash;29}30if (base_name) {31const char *last_slash = strrchr(result[i], '/');32if (last_slash)33result[i] = last_slash + 1;34}35}36return get_pathspec(prefix, result);37}3839static void show_list(const char *label, struct path_list *list)40{41if (list->nr > 0) {42int i;43printf("%s", label);44for (i = 0; i < list->nr; i++)45printf("%s%s", i > 0 ? ", " : "", list->items[i].path);46putchar('\n');47}48}4950static const char *add_slash(const char *path)51{52int len = strlen(path);53if (path[len - 1] != '/') {54char *with_slash = xmalloc(len + 2);55memcpy(with_slash, path, len);56with_slash[len++] = '/';57with_slash[len] = 0;58return with_slash;59}60return path;61}6263static struct lock_file lock_file;6465int cmd_mv(int argc, const char **argv, const char *prefix)66{67int i, newfd, count;68int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;69const char **source, **destination, **dest_path;70enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;71struct stat st;72struct path_list overwritten = {NULL, 0, 0, 0};73struct path_list src_for_dst = {NULL, 0, 0, 0};74struct path_list added = {NULL, 0, 0, 0};75struct path_list deleted = {NULL, 0, 0, 0};76struct path_list changed = {NULL, 0, 0, 0};7778git_config(git_default_config);7980newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);81if (read_cache() < 0)82die("index file corrupt");8384for (i = 1; i < argc; i++) {85const char *arg = argv[i];8687if (arg[0] != '-')88break;89if (!strcmp(arg, "--")) {90i++;91break;92}93if (!strcmp(arg, "-n")) {94show_only = 1;95continue;96}97if (!strcmp(arg, "-f")) {98force = 1;99continue;100}101if (!strcmp(arg, "-k")) {102ignore_errors = 1;103continue;104}105usage(builtin_mv_usage);106}107count = argc - i - 1;108if (count < 1)109usage(builtin_mv_usage);110111source = copy_pathspec(prefix, argv + i, count, 0);112modes = xcalloc(count, sizeof(enum update_mode));113dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0);114115if (dest_path[0][0] == '\0')116/* special case: "." was normalized to "" */117destination = copy_pathspec(dest_path[0], argv + i, count, 1);118else if (!lstat(dest_path[0], &st) &&119S_ISDIR(st.st_mode)) {120dest_path[0] = add_slash(dest_path[0]);121destination = copy_pathspec(dest_path[0], argv + i, count, 1);122} else {123if (count != 1)124usage(builtin_mv_usage);125destination = dest_path;126}127128/* Checking */129for (i = 0; i < count; i++) {130const char *src = source[i], *dst = destination[i];131int length, src_is_dir;132const char *bad = NULL;133134if (show_only)135printf("Checking rename of '%s' to '%s'\n", src, dst);136137length = strlen(src);138if (lstat(src, &st) < 0)139bad = "bad source";140else if (!strncmp(src, dst, length) &&141(dst[length] == 0 || dst[length] == '/')) {142bad = "can not move directory into itself";143} else if ((src_is_dir = S_ISDIR(st.st_mode))144&& lstat(dst, &st) == 0)145bad = "cannot move directory over file";146else if (src_is_dir) {147const char *src_w_slash = add_slash(src);148int len_w_slash = length + 1;149int first, last;150151modes[i] = WORKING_DIRECTORY;152153first = cache_name_pos(src_w_slash, len_w_slash);154if (first >= 0)155die ("Huh? %.*s is in index?",156len_w_slash, src_w_slash);157158first = -1 - first;159for (last = first; last < active_nr; last++) {160const char *path = active_cache[last]->name;161if (strncmp(path, src_w_slash, len_w_slash))162break;163}164free((char *)src_w_slash);165166if (last - first < 1)167bad = "source directory is empty";168else {169int j, dst_len;170171if (last - first > 0) {172source = xrealloc(source,173(count + last - first)174* sizeof(char *));175destination = xrealloc(destination,176(count + last - first)177* sizeof(char *));178modes = xrealloc(modes,179(count + last - first)180* sizeof(enum update_mode));181}182183dst = add_slash(dst);184dst_len = strlen(dst) - 1;185186for (j = 0; j < last - first; j++) {187const char *path =188active_cache[first + j]->name;189source[count + j] = path;190destination[count + j] =191prefix_path(dst, dst_len,192path + length);193modes[count + j] = INDEX;194}195count += last - first;196}197} else if (lstat(dst, &st) == 0) {198bad = "destination exists";199if (force) {200/*201* only files can overwrite each other:202* check both source and destination203*/204if (S_ISREG(st.st_mode)) {205fprintf(stderr, "Warning: %s;"206" will overwrite!\n",207bad);208bad = NULL;209path_list_insert(dst, &overwritten);210} else211bad = "Cannot overwrite";212}213} else if (cache_name_pos(src, length) < 0)214bad = "not under version control";215else if (path_list_has_path(&src_for_dst, dst))216bad = "multiple sources for the same target";217else218path_list_insert(dst, &src_for_dst);219220if (bad) {221if (ignore_errors) {222if (--count > 0) {223memmove(source + i, source + i + 1,224(count - i) * sizeof(char *));225memmove(destination + i,226destination + i + 1,227(count - i) * sizeof(char *));228}229} else230die ("%s, source=%s, destination=%s",231bad, src, dst);232}233}234235for (i = 0; i < count; i++) {236const char *src = source[i], *dst = destination[i];237enum update_mode mode = modes[i];238if (show_only || verbose)239printf("Renaming %s to %s\n", src, dst);240if (!show_only && mode != INDEX &&241rename(src, dst) < 0 && !ignore_errors)242die ("renaming %s failed: %s", src, strerror(errno));243244if (mode == WORKING_DIRECTORY)245continue;246247if (cache_name_pos(src, strlen(src)) >= 0) {248path_list_insert(src, &deleted);249250/* destination can be a directory with 1 file inside */251if (path_list_has_path(&overwritten, dst))252path_list_insert(dst, &changed);253else254path_list_insert(dst, &added);255} else256path_list_insert(dst, &added);257}258259if (show_only) {260show_list("Changed : ", &changed);261show_list("Adding : ", &added);262show_list("Deleting : ", &deleted);263} else {264for (i = 0; i < changed.nr; i++) {265const char *path = changed.items[i].path;266int j = cache_name_pos(path, strlen(path));267struct cache_entry *ce = active_cache[j];268269if (j < 0)270die ("Huh? Cache entry for %s unknown?", path);271refresh_cache_entry(ce, 0);272}273274for (i = 0; i < added.nr; i++) {275const char *path = added.items[i].path;276add_file_to_index(path, verbose);277}278279for (i = 0; i < deleted.nr; i++) {280const char *path = deleted.items[i].path;281remove_file_from_cache(path);282cache_tree_invalidate_path(active_cache_tree, path);283}284285if (active_cache_changed) {286if (write_cache(newfd, active_cache, active_nr) ||287close(newfd) ||288commit_lock_file(&lock_file))289die("Unable to write new index file");290}291}292293return 0;294}