From 1ae4465b28940f774866ee434bc7ed6b5c4f4cba Mon Sep 17 00:00:00 2001 From: "GPT 5.5" Date: Tue, 26 May 2026 18:50:01 +0800 Subject: [PATCH 1/3] feat: add support for relative worktree directories Co-authored-by: Sebastian Thiel --- gix-discover/src/is.rs | 2 +- gix-discover/src/path.rs | 48 ++++++++++--- gix-discover/tests/discover/path.rs | 64 ++++++++++++++++++ gix-discover/tests/discover/upwards/mod.rs | 60 ++++++++++++++++ .../make_worktree_relative_linking.tar | Bin 0 -> 135168 bytes .../make_worktree_relative_linking.sh | 21 ++++++ 6 files changed, 185 insertions(+), 10 deletions(-) create mode 100644 gix-discover/tests/fixtures/generated-archives/make_worktree_relative_linking.tar create mode 100755 gix-discover/tests/fixtures/make_worktree_relative_linking.sh diff --git a/gix-discover/src/is.rs b/gix-discover/src/is.rs index 57dcda9338a..a09c7d9ec40 100644 --- a/gix-discover/src/is.rs +++ b/gix-discover/src/is.rs @@ -106,7 +106,7 @@ pub(crate) fn git_with_metadata( let worktree_and_common_dir = crate::path::from_plain_file(&common_dir) .and_then(Result::ok) .and_then(|cd| { - crate::path::from_plain_file(&dot_git.join("gitdir")) + crate::path::from_plain_file_relative_to_file(&dot_git.join("gitdir")) .and_then(Result::ok) .map(|worktree_gitfile| (crate::path::without_dot_git_dir(worktree_gitfile), cd)) }); diff --git a/gix-discover/src/path.rs b/gix-discover/src/path.rs index e783c656a40..674bacfc324 100644 --- a/gix-discover/src/path.rs +++ b/gix-discover/src/path.rs @@ -44,6 +44,30 @@ fn read_regular_file_content_with_size_limit(path: &std::path::Path) -> std::io: Ok(buf) } +/// Read a plain path file, returning `None` if the file is missing. +/// +/// Linked-worktree `gitdir` files are plain path files in Git, not `gitdir:` +/// files. Match Git's `get_linked_worktree()` behavior by trimming trailing +/// whitespace before interpreting the content. Empty or whitespace-only path +/// files are invalid. +fn read_plain_file_content(path: &std::path::Path) -> Option>> { + use bstr::ByteSlice; + let mut buf = match read_regular_file_content_with_size_limit(path) { + Ok(buf) => buf, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return None, + Err(err) => return Some(Err(err)), + }; + let trimmed_len = buf.trim_end().len(); + buf.truncate(trimmed_len); + if buf.is_empty() { + return Some(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Refusing to read an empty path from '{}'", path.display()), + ))); + } + Some(Ok(buf)) +} + /// Guess the kind of repository by looking at its `git_dir` path and return it. /// Return `None` if `git_dir` isn't called `.git` or isn't within `.git/worktrees` or `.git/modules`, or if it's /// a `.git` suffix like in `foo.git`. @@ -74,15 +98,21 @@ pub fn repository_kind(git_dir: &Path) -> Option { /// Reads a plain path from a file that contains it as its only content, with trailing newlines trimmed. pub fn from_plain_file(path: &std::path::Path) -> Option> { - use bstr::ByteSlice; - let mut buf = match read_regular_file_content_with_size_limit(path) { - Ok(buf) => buf, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => return None, - Err(err) => return Some(Err(err)), - }; - let trimmed_len = buf.trim_end().len(); - buf.truncate(trimmed_len); - Some(Ok(gix_path::from_bstring(buf))) + read_plain_file_content(path).map(|res| res.map(gix_path::from_bstring)) +} + +/// Reads a plain path from a file like [`from_plain_file()`], resolving relative paths against +/// the file's containing directory as needed. +pub fn from_plain_file_relative_to_file(path: &std::path::Path) -> Option> { + read_plain_file_content(path).map(|res| { + res.map(|buf| { + let plain_path = gix_path::from_bstring(buf); + match (plain_path.is_relative(), path.parent()) { + (true, Some(parent)) => parent.join(plain_path), + _ => plain_path, + } + }) + }) } /// Reads typical `gitdir: ` files from disk as used by worktrees and submodules. diff --git a/gix-discover/tests/discover/path.rs b/gix-discover/tests/discover/path.rs index 1f48a7e24ab..8412abc2f07 100644 --- a/gix-discover/tests/discover/path.rs +++ b/gix-discover/tests/discover/path.rs @@ -44,6 +44,70 @@ mod from_git_dir_file { } } +mod from_plain_file_relative_to_file { + use crate::path::plain_file_with_content; + use std::path::{Path, PathBuf}; + + #[test] + fn relative_path_is_made_absolute_relative_to_containing_dir() -> crate::Result { + let (path, plain_file) = write_and_read(b"relative/path\n")?; + assert_eq!(path, plain_file.parent().unwrap().join(Path::new("relative/path"))); + Ok(()) + } + + #[test] + fn empty_or_whitespace_only_path_is_invalid() -> crate::Result { + for content in [b"".as_slice(), b" \n".as_slice()] { + let file = plain_file_with_content(content)?; + let err = gix_discover::path::from_plain_file_relative_to_file(file.path()) + .expect("file exists") + .expect_err("empty paths must be rejected"); + assert_eq!( + err.kind(), + std::io::ErrorKind::InvalidData, + "empty plain path files are malformed, just like in Git" + ); + } + Ok(()) + } + + fn write_and_read(content: &[u8]) -> crate::Result<(PathBuf, PathBuf)> { + let file = plain_file_with_content(content)?; + Ok(( + gix_discover::path::from_plain_file_relative_to_file(file.path()) + .expect("file exists") + .expect("valid plain path"), + file.path().into(), + )) + } +} + +mod from_plain_file { + use crate::path::plain_file_with_content; + + #[test] + fn empty_or_whitespace_only_path_is_invalid() -> crate::Result { + for content in [b"".as_slice(), b" \n".as_slice()] { + let file = plain_file_with_content(content)?; + let err = gix_discover::path::from_plain_file(file.path()) + .expect("file exists") + .expect_err("empty paths must be rejected"); + assert_eq!( + err.kind(), + std::io::ErrorKind::InvalidData, + "empty plain path files are malformed, just like in Git" + ); + } + Ok(()) + } +} + +fn plain_file_with_content(content: &[u8]) -> std::io::Result { + let mut file = tempfile::NamedTempFile::new()?; + std::io::Write::write_all(&mut file, content)?; + Ok(file) +} + #[test] fn repository_kind() { use gix_discover::path::{RepositoryKind::*, repository_kind}; diff --git a/gix-discover/tests/discover/upwards/mod.rs b/gix-discover/tests/discover/upwards/mod.rs index f5583d2cfe3..5ad0623ce7f 100644 --- a/gix-discover/tests/discover/upwards/mod.rs +++ b/gix-discover/tests/discover/upwards/mod.rs @@ -235,6 +235,66 @@ fn from_existing_worktree() -> crate::Result { Ok(()) } +#[test] +fn from_existing_worktree_with_relative_linking_files() -> crate::Result { + let fixture = gix_testtools::scripted_fixture_read_only_needs_archive("make_worktree_relative_linking.sh")?; + let main = fixture.join("main"); + let linked = fixture.join("linked"); + let private_git_dir = main.join(".git/worktrees/linked"); + assert_eq!( + std::fs::read_to_string(linked.join(".git"))?, + "gitdir: ../main/.git/worktrees/linked\n", + "the linked checkout uses a relative gitdir file" + ); + let backlink = std::fs::read_to_string(private_git_dir.join("gitdir"))?; + assert_eq!( + backlink, "../../../../linked/.git\n", + "the private git dir points back to the checkout with a relative path" + ); + + for discover_path in [&linked, &private_git_dir] { + let (path, trust) = gix_discover::upwards(discover_path)?; + assert_eq!(trust, expected_trust()); + let (actual_git_dir, actual_worktree) = path.into_repository_and_work_tree_directories(); + assert_eq!( + gix_path::realpath(&actual_git_dir)?, + gix_path::realpath(&private_git_dir)?, + "discovery resolves the private git dir from relative worktree metadata" + ); + assert_eq!( + actual_worktree.as_deref().map(gix_path::realpath).transpose()?, + Some(gix_path::realpath(&linked)?), + "discovery resolves the linked worktree from relative worktree metadata" + ); + } + + Ok(()) +} + +#[test] +#[cfg(unix)] +fn from_symlinked_worktree_with_relative_linking_files() -> crate::Result { + let fixture = gix_testtools::scripted_fixture_read_only_needs_archive("make_worktree_relative_linking.sh")?; + let main = fixture.join("actual/main"); + let linked_symlink = fixture.join("linked-symlink"); + + let (path, trust) = gix_discover::upwards(&linked_symlink)?; + assert_eq!(trust, expected_trust()); + let (actual_git_dir, actual_worktree) = path.into_repository_and_work_tree_directories(); + assert_eq!( + gix_path::realpath(&actual_git_dir)?, + gix_path::realpath(main.join(".git/worktrees/linked"))?, + "the private git dir is found through a relative gitdir file reached via a symlinked checkout" + ); + assert_eq!( + actual_worktree.as_deref(), + Some(linked_symlink.as_path()), + "the discovered worktree remains the user-provided symlinked checkout" + ); + + Ok(()) +} + #[cfg(target_os = "macos")] #[test] fn cross_fs() -> crate::Result { diff --git a/gix-discover/tests/fixtures/generated-archives/make_worktree_relative_linking.tar b/gix-discover/tests/fixtures/generated-archives/make_worktree_relative_linking.tar new file mode 100644 index 0000000000000000000000000000000000000000..5a11d963e52b9ae9d09053b797c7402caaea3454 GIT binary patch literal 135168 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 O49qYv!@zeG1OFeICP%ga literal 0 HcmV?d00001 diff --git a/gix-discover/tests/fixtures/make_worktree_relative_linking.sh b/gix-discover/tests/fixtures/make_worktree_relative_linking.sh new file mode 100755 index 00000000000..3ce37247680 --- /dev/null +++ b/gix-discover/tests/fixtures/make_worktree_relative_linking.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +make_repo_with_relative_worktree_links() { + local base=$1 + + mkdir -p "$base" + git -C "$base" init -q main + ( + cd "$base/main" + git commit -q --allow-empty -m init + git worktree add -q --detach --relative-paths ../linked HEAD + ) +} + +make_repo_with_relative_worktree_links . +make_repo_with_relative_worktree_links actual + +# The symlinked checkout variant is only used by Unix tests. On platforms where +# creating symlinks is unavailable, keep the rest of the fixture useful. +ln -s actual/linked linked-symlink 2>/dev/null || true From bd2881eb781c85e789b2e7c983621eb1f1993ec8 Mon Sep 17 00:00:00 2001 From: "GPT 5.5" Date: Sat, 16 May 2026 10:03:19 +0800 Subject: [PATCH 2/3] fix: handle relative worktree gidir files Git 2.48 can link worktrees with relative paths. In that layout the checkout .git file points at the private git dir relative to the checkout, while worktrees//gitdir points back to the checkout relative to the private git dir. Discovery already handled the checkout-side gitdir file, but paths read from the private git dir were treated as-is. That made discovery from .git/worktrees/ and Repository::worktrees() proxy base resolution produce relative paths anchored to the process cwd instead of the gitdir file location. Git reference: /Users/byron/dev/github.com/git/git worktree.c:write_worktree_linking_files writes both relative links with `relative_path()`, and t/t2400-worktree-add.sh covers the resulting relative files. Co-authored-by: Sebastian Thiel --- gix/src/worktree/proxy.rs | 2 +- .../make_worktree_relative_linking.tar | Bin 0 -> 135168 bytes .../make_worktree_relative_linking.sh | 21 +++++++ gix/tests/gix/repository/worktree.rs | 52 ++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 gix/tests/fixtures/generated-archives/make_worktree_relative_linking.tar create mode 100755 gix/tests/fixtures/make_worktree_relative_linking.sh diff --git a/gix/src/worktree/proxy.rs b/gix/src/worktree/proxy.rs index 222fb2685d9..dd7cd856d0c 100644 --- a/gix/src/worktree/proxy.rs +++ b/gix/src/worktree/proxy.rs @@ -47,7 +47,7 @@ impl Proxy<'_> { /// Note that the location might not exist. pub fn base(&self) -> std::io::Result { let git_dir = self.git_dir.join("gitdir"); - let base_dot_git = gix_discover::path::from_plain_file(&git_dir).ok_or_else(|| { + let base_dot_git = gix_discover::path::from_plain_file_relative_to_file(&git_dir).ok_or_else(|| { std::io::Error::new( std::io::ErrorKind::NotFound, format!("Required file '{}' does not exist", git_dir.display()), diff --git a/gix/tests/fixtures/generated-archives/make_worktree_relative_linking.tar b/gix/tests/fixtures/generated-archives/make_worktree_relative_linking.tar new file mode 100644 index 0000000000000000000000000000000000000000..3a4ed0d459643e9a0660eeb553231edcc3a987dc GIT binary patch literal 135168 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

