Commit 81dc2307 authored by Petr Baudis's avatar Petr Baudis Committed by Junio C Hamano

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: default avatarPetr Baudis <pasky@suse.cz>
Signed-off-by: default avatarJunio C Hamano <gitster@pobox.com>
parent f6c52fe4
......@@ -36,18 +36,6 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
return get_pathspec(prefix, result);
}
static void show_list(const char *label, struct string_list *list)
{
if (list->nr > 0) {
int i;
printf("%s", label);
for (i = 0; i < list->nr; i++)
printf("%s%s", i > 0 ? ", " : "",
list->items[i].string);
putchar('\n');
}
}
static const char *add_slash(const char *path)
{
int len = strlen(path);
......@@ -76,11 +64,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
const char **source, **destination, **dest_path;
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
struct stat st;
struct string_list overwritten = {NULL, 0, 0, 0};
struct string_list src_for_dst = {NULL, 0, 0, 0};
struct string_list added = {NULL, 0, 0, 0};
struct string_list deleted = {NULL, 0, 0, 0};
struct string_list changed = {NULL, 0, 0, 0};
git_config(git_default_config, NULL);
......@@ -185,12 +169,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
* only files can overwrite each other:
* check both source and destination
*/
if (S_ISREG(st.st_mode)) {
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
fprintf(stderr, "Warning: %s;"
" will overwrite!\n",
bad);
bad = NULL;
string_list_insert(dst, &overwritten);
} else
bad = "Cannot overwrite";
}
......@@ -219,6 +202,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
for (i = 0; i < argc; i++) {
const char *src = source[i], *dst = destination[i];
enum update_mode mode = modes[i];
int pos;
if (show_only || verbose)
printf("Renaming %s to %s\n", src, dst);
if (!show_only && mode != INDEX &&
......@@ -228,45 +212,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (mode == WORKING_DIRECTORY)
continue;
assert(cache_name_pos(src, strlen(src)) >= 0);
string_list_insert(src, &deleted);
/* destination can be a directory with 1 file inside */
if (string_list_has_string(&overwritten, dst))
string_list_insert(dst, &changed);
else
string_list_insert(dst, &added);
pos = cache_name_pos(src, strlen(src));
assert(pos >= 0);
if (!show_only)
rename_cache_entry_at(pos, dst);
}
if (show_only) {
show_list("Changed : ", &changed);
show_list("Adding : ", &added);
show_list("Deleting : ", &deleted);
} else {
for (i = 0; i < changed.nr; i++) {
const char *path = changed.items[i].string;
int j = cache_name_pos(path, strlen(path));
struct cache_entry *ce = active_cache[j];
if (j < 0)
die ("Huh? Cache entry for %s unknown?", path);
refresh_cache_entry(ce, 0);
}
for (i = 0; i < added.nr; i++) {
const char *path = added.items[i].string;
if (add_file_to_cache(path, verbose ? ADD_CACHE_VERBOSE : 0))
die("updating index entries failed");
}
for (i = 0; i < deleted.nr; i++)
remove_file_from_cache(deleted.items[i].string);
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
die("Unable to write new index file");
}
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
die("Unable to write new index file");
}
return 0;
......
......@@ -260,6 +260,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
#define unmerged_cache() unmerged_index(&the_index)
#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
#define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
#define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name))
#define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
#define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
#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
#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */
extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
extern int remove_index_entry_at(struct index_state *, int pos);
extern int remove_file_from_index(struct index_state *, const char *path);
#define ADD_CACHE_VERBOSE 1
......
......@@ -38,6 +38,22 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
istate->cache_changed = 1;
}
void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name)
{
struct cache_entry *old = istate->cache[nr], *new;
int namelen = strlen(new_name);
new = xmalloc(cache_entry_size(namelen));
copy_cache_entry(new, old);
new->ce_flags &= ~(CE_STATE_MASK | CE_NAMEMASK);
new->ce_flags |= (namelen >= CE_NAMEMASK ? CE_NAMEMASK : namelen);
memcpy(new->name, new_name, namelen + 1);
cache_tree_invalidate_path(istate->cache_tree, old->name);
remove_index_entry_at(istate, nr);
add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}
/*
* This only updates the "non-critical" parts of the directory
* cache, ie the parts that aren't tracked by GIT, and only used
......
......@@ -156,4 +156,56 @@ test_expect_success 'absolute pathname outside should fail' '(
)'
test_expect_success 'git mv should not change sha1 of moved cache entry' '
rm -fr .git &&
git init &&
echo 1 >dirty &&
git add dirty &&
entry="$(git ls-files --stage dirty | cut -f 1)"
git mv dirty dirty2 &&
[ "$entry" = "$(git ls-files --stage dirty2 | cut -f 1)" ] &&
echo 2 >dirty2 &&
git mv dirty2 dirty &&
[ "$entry" = "$(git ls-files --stage dirty | cut -f 1)" ]
'
rm -f dirty dirty2
test_expect_success 'git mv should overwrite symlink to a file' '
rm -fr .git &&
git init &&
echo 1 >moved &&
ln -s moved symlink &&
git add moved symlink &&
test_must_fail git mv moved symlink &&
git mv -f moved symlink &&
! test -e moved &&
test -f symlink &&
test "$(cat symlink)" = 1 &&
git diff-files --quiet
'
rm -f moved symlink
test_expect_success 'git mv should overwrite file with a symlink' '
rm -fr .git &&
git init &&
echo 1 >moved &&
ln -s moved symlink &&
git add moved symlink &&
test_must_fail git mv symlink moved &&
git mv -f symlink moved &&
! test -e symlink &&
test -h moved &&
git diff-files --quiet
'
rm -f moved symlink
test_done
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment