mv: let 'git mv file no-such-dir/' error out
Git used to trim the trailing slash, and make the command equivalent to 'git mv file no-such-dir', which created the file no-such-dir (while the trailing slash explicitly stated that it could only be a directory). This patch skips the trailing slash removal for the destination path. The path with its trailing slash is passed to rename(2), which errors out with the appropriate message: $ git mv file no-such-dir/ fatal: renaming 'file' failed: Not a directory Original-patch-by: Duy Nguyen <pclouds@gmail.com> Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Matthieu Moy committed
Dec 3, 2013 at 09:32 UTC
c57f6281ff847db7094d708f5c5b939d378f7120
2 files changed
+45
-7
builtin/mv.c
+16
-7
@@ -16,9 +16,12 @@ static const char * const builtin_mv_usage[] = {
16
NULL
17
};
18
19
+#define DUP_BASENAME 1
20
+#define KEEP_TRAILING_SLASH 2
21
+
22
static const char **internal_copy_pathspec(const char *prefix,
23
const char **pathspec,
21
- int count, int base_name)
24
+ int count, unsigned flags)
25
{
26
int i;
27
const char **result = xmalloc((count + 1) * sizeof(const char *));
@@ -27,11 +30,12 @@ static const char **internal_copy_pathspec(const char *prefix,
30
for (i = 0; i < count; i++) {
31
int length = strlen(result[i]);
32
int to_copy = length;
30
- while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
33
+ while (!(flags & KEEP_TRAILING_SLASH) &&
34
+ to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
35
to_copy--;
32
- if (to_copy != length || base_name) {
36
+ if (to_copy != length || flags & DUP_BASENAME) {
37
char *it = xmemdupz(result[i], to_copy);
34
- if (base_name) {
38
+ if (flags & DUP_BASENAME) {
39
result[i] = xstrdup(basename(it));
40
free(it);
41
} else
@@ -87,16 +91,21 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
91
92
source = internal_copy_pathspec(prefix, argv, argc, 0);
93
modes = xcalloc(argc, sizeof(enum update_mode));
90
- dest_path = internal_copy_pathspec(prefix, argv + argc, 1, 0);
94
+ /*
95
+ * Keep trailing slash, needed to let
96
+ * "git mv file no-such-dir/" error out.
97
+ */
98
+ dest_path = internal_copy_pathspec(prefix, argv + argc, 1,
99
+ KEEP_TRAILING_SLASH);
100
submodule_gitfile = xcalloc(argc, sizeof(char *));
101
102
if (dest_path[0][0] == '\0')
103
/* special case: "." was normalized to "" */
95
- destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
104
+ destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
105
else if (!lstat(dest_path[0], &st) &&
106
S_ISDIR(st.st_mode)) {
107
dest_path[0] = add_slash(dest_path[0]);
99
- destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
108
+ destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
109
} else {
110
if (argc != 1)
111
die("destination '%s' is not a directory", dest_path[0]);
t/t7001-mv.sh
+29
@@ -70,6 +70,35 @@ test_expect_success \
70
rm -f idontexist untracked1 untracked2 \
71
path0/idontexist path0/untracked1 path0/untracked2 \
72
.git/index.lock
73
+rmdir path1
74
+
75
+test_expect_success \
76
+ 'moving to absent target with trailing slash' \
77
+ 'test_must_fail git mv path0/COPYING no-such-dir/ &&
78
+ test_must_fail git mv path0/COPYING no-such-dir// &&
79
+ git mv path0/ no-such-dir/ &&
80
+ test_path_is_dir no-such-dir'
81
+
82
+test_expect_success \
83
+ 'clean up' \
84
+ 'git reset --hard'
85
+
86
+test_expect_success \
87
+ 'moving to existing untracked target with trailing slash' \
88
+ 'mkdir path1 &&
89
+ git mv path0/ path1/ &&
90
+ test_path_is_dir path1/path0/'
91
+
92
+test_expect_success \
93
+ 'moving to existing tracked target with trailing slash' \
94
+ 'mkdir path2 &&
95
+ >path2/file && git add path2/file &&
96
+ git mv path1/path0/ path2/ &&
97
+ test_path_is_dir path2/path0/'
98
+
99
+test_expect_success \
100
+ 'clean up' \
101
+ 'git reset --hard'
102
103
test_expect_success \
104
'adding another file' \