Tc^;Ijyi~xUo@pcVQq>3 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 J;5&+e{|`c!Moj/dev/null || true diff --git a/gix/tests/gix/repository/worktree.rs b/gix/tests/gix/repository/worktree.rs index 94624f73aa1..436459ee0d2 100644 --- a/gix/tests/gix/repository/worktree.rs +++ b/gix/tests/gix/repository/worktree.rs @@ -261,6 +261,58 @@ fn from_nonbare_parent_repo() { run_assertions(repo, false /* bare */); } +#[test] +fn linked_worktree_proxy_base_with_relative_linking_files() -> crate::Result { + let fixture = gix_testtools::scripted_fixture_read_only("make_worktree_relative_linking.sh")?; + let main = fixture.join("main"); + let linked = fixture.join("linked"); + let private_git_dir = main.join(".git/worktrees/linked"); + let repo = gix::open(&main)?; + let worktrees = repo.worktrees()?; + assert_eq!(worktrees.len(), 1, "the relative-path fixture has one linked worktree"); + let proxy = worktrees.into_iter().next().expect("one worktree"); + + assert_eq!( + gix_path::realpath(proxy.base()?)?, + gix_path::realpath(&linked)?, + "proxy bases resolve relative worktrees//gitdir paths against the private git dir" + ); + let linked_repo = proxy.into_repo()?; + assert_eq!( + linked_repo.workdir().map(gix_path::realpath).transpose()?, + Some(gix_path::realpath(&linked)?) + ); + assert_eq!(linked_repo.git_dir(), private_git_dir); + + Ok(()) +} + +#[cfg(unix)] +#[test] +fn linked_worktree_proxy_base_with_symlinked_main_repo() -> crate::Result { + let fixture = gix_testtools::scripted_fixture_read_only("make_worktree_relative_linking.sh")?; + let linked = fixture.join("actual/linked"); + let main_symlink = fixture.join("main-symlink"); + + let repo = gix::open(&main_symlink)?; + let worktrees = repo.worktrees()?; + assert_eq!(worktrees.len(), 1, "the relative-path fixture has one linked worktree"); + let proxy = worktrees.into_iter().next().expect("one worktree"); + + assert_eq!( + gix_path::realpath(proxy.base()?)?, + gix_path::realpath(&linked)?, + "proxy bases preserve symlink semantics when resolving relative worktrees//gitdir paths" + ); + let repo = proxy.into_repo()?; + assert_eq!( + repo.workdir().map(gix_path::realpath).transpose()?, + Some(gix_path::realpath(&linked)?) + ); + + Ok(()) +} + #[test] fn from_nonbare_parent_repo_set_workdir() -> gix_testtools::Result { if gix_testtools::should_skip_as_git_version_is_smaller_than(2, 31, 0) { From 1d80b47c9a36b63c7c0ab28b389d9b5827e3a24e Mon Sep 17 00:00:00 2001 From: "GPT 5.5" Date: Tue, 26 May 2026 18:50:57 +0800 Subject: [PATCH 3/3] address auto-review - need to make sure the ignored-archive tests skip if the version isn't recent enough. - only works on Unix it seems, on Windows the flag doesn't exist. Related to symlinks. Co-authored-by: Sebastian Thiel --- gix/tests/gix/repository/worktree.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gix/tests/gix/repository/worktree.rs b/gix/tests/gix/repository/worktree.rs index 436459ee0d2..10554cdc2f8 100644 --- a/gix/tests/gix/repository/worktree.rs +++ b/gix/tests/gix/repository/worktree.rs @@ -263,7 +263,7 @@ fn from_nonbare_parent_repo() { #[test] fn linked_worktree_proxy_base_with_relative_linking_files() -> crate::Result { - let fixture = gix_testtools::scripted_fixture_read_only("make_worktree_relative_linking.sh")?; + let fixture = gix_testtools::scripted_fixture_read_only_needs_archive("make_worktree_relative_linking.sh")?; let main = fixture.join("main"); let linked = fixture.join("linked"); let private_git_dir = main.join(".git/worktrees/linked"); @@ -287,10 +287,10 @@ fn linked_worktree_proxy_base_with_relative_linking_files() -> crate::Result { Ok(()) } -#[cfg(unix)] #[test] +#[cfg(unix)] fn linked_worktree_proxy_base_with_symlinked_main_repo() -> crate::Result { - let fixture = gix_testtools::scripted_fixture_read_only("make_worktree_relative_linking.sh")?; + let fixture = gix_testtools::scripted_fixture_read_only_needs_archive("make_worktree_relative_linking.sh")?; let linked = fixture.join("actual/linked"); let main_symlink = fixture.join("main-symlink");