libgit2 / libgit2/libgit2

Windows: Symlinks are not treated as a directory in gitignore

Open
#6,250 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug git compatibility v.next
Dominant language
C
Stars
10.6k
Forks
2.7k
PR merge metrics
No merged PRs in 30d

Description

When there is a gitignore entry with a trailing slash, git_index_add_all will add symlinks that match the gitignore patterns on Windows but not other platforms.

Reproduction steps

The following Rust program demonstrates the problem. Sorry for only including a Rust reproduction, hopefully it is easy to see the equivalent C api calls (it is mostly 1:1). I couldn't figure out how to build a project with Visual Studio linking to libgit2 (if there are docs somewhere on how to do that, I'd be happy to make a C example).

use git2::*;
use std::fs;

fn main() {
    // Create a repo with a gitignore file and a symlink.
    fs::create_dir_all("repo/src").unwrap();
    fs::write("repo/src/samplefile", "test").unwrap();
    #[cfg(windows)]
    {
        std::os::windows::fs::symlink_dir("src", "repo/src2").unwrap();
    }
    #[cfg(unix)]
    {
        std::os::unix::fs::symlink("src", "repo/src2").unwrap();
    }
    fs::write("repo/.gitignore", "/src2/").unwrap();
    let repo = Repository::init("repo").unwrap();
    // Add all and commit.
    let mut index = repo.index().unwrap();
    index
        .add_all(["*"], git2::IndexAddOption::DEFAULT, None)
        .unwrap();
    index.write().unwrap();
    let tree_id = index.write_tree().unwrap();
    let tree_oid = repo.find_tree(tree_id).unwrap();
    let sig = repo.signature().unwrap();
    let oid = repo
        .commit(Some("HEAD"), &sig, &sig, "initial commit", &tree_oid, &[])
        .unwrap();
    // Check the contents of the commit.
    let commit = repo.find_commit(oid).unwrap();
    let mut names = Vec::new();
    commit
        .tree()
        .unwrap()
        .walk(TreeWalkMode::PreOrder, |_name, entry| {
            names.push(entry.name().unwrap().to_string());
            TreeWalkResult::Ok
        })
        .unwrap();
    names.sort();
    assert_eq!(names, [".gitignore", "samplefile", "src"]);
}

In this example, there is a file src/samplefile and a symlink src2 -> src. The .gitignore has a pattern /src2/ intending to prevent src2 from being added.

Expected behavior

git_index_add_all will only add .gitignore and src/samplefile.

Actual behavior

On Windows, it also adds the src2 symlink, causing the final assert to fail.

This seems to only happen with patterns with a trailing slash. A summary of the the gitignore pattern behavior:

  • /src2/ and src2/ fail on Windows.
  • /src2 and src2 work as expected on all platforms.

I've also tested with core.symlinks=true or false, it doesn't seem to affect it.

Version of libgit2 (release number or SHA1)

2a0d0bd19b5d13e2ab7f3780e094404828cbb9a7

Operating system(s) tested

Windows, macOS, Linux

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the git_index_add_all entry point and compare how trailing-slash gitignore patterns handle symlinks on Windows versus macOS and Linux. Reproduce the case with the provided Rust program, then add or update coverage so the expected tree excludes src2 while retaining src/samplefile.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, rust
Domain
devtools, operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.