-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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/
andsrc2/
fail on Windows./src2
andsrc2
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)
Operating system(s) tested
Windows, macOS, Linux
Activity
ethomson commentedon Mar 24, 2022
Thanks @ehuss - you need not apologize for including rust repro steps.
Windows junction points / symlinks are ... complex, so apologies for the question. When you say that it's a symlink - how was this created?
mklink /D ...
?ehuss commentedon Mar 24, 2022
Yea,
CreateSymbolicLinkW
with SYMBOLIC_LINK_FLAG_DIRECTORY, which I'm pretty certain is equivalent tomklink /d
.Byron commentedon Mar 17, 2024
I am investigating the issue for
gitoxide
and wanted to share some findings that might be relevant forlibgit2
as well.When reproducing the issue on MacOS with this script…
…and running
git status
in `excluded-symlinks-to-dir……I would have expected that
git
(git version 2.39.3 (Apple Git-146)) will only showsrc
as untracked as it will resolvesrc2
as directory which matchessrc2/
in the.gitignore
file.Thus it seems that Git also doesn't handle this case. Arguments can probably be made for and against this behaviour, such that…
…by the exclude/gitignore machinery of the directory walk.
It seems that
libgit2
changes the behaviour that I can reproduce with Git.ethomson commentedon Mar 17, 2024
I would expect us to match git's behavior on POSIXy systems. But I think that windows is just different. There's several different types of things that are "links", all (IIRC) implemented as "reparse points", and potentially all treated a little bit differently. We should probably match git for windows behavior here, but I don't think that there's been some well-designed, thoughtful plan around this, I think it's just "whatever mingw does". So it will most likely be a disappointing change.
.git/info/exclude
altsem/gitu#180