Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

git-mv: Keep moved index entries inact

The rewrite of git-mv from a shell script to a builtin was perhaps
a little too straightforward: the git add and git rm queues were
emulated directly, which resulted in a rather complicated code and
caused an inconsistent behaviour when moving dirty index entries;
git mv would update the entry based on working tree state,
except in case of overwrites, where the new entry would still have
sha1 of the old file.

This patch introduces rename_index_entry_at() into the index toolkit,
which will rename an entry while removing any entries the new entry
might render duplicate. This is then used in git mv instead
of all the file queues, resulting in a major simplification
of the code and an inevitable change in git mv -n output format.

Also the code used to refuse renaming overwriting symlink with a regular
file and vice versa; there is no need for that.

A few new tests have been added to the testsuite to reflect this change.

Signed-off-by: Petr Baudis <pasky@suse.cz>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information...
commit 81dc2307d0ad87a4da2e753a9d1b5586d6456eed 1 parent f6c52fe
authored July 21, 2008 gitster committed July 27, 2008
65  builtin-mv.c
@@ -36,18 +36,6 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
36 36
 	return get_pathspec(prefix, result);
37 37
 }
38 38
 
39  
-static void show_list(const char *label, struct string_list *list)
40  
-{
41  
-	if (list->nr > 0) {
42  
-		int i;
43  
-		printf("%s", label);
44  
-		for (i = 0; i < list->nr; i++)
45  
-			printf("%s%s", i > 0 ? ", " : "",
46  
-					list->items[i].string);
47  
-		putchar('\n');
48  
-	}
49  
-}
50  
-
51 39
 static const char *add_slash(const char *path)
52 40
 {
53 41
 	int len = strlen(path);
@@ -76,11 +64,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
76 64
 	const char **source, **destination, **dest_path;
77 65
 	enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
78 66
 	struct stat st;
79  
-	struct string_list overwritten = {NULL, 0, 0, 0};
80 67
 	struct string_list src_for_dst = {NULL, 0, 0, 0};
81  
-	struct string_list added = {NULL, 0, 0, 0};
82  
-	struct string_list deleted = {NULL, 0, 0, 0};
83  
-	struct string_list changed = {NULL, 0, 0, 0};
84 68
 
85 69
 	git_config(git_default_config, NULL);
86 70
 
@@ -185,12 +169,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
185 169
 				 * only files can overwrite each other:
186 170
 				 * check both source and destination
187 171
 				 */
188  
-				if (S_ISREG(st.st_mode)) {
  172
+				if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
189 173
 					fprintf(stderr, "Warning: %s;"
190 174
 							" will overwrite!\n",
191 175
 							bad);
192 176
 					bad = NULL;
193  
-					string_list_insert(dst, &overwritten);
194 177
 				} else
195 178
 					bad = "Cannot overwrite";
196 179
 			}
@@ -219,6 +202,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
219 202
 	for (i = 0; i < argc; i++) {
220 203
 		const char *src = source[i], *dst = destination[i];
221 204
 		enum update_mode mode = modes[i];
  205
+		int pos;
222 206
 		if (show_only || verbose)
223 207
 			printf("Renaming %s to %s\n", src, dst);
224 208
 		if (!show_only && mode != INDEX &&
@@ -228,45 +212,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
228 212
 		if (mode == WORKING_DIRECTORY)
229 213
 			continue;
230 214
 
231  
-		assert(cache_name_pos(src, strlen(src)) >= 0);
232  
-
233  
-		string_list_insert(src, &deleted);
234  
-		/* destination can be a directory with 1 file inside */
235  
-		if (string_list_has_string(&overwritten, dst))
236  
-			string_list_insert(dst, &changed);
237  
-		else
238  
-			string_list_insert(dst, &added);
  215
+		pos = cache_name_pos(src, strlen(src));
  216
+		assert(pos >= 0);
  217
+		if (!show_only)
  218
+			rename_cache_entry_at(pos, dst);
239 219
 	}
240 220
 
241  
-	if (show_only) {
242  
-		show_list("Changed  : ", &changed);
243  
-		show_list("Adding   : ", &added);
244  
-		show_list("Deleting : ", &deleted);
245  
-	} else {
246  
-		for (i = 0; i < changed.nr; i++) {
247  
-			const char *path = changed.items[i].string;
248  
-			int j = cache_name_pos(path, strlen(path));
249  
-			struct cache_entry *ce = active_cache[j];
250  
-
251  
-			if (j < 0)
252  
-				die ("Huh? Cache entry for %s unknown?", path);
253  
-			refresh_cache_entry(ce, 0);
254  
-		}
255  
-
256  
-		for (i = 0; i < added.nr; i++) {
257  
-			const char *path = added.items[i].string;
258  
-			if (add_file_to_cache(path, verbose ? ADD_CACHE_VERBOSE : 0))
259  
-				die("updating index entries failed");
260  
-		}
261  
-
262  
-		for (i = 0; i < deleted.nr; i++)
263  
-			remove_file_from_cache(deleted.items[i].string);
264  
-
265  
-		if (active_cache_changed) {
266  
-			if (write_cache(newfd, active_cache, active_nr) ||
267  
-			    commit_locked_index(&lock_file))
268  
-				die("Unable to write new index file");
269  
-		}
  221
+	if (active_cache_changed) {
  222
+		if (write_cache(newfd, active_cache, active_nr) ||
  223
+		    commit_locked_index(&lock_file))
  224
+			die("Unable to write new index file");
270 225
 	}
271 226
 
272 227
 	return 0;
2  cache.h
@@ -260,6 +260,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
260 260
 #define unmerged_cache() unmerged_index(&the_index)
261 261
 #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
262 262
 #define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
  263
+#define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name))
263 264
 #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
264 265
 #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
265 266
 #define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
@@ -370,6 +371,7 @@ extern int index_name_pos(const struct index_state *, const char *name, int name
370 371
 #define ADD_CACHE_JUST_APPEND 8		/* Append only; tree.c::read_tree() */
371 372
 extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
372 373
 extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
  374
+extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
373 375
 extern int remove_index_entry_at(struct index_state *, int pos);
374 376
 extern int remove_file_from_index(struct index_state *, const char *path);
375 377
 #define ADD_CACHE_VERBOSE 1
16  read-cache.c
@@ -38,6 +38,22 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
38 38
 	istate->cache_changed = 1;
39 39
 }
40 40
 
  41
+void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name)
  42
+{
  43
+	struct cache_entry *old = istate->cache[nr], *new;
  44
+	int namelen = strlen(new_name);
  45
+
  46
+	new = xmalloc(cache_entry_size(namelen));
  47
+	copy_cache_entry(new, old);
  48
+	new->ce_flags &= ~(CE_STATE_MASK | CE_NAMEMASK);
  49
+	new->ce_flags |= (namelen >= CE_NAMEMASK ? CE_NAMEMASK : namelen);
  50
+	memcpy(new->name, new_name, namelen + 1);
  51
+
  52
+	cache_tree_invalidate_path(istate->cache_tree, old->name);
  53
+	remove_index_entry_at(istate, nr);
  54
+	add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
  55
+}
  56
+
41 57
 /*
42 58
  * This only updates the "non-critical" parts of the directory
43 59
  * cache, ie the parts that aren't tracked by GIT, and only used
52  t/t7001-mv.sh
@@ -156,4 +156,56 @@ test_expect_success 'absolute pathname outside should fail' '(
156 156
 
157 157
 )'
158 158
 
  159
+test_expect_success 'git mv should not change sha1 of moved cache entry' '
  160
+
  161
+	rm -fr .git &&
  162
+	git init &&
  163
+	echo 1 >dirty &&
  164
+	git add dirty &&
  165
+	entry="$(git ls-files --stage dirty | cut -f 1)"
  166
+	git mv dirty dirty2 &&
  167
+	[ "$entry" = "$(git ls-files --stage dirty2 | cut -f 1)" ] &&
  168
+	echo 2 >dirty2 &&
  169
+	git mv dirty2 dirty &&
  170
+	[ "$entry" = "$(git ls-files --stage dirty | cut -f 1)" ]
  171
+
  172
+'
  173
+
  174
+rm -f dirty dirty2
  175
+
  176
+test_expect_success 'git mv should overwrite symlink to a file' '
  177
+
  178
+	rm -fr .git &&
  179
+	git init &&
  180
+	echo 1 >moved &&
  181
+	ln -s moved symlink &&
  182
+	git add moved symlink &&
  183
+	test_must_fail git mv moved symlink &&
  184
+	git mv -f moved symlink &&
  185
+	! test -e moved &&
  186
+	test -f symlink &&
  187
+	test "$(cat symlink)" = 1 &&
  188
+	git diff-files --quiet
  189
+
  190
+'
  191
+
  192
+rm -f moved symlink
  193
+
  194
+test_expect_success 'git mv should overwrite file with a symlink' '
  195
+
  196
+	rm -fr .git &&
  197
+	git init &&
  198
+	echo 1 >moved &&
  199
+	ln -s moved symlink &&
  200
+	git add moved symlink &&
  201
+	test_must_fail git mv symlink moved &&
  202
+	git mv -f symlink moved &&
  203
+	! test -e symlink &&
  204
+	test -h moved &&
  205
+	git diff-files --quiet
  206
+
  207
+'
  208
+
  209
+rm -f moved symlink
  210
+
159 211
 test_done

0 notes on commit 81dc230

Please sign in to comment.
Something went wrong with that request. Please try again.