1/*
2* "git mv" builtin command
3*
4* Copyright (C) 2006 Johannes Schindelin
5*/
6#include "cache.h"
7#include "builtin.h"
8#include "dir.h"
9#include "cache-tree.h"
10#include "path-list.h"
1112
static const char builtin_mv_usage[] =
13"git-mv [-n] [-f] (<source> <destination> | [-k] <source>... <destination>)";
1415
static 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}
3839
static 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}
4950
static 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}
6263
static struct lock_file lock_file;
6465
int 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};
7778
git_config(git_default_config);
7980
newfd = hold_locked_index(&lock_file, 1);
81if (read_cache() < 0)
82die("index file corrupt");
8384
for (i = 1; i < argc; i++) {
85const char *arg = argv[i];
8687
if (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);
110111
source = copy_pathspec(prefix, argv + i, count, 0);
112modes = xcalloc(count, sizeof(enum update_mode));
113dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0);
114115
if (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;
133134
if (show_only)
135printf("Checking rename of '%s' to '%s'\n", src, dst);
136137
length = 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;
150151
modes[i] = WORKING_DIRECTORY;
152153
first = cache_name_pos(src_w_slash, len_w_slash);
154if (first >= 0)
155die ("Huh? %.*s is in index?",
156len_w_slash, src_w_slash);
157158
first = -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);
165166
if (last - first < 1)
167bad = "source directory is empty";
168else {
169int j, dst_len;
170171
if (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}
182183
dst = add_slash(dst);
184dst_len = strlen(dst) - 1;
185186
for (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 destination
203*/
204if (S_ISREG(st.st_mode)) {
205fprintf(stderr, "Warning: %s;"
206" will overwrite!\n",
207bad);
208bad = NULL;
209path_list_insert(dst, &overwritten);
210} else
211bad = "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";
217else
218path_list_insert(dst, &src_for_dst);
219220
if (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} else
230die ("%s, source=%s, destination=%s",
231bad, src, dst);
232}
233}
234235
for (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));
243244
if (mode == WORKING_DIRECTORY)
245continue;
246247
if (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);
253else
254path_list_insert(dst, &added);
255} else
256path_list_insert(dst, &added);
257}
258259
if (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];
268269
if (j < 0)
270die ("Huh? Cache entry for %s unknown?", path);
271refresh_cache_entry(ce, 0);
272}
273274
for (i = 0; i < added.nr; i++) {
275const char *path = added.items[i].path;
276add_file_to_cache(path, verbose);
277}
278279
for (i = 0; i < deleted.nr; i++)
280remove_file_from_cache(deleted.items[i].path);
281282
if (active_cache_changed) {
283if (write_cache(newfd, active_cache, active_nr) ||
284close(newfd) ||
285commit_locked_index(&lock_file))
286die("Unable to write new index file");
287}
288}
289290
return 0;
291}