read-cache: disable renames in add_files_to_cache
add_files_to_cache() refreshes the index from worktree changes and does not need rename detection. When unmerged entries and a deleted stage-0 path are present together, rename detection can pair them and rewrite an unmerged diff pair to point at the deleted path. That later makes "git commit -a" and "git add -u" try to stat the deleted path and die with "unable to stat". Disable rename detection in this callback-driven staging path and add a regression test covering the crash. Signed-off-by: Nick Golden <blindmansion@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nick Golden committed
Apr 1, 2026 at 15:00 UTC
c0ce43376b178d876bce2073b81737ce49657def
2 files changed
+39
read-cache.c
+1
@@ -3975,6 +3975,7 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
3975
rev.diffopt.format_callback = update_callback;
3976
rev.diffopt.format_callback_data = &data;
3977
rev.diffopt.flags.override_submodule_config = 1;
3978
+ rev.diffopt.detect_rename = 0; /* staging worktree changes does not need renames */
3979
rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
3980
3981
/*
t/t2200-add-update.sh
+38
@@ -200,6 +200,44 @@ test_expect_success 'add -u resolves unmerged paths' '
200
test_cmp expect actual
201
'
202
203
+test_expect_success 'add -u avoids rename pairing on unmerged paths' '
204
+ test_create_repo rename-crash &&
205
+ (
206
+ cd rename-crash &&
207
+ test_seq 1 100 |
208
+ sed "s/.*/line &: same text/" >conflict.txt &&
209
+ cp conflict.txt bystander.txt &&
210
+ git add conflict.txt bystander.txt &&
211
+ git commit -m "initial: two files with identical content" &&
212
+ main_branch=$(git symbolic-ref --short HEAD) &&
213
+ git checkout -b feature &&
214
+ sed "s/^line 50:.*/line 50: FEATURE/" \
215
+ conflict.txt >conflict.txt.tmp &&
216
+ mv conflict.txt.tmp conflict.txt &&
217
+ git add conflict.txt &&
218
+ git commit -m "feature: modify line 50" &&
219
+ git checkout "$main_branch" &&
220
+ sed "s/^line 50:.*/line 50: MAIN/" \
221
+ conflict.txt >conflict.txt.tmp &&
222
+ mv conflict.txt.tmp conflict.txt &&
223
+ git add conflict.txt &&
224
+ git commit -m "main: modify line 50 differently" &&
225
+ test_must_fail git merge feature &&
226
+ rm bystander.txt &&
227
+ git add -u >out &&
228
+ test_must_be_empty out &&
229
+ git ls-files -u >actual &&
230
+ test_must_be_empty actual &&
231
+ git ls-files bystander.txt conflict.txt >actual &&
232
+ cat >expect <<-\EOF &&
233
+ conflict.txt
234
+ EOF
235
+ test_cmp expect actual &&
236
+ git diff-files --name-only >actual &&
237
+ test_must_be_empty actual
238
+ )
239
+'
240
+
241
test_expect_success '"add -u non-existent" should fail' '
242
test_must_fail git add -u non-existent &&
243
git ls-files >actual &&