Skip to content

Commit b7f9130

Browse files
vdyegitster
authored andcommitted
mv: refresh stat info for moved entry
Update the stat info of the moved index entry in 'rename_index_entry_at()' if the entry is up-to-date with the index. Internally, 'git mv' uses 'rename_index_entry_at()' to move the source index entry to the destination. However, it directly copies the stat info of the original cache entry, which will not reflect the 'ctime' of the file renaming operation that happened as part of the move. If a file is otherwise up-to-date with the index, that difference in 'ctime' will make the entry appear out-of-date until the next index-refreshing operation (e.g., 'git status'). Some commands, such as 'git reset', use the cached stat information to determine whether a file is up-to-date; if this information is incorrect, the command will fail when it should pass. In order to ensure a moved entry is evaluated as 'up-to-date' when appropriate, refresh the destination index entry's stat info in 'git mv' if and only if the file is up-to-date. Note that the test added in 't7001-mv.sh' requires a "sleep 1" to ensure the 'ctime' of the file creation will be definitively older than the 'ctime' of the renamed file in 'git mv'. Reported-by: Maximilian Reichel <[email protected]> Signed-off-by: Victoria Dye <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 4c53a8c commit b7f9130

File tree

2 files changed

+34
-2
lines changed

2 files changed

+34
-2
lines changed

read-cache.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
133133

134134
void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name)
135135
{
136-
struct cache_entry *old_entry = istate->cache[nr], *new_entry;
136+
struct cache_entry *old_entry = istate->cache[nr], *new_entry, *refreshed;
137137
int namelen = strlen(new_name);
138138

139139
new_entry = make_empty_cache_entry(istate, namelen);
@@ -146,7 +146,20 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
146146
cache_tree_invalidate_path(istate, old_entry->name);
147147
untracked_cache_remove_from_index(istate, old_entry->name);
148148
remove_index_entry_at(istate, nr);
149-
add_index_entry(istate, new_entry, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
149+
150+
/*
151+
* Refresh the new index entry. Using 'refresh_cache_entry' ensures
152+
* we only update stat info if the entry is otherwise up-to-date (i.e.,
153+
* the contents/mode haven't changed). This ensures that we reflect the
154+
* 'ctime' of the rename in the index without (incorrectly) updating
155+
* the cached stat info to reflect unstaged changes on disk.
156+
*/
157+
refreshed = refresh_cache_entry(istate, new_entry, CE_MATCH_REFRESH);
158+
if (refreshed && refreshed != new_entry) {
159+
add_index_entry(istate, refreshed, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
160+
discard_cache_entry(new_entry);
161+
} else
162+
add_index_entry(istate, new_entry, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
150163
}
151164

152165
void fill_stat_data(struct stat_data *sd, struct stat *st)

t/t7001-mv.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ test_description='git mv in subdirs'
44
. ./test-lib.sh
55
. "$TEST_DIRECTORY"/lib-diff-data.sh
66

7+
test_expect_success 'mv -f refreshes updated index entry' '
8+
echo test >bar &&
9+
git add bar &&
10+
git commit -m test &&
11+
12+
echo foo >foo &&
13+
git add foo &&
14+
15+
# Wait one second to ensure ctime of rename will differ from original
16+
# file creation ctime.
17+
sleep 1 &&
18+
git mv -f foo bar &&
19+
git reset --merge HEAD &&
20+
21+
# Verify the index has been reset
22+
git diff-files >out &&
23+
test_must_be_empty out
24+
'
25+
726
test_expect_success 'prepare reference tree' '
827
mkdir path0 path1 &&
928
COPYING_test_data >path0/COPYING &&

0 commit comments

Comments
 (0)