@matheusbsbs / matheus-bagliano / commits / 81dc2307d0
Fork 0

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>

Petr Baudis committed Jul 21, 2008 at 02:25 UTC 81dc2307d0ad87a4da2e753a9d1b5586d6456eed
4 files changed +80 -55
builtin-mv.c
+10 -55
@@ -36,18 +36,6 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
36 return get_pathspec(prefix, result);
37 }
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 -
39 static const char *add_slash(const char *path)
40 {
41 int len = strlen(path);
@@ -76,11 +64,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
64 const char **source, **destination, **dest_path;
65 enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
66 struct stat st;
79 - struct string_list overwritten = {NULL, 0, 0, 0};
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};
68
69 git_config(git_default_config, NULL);
70
@@ -185,12 +169,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
169 * only files can overwrite each other:
170 * check both source and destination
171 */
188 - if (S_ISREG(st.st_mode)) {
172 + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
173 fprintf(stderr, "Warning: %s;"
174 " will overwrite!\n",
175 bad);
176 bad = NULL;
193 - string_list_insert(dst, &overwritten);
177 } else
178 bad = "Cannot overwrite";
179 }
@@ -219,6 +202,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
202 for (i = 0; i < argc; i++) {
203 const char *src = source[i], *dst = destination[i];
204 enum update_mode mode = modes[i];
205 + int pos;
206 if (show_only || verbose)
207 printf("Renaming %s to %s\n", src, dst);
208 if (!show_only && mode != INDEX &&
@@ -228,45 +212,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
212 if (mode == WORKING_DIRECTORY)
213 continue;
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);
219 }
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");
225 }
226
227 return 0;
cache.h
+2
@@ -260,6 +260,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
260 #define unmerged_cache() unmerged_index(&the_index)
261 #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
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))
264 #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
265 #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
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
371 #define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */
372 extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
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);
375 extern int remove_index_entry_at(struct index_state *, int pos);
376 extern int remove_file_from_index(struct index_state *, const char *path);
377 #define ADD_CACHE_VERBOSE 1
read-cache.c
+16
@@ -38,6 +38,22 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
38 istate->cache_changed = 1;
39 }
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 +
57 /*
58 * This only updates the "non-critical" parts of the directory
59 * cache, ie the parts that aren't tracked by GIT, and only used
t/t7001-mv.sh
+52
@@ -156,4 +156,56 @@ test_expect_success 'absolute pathname outside should fail' '(
156
157 )'
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 +
211 test_done