Skip to content

Commit 02b6f14

Browse files
authored
fix git repo detection on symlinks (#11732)
1 parent 57ec3b7 commit 02b6f14

File tree

2 files changed

+49
-10
lines changed

2 files changed

+49
-10
lines changed

helix-vcs/src/git.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,24 @@ use crate::FileChange;
2222
#[cfg(test)]
2323
mod test;
2424

25+
#[inline]
26+
fn get_repo_dir(file: &Path) -> Result<&Path> {
27+
file.parent().context("file has no parent directory")
28+
}
29+
2530
pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
2631
debug_assert!(!file.exists() || file.is_file());
2732
debug_assert!(file.is_absolute());
33+
let file = gix::path::realpath(file).context("resolve symlinks")?;
2834

2935
// TODO cache repository lookup
3036

31-
let repo_dir = file.parent().context("file has no parent directory")?;
37+
let repo_dir = get_repo_dir(&file)?;
3238
let repo = open_repo(repo_dir)
3339
.context("failed to open git repo")?
3440
.to_thread_local();
3541
let head = repo.head_commit()?;
36-
let file_oid = find_file_in_commit(&repo, &head, file)?;
42+
let file_oid = find_file_in_commit(&repo, &head, &file)?;
3743

3844
let file_object = repo.find_object(file_oid)?;
3945
let data = file_object.detach().data;
@@ -56,7 +62,9 @@ pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
5662
pub fn get_current_head_name(file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
5763
debug_assert!(!file.exists() || file.is_file());
5864
debug_assert!(file.is_absolute());
59-
let repo_dir = file.parent().context("file has no parent directory")?;
65+
let file = gix::path::realpath(file).context("resolve symlinks")?;
66+
67+
let repo_dir = get_repo_dir(&file)?;
6068
let repo = open_repo(repo_dir)
6169
.context("failed to open git repo")?
6270
.to_thread_local();

helix-vcs/src/git/test.rs

+38-7
Original file line numberDiff line numberDiff line change
@@ -98,24 +98,55 @@ fn directory() {
9898
assert!(git::get_diff_base(&dir).is_err());
9999
}
100100

101-
/// Test that `get_file_head` does not return content for a symlink.
102-
/// This is important to correctly cover cases where a symlink is removed and replaced by a file.
103-
/// If the contents of the symlink object were returned a diff between a path and the actual file would be produced (bad ui).
101+
/// Test that `get_diff_base` resolves symlinks so that the same diff base is
102+
/// used as the target file.
103+
///
104+
/// This is important to correctly cover cases where a symlink is removed and
105+
/// replaced by a file. If the contents of the symlink object were returned
106+
/// a diff between a literal file path and the actual file content would be
107+
/// produced (bad ui).
104108
#[cfg(any(unix, windows))]
105109
#[test]
106110
fn symlink() {
107111
#[cfg(unix)]
108112
use std::os::unix::fs::symlink;
109113
#[cfg(not(unix))]
110114
use std::os::windows::fs::symlink_file as symlink;
115+
111116
let temp_git = empty_git_repo();
112117
let file = temp_git.path().join("file.txt");
113-
let contents = b"foo".as_slice();
114-
File::create(&file).unwrap().write_all(contents).unwrap();
118+
let contents = Vec::from(b"foo");
119+
File::create(&file).unwrap().write_all(&contents).unwrap();
115120
let file_link = temp_git.path().join("file_link.txt");
121+
116122
symlink("file.txt", &file_link).unwrap();
123+
create_commit(temp_git.path(), true);
124+
125+
assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
126+
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
127+
}
128+
129+
/// Test that `get_diff_base` returns content when the file is a symlink to
130+
/// another file that is in a git repo, but the symlink itself is not.
131+
#[cfg(any(unix, windows))]
132+
#[test]
133+
fn symlink_to_git_repo() {
134+
#[cfg(unix)]
135+
use std::os::unix::fs::symlink;
136+
#[cfg(not(unix))]
137+
use std::os::windows::fs::symlink_file as symlink;
138+
139+
let temp_dir = tempfile::tempdir().expect("create temp dir");
140+
let temp_git = empty_git_repo();
117141

142+
let file = temp_git.path().join("file.txt");
143+
let contents = Vec::from(b"foo");
144+
File::create(&file).unwrap().write_all(&contents).unwrap();
118145
create_commit(temp_git.path(), true);
119-
assert!(git::get_diff_base(&file_link).is_err());
120-
assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));
146+
147+
let file_link = temp_dir.path().join("file_link.txt");
148+
symlink(&file, &file_link).unwrap();
149+
150+
assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
151+
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
121152
}

0 commit comments

Comments
 (0)