Skip to content

index: improve file/dir conflicts handling - #7332

Merged
ethomson merged 1 commit into
libgit2:mainfrom
tarann26:fix-7160-index-df-conflict-position
Aug 3, 2026
Merged

ethomson merged 1 commit into
libgit2:mainfrom
tarann26:fix-7160-index-df-conflict-position

Conversation

@tarann26

Copy link
Copy Markdown
Contributor

Problem

git_index_add silently ignores a file/directory conflict: adding a blob at a path that collides with an existing tree entry (or vice versa) should replace the conflicting entry the way git does, but the stale entry is left in place and the add "succeeds".

Cause

In index_existing_and_best() (src/libgit2/index.c), when the entry is not already in the index, index_find has computed pos — the sorted insertion position — but the function discards it and hardcodes the out-param to 0:

    *existing = NULL;
    *existing_position = 0;   /* discards the computed pos */
    *best = NULL;

The sole caller, index_insert, passes that position into check_file_directory_collision() → has_file_name(), which scans forward from it. Starting the scan at 0 instead of the real insertion position means an earlier-sorting sibling entry can end the prefix scan before the actual conflict is reached, so the collision is never found. The bug is only observable when the insertion position is non-zero — which is why the existing collision tests (each with a single prior entry, whose insertion position happens to be 0) don't catch it.

Fix

Report the real insertion position on the not-found branch:

-    *existing_position = 0;
+    *existing_position = pos;

pos is valid here: git__bsearch sets it to the insertion index even on GIT_ENOTFOUND, and the stage-matching loop just below already relies on it. index_existing_and_best is static with one caller, and nothing depends on the old existing == NULL ⇒ position == 0 behaviour.

Test

Adds add_blob_with_conflicting_dir_not_at_start, which seeds sibling entries so the conflicting blobtree lands at a non-zero insertion position, reproducing the missed conflict that the fix resolves.

Fixes #7160.

index_existing_and_best() computes the correct sorted insertion
position via index_find(), and even uses that position for the
stage-matching loop a few lines later, but on the "entry not found"
branch it discarded the computed position and hardcoded the
*existing_position out-parameter to 0 instead.

index_insert() passes this position straight through to
check_file_directory_collision() and has_file_name(), which scan
forward from it looking for a directory/file conflict. Starting the
scan from 0 instead of the real insertion point made it bail out on
the first unrelated entry sorted before the conflict, so
git_index_add() silently accepted paths that collide with an
existing file/directory and left the stale conflicting entry behind.

Use the already-computed position instead of hardcoding 0. Add a
regression test with multiple sibling entries, since the existing
collision tests only ever had a single prior index entry, where the
correct position happens to be 0 anyway and the bug stayed hidden.

Fixes libgit2#7160
@ethomson ethomson changed the title index: report the real insertion position in the D/F conflict check index: improve file/dir conflicts handling Aug 3, 2026
@ethomson
ethomson merged commit 939362a into libgit2:main Aug 3, 2026
22 checks passed
@ethomson ethomson added the bug label Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

git_index_add ignores file/dir conflicts

2 participants