2006-07-26 01:52:35 +00:00
|
|
|
/*
|
|
|
|
* "git mv" builtin command
|
|
|
|
*
|
|
|
|
* Copyright (C) 2006 Johannes Schindelin
|
|
|
|
*/
|
|
|
|
#include "cache.h"
|
|
|
|
#include "builtin.h"
|
|
|
|
#include "dir.h"
|
|
|
|
#include "cache-tree.h"
|
2008-07-21 18:03:49 +00:00
|
|
|
#include "string-list.h"
|
2007-10-07 12:19:33 +00:00
|
|
|
#include "parse-options.h"
|
2006-07-26 01:52:35 +00:00
|
|
|
|
2007-10-07 12:19:33 +00:00
|
|
|
static const char * const builtin_mv_usage[] = {
|
2008-07-13 13:36:15 +00:00
|
|
|
"git mv [options] <source>... <destination>",
|
2007-10-07 12:19:33 +00:00
|
|
|
NULL
|
|
|
|
};
|
2006-07-26 01:52:35 +00:00
|
|
|
|
|
|
|
static const char **copy_pathspec(const char *prefix, const char **pathspec,
|
|
|
|
int count, int base_name)
|
|
|
|
{
|
2006-08-16 08:44:02 +00:00
|
|
|
int i;
|
2006-07-26 01:52:35 +00:00
|
|
|
const char **result = xmalloc((count + 1) * sizeof(const char *));
|
|
|
|
memcpy(result, pathspec, count * sizeof(const char *));
|
|
|
|
result[count] = NULL;
|
2006-08-16 08:44:02 +00:00
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
int length = strlen(result[i]);
|
2010-01-22 22:17:06 +00:00
|
|
|
int to_copy = length;
|
|
|
|
while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
|
|
|
|
to_copy--;
|
|
|
|
if (to_copy != length || base_name) {
|
|
|
|
char *it = xmemdupz(result[i], to_copy);
|
|
|
|
result[i] = base_name ? strdup(basename(it)) : it;
|
|
|
|
}
|
2006-07-26 01:52:35 +00:00
|
|
|
}
|
2008-03-07 07:29:40 +00:00
|
|
|
return get_pathspec(prefix, result);
|
2006-07-26 01:52:35 +00:00
|
|
|
}
|
|
|
|
|
2006-07-26 17:47:54 +00:00
|
|
|
static const char *add_slash(const char *path)
|
|
|
|
{
|
|
|
|
int len = strlen(path);
|
|
|
|
if (path[len - 1] != '/') {
|
|
|
|
char *with_slash = xmalloc(len + 2);
|
|
|
|
memcpy(with_slash, path, len);
|
2006-08-08 19:21:33 +00:00
|
|
|
with_slash[len++] = '/';
|
|
|
|
with_slash[len] = 0;
|
2006-07-26 17:47:54 +00:00
|
|
|
return with_slash;
|
|
|
|
}
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2006-07-26 01:52:35 +00:00
|
|
|
static struct lock_file lock_file;
|
|
|
|
|
2006-07-29 08:54:54 +00:00
|
|
|
int cmd_mv(int argc, const char **argv, const char *prefix)
|
2006-07-26 01:52:35 +00:00
|
|
|
{
|
2007-10-07 12:19:33 +00:00
|
|
|
int i, newfd;
|
2006-07-26 01:52:35 +00:00
|
|
|
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
|
2007-10-07 12:19:33 +00:00
|
|
|
struct option builtin_mv_options[] = {
|
2011-12-12 07:51:24 +00:00
|
|
|
OPT__VERBOSE(&verbose, "be verbose"),
|
2010-11-08 17:58:51 +00:00
|
|
|
OPT__DRY_RUN(&show_only, "dry run"),
|
2010-11-08 18:01:54 +00:00
|
|
|
OPT__FORCE(&force, "force move/rename even if target exists"),
|
2007-10-07 12:19:33 +00:00
|
|
|
OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
|
|
|
|
OPT_END(),
|
|
|
|
};
|
2006-07-26 01:52:35 +00:00
|
|
|
const char **source, **destination, **dest_path;
|
2006-07-26 17:47:54 +00:00
|
|
|
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
|
2006-07-26 01:52:35 +00:00
|
|
|
struct stat st;
|
2010-07-04 19:46:19 +00:00
|
|
|
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
|
2006-07-26 01:52:35 +00:00
|
|
|
|
2008-05-14 17:46:53 +00:00
|
|
|
git_config(git_default_config, NULL);
|
2006-07-26 01:52:35 +00:00
|
|
|
|
2009-05-23 18:53:12 +00:00
|
|
|
argc = parse_options(argc, argv, prefix, builtin_mv_options,
|
|
|
|
builtin_mv_usage, 0);
|
2007-10-07 12:19:33 +00:00
|
|
|
if (--argc < 1)
|
|
|
|
usage_with_options(builtin_mv_usage, builtin_mv_options);
|
2006-07-26 01:52:35 +00:00
|
|
|
|
2009-11-09 15:05:01 +00:00
|
|
|
newfd = hold_locked_index(&lock_file, 1);
|
|
|
|
if (read_cache() < 0)
|
2011-02-22 23:42:03 +00:00
|
|
|
die(_("index file corrupt"));
|
2009-11-09 15:05:01 +00:00
|
|
|
|
2007-10-07 12:19:33 +00:00
|
|
|
source = copy_pathspec(prefix, argv, argc, 0);
|
|
|
|
modes = xcalloc(argc, sizeof(enum update_mode));
|
|
|
|
dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
|
2006-07-26 01:52:35 +00:00
|
|
|
|
2006-08-18 10:42:39 +00:00
|
|
|
if (dest_path[0][0] == '\0')
|
|
|
|
/* special case: "." was normalized to "" */
|
2007-10-07 12:19:33 +00:00
|
|
|
destination = copy_pathspec(dest_path[0], argv, argc, 1);
|
2006-08-18 10:42:39 +00:00
|
|
|
else if (!lstat(dest_path[0], &st) &&
|
2006-07-26 17:47:54 +00:00
|
|
|
S_ISDIR(st.st_mode)) {
|
|
|
|
dest_path[0] = add_slash(dest_path[0]);
|
2007-10-07 12:19:33 +00:00
|
|
|
destination = copy_pathspec(dest_path[0], argv, argc, 1);
|
2006-07-26 17:47:54 +00:00
|
|
|
} else {
|
2007-10-07 12:19:33 +00:00
|
|
|
if (argc != 1)
|
2011-12-12 07:51:36 +00:00
|
|
|
die("destination '%s' is not a directory", dest_path[0]);
|
2006-07-26 01:52:35 +00:00
|
|
|
destination = dest_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checking */
|
2007-10-07 12:19:33 +00:00
|
|
|
for (i = 0; i < argc; i++) {
|
2006-08-19 14:52:21 +00:00
|
|
|
const char *src = source[i], *dst = destination[i];
|
|
|
|
int length, src_is_dir;
|
2006-07-26 01:52:35 +00:00
|
|
|
const char *bad = NULL;
|
|
|
|
|
|
|
|
if (show_only)
|
2011-02-22 23:42:03 +00:00
|
|
|
printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
|
2006-07-26 01:52:35 +00:00
|
|
|
|
2006-08-19 14:52:21 +00:00
|
|
|
length = strlen(src);
|
|
|
|
if (lstat(src, &st) < 0)
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("bad source");
|
2006-08-19 14:52:21 +00:00
|
|
|
else if (!strncmp(src, dst, length) &&
|
|
|
|
(dst[length] == 0 || dst[length] == '/')) {
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("can not move directory into itself");
|
2006-08-19 14:52:21 +00:00
|
|
|
} else if ((src_is_dir = S_ISDIR(st.st_mode))
|
|
|
|
&& lstat(dst, &st) == 0)
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("cannot move directory over file");
|
2006-08-19 14:52:21 +00:00
|
|
|
else if (src_is_dir) {
|
2006-12-03 19:42:47 +00:00
|
|
|
const char *src_w_slash = add_slash(src);
|
|
|
|
int len_w_slash = length + 1;
|
2006-08-19 14:52:21 +00:00
|
|
|
int first, last;
|
2006-07-26 17:47:54 +00:00
|
|
|
|
|
|
|
modes[i] = WORKING_DIRECTORY;
|
|
|
|
|
2006-12-03 19:42:47 +00:00
|
|
|
first = cache_name_pos(src_w_slash, len_w_slash);
|
2006-07-26 17:47:54 +00:00
|
|
|
if (first >= 0)
|
2011-02-22 23:42:03 +00:00
|
|
|
die (_("Huh? %.*s is in index?"),
|
2006-12-03 19:42:47 +00:00
|
|
|
len_w_slash, src_w_slash);
|
2006-07-26 17:47:54 +00:00
|
|
|
|
|
|
|
first = -1 - first;
|
|
|
|
for (last = first; last < active_nr; last++) {
|
|
|
|
const char *path = active_cache[last]->name;
|
2006-12-03 19:42:47 +00:00
|
|
|
if (strncmp(path, src_w_slash, len_w_slash))
|
2006-07-26 17:47:54 +00:00
|
|
|
break;
|
|
|
|
}
|
2006-12-03 19:42:47 +00:00
|
|
|
free((char *)src_w_slash);
|
2006-07-26 17:47:54 +00:00
|
|
|
|
|
|
|
if (last - first < 1)
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("source directory is empty");
|
2006-08-19 14:52:21 +00:00
|
|
|
else {
|
|
|
|
int j, dst_len;
|
2006-07-26 17:47:54 +00:00
|
|
|
|
|
|
|
if (last - first > 0) {
|
2006-08-26 14:16:18 +00:00
|
|
|
source = xrealloc(source,
|
2007-10-07 12:19:33 +00:00
|
|
|
(argc + last - first)
|
2006-07-26 17:47:54 +00:00
|
|
|
* sizeof(char *));
|
2006-08-26 14:16:18 +00:00
|
|
|
destination = xrealloc(destination,
|
2007-10-07 12:19:33 +00:00
|
|
|
(argc + last - first)
|
2006-07-26 17:47:54 +00:00
|
|
|
* sizeof(char *));
|
2006-08-26 14:16:18 +00:00
|
|
|
modes = xrealloc(modes,
|
2007-10-07 12:19:33 +00:00
|
|
|
(argc + last - first)
|
2006-07-26 17:47:54 +00:00
|
|
|
* sizeof(enum update_mode));
|
|
|
|
}
|
|
|
|
|
2006-08-19 14:52:21 +00:00
|
|
|
dst = add_slash(dst);
|
setup: sanitize absolute and funny paths in get_pathspec()
The prefix_path() function called from get_pathspec() is
responsible for translating list of user-supplied pathspecs to
list of pathspecs that is relative to the root of the work
tree. When working inside a subdirectory, the user-supplied
pathspecs are taken to be relative to the current subdirectory.
Among special path components in pathspecs, we used to accept
and interpret only "." ("the directory", meaning a no-op) and
".." ("up one level") at the beginning. Everything else was
passed through as-is.
For example, if you are in Documentation/ directory of the
project, you can name Documentation/howto/maintain-git.txt as:
howto/maintain-git.txt
../Documentation/howto/maitain-git.txt
../././Documentation/howto/maitain-git.txt
but not as:
howto/./maintain-git.txt
$(pwd)/howto/maintain-git.txt
This patch updates prefix_path() in several ways:
- If the pathspec is not absolute, prefix (i.e. the current
subdirectory relative to the root of the work tree, with
terminating slash, if not empty) and the pathspec is
concatenated first and used in the next step. Otherwise,
that absolute pathspec is used in the next step.
- Then special path components "." (no-op) and ".." (up one
level) are interpreted to simplify the path. It is an error
to have too many ".." to cause the intermediate result to
step outside of the input to this step.
- If the original pathspec was not absolute, the result from
the previous step is the resulting "sanitized" pathspec.
Otherwise, the result from the previous step is still
absolute, and it is an error if it does not begin with the
directory that corresponds to the root of the work tree. The
directory is stripped away from the result and is returned.
- In any case, the resulting pathspec in the array
get_pathspec() returns omit the ones that caused errors.
With this patch, the last two examples also behave as expected.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-01-29 06:44:27 +00:00
|
|
|
dst_len = strlen(dst);
|
2006-07-26 17:47:54 +00:00
|
|
|
|
|
|
|
for (j = 0; j < last - first; j++) {
|
|
|
|
const char *path =
|
|
|
|
active_cache[first + j]->name;
|
2007-10-07 12:19:33 +00:00
|
|
|
source[argc + j] = path;
|
|
|
|
destination[argc + j] =
|
2006-08-19 14:52:21 +00:00
|
|
|
prefix_path(dst, dst_len,
|
setup: sanitize absolute and funny paths in get_pathspec()
The prefix_path() function called from get_pathspec() is
responsible for translating list of user-supplied pathspecs to
list of pathspecs that is relative to the root of the work
tree. When working inside a subdirectory, the user-supplied
pathspecs are taken to be relative to the current subdirectory.
Among special path components in pathspecs, we used to accept
and interpret only "." ("the directory", meaning a no-op) and
".." ("up one level") at the beginning. Everything else was
passed through as-is.
For example, if you are in Documentation/ directory of the
project, you can name Documentation/howto/maintain-git.txt as:
howto/maintain-git.txt
../Documentation/howto/maitain-git.txt
../././Documentation/howto/maitain-git.txt
but not as:
howto/./maintain-git.txt
$(pwd)/howto/maintain-git.txt
This patch updates prefix_path() in several ways:
- If the pathspec is not absolute, prefix (i.e. the current
subdirectory relative to the root of the work tree, with
terminating slash, if not empty) and the pathspec is
concatenated first and used in the next step. Otherwise,
that absolute pathspec is used in the next step.
- Then special path components "." (no-op) and ".." (up one
level) are interpreted to simplify the path. It is an error
to have too many ".." to cause the intermediate result to
step outside of the input to this step.
- If the original pathspec was not absolute, the result from
the previous step is the resulting "sanitized" pathspec.
Otherwise, the result from the previous step is still
absolute, and it is an error if it does not begin with the
directory that corresponds to the root of the work tree. The
directory is stripped away from the result and is returned.
- In any case, the resulting pathspec in the array
get_pathspec() returns omit the ones that caused errors.
With this patch, the last two examples also behave as expected.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-01-29 06:44:27 +00:00
|
|
|
path + length + 1);
|
2007-10-07 12:19:33 +00:00
|
|
|
modes[argc + j] = INDEX;
|
2006-07-26 17:47:54 +00:00
|
|
|
}
|
2007-10-07 12:19:33 +00:00
|
|
|
argc += last - first;
|
2006-07-26 17:47:54 +00:00
|
|
|
}
|
2009-02-04 09:32:08 +00:00
|
|
|
} else if (cache_name_pos(src, length) < 0)
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("not under version control");
|
2009-02-04 09:32:08 +00:00
|
|
|
else if (lstat(dst, &st) == 0) {
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("destination exists");
|
2006-07-26 01:52:35 +00:00
|
|
|
if (force) {
|
|
|
|
/*
|
|
|
|
* only files can overwrite each other:
|
|
|
|
* check both source and destination
|
|
|
|
*/
|
2008-07-21 00:25:56 +00:00
|
|
|
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
|
mv: improve overwrite warning
When we try to "git mv" over an existing file, the error
message is fairly informative:
$ git mv one two
fatal: destination exists, source=one, destination=two
When the user forces the overwrite, we give a warning:
$ git mv -f one two
warning: destination exists; will overwrite!
This is less informative, but still sufficient in the simple
rename case, as there is only one rename happening.
But when moving files from one directory to another, it
becomes useless:
$ mkdir three
$ touch one two three/one
$ git add .
$ git mv one two three
fatal: destination exists, source=one, destination=three/one
$ git mv -f one two three
warning: destination exists; will overwrite!
The first message is helpful, but the second one gives us no
clue about what was overwritten. Let's mention the name of
the destination file:
$ git mv -f one two three
warning: overwriting 'three/one'
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-12-12 21:54:17 +00:00
|
|
|
warning(_("overwriting '%s'"), dst);
|
2006-07-26 01:52:35 +00:00
|
|
|
bad = NULL;
|
|
|
|
} else
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("Cannot overwrite");
|
2006-07-26 01:52:35 +00:00
|
|
|
}
|
2009-02-04 09:32:08 +00:00
|
|
|
} else if (string_list_has_string(&src_for_dst, dst))
|
2011-02-22 23:42:04 +00:00
|
|
|
bad = _("multiple sources for the same target");
|
2006-08-19 14:52:21 +00:00
|
|
|
else
|
2010-06-25 23:41:35 +00:00
|
|
|
string_list_insert(&src_for_dst, dst);
|
2006-07-26 01:52:35 +00:00
|
|
|
|
|
|
|
if (bad) {
|
|
|
|
if (ignore_errors) {
|
2007-10-07 12:19:33 +00:00
|
|
|
if (--argc > 0) {
|
2006-07-26 01:52:35 +00:00
|
|
|
memmove(source + i, source + i + 1,
|
2007-10-07 12:19:33 +00:00
|
|
|
(argc - i) * sizeof(char *));
|
2006-07-26 01:52:35 +00:00
|
|
|
memmove(destination + i,
|
|
|
|
destination + i + 1,
|
2007-10-07 12:19:33 +00:00
|
|
|
(argc - i) * sizeof(char *));
|
2009-01-14 17:03:22 +00:00
|
|
|
i--;
|
2006-07-26 01:52:35 +00:00
|
|
|
}
|
|
|
|
} else
|
2011-02-22 23:42:03 +00:00
|
|
|
die (_("%s, source=%s, destination=%s"),
|
2006-08-19 14:52:21 +00:00
|
|
|
bad, src, dst);
|
2006-07-26 01:52:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-10-07 12:19:33 +00:00
|
|
|
for (i = 0; i < argc; i++) {
|
2006-08-19 14:52:21 +00:00
|
|
|
const char *src = source[i], *dst = destination[i];
|
|
|
|
enum update_mode mode = modes[i];
|
2008-07-21 00:25:56 +00:00
|
|
|
int pos;
|
2006-07-26 01:52:35 +00:00
|
|
|
if (show_only || verbose)
|
2011-02-22 23:42:03 +00:00
|
|
|
printf(_("Renaming %s to %s\n"), src, dst);
|
2006-08-19 14:52:21 +00:00
|
|
|
if (!show_only && mode != INDEX &&
|
|
|
|
rename(src, dst) < 0 && !ignore_errors)
|
2011-02-22 23:42:03 +00:00
|
|
|
die_errno (_("renaming '%s' failed"), src);
|
2006-08-19 14:52:21 +00:00
|
|
|
|
|
|
|
if (mode == WORKING_DIRECTORY)
|
2006-07-26 17:47:54 +00:00
|
|
|
continue;
|
|
|
|
|
2008-07-21 00:25:56 +00:00
|
|
|
pos = cache_name_pos(src, strlen(src));
|
|
|
|
assert(pos >= 0);
|
|
|
|
if (!show_only)
|
|
|
|
rename_cache_entry_at(pos, dst);
|
2006-07-26 01:52:35 +00:00
|
|
|
}
|
|
|
|
|
2008-07-21 00:25:56 +00:00
|
|
|
if (active_cache_changed) {
|
|
|
|
if (write_cache(newfd, active_cache, active_nr) ||
|
|
|
|
commit_locked_index(&lock_file))
|
2011-02-22 23:42:03 +00:00
|
|
|
die(_("Unable to write new index file"));
|
2006-07-26 01:52:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|