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] == '/') {25result[i] = xmemdupz(result[i], length - 1);26}27if (base_name) {28const char *last_slash = strrchr(result[i], '/');29if (last_slash)30result[i] = last_slash + 1;31}32}33return get_pathspec(prefix, result);34}3536static void show_list(const char *label, struct path_list *list)37{38if (list->nr > 0) {39int i;40printf("%s", label);41for (i = 0; i < list->nr; i++)42printf("%s%s", i > 0 ? ", " : "", list->items[i].path);43putchar('\n');44}45}4647static const char *add_slash(const char *path)48{49int len = strlen(path);50if (path[len - 1] != '/') {51char *with_slash = xmalloc(len + 2);52memcpy(with_slash, path, len);53with_slash[len++] = '/';54with_slash[len] = 0;55return with_slash;56}57return path;58}5960static struct lock_file lock_file;6162int cmd_mv(int argc, const char **argv, const char *prefix)63{64int i, newfd, count;65int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;66const char **source, **destination, **dest_path;67enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;68struct stat st;69struct path_list overwritten = {NULL, 0, 0, 0};70struct path_list src_for_dst = {NULL, 0, 0, 0};71struct path_list added = {NULL, 0, 0, 0};72struct path_list deleted = {NULL, 0, 0, 0};73struct path_list changed = {NULL, 0, 0, 0};7475git_config(git_default_config);7677newfd = hold_locked_index(&lock_file, 1);78if (read_cache() < 0)79die("index file corrupt");8081for (i = 1; i < argc; i++) {82const char *arg = argv[i];8384if (arg[0] != '-')85break;86if (!strcmp(arg, "--")) {87i++;88break;89}90if (!strcmp(arg, "-n")) {91show_only = 1;92continue;93}94if (!strcmp(arg, "-f")) {95force = 1;96continue;97}98if (!strcmp(arg, "-k")) {99ignore_errors = 1;100continue;101}102usage(builtin_mv_usage);103}104count = argc - i - 1;105if (count < 1)106usage(builtin_mv_usage);107108source = copy_pathspec(prefix, argv + i, count, 0);109modes = xcalloc(count, sizeof(enum update_mode));110dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0);111112if (dest_path[0][0] == '\0')113/* special case: "." was normalized to "" */114destination = copy_pathspec(dest_path[0], argv + i, count, 1);115else if (!lstat(dest_path[0], &st) &&116S_ISDIR(st.st_mode)) {117dest_path[0] = add_slash(dest_path[0]);118destination = copy_pathspec(dest_path[0], argv + i, count, 1);119} else {120if (count != 1)121usage(builtin_mv_usage);122destination = dest_path;123}124125/* Checking */126for (i = 0; i < count; i++) {127const char *src = source[i], *dst = destination[i];128int length, src_is_dir;129const char *bad = NULL;130131if (show_only)132printf("Checking rename of '%s' to '%s'\n", src, dst);133134length = strlen(src);135if (lstat(src, &st) < 0)136bad = "bad source";137else if (!strncmp(src, dst, length) &&138(dst[length] == 0 || dst[length] == '/')) {139bad = "can not move directory into itself";140} else if ((src_is_dir = S_ISDIR(st.st_mode))141&& lstat(dst, &st) == 0)142bad = "cannot move directory over file";143else if (src_is_dir) {144const char *src_w_slash = add_slash(src);145int len_w_slash = length + 1;146int first, last;147148modes[i] = WORKING_DIRECTORY;149150first = cache_name_pos(src_w_slash, len_w_slash);151if (first >= 0)152die ("Huh? %.*s is in index?",153len_w_slash, src_w_slash);154155first = -1 - first;156for (last = first; last < active_nr; last++) {157const char *path = active_cache[last]->name;158if (strncmp(path, src_w_slash, len_w_slash))159break;160}161free((char *)src_w_slash);162163if (last - first < 1)164bad = "source directory is empty";165else {166int j, dst_len;167168if (last - first > 0) {169source = xrealloc(source,170(count + last - first)171* sizeof(char *));172destination = xrealloc(destination,173(count + last - first)174* sizeof(char *));175modes = xrealloc(modes,176(count + last - first)177* sizeof(enum update_mode));178}179180dst = add_slash(dst);181dst_len = strlen(dst) - 1;182183for (j = 0; j < last - first; j++) {184const char *path =185active_cache[first + j]->name;186source[count + j] = path;187destination[count + j] =188prefix_path(dst, dst_len,189path + length);190modes[count + j] = INDEX;191}192count += last - first;193}194} else if (lstat(dst, &st) == 0) {195bad = "destination exists";196if (force) {197/*198* only files can overwrite each other:199* check both source and destination200*/201if (S_ISREG(st.st_mode)) {202fprintf(stderr, "Warning: %s;"203" will overwrite!\n",204bad);205bad = NULL;206path_list_insert(dst, &overwritten);207} else208bad = "Cannot overwrite";209}210} else if (cache_name_pos(src, length) < 0)211bad = "not under version control";212else if (path_list_has_path(&src_for_dst, dst))213bad = "multiple sources for the same target";214else215path_list_insert(dst, &src_for_dst);216217if (bad) {218if (ignore_errors) {219if (--count > 0) {220memmove(source + i, source + i + 1,221(count - i) * sizeof(char *));222memmove(destination + i,223destination + i + 1,224(count - i) * sizeof(char *));225}226} else227die ("%s, source=%s, destination=%s",228bad, src, dst);229}230}231232for (i = 0; i < count; i++) {233const char *src = source[i], *dst = destination[i];234enum update_mode mode = modes[i];235if (show_only || verbose)236printf("Renaming %s to %s\n", src, dst);237if (!show_only && mode != INDEX &&238rename(src, dst) < 0 && !ignore_errors)239die ("renaming %s failed: %s", src, strerror(errno));240241if (mode == WORKING_DIRECTORY)242continue;243244if (cache_name_pos(src, strlen(src)) >= 0) {245path_list_insert(src, &deleted);246247/* destination can be a directory with 1 file inside */248if (path_list_has_path(&overwritten, dst))249path_list_insert(dst, &changed);250else251path_list_insert(dst, &added);252} else253path_list_insert(dst, &added);254}255256if (show_only) {257show_list("Changed : ", &changed);258show_list("Adding : ", &added);259show_list("Deleting : ", &deleted);260} else {261for (i = 0; i < changed.nr; i++) {262const char *path = changed.items[i].path;263int j = cache_name_pos(path, strlen(path));264struct cache_entry *ce = active_cache[j];265266if (j < 0)267die ("Huh? Cache entry for %s unknown?", path);268refresh_cache_entry(ce, 0);269}270271for (i = 0; i < added.nr; i++) {272const char *path = added.items[i].path;273add_file_to_cache(path, verbose);274}275276for (i = 0; i < deleted.nr; i++)277remove_file_from_cache(deleted.items[i].path);278279if (active_cache_changed) {280if (write_cache(newfd, active_cache, active_nr) ||281close(newfd) ||282commit_locked_index(&lock_file))283die("Unable to write new index file");284}285}286287return 0;288}