{{ message }}
Repository navigation
checkout: honor core.symlinks in the dirtiness check (#7363) - #7368
Merged
ethomson merged 1 commit intoOct 13, 2026
Merged
Conversation
When core.symlinks=false — a filesystem without symlink support, or one where the caller wants libgit2 to materialize symlinks as regular files via git_futils_fake_symlink (see blob_content_to_link) — a SAFE-checkout that only needs to *delete* an unmodified symlink entry was refused with GIT_ECONFLICT / "1 conflict prevents checkout", while git_status on the same workdir reported clean. is_filemode_changed already normalizes S_IFLNK when core.filemode=false (the respect_filemode branch). The status/diff side already does the right thing for symlinks via GIT_CONFIGMAP_SYMLINKS (diff_file.c diff_file_content__load_workdir). Only checkout's dirtiness check never consulted that config, so a workdir plain file (that libgit2 itself wrote) was compared to an S_IFLNK index/base entry and judged modified. Add a respect_symlinks parameter to is_filemode_changed and pass data->can_symlink at the three call sites in checkout_is_workdir_modified. When !respect_symlinks, both sides are normalized to GIT_FILEMODE_BLOB before comparison, matching what diff_file already does. The same phantom conflict also hit GIT_DELTA_MODIFIED (symlink whose target text changed between branches) and GIT_DELTA_TYPECHANGE (symlink <-> blob/tree); those flow through the same checkout_is_workdir_modified check and are fixed by this too. Reproduction: the standalone C harness in the issue, or the sequence git init && git config core.symlinks false && add a symlink entry via update-index --cacheinfo 120000 && commit && drop it && checkout — libgit2 1.8.0 (and current main pre-fix) returned rc=-13 with the '1 conflict' message; the CLI git accepts the same workdir as clean. With this change libgit2 rc=0 and removes the materialized file, and the untouched valid-audio path still takes the scaled compare. Reported-by: liz7up Signed-off-by: Brandon Barrante <aetherai@aethersystems.net>
liz7up
approved these changes
Sep 8, 2026
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fix for #7363.
What's happening
On a repo with
core.symlinks=false(a filesystem without symlinksupport, or a caller that asks libgit2 to materialize symlinks as
regular files via
git_futils_fake_symlinkinblob_content_to_link),a
GIT_CHECKOUT_SAFEthat only needs to delete an unmodifiedsymlink entry fails with
GIT_ECONFLICT/ "N conflicts preventcheckout" — while
git_statuson the same workdir reports fullyclean, and the git CLI does the same checkout without complaint.
The status/diff side already handles this:
diff_file_content__load_workdirlooks up
GIT_CONFIGMAP_SYMLINKSand treats a link entry as filecontent when symlinks are unsupported. Only
checkout.c's dirtinesscheck never consulted that config —
is_filemode_changednormalizesS_IFLNKonly forcore.filemode=false, and the workdir plain filethat libgit2 itself wrote is compared against an
S_IFLNKindex/baseentry as if the mode had changed.
The same phantom conflict hits
GIT_DELTA_MODIFIED(symlink whosetarget text changed between branches) and
GIT_DELTA_TYPECHANGE(symlink ↔ blob/tree) — anything that flows through
checkout_is_workdir_modifiedwith a link on either side.The change
Add a
respect_symlinksparameter tois_filemode_changedand passdata->can_symlinkat the three call sites incheckout_is_workdir_modified. When!respect_symlinks, both sidesare normalized to
GIT_FILEMODE_BLOBbefore comparison. The twobranches (
respect_filemodeandrespect_symlinks) are independent —respect_filemode=falsestill handles the executable bit as before,and the new block only touches
S_IFLNK.+15 / −4,
src/libgit2/checkout.conly.How it was verified
Ran the reporter's self-contained C reproducer verbatim, against a
Debugbuild ofmainat0551dfd:workdir status entries: 0 (clean),SAFE checkout B: rc=-13 message="1 conflict prevents checkout"—matches the report exactly.
workdir status entries: 0 (clean),SAFE checkout B: rc=0 message="", and the materializedAGENTS.mdis removed.CLAUDE.md(unchanged blob) still evaluatesas unmodified on the same path — verified with a stderr trace at
is_filemode_changedshowing the second call returns false the sameway it always did (
a=100644 b=100644 rs=0).Reporter
Thanks to @liz7up — the report has the syscall/API-level trace and
the exact fix direction, so this could ship as a small change with
GIT_CONFIGMAP_SYMLINKSalready plumbed throughcheckout_data.AI assistance
I used an AI assistant to help draft the change, the reproduction and
this description. Every line was read, checked and run by me.