Skip to content

git_index_add_bypath accepts a path beyond a symbolic link, where git add refuses #7380

Description

@rawsun007

Reproduction steps

git refuses a pathspec whose non-final component is a symlink; git_index_add_bypath accepts it.

git init r && cd r
mkdir realdir && echo hi > realdir/file.txt
ln -s realdir linkdir
git add linkdir/file.txt
# fatal: pathspec 'linkdir/file.txt' is beyond a symbolic link
git_repository_open(&repo, "r");
git_repository_index(&index, repo);
int err = git_index_add_bypath(index, "linkdir/file.txt");
libgit2 1.9.7
git_index_add_bypath("linkdir/file.txt") -> 0 (accepted)
  index entry present: linkdir/file.txt

Committing that index produces a tree git cannot reconcile:

$ git ls-tree -r HEAD
100644 blob 45b983be...	linkdir/file.txt

$ git fsck            # clean
$ git status --porcelain
 D linkdir/file.txt
?? linkdir

The path is in the commit, fsck is happy, and git reports it deleted, because git will not walk through the symlink to find it. A checkout of that commit has to create linkdir as a directory, so the symlink the repository also tracks and the committed path cannot both exist.

Expected behavior

git_index_add_bypath rejects a path whose non-final component is a symlink, as git add does.

Actual behavior

It is accepted and an index entry is created at the literal path.

Version of libgit2 (release number or SHA1)

1.9.7

Operating system(s) tested

macOS 26.6, arm64. Reproduced with a C program linked against libgit2 directly; originally found through pygit2 in libgit2/pygit2#1375, where @calestyo asked for it to be raised here.

One related asymmetry from that thread, in case it bears on where the check belongs: git_index_add_all with the same path adds nothing and returns success, so the two entry points already disagree with each other as well as with git.

I have not checked whether this is deliberate — docs/differences-from-git.md does not mention it, but "libgit2 is not a pathspec parser" is a reasonable position and would make this a documentation note instead.

Filed by Claude Opus 5 (Claude Code) running under my account; the reproduction above was compiled and run rather than reasoned about.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions