Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix git repo detection on symlinks #11732

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions helix-vcs/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@ use crate::FileChange;
#[cfg(test)]
mod test;

#[inline]
fn get_repo_dir(file: &Path) -> Result<&Path> {
file.parent().context("file has no parent directory")
}

pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());
let file = gix::path::realpath(file).context("resolve symlinks")?;

// TODO cache repository lookup

let repo_dir = file.parent().context("file has no parent directory")?;
let repo_dir = get_repo_dir(&file)?;
let repo = open_repo(repo_dir)
.context("failed to open git repo")?
.to_thread_local();
let head = repo.head_commit()?;
let file_oid = find_file_in_commit(&repo, &head, file)?;
let file_oid = find_file_in_commit(&repo, &head, &file)?;

let file_object = repo.find_object(file_oid)?;
let data = file_object.detach().data;
Expand All @@ -56,7 +62,9 @@ pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
pub fn get_current_head_name(file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());
let repo_dir = file.parent().context("file has no parent directory")?;
let file = gix::path::realpath(file).context("resolve symlinks")?;

let repo_dir = get_repo_dir(&file)?;
let repo = open_repo(repo_dir)
.context("failed to open git repo")?
.to_thread_local();
Expand Down
45 changes: 38 additions & 7 deletions helix-vcs/src/git/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,24 +98,55 @@ fn directory() {
assert!(git::get_diff_base(&dir).is_err());
}

/// Test that `get_file_head` does not return content for a symlink.
/// This is important to correctly cover cases where a symlink is removed and replaced by a file.
/// If the contents of the symlink object were returned a diff between a path and the actual file would be produced (bad ui).
/// Test that `get_diff_base` resolves symlinks so that the same diff base is
/// used as the target file.
///
/// This is important to correctly cover cases where a symlink is removed and
/// replaced by a file. If the contents of the symlink object were returned
/// a diff between a literal file path and the actual file content would be
/// produced (bad ui).
#[cfg(any(unix, windows))]
#[test]
fn symlink() {
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(not(unix))]
use std::os::windows::fs::symlink_file as symlink;

let temp_git = empty_git_repo();
let file = temp_git.path().join("file.txt");
let contents = b"foo".as_slice();
File::create(&file).unwrap().write_all(contents).unwrap();
let contents = Vec::from(b"foo");
File::create(&file).unwrap().write_all(&contents).unwrap();
let file_link = temp_git.path().join("file_link.txt");

symlink("file.txt", &file_link).unwrap();
create_commit(temp_git.path(), true);

assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
}

/// Test that `get_diff_base` returns content when the file is a symlink to
/// another file that is in a git repo, but the symlink itself is not.
#[cfg(any(unix, windows))]
#[test]
fn symlink_to_git_repo() {
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(not(unix))]
use std::os::windows::fs::symlink_file as symlink;

let temp_dir = tempfile::tempdir().expect("create temp dir");
let temp_git = empty_git_repo();

let file = temp_git.path().join("file.txt");
let contents = Vec::from(b"foo");
File::create(&file).unwrap().write_all(&contents).unwrap();
create_commit(temp_git.path(), true);
assert!(git::get_diff_base(&file_link).is_err());
assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));

let file_link = temp_dir.path().join("file_link.txt");
symlink(&file, &file_link).unwrap();

assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
}