checkout: phantom `GIT_ECONFLICT` when `core.symlinks=false` and the index contains symlink entries — status reports the workdir clean, SAFE checkout refuses with "N conflicts prevent checkout" · Issue #7363 · libgit2/libgit2 · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
checkout: phantom GIT_ECONFLICT when core.symlinks=false and the index contains symlink entries — status reports the workdir clean, SAFE checkout refuses with "N conflicts prevent checkout" #7363
On a repository where core.symlinks=false (filesystem without symlink support; libgit2 itself materializes symlinks as regular files containing the link-target text via git_futils_fake_symlink in blob_content_to_link), a GIT_CHECKOUT_SAFE that only needs to delete an unmodified symlink entry fails with GIT_ECONFLICT / "1 conflict prevents checkout" — while git_status on the same workdir reports fully clean.
git CLI (tested 2.43) behaves correctly in the identical situation: status is clean and the checkout succeeds.
The asymmetry: the diff/status code path honors GIT_CONFIGMAP_SYMLINKS (diff_file.c, diff_file_content__load_workdir: a GIT_FILEMODE_LINK entry is compared as file content when symlinks are unsupported), but checkout.c's dirtiness check does not — checkout_is_workdir_modified → is_filemode_changed(..., data->respect_filemode) only normalizes S_IFLNK when core.filemode is false, and never consults core.symlinks. Verified still present on main (2026-09-03): is_filemode_changed is unchanged there.
Reproduction
libgit2 v1.8.0, Linux x86_64. Self-contained repro (creates its own repo):
git_checkout_tree returns 0 and removes the materialized AGENTS.md, matching both git CLI (verified: git status clean, git checkout succeeds) and libgit2's own status/diff behavior.
src/libgit2/checkout.c, checkout_is_workdir_modified: the workdir-vs-baseline comparison calls is_filemode_changed(baseitem->mode, wditem->mode, data->respect_filemode), and is_filemode_changed only maps S_IFLNK to GIT_FILEMODE_BLOB when respect_filemode (from core.filemode) is false. With core.symlinks=false + core.filemode=true (the normal Linux-like case on a filesystem without symlink support), an index entry GIT_FILEMODE_LINK vs. a workdir regular file (that libgit2 itself wrote via git_futils_fake_symlink) is therefore judged "modified", and the GIT_DELTA_DELETED branch of checkout_action_with_wd turns that into CHECKOUT_ACTION__CONFLICT under GIT_CHECKOUT_SAFE.
The status/diff side does handle this correctly: src/libgit2/diff_file.c (diff_file_content__load_workdir) looks up GIT_CONFIGMAP_SYMLINKS and treats link entries as plain file content when symlinks are unsupported, which is why git_status reports the workdir clean while checkout refuses to touch it.
The same phantom conflict also hits GIT_DELTA_MODIFIED (symlink whose target changed between branches) and GIT_DELTA_TYPECHANGE (symlink ↔ blob/tree) — any SAFE checkout whose baseline contains a link entry and the workdir holds its materialized form.
Suggested fix direction
Make checkout's dirtiness check honor core.symlinks=false the same way diff does — e.g. carry a respect_symlinks flag (from GIT_CONFIGMAP_SYMLINKS, like can_symlink already is at checkout.c:2458) into checkout_is_workdir_modified and normalize S_IFLNK in is_filemode_changed when symlinks are unsupported.
Environment
libgit2 version: 1.8.0 (static), also reproduced against current main (code unchanged as of 2026-09-03)
OS: Linux x86_64 (also observed on HarmonyOS OHOS sandbox where the filesystem lacks symlink support, which is how we hit this in production)
Activity
checkout: honor core.symlinks in the dirtiness check (#7363)
Merge pull request #7368 from AetherAI3/fix/7363-checkout-honor-core-…