git-utils: centralize repository authority and trusted Git launch

This commit is contained in:
Chris Bookholt
2026-07-01 21:19:17 -07:00
parent 9a21b607e3
commit bcf026cbca
21 changed files with 4820 additions and 389 deletions
+1
View File
@@ -3137,6 +3137,7 @@ dependencies = [
"once_cell",
"pretty_assertions",
"regex",
"same-file",
"schemars 0.8.22",
"serde",
"similar",
+1
View File
@@ -381,6 +381,7 @@ rustls = { version = "0.23", default-features = false, features = [
] }
rustls-native-certs = "0.8.3"
rustls-pki-types = "1.14.0"
same-file = "1.0.6"
schemars = "0.8.22"
seccompiler = "0.5.0"
semver = "1.0"
+1
View File
@@ -19,6 +19,7 @@ futures = { workspace = true, features = ["alloc"] }
gix = { workspace = true }
once_cell = { workspace = true }
regex = "1"
same-file = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
similar = { workspace = true }
+30 -5
View File
@@ -127,12 +127,11 @@ fn resolve_git_root(
git_config_args: &[String],
) -> io::Result<PathBuf> {
let requested_cwd = std::fs::canonicalize(cwd)?;
let mut command = git.command();
let mut command = git.command_for_cwd(&requested_cwd)?;
command
.args(git_config_args)
.arg("rev-parse")
.arg("--show-toplevel")
.current_dir(&requested_cwd);
.arg("--show-toplevel");
let out = git.output(command)?;
let code = out.status.code().unwrap_or(-1);
if code != 0 {
@@ -198,14 +197,13 @@ pub(crate) fn run_git(
git_cfg: &[String],
args: &[String],
) -> io::Result<(i32, String, String)> {
let mut cmd = git.command();
let mut cmd = git.command_for_cwd(cwd)?;
for p in git_cfg {
cmd.arg(p);
}
for a in args {
cmd.arg(a);
}
cmd.current_dir(cwd);
let out = git.output(cmd)?;
let code = out.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
@@ -666,4 +664,31 @@ diff --git a/ghost.txt b/ghost.txt\n--- a/ghost.txt\n+++ b/ghost.txt\n@@ -1,1 +1
assert!(error.to_string().contains("instead of expected worktree"));
}
}
#[cfg(unix)]
#[test]
fn apply_propagates_unsafe_repository_metadata_before_git_launch() {
use std::os::unix::fs::symlink;
let repo = init_repo();
let root = repo.path();
let external = tempfile::tempdir().expect("external metadata parent");
let admin = external.path().join("admin");
std::fs::rename(root.join(".git"), &admin).expect("move metadata external");
symlink(&admin, root.join("switch")).expect("worktree metadata switch");
std::fs::write(root.join(".git"), "gitdir: switch\n").expect("write gitdir marker");
let (code, _, stderr) = run(root, &["git", "rev-parse", "--absolute-git-dir"]);
assert_eq!(code, 0, "native Git fixture: {stderr}");
let request = ApplyGitRequest {
cwd: root.to_path_buf(),
diff: String::new(),
revert: false,
preflight: true,
};
let error = apply_git_patch(&request).expect_err("unsafe metadata route");
assert_eq!(error.kind(), io::ErrorKind::PermissionDenied);
assert!(error.to_string().contains("Git metadata route crosses"));
assert!(!error.to_string().contains("PATH"));
}
}
+27 -1
View File
@@ -36,8 +36,34 @@ pub enum GitToolingError {
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub(crate) enum GitReadError {
#[error("no trusted Git executable is available")]
#[error(
"no trusted native Git executable is available; script-based and non-native Git wrappers are skipped, so install a native Git binary outside the repository and place its directory on PATH"
)]
NoTrustedGit,
#[error(
"refusing non-bare linked worktree because its primary worktree cannot be proven from Git metadata: {common_dir}; run the operation from the primary worktree, or use a standard linked-worktree or plain bare-backed layout"
)]
UnprovenPrimaryAuthority { common_dir: String },
#[error("unsafe Git repository metadata at {path:?}: {reason}")]
UnsafeRepositoryMetadata { path: PathBuf, reason: String },
#[error("invalid or unsupported Git repository metadata at {path:?}: {reason}")]
InvalidRepositoryMetadata { path: PathBuf, reason: String },
#[error("{path:?} is not a Git repository")]
NotRepository { path: PathBuf },
}
impl GitReadError {
pub(crate) fn io_kind(&self) -> std::io::ErrorKind {
match self {
Self::NoTrustedGit | Self::NotRepository { .. } => std::io::ErrorKind::NotFound,
Self::UnprovenPrimaryAuthority { .. } | Self::UnsafeRepositoryMetadata { .. } => {
std::io::ErrorKind::PermissionDenied
}
Self::InvalidRepositoryMetadata { .. } => std::io::ErrorKind::InvalidData,
}
}
pub(crate) fn into_io_error(self) -> std::io::Error {
std::io::Error::new(self.io_kind(), self)
}
}
+95 -331
View File
@@ -1,368 +1,132 @@
use std::ffi::OsStr;
use std::io;
#[cfg(windows)]
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
#[cfg(windows)]
use std::path::Prefix;
use std::process::Command;
use crate::errors::GitReadError;
use crate::git_config::path_is_within;
#[cfg(test)]
use crate::git_executable::git_executable_name;
use crate::git_executable::harden_git_launch_environment;
#[cfg(test)]
use crate::git_executable::path_is_untrusted;
#[cfg(test)]
use crate::git_executable::search_directory_is_untrusted;
use crate::git_executable::select_git_executable;
#[cfg(all(test, windows))]
use crate::git_executable::windows_path_requires_fail_closed;
use crate::repository_authority::RepositoryAuthority;
#[cfg(test)]
use crate::repository_authority::parse_marker_path as parse_git_marker_path;
use crate::safe_git::isolate_git_command_environment;
/// A Git executable outside the repository-controlled roots for one operation.
#[derive(Clone, Debug)]
#[derive(Debug)]
pub(crate) struct GitRunner {
/// Canonical executable target pinned at selection time. Never execute the
/// mutable PATH spelling after validation.
executable: PathBuf,
argv0: PathBuf,
safe_path: std::ffi::OsString,
authority: RepositoryAuthority,
}
struct UntrustedGitLocations {
roots: Vec<PathBuf>,
common_dirs: Vec<PathBuf>,
/// A Git command that can only be spawned through [`GitRunner::output`],
/// keeping metadata revalidation and launch hardening at one choke point.
pub(crate) struct GitCommand {
inner: Command,
}
impl GitCommand {
pub(crate) fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.inner.arg(arg);
self
}
pub(crate) fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.inner.args(args);
self
}
pub(crate) fn env(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
self.inner.env(key, value);
self
}
}
impl GitRunner {
pub(crate) fn for_cwd(cwd: &Path) -> Result<Self, GitReadError> {
let locations = untrusted_git_locations_for_cwd(cwd)?;
let authority = repository_authority_for_cwd(cwd)?;
let search_path = std::env::var_os("PATH").ok_or(GitReadError::NoTrustedGit)?;
Self::from_search_path(&locations, &search_path)
Self::from_search_path(authority, &search_path)
}
pub(crate) fn for_cwd_io(cwd: &Path) -> io::Result<Self> {
Self::for_cwd(cwd).map_err(|error| io::Error::new(io::ErrorKind::NotFound, error))
Self::for_cwd(cwd).map_err(GitReadError::into_io_error)
}
pub(crate) fn command(&self) -> Command {
Command::new(&self.executable)
pub(crate) fn command(&self) -> GitCommand {
let mut command = Command::new(&self.executable);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
command.arg0(&self.argv0);
}
if let Some(parent) = self.executable.parent() {
command.current_dir(parent);
}
harden_git_launch_environment(&mut command, &self.safe_path);
GitCommand { inner: command }
}
pub(crate) fn output(&self, mut command: Command) -> io::Result<std::process::Output> {
isolate_git_command_environment(&mut command);
command.envs(crate::local_only_git_env());
command.output()
pub(crate) fn command_for_cwd(&self, cwd: &Path) -> io::Result<GitCommand> {
let cwd = if cwd.is_absolute() {
cwd.to_path_buf()
} else {
std::env::current_dir()?.join(cwd)
};
let cwd = self.authority.canonical_command_cwd(&cwd)?;
let mut command = self.command();
command.arg("-C").arg(cwd);
Ok(command)
}
pub(crate) fn output(&self, mut command: GitCommand) -> io::Result<std::process::Output> {
self.revalidate_active_repository_metadata()?;
isolate_git_command_environment(&mut command.inner);
command.inner.envs(crate::local_only_git_env());
harden_git_launch_environment(&mut command.inner, &self.safe_path);
command.inner.output()
}
fn revalidate_active_repository_metadata(&self) -> io::Result<()> {
self.authority.revalidate_active_repository_metadata()
}
fn from_search_path(
untrusted: &UntrustedGitLocations,
authority: RepositoryAuthority,
search_path: &OsStr,
) -> Result<Self, GitReadError> {
for directory in std::env::split_paths(search_path) {
if !directory.is_absolute() {
continue;
}
// Check the raw PATH spelling before canonicalization. A search
// directory can traverse through an untrusted repository and
// resolve outside it; checking only the resolved parent or
// candidate would lose that provenance.
if search_directory_is_untrusted(&directory, untrusted) {
continue;
}
let candidate = directory.join(git_executable_name());
if path_is_untrusted(&candidate, untrusted) {
continue;
}
let Ok(canonical_parent) = std::fs::canonicalize(&directory) else {
continue;
};
if path_is_untrusted(&canonical_parent, untrusted) {
continue;
}
let Ok(canonical_candidate) = std::fs::canonicalize(&candidate) else {
continue;
};
if path_is_untrusted(&canonical_candidate, untrusted)
|| !is_native_executable_file(&canonical_candidate)
{
continue;
}
return Ok(Self {
// Preserve multicall spelling because argv[0] may select mode.
executable: candidate,
});
}
Err(GitReadError::NoTrustedGit)
authority.ensure_primary_authority()?;
let selected = select_git_executable(&authority, search_path)?;
Ok(Self {
executable: selected.executable,
argv0: selected.argv0,
safe_path: selected.safe_path,
authority,
})
}
}
fn untrusted_git_locations_for_cwd(cwd: &Path) -> Result<UntrustedGitLocations, GitReadError> {
let lexical_cwd = if cwd.is_absolute() {
cwd.to_path_buf()
} else {
std::env::current_dir()
.map_err(|_| GitReadError::NotRepository {
path: cwd.to_path_buf(),
})?
.join(cwd)
};
let canonical_cwd = std::fs::canonicalize(cwd).map_err(|_| GitReadError::NotRepository {
path: cwd.to_path_buf(),
})?;
let worktree_root = crate::get_git_repo_root(&canonical_cwd)
.and_then(|root| std::fs::canonicalize(root).ok())
.unwrap_or_else(|| canonical_cwd.clone());
let mut locations = UntrustedGitLocations {
roots: Vec::new(),
common_dirs: Vec::new(),
};
record_repository_ancestry(&worktree_root, &mut locations)?;
// Canonicalization can erase a repository-controlled symlink prefix. Walk
// the requested spelling too, deliberately retaining symlink and `..`
// components so every lexical enclosing checkout remains untrusted.
let lexical_base = if lexical_cwd.is_dir() {
lexical_cwd
} else {
lexical_cwd
.parent()
.ok_or_else(|| GitReadError::NotRepository {
path: cwd.to_path_buf(),
})?
.to_path_buf()
};
record_repository_ancestry(&lexical_base, &mut locations)?;
// Callers commonly obtain their default cwd from `current_dir()`, which
// can already have erased a symlink spelling. Recover the standard logical
// process cwd only when it is absolute and resolves to both the requested
// cwd and the process cwd. Treating extra roots as untrusted cannot widen
// executable selection.
if let Some(logical_cwd) = validated_logical_process_cwd(&canonical_cwd) {
record_repository_ancestry(&logical_cwd, &mut locations)?;
}
Ok(locations)
}
fn validated_logical_process_cwd(canonical_cwd: &Path) -> Option<PathBuf> {
let process_cwd = std::fs::canonicalize(std::env::current_dir().ok()?).ok()?;
if !paths_equal(&process_cwd, canonical_cwd) {
return None;
}
let logical_cwd = PathBuf::from(std::env::var_os("PWD")?);
if !logical_cwd.is_absolute() {
return None;
}
let canonical_logical_cwd = std::fs::canonicalize(&logical_cwd).ok()?;
paths_equal(&canonical_logical_cwd, canonical_cwd).then_some(logical_cwd)
}
fn record_repository_ancestry(
start: &Path,
locations: &mut UntrustedGitLocations,
) -> Result<(), GitReadError> {
push_unique(&mut locations.roots, start.to_path_buf());
record_repository_marker(start, locations)?;
for ancestor in start.parent().into_iter().flat_map(Path::ancestors) {
let dot_git = ancestor.join(".git");
match std::fs::symlink_metadata(&dot_git) {
Ok(_) => {
push_unique(&mut locations.roots, ancestor.to_path_buf());
let canonical_root =
std::fs::canonicalize(ancestor).map_err(|_| GitReadError::NoTrustedGit)?;
push_unique(&mut locations.roots, canonical_root.clone());
record_repository_marker(ancestor, locations)?;
}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(_) => return Err(GitReadError::NoTrustedGit),
}
}
Ok(())
}
fn record_repository_marker(
worktree_root: &Path,
locations: &mut UntrustedGitLocations,
) -> Result<(), GitReadError> {
let dot_git = worktree_root.join(".git");
let common_dir = match std::fs::symlink_metadata(&dot_git) {
Ok(_) => resolve_common_git_dir(&dot_git).map_err(|()| GitReadError::NoTrustedGit)?,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(_) => return Err(GitReadError::NoTrustedGit),
};
if !path_is_within(&common_dir, worktree_root) {
push_unique(&mut locations.roots, common_dir.clone());
}
push_unique(&mut locations.common_dirs, common_dir);
Ok(())
}
fn push_unique(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.iter().any(|existing| paths_equal(existing, &path)) {
paths.push(path);
}
}
fn path_is_untrusted(path: &Path, locations: &UntrustedGitLocations) -> bool {
if locations
.roots
.iter()
.any(|root| path_is_within(path, root))
{
return true;
}
locations
.common_dirs
.iter()
.any(|common_dir| path_is_in_worktree_for_common_dir(path, common_dir))
}
fn search_directory_is_untrusted(directory: &Path, locations: &UntrustedGitLocations) -> bool {
#[cfg(windows)]
if windows_path_requires_fail_closed(directory)
|| windows_path_has_untrusted_canonical_ancestor(directory, locations)
{
return true;
}
path_is_untrusted(directory, locations)
}
#[cfg(windows)]
fn windows_path_requires_fail_closed(path: &Path) -> bool {
let mut components = path.components();
let supported_namespace = match components.next() {
Some(Component::Prefix(prefix)) => match prefix.kind() {
Prefix::Disk(_)
| Prefix::VerbatimDisk(_)
| Prefix::UNC(_, _)
| Prefix::VerbatimUNC(_, _) => true,
Prefix::DeviceNS(device) => windows_device_namespace_is_filesystem(device),
Prefix::Verbatim(namespace) => namespace
.to_str()
.is_some_and(|namespace| namespace.eq_ignore_ascii_case("UNC")),
},
_ => false,
};
!supported_namespace || components.any(|component| matches!(component, Component::ParentDir))
}
#[cfg(windows)]
fn windows_device_namespace_is_filesystem(device: &OsStr) -> bool {
let bytes = device.as_encoded_bytes();
bytes.eq_ignore_ascii_case(b"UNC")
|| matches!(bytes, [drive, b':'] if drive.is_ascii_alphabetic())
}
#[cfg(windows)]
fn windows_path_has_untrusted_canonical_ancestor(
path: &Path,
locations: &UntrustedGitLocations,
) -> bool {
let Ok(canonical_path) = std::fs::canonicalize(path) else {
return true;
};
if path_is_untrusted(&canonical_path, locations) {
return true;
}
for ancestor in path.ancestors().skip(1) {
let canonical_ancestor = match std::fs::canonicalize(ancestor) {
Ok(canonical_ancestor) => canonical_ancestor,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::InvalidInput
) =>
{
continue;
}
Err(_) => return true,
};
if path_is_untrusted(&canonical_ancestor, locations) {
return true;
}
}
false
}
fn path_is_in_worktree_for_common_dir(path: &Path, expected_common_dir: &Path) -> bool {
let path = if path.is_dir() {
path
} else {
path.parent().unwrap_or(path)
};
for ancestor in path.ancestors() {
let dot_git = ancestor.join(".git");
match std::fs::symlink_metadata(&dot_git) {
Ok(_) => match resolve_common_git_dir(&dot_git) {
Ok(common_dir) if paths_equal(&common_dir, expected_common_dir) => return true,
Ok(_) => {}
Err(()) => return true,
},
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(_) => return true,
}
}
false
}
fn paths_equal(left: &Path, right: &Path) -> bool {
path_is_within(left, right) && path_is_within(right, left)
}
fn resolve_common_git_dir(dot_git: &Path) -> Result<PathBuf, ()> {
if dot_git.is_dir() {
return std::fs::canonicalize(dot_git).map_err(|_| ());
}
let contents = std::fs::read_to_string(dot_git).map_err(|_| ())?;
let git_dir = contents
.trim()
.strip_prefix("gitdir:")
.map(str::trim)
.filter(|path| !path.is_empty())
.ok_or(())?;
let git_dir = canonicalize_from(dot_git.parent().ok_or(())?, git_dir)?;
let commondir = git_dir.join("commondir");
if commondir.is_file() {
let common_dir = std::fs::read_to_string(commondir).map_err(|_| ())?;
let common_dir = common_dir.trim();
if common_dir.is_empty() {
return Err(());
}
return canonicalize_from(&git_dir, common_dir);
}
if git_dir
.parent()
.is_some_and(|parent| parent.file_name() == Some(OsStr::new("worktrees")))
{
return std::fs::canonicalize(git_dir.parent().and_then(Path::parent).ok_or(())?)
.map_err(|_| ());
}
Ok(git_dir)
}
fn canonicalize_from(base: &Path, path: &str) -> Result<PathBuf, ()> {
std::fs::canonicalize(base.join(path)).map_err(|_| ())
}
#[cfg(windows)]
fn git_executable_name() -> &'static str {
"git.exe"
}
#[cfg(not(windows))]
fn git_executable_name() -> &'static str {
"git"
}
#[cfg(unix)]
fn is_native_executable_file(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path)
.is_ok_and(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
}
#[cfg(windows)]
fn is_native_executable_file(path: &Path) -> bool {
path.extension()
.and_then(OsStr::to_str)
.is_some_and(|extension| extension.eq_ignore_ascii_case("exe"))
&& std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file())
}
#[cfg(not(any(unix, windows)))]
fn is_native_executable_file(path: &Path) -> bool {
std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file())
pub(crate) fn repository_authority_for_cwd(
cwd: &Path,
) -> Result<RepositoryAuthority, GitReadError> {
RepositoryAuthority::discover(cwd)
}
#[cfg(test)]
File diff suppressed because it is too large Load Diff
+283
View File
@@ -0,0 +1,283 @@
use std::ffi::OsStr;
use std::ffi::OsString;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use crate::errors::GitReadError;
use crate::repository_authority::RepositoryAuthority;
#[cfg(windows)]
use std::path::Component;
#[cfg(windows)]
use std::path::Prefix;
pub(crate) struct SelectedGitExecutable {
pub(crate) executable: PathBuf,
pub(crate) argv0: PathBuf,
pub(crate) safe_path: OsString,
}
pub(crate) fn select_git_executable(
authority: &RepositoryAuthority,
search_path: &OsStr,
) -> Result<SelectedGitExecutable, GitReadError> {
let mut safe_directories = Vec::new();
let mut selected = None;
for directory in std::env::split_paths(search_path) {
if !directory.is_absolute() || search_directory_is_untrusted(&directory, authority) {
continue;
}
let Ok(canonical_directory) = std::fs::canonicalize(&directory) else {
continue;
};
if path_is_untrusted(&canonical_directory, authority)
|| !std::fs::metadata(&canonical_directory).is_ok_and(|metadata| metadata.is_dir())
{
continue;
}
push_unique_path(&mut safe_directories, canonical_directory);
if selected.is_some() {
continue;
}
let candidate = directory.join(git_executable_name());
if path_is_untrusted(&candidate, authority) {
continue;
}
let Ok(canonical_candidate) = std::fs::canonicalize(&candidate) else {
continue;
};
if path_is_untrusted(&canonical_candidate, authority)
|| !is_native_executable_file(&canonical_candidate)
{
continue;
}
if let Some(parent) = canonical_candidate.parent() {
push_unique_path(&mut safe_directories, parent.to_path_buf());
}
selected = Some((canonical_candidate, candidate));
}
let (executable, argv0) = selected.ok_or(GitReadError::NoTrustedGit)?;
let safe_path =
std::env::join_paths(safe_directories).map_err(|_| GitReadError::NoTrustedGit)?;
Ok(SelectedGitExecutable {
executable,
argv0,
safe_path,
})
}
pub(crate) fn harden_git_launch_environment(
command: &mut std::process::Command,
safe_path: &OsStr,
) {
let mut names = std::env::vars_os()
.map(|(name, _)| name)
.filter(|name| startup_injection_variable(name))
.collect::<Vec<_>>();
names.extend(
command
.get_envs()
.filter(|&(name, _)| startup_injection_variable(name))
.map(|(name, _)| name.to_os_string()),
);
for name in names {
command.env_remove(name);
}
command.env("PATH", safe_path);
#[cfg(windows)]
command.env("NoDefaultCurrentDirectoryInExePath", "1");
}
fn startup_injection_variable(name: &OsStr) -> bool {
let name = name.to_string_lossy().to_ascii_uppercase();
name.starts_with("DYLD_")
|| name.starts_with("LD_")
|| name == "LIBPATH"
|| name == "SHLIB_PATH"
|| name.starts_with("CORECLR_")
|| name.starts_with("COR_")
|| name.starts_with("DOTNET_")
|| name == "GCONV_PATH"
|| name == "NIX_LD"
|| name == "NIX_LD_LIBRARY_PATH"
}
pub(crate) fn path_is_untrusted(path: &Path, authority: &RepositoryAuthority) -> bool {
authority.path_is_untrusted_for_executable(path)
}
pub(crate) fn search_directory_is_untrusted(
directory: &Path,
authority: &RepositoryAuthority,
) -> bool {
#[cfg(windows)]
if windows_path_requires_fail_closed(directory)
|| windows_path_has_untrusted_canonical_ancestor(directory, authority)
{
return true;
}
path_is_untrusted(directory, authority)
}
fn push_unique_path(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.iter().any(|existing| existing == &path) {
paths.push(path);
}
}
#[cfg(windows)]
pub(crate) fn windows_path_requires_fail_closed(path: &Path) -> bool {
let mut components = path.components();
let supported_namespace = match components.next() {
Some(Component::Prefix(prefix)) => match prefix.kind() {
Prefix::Disk(_)
| Prefix::VerbatimDisk(_)
| Prefix::UNC(_, _)
| Prefix::VerbatimUNC(_, _) => true,
Prefix::DeviceNS(device) => windows_device_namespace_is_filesystem(device),
Prefix::Verbatim(namespace) => namespace
.to_str()
.is_some_and(|namespace| namespace.eq_ignore_ascii_case("UNC")),
},
_ => false,
};
!supported_namespace || components.any(|component| matches!(component, Component::ParentDir))
}
#[cfg(windows)]
fn windows_device_namespace_is_filesystem(device: &OsStr) -> bool {
let bytes = device.as_encoded_bytes();
bytes.eq_ignore_ascii_case(b"UNC")
|| matches!(bytes, [drive, b':'] if drive.is_ascii_alphabetic())
}
#[cfg(windows)]
fn windows_path_has_untrusted_canonical_ancestor(
path: &Path,
authority: &RepositoryAuthority,
) -> bool {
let Ok(canonical_path) = std::fs::canonicalize(path) else {
return true;
};
if path_is_untrusted(&canonical_path, authority) {
return true;
}
for ancestor in path.ancestors().skip(1) {
let canonical_ancestor = match std::fs::canonicalize(ancestor) {
Ok(canonical_ancestor) => canonical_ancestor,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::InvalidInput
) =>
{
continue;
}
Err(_) => return true,
};
if path_is_untrusted(&canonical_ancestor, authority) {
return true;
}
}
false
}
#[cfg(windows)]
pub(crate) fn git_executable_name() -> &'static str {
"git.exe"
}
#[cfg(not(windows))]
pub(crate) fn git_executable_name() -> &'static str {
"git"
}
#[cfg(target_os = "macos")]
pub(crate) fn is_native_executable_file(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
let Ok(metadata) = std::fs::metadata(path) else {
return false;
};
if !metadata.is_file() || metadata.permissions().mode() & 0o111 == 0 {
return false;
}
let Ok(bytes) = read_prefix(path, 4) else {
return false;
};
matches!(
bytes.as_slice(),
[0xfe, 0xed, 0xfa, 0xce]
| [0xfe, 0xed, 0xfa, 0xcf]
| [0xce, 0xfa, 0xed, 0xfe]
| [0xcf, 0xfa, 0xed, 0xfe]
| [0xca, 0xfe, 0xba, 0xbe]
| [0xbe, 0xba, 0xfe, 0xca]
| [0xca, 0xfe, 0xba, 0xbf]
| [0xbf, 0xba, 0xfe, 0xca]
)
}
#[cfg(all(unix, not(target_os = "macos")))]
pub(crate) fn is_native_executable_file(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
let Ok(metadata) = std::fs::metadata(path) else {
return false;
};
metadata.is_file()
&& metadata.permissions().mode() & 0o111 != 0
&& read_prefix(path, 4).is_ok_and(|bytes| bytes == b"\x7fELF")
}
#[cfg(windows)]
pub(crate) fn is_native_executable_file(path: &Path) -> bool {
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
if !path
.extension()
.and_then(OsStr::to_str)
.is_some_and(|extension| extension.eq_ignore_ascii_case("exe"))
{
return false;
}
let Ok(metadata) = std::fs::metadata(path) else {
return false;
};
if !metadata.is_file() || metadata.len() < 68 {
return false;
}
let Ok(mut file) = std::fs::File::open(path) else {
return false;
};
let mut dos = [0_u8; 64];
if file.read_exact(&mut dos).is_err() || &dos[..2] != b"MZ" {
return false;
}
let offset = u32::from_le_bytes(dos[60..64].try_into().expect("PE offset bytes")) as u64;
if offset > 1024 * 1024 || offset + 4 > metadata.len() {
return false;
}
let mut signature = [0_u8; 4];
file.seek(SeekFrom::Start(offset)).is_ok()
&& file.read_exact(&mut signature).is_ok()
&& signature == *b"PE\0\0"
}
#[cfg(not(any(unix, windows)))]
pub(crate) fn is_native_executable_file(_path: &Path) -> bool {
false
}
#[cfg(unix)]
fn read_prefix(path: &Path, length: usize) -> io::Result<Vec<u8>> {
use std::io::Read;
let mut file = std::fs::File::open(path)?;
let mut bytes = vec![0; length];
file.read_exact(&mut bytes)?;
Ok(bytes)
}
+3
View File
@@ -6,11 +6,14 @@ mod errors;
mod fsmonitor;
mod git_command;
mod git_config;
mod git_executable;
mod info;
mod local_only;
mod operations;
mod patch_paths;
mod path_authority;
mod platform;
mod repository_authority;
mod safe_git;
pub use apply::ApplyGitRequest;
+1 -2
View File
@@ -110,8 +110,7 @@ where
}
let command_string = build_command_string(&args_vec);
let git = GitRunner::for_cwd_io(dir)?;
let mut command = git.command();
command.current_dir(dir);
let mut command = git.command_for_cwd(dir)?;
if let Some(envs) = env {
for (key, value) in envs {
command.env(key, value);
+3 -4
View File
@@ -63,14 +63,13 @@ fn git_apply_numstat_paths(
patch_path: &Path,
revert: bool,
) -> io::Result<Vec<String>> {
let mut cmd = git.command();
let command_cwd = patch_path.parent().unwrap_or_else(|| Path::new("."));
let mut cmd = git.command_for_cwd(command_cwd)?;
cmd.args(["apply", "--numstat", "-z"]);
if revert {
cmd.arg("-R");
}
cmd.arg("--")
.arg(patch_path)
.current_dir(patch_path.parent().unwrap_or_else(|| Path::new(".")));
cmd.arg("--").arg(patch_path);
let out = git.output(cmd)?;
if !out.status.success() {
return Err(io::Error::new(
+476
View File
@@ -0,0 +1,476 @@
use std::io;
use std::path::Path;
use std::path::PathBuf;
use same_file::Handle;
use crate::git_config::path_is_within;
mod cancellation;
mod route_walker;
#[cfg(any(windows, test))]
mod windows_path;
use route_walker::RawRouteObservation;
use route_walker::RouteObservationSnapshot;
use route_walker::observe_route;
#[cfg(any(windows, test))]
pub(crate) use windows_path::windows_authority_path_is_ambiguous;
#[cfg(windows)]
pub(crate) use windows_path::windows_path_is_ambiguous;
#[derive(Clone, Copy, Eq, PartialEq)]
enum PathBoundary {
Worktree,
Metadata,
}
#[derive(Clone, Copy, Eq, PartialEq)]
enum CandidateBoundary {
Outside,
Metadata,
ExactWorktree,
Worktree,
}
#[derive(Debug, Default)]
pub(crate) struct RepositoryRouteBoundaries {
worktree_roots: Vec<PathBuf>,
metadata_dirs: Vec<PathBuf>,
worktree_identities: Vec<Handle>,
metadata_identities: Vec<Handle>,
}
pub(crate) struct RouteInspection {
pub(crate) crosses_worktree: bool,
pub(crate) touches_worktree: bool,
pub(crate) crosses_metadata: bool,
pub(crate) observed_paths: Vec<PathBuf>,
}
impl RepositoryRouteBoundaries {
pub(crate) fn inspect_route(&self, route: &Path) -> io::Result<RouteInspection> {
inspect_route(route, self)
}
pub(crate) fn contains_known_boundary(&self, path: &Path) -> io::Result<bool> {
Ok(classify_candidate(
path,
&self.worktree_roots,
&self.metadata_dirs,
&self.worktree_identities,
&self.metadata_identities,
)? != CandidateBoundary::Outside)
}
}
pub(crate) fn repository_route_boundaries(
roots: &[PathBuf],
common_dirs: &[PathBuf],
) -> io::Result<RepositoryRouteBoundaries> {
let mut worktree_roots = Vec::new();
for root in roots {
push_unique_location(&mut worktree_roots, root.clone())?;
}
let worktree_identities = directory_identities(&worktree_roots)?;
let mut metadata_dirs = Vec::new();
for root in &worktree_roots {
let marker = root.join(".git");
let Ok(metadata) = std::fs::symlink_metadata(&marker) else {
continue;
};
if !metadata.is_dir() || metadata.file_type().is_symlink() {
continue;
}
let canonical_root = std::fs::canonicalize(root)?;
let canonical_marker = std::fs::canonicalize(&marker)?;
if canonical_marker == canonical_root.join(".git") {
push_unique_location(&mut metadata_dirs, canonical_marker)?;
}
}
for common in common_dirs {
let within_standard_metadata = metadata_dirs
.iter()
.any(|metadata| path_is_within(common, metadata));
let within_worktree = worktree_roots
.iter()
.any(|root| path_is_within(common, root))
|| nearest_identity_boundary(common, &worktree_identities, &[])?
== Some(PathBoundary::Worktree);
if within_standard_metadata || !within_worktree {
push_unique_location(&mut metadata_dirs, common.clone())?;
}
}
let metadata_identities = directory_identities(&metadata_dirs)?;
Ok(RepositoryRouteBoundaries {
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
})
}
fn push_unique_location(paths: &mut Vec<PathBuf>, path: PathBuf) -> io::Result<()> {
for existing in paths.iter() {
if paths_refer_to_same_location(existing, &path)? {
return Ok(());
}
}
paths.push(path);
Ok(())
}
fn paths_refer_to_same_location(left: &Path, right: &Path) -> io::Result<bool> {
if path_is_within(left, right) && path_is_within(right, left) {
return Ok(true);
}
if let (Ok(left), Ok(right)) = (Handle::from_path(left), Handle::from_path(right)) {
return Ok(left == right);
}
match (std::fs::canonicalize(left), std::fs::canonicalize(right)) {
(Ok(left), Ok(right)) => Ok(left == right),
(Err(left), _) if is_missing(&left) => Ok(false),
(_, Err(right)) if is_missing(&right) => Ok(false),
(Err(error), _) | (_, Err(error)) => Err(error),
}
}
fn is_missing(error: &io::Error) -> bool {
matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
)
}
fn inspect_route(
route: &Path,
boundaries: &RepositoryRouteBoundaries,
) -> io::Result<RouteInspection> {
#[cfg(windows)]
{
let route = route
.to_str()
.ok_or_else(|| invalid_data("non-UTF-8 Windows authority path"))?;
if windows_path_is_ambiguous(route) {
return Ok(RouteInspection {
crosses_worktree: true,
touches_worktree: true,
crosses_metadata: true,
observed_paths: Vec::new(),
});
}
}
let observation = observe_route(route)?;
let crosses_worktree = observation_crosses_worktree(
&observation,
&boundaries.worktree_roots,
&boundaries.metadata_dirs,
&boundaries.worktree_identities,
&boundaries.metadata_identities,
)?;
let observed_paths = observation.observed_paths();
let mut touches_worktree = false;
let mut crosses_metadata = false;
for path in &observed_paths {
match classify_candidate(
path,
&boundaries.worktree_roots,
&boundaries.metadata_dirs,
&boundaries.worktree_identities,
&boundaries.metadata_identities,
)? {
CandidateBoundary::Worktree | CandidateBoundary::ExactWorktree => {
touches_worktree = true;
}
CandidateBoundary::Metadata => crosses_metadata = true,
CandidateBoundary::Outside => {}
}
}
Ok(RouteInspection {
crosses_worktree,
touches_worktree,
crosses_metadata,
observed_paths,
})
}
fn observation_crosses_worktree(
observation: &RouteObservationSnapshot,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
let raw_pivot = raw_observation_has_worktree_descendant_pivot(
&observation.raw,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)?;
let normalized_crosses = strict_candidate_crosses(
&observation.raw.normalized,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)?;
let projected_crosses = strict_candidate_crosses(
&observation.raw.projected,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)?;
if raw_pivot || normalized_crosses || projected_crosses {
return Ok(true);
}
for hop in &observation.symlink_hops {
// A symlink entry located directly under an exact worktree root is
// already a mutable descendant; classify its parent strictly.
if strict_candidate_crosses(
&hop.parent,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? || intermediate_candidate_crosses(
&hop.entry,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? || raw_observation_has_worktree_descendant_pivot(
&hop.target,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? || intermediate_observation_crosses(
&hop.target,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? || raw_observation_has_worktree_descendant_pivot(
&hop.projected,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? || terminal_observation_crosses(
&hop.projected,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? {
return Ok(true);
}
}
Ok(false)
}
fn intermediate_observation_crosses(
observation: &RawRouteObservation,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
Ok(intermediate_candidate_crosses(
&observation.normalized,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? || intermediate_candidate_crosses(
&observation.projected,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)?)
}
fn terminal_observation_crosses(
observation: &RawRouteObservation,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
Ok(strict_candidate_crosses(
&observation.normalized,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? || strict_candidate_crosses(
&observation.projected,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)?)
}
fn strict_candidate_crosses(
path: &Path,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
Ok(matches!(
classify_candidate(
path,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)?,
CandidateBoundary::ExactWorktree | CandidateBoundary::Worktree
))
}
fn intermediate_candidate_crosses(
path: &Path,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
// Exact roots remain valid intermediates for Git-generated active metadata
// routes such as a nested submodule's `nested/../.git/modules/...`.
Ok(classify_candidate(
path,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)? == CandidateBoundary::Worktree)
}
fn raw_observation_has_worktree_descendant_pivot(
observation: &RawRouteObservation,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
for prefix in &observation.normalized_prefixes {
// Active metadata routes are revalidated before each child, so an
// exact nested root is allowed here. Retained registry and standard
// directory routes use the stricter role-aware cancellation policy.
let boundary = classify_candidate(
prefix,
worktree_roots,
metadata_dirs,
worktree_identities,
metadata_identities,
)?;
if boundary == CandidateBoundary::Worktree {
return Ok(true);
}
}
Ok(false)
}
fn classify_candidate(
path: &Path,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<CandidateBoundary> {
if metadata_dirs
.iter()
.any(|metadata| path_is_within(path, metadata))
{
return Ok(CandidateBoundary::Metadata);
}
if worktree_roots
.iter()
.any(|root| path_is_within(path, root) && path_is_within(root, path))
{
return Ok(CandidateBoundary::ExactWorktree);
}
if worktree_roots.iter().any(|root| path_is_within(path, root)) {
return Ok(CandidateBoundary::Worktree);
}
for (depth, ancestor) in path.ancestors().enumerate() {
let metadata = match std::fs::metadata(ancestor) {
Ok(metadata) if metadata.is_dir() => metadata,
Ok(_) => continue,
Err(error) if is_missing(&error) => continue,
Err(error) => return Err(error),
};
let identity = Handle::from_path(ancestor)?;
if metadata_identities.contains(&identity) {
return Ok(CandidateBoundary::Metadata);
}
if worktree_identities.contains(&identity) {
return Ok(if depth == 0 && metadata.is_dir() {
CandidateBoundary::ExactWorktree
} else {
CandidateBoundary::Worktree
});
}
}
Ok(CandidateBoundary::Outside)
}
fn directory_identities(paths: &[PathBuf]) -> io::Result<Vec<Handle>> {
paths
.iter()
.filter_map(|path| match std::fs::metadata(path) {
Ok(metadata) if metadata.is_dir() => Some(Handle::from_path(path)),
Ok(_) => None,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
None
}
Err(error) => Some(Err(error)),
})
.collect()
}
fn nearest_identity_boundary(
path: &Path,
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<Option<PathBoundary>> {
for ancestor in path.ancestors() {
let metadata = match std::fs::metadata(ancestor) {
Ok(metadata) => metadata,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
continue;
}
Err(error) => return Err(error),
};
if !metadata.is_dir() {
continue;
}
let identity = Handle::from_path(ancestor)?;
if metadata_identities.contains(&identity) {
return Ok(Some(PathBoundary::Metadata));
}
if worktree_identities.contains(&identity) {
return Ok(Some(PathBoundary::Worktree));
}
}
Ok(None)
}
@@ -0,0 +1,161 @@
use std::io;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
use same_file::Handle;
use crate::git_config::path_is_within;
use super::CandidateBoundary;
use super::RepositoryRouteBoundaries;
use super::classify_candidate;
use super::is_missing;
use super::route_walker::RouteObservationSnapshot;
use super::route_walker::observe_route;
#[cfg(windows)]
use super::windows_path_is_ambiguous;
impl RepositoryRouteBoundaries {
pub(crate) fn route_cancels_worktree_descendant(&self, route: &Path) -> io::Result<bool> {
if route_requires_fail_closed(route)? {
return Ok(true);
}
observation_cancels_worktree_descendant(&observe_route(route)?, self)
}
pub(crate) fn retained_route_is_untrusted(&self, route: &Path) -> io::Result<bool> {
if route_requires_fail_closed(route)? {
return Ok(true);
}
let observation = observe_route(route)?;
if observation_cancels_worktree_descendant(&observation, self)? {
return Ok(true);
}
// Unlike active metadata, retained registry routes are not rebound
// before every child. A symlink or junction entry below a worktree is
// therefore mutable authority even when it currently aliases metadata.
// Parent identity classification covers Windows Unicode case aliases.
for hop in &observation.symlink_hops {
if matches!(
classify_candidate(
&hop.parent,
&self.worktree_roots,
&self.metadata_dirs,
&self.worktree_identities,
&self.metadata_identities,
)?,
CandidateBoundary::ExactWorktree | CandidateBoundary::Worktree
) {
return Ok(true);
}
}
Ok(false)
}
}
fn observation_cancels_worktree_descendant(
observation: &RouteObservationSnapshot,
boundaries: &RepositoryRouteBoundaries,
) -> io::Result<bool> {
if raw_spelling_cancels_worktree_descendant(&observation.raw.spelling, boundaries)? {
return Ok(true);
}
for hop in &observation.symlink_hops {
if raw_spelling_cancels_worktree_descendant(&hop.target.spelling, boundaries)?
|| raw_spelling_cancels_worktree_descendant(&hop.projected.spelling, boundaries)?
{
return Ok(true);
}
}
Ok(false)
}
fn route_requires_fail_closed(route: &Path) -> io::Result<bool> {
#[cfg(windows)]
{
let route = route.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"non-UTF-8 Windows authority path",
)
})?;
Ok(windows_path_is_ambiguous(route))
}
#[cfg(not(windows))]
{
let _ = route;
Ok(false)
}
}
fn raw_spelling_cancels_worktree_descendant(
spelling: &Path,
boundaries: &RepositoryRouteBoundaries,
) -> io::Result<bool> {
let mut prefix = PathBuf::new();
for component in spelling.components() {
match component {
Component::ParentDir => {
if candidate_is_proper_worktree_descendant(
&prefix,
&boundaries.worktree_roots,
&boundaries.metadata_dirs,
&boundaries.worktree_identities,
&boundaries.metadata_identities,
)? {
return Ok(true);
}
prefix.pop();
}
Component::CurDir => {}
_ => prefix.push(component.as_os_str()),
}
}
Ok(false)
}
pub(super) fn candidate_is_proper_worktree_descendant(
path: &Path,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
if metadata_dirs
.iter()
.any(|metadata| path_is_within(path, metadata))
{
return Ok(false);
}
// A worktree-controlled spelling cannot become trusted merely because a
// mutable symlink or junction currently aliases protected metadata. Keep
// genuinely metadata-spelled paths exempt above, but reject lexical
// worktree descendants before following identities.
if worktree_roots
.iter()
.any(|root| path_is_within(path, root) && !path_is_within(root, path))
{
return Ok(true);
}
let mut ancestor_identities = Vec::new();
for (depth, ancestor) in path.ancestors().enumerate() {
match std::fs::metadata(ancestor) {
Ok(metadata) if metadata.is_dir() => {}
Ok(_) => continue,
Err(error) if is_missing(&error) => continue,
Err(error) => return Err(error),
}
let identity = Handle::from_path(ancestor)?;
if metadata_identities.contains(&identity) {
return Ok(false);
}
ancestor_identities.push((depth, identity));
}
Ok(ancestor_identities
.into_iter()
.any(|(depth, identity)| depth > 0 && worktree_identities.contains(&identity)))
}
@@ -0,0 +1,221 @@
use std::collections::BTreeSet;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use codex_utils_absolute_path::AbsolutePathBuf;
#[cfg(windows)]
use super::windows_path::windows_path_is_ambiguous;
#[derive(Clone, Debug)]
pub(super) struct RawRouteObservation {
pub(super) spelling: PathBuf,
pub(super) normalized_prefixes: Vec<PathBuf>,
pub(super) normalized: PathBuf,
pub(super) projected: PathBuf,
}
#[derive(Clone, Debug)]
pub(super) struct SymlinkRouteObservation {
pub(super) entry: PathBuf,
pub(super) parent: PathBuf,
pub(super) target: RawRouteObservation,
pub(super) projected: RawRouteObservation,
}
#[derive(Clone, Debug)]
pub(super) struct RouteObservationSnapshot {
pub(super) raw: RawRouteObservation,
pub(super) symlink_hops: Vec<SymlinkRouteObservation>,
}
impl RouteObservationSnapshot {
pub(super) fn observed_paths(&self) -> Vec<PathBuf> {
let mut paths = Vec::new();
append_raw_observation_paths(&mut paths, &self.raw);
for hop in &self.symlink_hops {
push_unique_spelling(&mut paths, hop.parent.clone());
push_unique_spelling(&mut paths, hop.entry.clone());
append_raw_observation_paths(&mut paths, &hop.target);
append_raw_observation_paths(&mut paths, &hop.projected);
}
paths
}
}
#[derive(Clone, Debug)]
struct SymlinkRouteHop {
entry: PathBuf,
parent: PathBuf,
target: PathBuf,
projected: PathBuf,
}
pub(super) fn observe_route(path: &Path) -> io::Result<RouteObservationSnapshot> {
let raw = observe_raw_route(path)?;
let symlink_hops = symlink_route_hops(path)?
.into_iter()
.map(|hop| {
Ok(SymlinkRouteObservation {
entry: hop.entry,
parent: hop.parent,
target: observe_raw_route(&hop.target)?,
projected: observe_raw_route(&hop.projected)?,
})
})
.collect::<io::Result<Vec<_>>>()?;
Ok(RouteObservationSnapshot { raw, symlink_hops })
}
fn observe_raw_route(route: &Path) -> io::Result<RawRouteObservation> {
let base = route
.ancestors()
.last()
.ok_or_else(|| invalid_data("authority path has no root"))?;
let normalized = AbsolutePathBuf::resolve_path_against_base(route, base).into_path_buf();
let normalized_prefixes = route
.ancestors()
.map(|prefix| AbsolutePathBuf::resolve_path_against_base(prefix, base).into_path_buf())
.collect();
let projected = project_through_longest_existing_ancestor(route)?;
Ok(RawRouteObservation {
spelling: route.to_path_buf(),
normalized_prefixes,
normalized,
projected,
})
}
fn append_raw_observation_paths(paths: &mut Vec<PathBuf>, observation: &RawRouteObservation) {
for prefix in &observation.normalized_prefixes {
push_unique_spelling(paths, prefix.clone());
}
push_unique_spelling(paths, observation.projected.clone());
}
fn push_unique_spelling(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.iter().any(|existing| existing == &path) {
paths.push(path);
}
}
fn symlink_route_hops(path: &Path) -> io::Result<Vec<SymlinkRouteHop>> {
let mut hops = Vec::new();
let mut seen = BTreeSet::new();
collect_symlink_route_hops(path, 0, &mut seen, &mut hops)?;
Ok(hops)
}
fn collect_symlink_route_hops(
path: &Path,
depth: usize,
seen: &mut BTreeSet<PathBuf>,
hops: &mut Vec<SymlinkRouteHop>,
) -> io::Result<()> {
if depth > 40 {
return Err(invalid_data("authority path symlink cycle"));
}
if !seen.insert(path.to_path_buf()) {
return Ok(());
}
for ancestor in path.ancestors() {
match std::fs::symlink_metadata(ancestor) {
Ok(_) => match std::fs::read_link(ancestor) {
Ok(target) => {
let parent = ancestor
.parent()
.ok_or_else(|| invalid_data("authority symlink has no parent"))?;
let target = resolve_literal_path(target, parent);
#[cfg(windows)]
if target.to_str().is_none_or(windows_path_is_ambiguous) {
return Err(invalid_data("ambiguous Windows authority symlink"));
}
let suffix = path
.strip_prefix(ancestor)
.map_err(|_| invalid_data("failed to project authority symlink path"))?;
let projected = resolve_literal_path(suffix, &target);
hops.push(SymlinkRouteHop {
entry: ancestor.to_path_buf(),
parent: parent.to_path_buf(),
target,
projected: projected.clone(),
});
collect_symlink_route_hops(&projected, depth + 1, seen, hops)?;
}
Err(error) if error.kind() == io::ErrorKind::InvalidInput => {}
Err(error) => return Err(error),
},
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(error) => return Err(error),
}
}
Ok(())
}
fn project_through_longest_existing_ancestor(path: &Path) -> io::Result<PathBuf> {
project_path(path, 0)
}
fn project_path(path: &Path, symlink_depth: usize) -> io::Result<PathBuf> {
if symlink_depth > 40 {
return Err(invalid_data("too many authority path symlinks"));
}
if let Ok(canonical) = std::fs::canonicalize(path) {
return Ok(canonical);
}
for ancestor in path.ancestors() {
if std::fs::symlink_metadata(ancestor).is_ok()
&& let Ok(target) = std::fs::read_link(ancestor)
{
let parent = ancestor
.parent()
.ok_or_else(|| invalid_data("authority symlink has no parent"))?;
let target = resolve_literal_path(target, parent);
#[cfg(windows)]
if target.to_str().is_none_or(windows_path_is_ambiguous) {
return Err(invalid_data("ambiguous Windows authority symlink"));
}
let suffix = path
.strip_prefix(ancestor)
.map_err(|_| invalid_data("failed to project authority path"))?;
return project_path(&resolve_literal_path(suffix, &target), symlink_depth + 1);
}
}
for ancestor in path.ancestors() {
match std::fs::canonicalize(ancestor) {
Ok(canonical) => {
let suffix = path
.strip_prefix(ancestor)
.map_err(|_| invalid_data("failed to project authority path"))?;
return Ok(
AbsolutePathBuf::resolve_path_against_base(suffix, canonical).into_path_buf(),
);
}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(error) => return Err(error),
}
}
Err(invalid_data("authority path has no existing ancestor"))
}
fn resolve_literal_path(path: impl AsRef<Path>, base: &Path) -> PathBuf {
let path = path.as_ref();
if path.is_absolute() {
path.to_path_buf()
} else {
base.join(path)
}
}
pub(super) fn invalid_data(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, message)
}
@@ -0,0 +1,69 @@
pub(crate) fn windows_path_is_ambiguous(path: &str) -> bool {
let path = path.replace('/', "\\");
if path.starts_with(r"\??\") {
return true;
}
let path = if path
.get(..8)
.is_some_and(|prefix| prefix.eq_ignore_ascii_case(r"\\?\UNC\"))
|| path
.get(..8)
.is_some_and(|prefix| prefix.eq_ignore_ascii_case(r"\\.\UNC\"))
{
format!(r"\\{}", &path[8..])
} else if path.starts_with(r"\\?\") || path.starts_with(r"\\.\") {
let path = &path[4..];
if !matches!(path.as_bytes(), [drive, b':', b'\\', ..] if drive.is_ascii_alphabetic()) {
return true;
}
path.to_string()
} else {
path
};
let bytes = path.as_bytes();
let remainder = if matches!(bytes, [drive, b':', separator, ..]
if drive.is_ascii_alphabetic() && *separator == b'\\')
{
&path[3..]
} else {
path.as_str()
};
if remainder.contains(':') {
return true;
}
remainder.split('\\').any(|component| {
!matches!(component, "." | "..")
&& (component.ends_with(['.', ' ']) || windows_reserved_component(component))
})
}
pub(crate) fn windows_authority_path_is_ambiguous(path: &str) -> bool {
windows_path_is_ambiguous(path)
|| path
.replace('/', "\\")
.split('\\')
.any(|component| component == "..")
}
fn windows_reserved_component(component: &str) -> bool {
let stem = component
.trim_end_matches(['.', ' '])
.split_once('.')
.map_or(component, |(stem, _)| stem);
stem.eq_ignore_ascii_case("AUX")
|| stem.eq_ignore_ascii_case("CON")
|| stem.eq_ignore_ascii_case("CONIN$")
|| stem.eq_ignore_ascii_case("CONOUT$")
|| stem.eq_ignore_ascii_case("NUL")
|| stem.eq_ignore_ascii_case("PRN")
|| ["COM", "LPT"].iter().any(|prefix| {
let Some(rest) = stem.get(3..) else {
return false;
};
let mut chars = rest.chars();
stem.get(..3)
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix))
&& matches!(chars.next(), Some('1'..='9' | '¹' | '²' | '³'))
&& chars.as_str().is_empty()
})
}
@@ -0,0 +1,492 @@
use std::io;
use std::path::Path;
use std::path::PathBuf;
use codex_utils_absolute_path::AbsolutePathBuf;
use same_file::Handle;
use helpers::RegisteredWorktree;
use helpers::RegisteredWorktreeReadError;
use helpers::registry_error;
mod authority;
mod helpers;
mod plain_config;
pub(crate) use authority::RepositoryAuthority;
pub(crate) use plain_config::CommonConfigAuthority;
pub(crate) use plain_config::inspect_plain_common_config_authority;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum RepositoryMetadataKind {
StandardPrimary,
SeparatePrimary,
Linked,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ResolvedRepositoryMetadata {
pub(crate) marker: PathBuf,
pub(crate) git_dir: PathBuf,
pub(crate) common_dir: PathBuf,
pub(crate) kind: RepositoryMetadataKind,
pub(crate) routes: Vec<RepositoryMetadataRoute>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct RepositoryMetadataRoute {
pub(crate) spelling: PathBuf,
pub(crate) target: PathBuf,
pub(crate) kind: RepositoryMetadataRouteKind,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum RepositoryMetadataRouteKind {
Traversal,
StandardDirectory,
}
pub(crate) fn read_bounded_marker(path: &Path) -> io::Result<Vec<u8>> {
#[cfg(test)]
BOUNDED_MARKER_READ_COUNT.with(|count| count.set(count.get() + 1));
read_bounded_file(path, 64 * 1024, "Git metadata marker is too large")
}
#[cfg(test)]
thread_local! {
static BOUNDED_MARKER_READ_COUNT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
pub(crate) fn reset_bounded_marker_read_count() {
BOUNDED_MARKER_READ_COUNT.with(|count| count.set(0));
}
#[cfg(test)]
pub(crate) fn bounded_marker_read_count() -> usize {
BOUNDED_MARKER_READ_COUNT.with(std::cell::Cell::get)
}
pub(crate) fn parse_marker_path(contents: &[u8], prefix: &[u8]) -> io::Result<PathBuf> {
let contents = contents
.strip_prefix(prefix)
.ok_or_else(|| invalid_data("malformed Git metadata marker"))?;
let contents = trim_trailing_ascii_whitespace(contents);
if contents.is_empty() || contents.contains(&0) {
return Err(invalid_data("empty Git metadata marker path"));
}
bytes_to_path(contents)
}
pub(crate) fn resolve_repository_metadata(
dot_git: &Path,
) -> io::Result<ResolvedRepositoryMetadata> {
let metadata = std::fs::symlink_metadata(dot_git)?;
if metadata.file_type().is_symlink() {
return Err(invalid_data("symlinked Git metadata marker"));
}
let marker_is_directory = metadata.is_dir();
let (git_dir, git_dir_route, default_kind) = if marker_is_directory {
(
std::fs::canonicalize(dot_git)?,
Some((
dot_git.to_path_buf(),
RepositoryMetadataRouteKind::StandardDirectory,
)),
RepositoryMetadataKind::StandardPrimary,
)
} else if metadata.is_file() {
let contents = read_bounded_marker(dot_git)?;
let route = raw_path_against(
parse_marker_path(&contents, b"gitdir: ")?,
dot_git
.parent()
.ok_or_else(|| invalid_data("Git metadata marker has no parent"))?,
);
let git_dir = std::fs::canonicalize(&route)?;
(
git_dir,
Some((route, RepositoryMetadataRouteKind::Traversal)),
RepositoryMetadataKind::SeparatePrimary,
)
} else {
return Err(invalid_data("unsupported Git metadata marker"));
};
let commondir = git_dir.join("commondir");
match std::fs::symlink_metadata(&commondir) {
Ok(metadata) => {
if !metadata.is_file() || metadata.file_type().is_symlink() {
return Err(invalid_data("unsupported Git common-dir marker"));
}
let contents = read_bounded_marker(&commondir)?;
let route = raw_path_against(parse_marker_path(&contents, b"")?, &git_dir);
let common_dir = std::fs::canonicalize(&route)?;
let mut routes = git_dir_route
.map(|(spelling, kind)| RepositoryMetadataRoute {
spelling,
target: git_dir.clone(),
kind,
})
.into_iter()
.collect::<Vec<_>>();
routes.push(RepositoryMetadataRoute {
spelling: route,
target: common_dir.clone(),
kind: RepositoryMetadataRouteKind::Traversal,
});
return Ok(ResolvedRepositoryMetadata {
marker: dot_git.to_path_buf(),
git_dir,
common_dir,
kind: RepositoryMetadataKind::Linked,
routes,
});
}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(error) => return Err(error),
}
if git_dir
.parent()
.is_some_and(|parent| parent.file_name() == Some(std::ffi::OsStr::new("worktrees")))
{
let common_dir = git_dir
.parent()
.and_then(Path::parent)
.ok_or_else(|| invalid_data("linked Git admin dir has no common parent"))?;
let common_dir = std::fs::canonicalize(common_dir)?;
let routes = git_dir_route
.map(|(spelling, kind)| RepositoryMetadataRoute {
spelling,
target: git_dir.clone(),
kind,
})
.into_iter()
.collect();
return Ok(ResolvedRepositoryMetadata {
marker: dot_git.to_path_buf(),
git_dir,
common_dir,
kind: RepositoryMetadataKind::Linked,
routes,
});
}
Ok(ResolvedRepositoryMetadata {
marker: dot_git.to_path_buf(),
common_dir: git_dir.clone(),
git_dir: git_dir.clone(),
kind: default_kind,
routes: git_dir_route
.map(|(spelling, kind)| RepositoryMetadataRoute {
spelling,
target: git_dir,
kind,
})
.into_iter()
.collect(),
})
}
pub(crate) fn repository_common_dir_for_candidate_root(root: &Path) -> io::Result<Option<PathBuf>> {
let marker = root.join(".git");
match std::fs::symlink_metadata(&marker) {
Ok(_) => resolve_repository_metadata(&marker).map(|metadata| Some(metadata.common_dir)),
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
Ok(None)
}
Err(error) => Err(error),
}
}
pub(crate) fn linked_common_dir_for_root(root: &Path) -> io::Result<Option<PathBuf>> {
let marker = root.join(".git");
let metadata = match std::fs::symlink_metadata(&marker) {
Ok(metadata) => metadata,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(None);
}
Err(error) => return Err(error),
};
if metadata.file_type().is_symlink() {
return Err(invalid_data("symlinked Git metadata marker"));
}
if !metadata.is_file() && !metadata.is_dir() {
return Ok(None);
}
let resolved = resolve_repository_metadata(&marker)?;
Ok((resolved.kind == RepositoryMetadataKind::Linked).then_some(resolved.common_dir))
}
fn registered_worktrees(
common_dir: &Path,
) -> Result<Vec<RegisteredWorktree>, RegisteredWorktreeReadError> {
let worktrees = common_dir.join("worktrees");
match std::fs::symlink_metadata(&worktrees) {
Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => {}
Ok(_) => {
return Err(registry_error(
&worktrees,
invalid_data("unsupported Git worktree registry"),
));
}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(Vec::new());
}
Err(error) => return Err(registry_error(&worktrees, error)),
}
let mut registrations = Vec::new();
let entries =
std::fs::read_dir(&worktrees).map_err(|error| registry_error(&worktrees, error))?;
for entry in entries {
let admin_dir = entry
.map_err(|error| registry_error(&worktrees, error))?
.path();
let metadata = std::fs::symlink_metadata(&admin_dir)
.map_err(|error| registry_error(&admin_dir, error))?;
if !metadata.is_dir() || metadata.file_type().is_symlink() {
return Err(registry_error(
&admin_dir,
invalid_data("unsupported Git worktree registry entry"),
));
}
let gitdir_marker = admin_dir.join("gitdir");
let metadata = std::fs::symlink_metadata(&gitdir_marker)
.map_err(|error| registry_error(&gitdir_marker, error))?;
if !metadata.is_file() || metadata.file_type().is_symlink() {
return Err(registry_error(
&gitdir_marker,
invalid_data("unsupported Git worktree gitdir marker"),
));
}
let contents = read_bounded_marker(&gitdir_marker)
.map_err(|error| registry_error(&gitdir_marker, error))?;
let marker_route = raw_path_against(
parse_marker_path(&contents, b"")
.map_err(|error| registry_error(&gitdir_marker, error))?,
&admin_dir,
);
#[cfg(windows)]
if marker_route
.to_str()
.is_none_or(crate::path_authority::windows_path_is_ambiguous)
{
return Err(registry_error(
&gitdir_marker,
invalid_data("ambiguous Windows Git worktree path"),
));
}
// Git writes this protected registry marker from real paths (either
// absolute or relative). Normalize it only after retaining the raw
// route long enough to apply platform path grammar.
let marker = resolve_path_against(marker_route.clone(), &admin_dir);
if marker.file_name() != Some(std::ffi::OsStr::new(".git")) {
return Err(registry_error(
&gitdir_marker,
invalid_data("malformed Git worktree gitdir marker"),
));
}
let root = marker
.parent()
.ok_or_else(|| {
registry_error(
&gitdir_marker,
invalid_data("Git worktree marker has no parent"),
)
})?
.to_path_buf();
registrations.push(RegisteredWorktree {
root,
admin_dir,
marker_route,
});
}
Ok(registrations)
}
pub(crate) fn primary_authority_is_proven(
current_root: &Path,
common_dir: &Path,
roots: &[PathBuf],
) -> io::Result<bool> {
for root in roots {
if directories_refer_to_same_location(root, current_root).unwrap_or(false) {
continue;
}
let marker = root.join(".git");
match std::fs::symlink_metadata(&marker) {
Ok(_) => {
let resolved = resolve_repository_metadata(&marker)?;
if resolved.kind != RepositoryMetadataKind::Linked
&& directories_refer_to_same_location(&resolved.common_dir, common_dir)?
{
return Ok(true);
}
}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(error) => return Err(error),
}
}
Ok(false)
}
pub(crate) fn directories_refer_to_same_location(left: &Path, right: &Path) -> io::Result<bool> {
let left_metadata = match std::fs::metadata(left) {
Ok(metadata) => metadata,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(false);
}
Err(error) => return Err(error),
};
let right_metadata = match std::fs::metadata(right) {
Ok(metadata) => metadata,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(false);
}
Err(error) => return Err(error),
};
if !left_metadata.is_dir() || !right_metadata.is_dir() {
return Ok(false);
}
Ok(Handle::from_path(left)? == Handle::from_path(right)?)
}
pub(crate) fn path_has_untrusted_root_identity_ancestor(path: &Path, roots: &[PathBuf]) -> bool {
let mut root_identities = Vec::new();
for root in roots {
match std::fs::metadata(root) {
Ok(metadata) if metadata.is_dir() => match Handle::from_path(root) {
Ok(identity) => root_identities.push(identity),
Err(_) => return true,
},
Ok(_) => return true,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(_) => return true,
}
}
if root_identities.is_empty() {
return false;
}
let start = match std::fs::metadata(path) {
Ok(metadata) if metadata.is_dir() => path,
Ok(_) => path.parent().unwrap_or(path),
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
path.parent().unwrap_or(path)
}
Err(_) => return true,
};
for ancestor in start.ancestors() {
match std::fs::metadata(ancestor) {
Ok(metadata) if metadata.is_dir() => match Handle::from_path(ancestor) {
Ok(identity) if root_identities.contains(&identity) => return true,
Ok(_) => {}
Err(_) => return true,
},
Ok(_) => continue,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(_) => return true,
}
}
false
}
fn read_bounded_file(path: &Path, max_bytes: u64, too_large: &str) -> io::Result<Vec<u8>> {
use std::io::Read;
let file = std::fs::File::open(path)?;
let mut contents = Vec::new();
file.take(max_bytes + 1).read_to_end(&mut contents)?;
if contents.len() as u64 > max_bytes {
return Err(invalid_data(too_large));
}
Ok(contents)
}
fn resolve_path_against(path: PathBuf, base: &Path) -> PathBuf {
AbsolutePathBuf::resolve_path_against_base(path, base).into_path_buf()
}
fn raw_path_against(path: PathBuf, base: &Path) -> PathBuf {
if path.is_absolute() {
path
} else {
base.join(path)
}
}
fn bytes_to_path(bytes: &[u8]) -> io::Result<PathBuf> {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStringExt;
Ok(PathBuf::from(std::ffi::OsString::from_vec(bytes.to_vec())))
}
#[cfg(not(unix))]
{
Ok(PathBuf::from(std::str::from_utf8(bytes).map_err(|_| {
invalid_data("non-UTF-8 Git filesystem path")
})?))
}
}
fn trim_trailing_ascii_whitespace(mut value: &[u8]) -> &[u8] {
while value.last().is_some_and(u8::is_ascii_whitespace) {
value = &value[..value.len() - 1];
}
value
}
fn invalid_data(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, message)
}
#[cfg(test)]
#[path = "repository_authority_tests.rs"]
mod tests;
@@ -0,0 +1,400 @@
use std::io;
use std::path::Path;
use std::path::PathBuf;
use crate::errors::GitReadError;
use crate::git_config::path_is_within;
use crate::path_authority::RepositoryRouteBoundaries;
use crate::path_authority::repository_route_boundaries;
use super::CommonConfigAuthority;
use super::RegisteredWorktree;
use super::ResolvedRepositoryMetadata;
use super::directories_refer_to_same_location;
use super::helpers::common_dir_is_within_untrusted_root;
use super::helpers::invalid_metadata;
use super::helpers::paths_equal;
use super::helpers::push_unique;
use super::helpers::validated_logical_process_cwd;
use super::inspect_plain_common_config_authority;
use super::linked_common_dir_for_root;
use super::primary_authority_is_proven;
use super::registered_worktrees;
use super::resolve_repository_metadata;
mod policy;
/// Repository and filesystem authority retained for the lifetime of one
/// trusted Git runner.
#[derive(Debug)]
pub(crate) struct RepositoryAuthority {
active_worktree_root: PathBuf,
roots: Vec<PathBuf>,
worktree_roots: Vec<PathBuf>,
common_dirs: Vec<PathBuf>,
unproven_primary_common_dir: Option<PathBuf>,
metadata_snapshots: Vec<ResolvedRepositoryMetadata>,
registered_worktrees: Vec<RegisteredWorktree>,
active_metadata: Option<ResolvedRepositoryMetadata>,
route_boundaries: RepositoryRouteBoundaries,
}
impl RepositoryAuthority {
pub(crate) fn discover(cwd: &Path) -> Result<Self, GitReadError> {
let lexical_cwd = if cwd.is_absolute() {
cwd.to_path_buf()
} else {
std::env::current_dir()
.map_err(|_| GitReadError::NotRepository {
path: cwd.to_path_buf(),
})?
.join(cwd)
};
let canonical_cwd =
std::fs::canonicalize(cwd).map_err(|_| GitReadError::NotRepository {
path: cwd.to_path_buf(),
})?;
let worktree_root = crate::get_git_repo_root(&canonical_cwd)
.and_then(|root| std::fs::canonicalize(root).ok())
.unwrap_or_else(|| canonical_cwd.clone());
let mut authority = Self {
active_worktree_root: worktree_root.clone(),
roots: Vec::new(),
worktree_roots: Vec::new(),
common_dirs: Vec::new(),
unproven_primary_common_dir: None,
metadata_snapshots: Vec::new(),
registered_worktrees: Vec::new(),
active_metadata: None,
route_boundaries: RepositoryRouteBoundaries::default(),
};
authority.record_repository_ancestry(&worktree_root)?;
// Canonicalization can erase a repository-controlled symlink prefix.
// Retain the requested spelling so lexical enclosing repositories stay
// in the authority boundary.
let lexical_base = if lexical_cwd.is_dir() {
lexical_cwd
} else {
lexical_cwd
.parent()
.ok_or_else(|| GitReadError::NotRepository {
path: cwd.to_path_buf(),
})?
.to_path_buf()
};
authority.record_repository_ancestry(&lexical_base)?;
if let Some(logical_cwd) = validated_logical_process_cwd(&canonical_cwd) {
authority.record_repository_ancestry(&logical_cwd)?;
}
authority.record_preselection_primary_authorities()?;
authority.active_metadata = authority
.metadata_snapshots
.iter()
.find(|snapshot| snapshot.marker == worktree_root.join(".git"))
.cloned();
authority.route_boundaries =
repository_route_boundaries(&authority.worktree_roots, &authority.common_dirs)
.map_err(|error| invalid_metadata(&worktree_root, error))?;
authority.validate_registered_worktree_routes()?;
authority.validate_metadata_routes()?;
Ok(authority)
}
pub(crate) fn ensure_primary_authority(&self) -> Result<(), GitReadError> {
if let Some(common_dir) = &self.unproven_primary_common_dir {
return Err(GitReadError::UnprovenPrimaryAuthority {
common_dir: common_dir.display().to_string(),
});
}
Ok(())
}
pub(crate) fn path_is_untrusted_for_executable(&self, path: &Path) -> bool {
self.path_is_untrusted_for_executable_result(path)
.unwrap_or(true)
}
pub(crate) fn revalidate_active_repository_metadata(&self) -> io::Result<()> {
let Some(expected) = &self.active_metadata else {
return Ok(());
};
let actual = resolve_repository_metadata(&expected.marker).map_err(|error| {
io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"repository metadata changed during Git operation at {}: {error}",
expected.marker.display()
),
)
})?;
if &actual != expected {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"repository metadata changed during Git operation at {}",
expected.marker.display()
),
));
}
self.validate_snapshot_routes(&actual).map_err(|error| {
io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"repository metadata changed during Git operation at {}: {error}",
expected.marker.display()
),
)
})
}
#[cfg(test)]
pub(crate) fn from_test_locations(
roots: Vec<PathBuf>,
worktree_roots: Vec<PathBuf>,
common_dirs: Vec<PathBuf>,
) -> Result<Self, GitReadError> {
let route_boundaries = repository_route_boundaries(&worktree_roots, &common_dirs)
.map_err(|error| invalid_metadata(Path::new("<test>"), error))?;
Ok(Self {
active_worktree_root: worktree_roots
.first()
.or_else(|| roots.first())
.cloned()
.unwrap_or_default(),
roots,
worktree_roots,
common_dirs,
unproven_primary_common_dir: None,
metadata_snapshots: Vec::new(),
registered_worktrees: Vec::new(),
active_metadata: None,
route_boundaries,
})
}
#[cfg(test)]
pub(crate) fn active_git_dir(&self) -> Option<&Path> {
self.active_metadata
.as_ref()
.map(|metadata| metadata.git_dir.as_path())
}
#[cfg(test)]
pub(crate) fn contains_root(&self, root: &Path) -> bool {
self.roots
.iter()
.any(|candidate| paths_equal(candidate, root))
}
#[cfg(test)]
pub(crate) fn roots(&self) -> &[PathBuf] {
&self.roots
}
fn record_preselection_primary_authorities(&mut self) -> Result<(), GitReadError> {
let mut inspected_common_dirs: Vec<PathBuf> = Vec::new();
loop {
let roots = self.roots.clone();
let mut next = None;
for root in roots {
let Some(common_dir) = linked_common_dir_for_root(&root)
.map_err(|error| invalid_metadata(&root.join(".git"), error))?
else {
continue;
};
let already_inspected = inspected_common_dirs.iter().try_fold(
false,
|found, inspected| -> Result<bool, GitReadError> {
Ok(found
|| directories_refer_to_same_location(inspected, &common_dir)
.map_err(|error| invalid_metadata(&common_dir, error))?)
},
)?;
if !already_inspected {
next = Some((root, common_dir));
break;
}
}
let Some((linked_root, common_dir)) = next else {
return Ok(());
};
inspected_common_dirs.push(common_dir.clone());
if primary_authority_is_proven(&linked_root, &common_dir, &self.roots)
.map_err(|error| invalid_metadata(&common_dir, error))?
{
continue;
}
if common_dir_is_within_untrusted_root(&common_dir, &self.roots) {
self.unproven_primary_common_dir.get_or_insert(common_dir);
continue;
}
match inspect_plain_common_config_authority(&common_dir)
.map_err(|error| invalid_metadata(&common_dir, error))?
{
CommonConfigAuthority::Bare => {}
CommonConfigAuthority::Worktree(root) => {
self.record_repository_ancestry(&root)?;
}
CommonConfigAuthority::Unproven => {
self.unproven_primary_common_dir.get_or_insert(common_dir);
}
}
}
}
fn record_repository_ancestry(&mut self, start: &Path) -> Result<(), GitReadError> {
push_unique(&mut self.roots, start.to_path_buf());
push_unique(&mut self.worktree_roots, start.to_path_buf());
self.record_repository_marker(start)?;
for ancestor in start.parent().into_iter().flat_map(Path::ancestors) {
let marker = ancestor.join(".git");
match std::fs::symlink_metadata(&marker) {
Ok(_) => {
push_unique(&mut self.roots, ancestor.to_path_buf());
push_unique(&mut self.worktree_roots, ancestor.to_path_buf());
let canonical_root = std::fs::canonicalize(ancestor)
.map_err(|error| invalid_metadata(&marker, error))?;
push_unique(&mut self.roots, canonical_root.clone());
push_unique(&mut self.worktree_roots, canonical_root);
self.record_repository_marker(ancestor)?;
}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => return Err(invalid_metadata(&marker, error)),
}
}
Ok(())
}
fn record_repository_marker(&mut self, worktree_root: &Path) -> Result<(), GitReadError> {
let marker = worktree_root.join(".git");
let snapshot = match std::fs::symlink_metadata(&marker) {
Ok(_) => resolve_repository_metadata(&marker)
.map_err(|error| invalid_metadata(&marker, error))?,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(invalid_metadata(&marker, error)),
};
let common_dir = snapshot.common_dir.clone();
if !self
.metadata_snapshots
.iter()
.any(|known| known.marker == snapshot.marker)
{
self.metadata_snapshots.push(snapshot);
}
if !path_is_within(&common_dir, worktree_root) {
push_unique(&mut self.roots, common_dir.clone());
}
push_unique(&mut self.common_dirs, common_dir.clone());
self.record_registered_worktrees(&common_dir)?;
self.record_common_dir_ancestry(common_dir)
}
fn record_registered_worktrees(&mut self, common_dir: &Path) -> Result<(), GitReadError> {
let registrations = registered_worktrees(common_dir)
.map_err(|error| invalid_metadata(&error.path, error.source))?;
for registration in registrations {
if !self
.registered_worktrees
.iter()
.any(|known| known.admin_dir == registration.admin_dir)
{
self.registered_worktrees.push(registration.clone());
}
let already_known = self
.roots
.iter()
.any(|known| paths_equal(known, &registration.root));
push_unique(&mut self.roots, registration.root.clone());
push_unique(&mut self.worktree_roots, registration.root.clone());
if let Ok(canonical_root) = std::fs::canonicalize(&registration.root) {
push_unique(&mut self.roots, canonical_root.clone());
push_unique(&mut self.worktree_roots, canonical_root);
}
self.validate_registered_worktree_backlink(&registration)?;
if !already_known {
self.record_repository_ancestry(&registration.root)?;
}
}
Ok(())
}
fn validate_registered_worktree_backlink(
&self,
registration: &RegisteredWorktree,
) -> Result<(), GitReadError> {
match std::fs::metadata(&registration.root) {
Ok(metadata) if metadata.is_dir() => {}
Ok(_) => {
return Err(GitReadError::InvalidRepositoryMetadata {
path: registration.root.clone(),
reason: "registered Git worktree is not a directory".to_string(),
});
}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(());
}
Err(error) => return Err(invalid_metadata(&registration.root, error)),
}
let marker = registration.root.join(".git");
match std::fs::symlink_metadata(&marker) {
Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => {}
Ok(_) => {
return Err(GitReadError::InvalidRepositoryMetadata {
path: marker,
reason: "unsupported registered Git worktree marker".to_string(),
});
}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(());
}
Err(error) => return Err(invalid_metadata(&marker, error)),
}
let resolved = resolve_repository_metadata(&marker)
.map_err(|error| invalid_metadata(&marker, error))?;
if !directories_refer_to_same_location(&resolved.git_dir, &registration.admin_dir)
.map_err(|error| invalid_metadata(&marker, error))?
{
return Err(GitReadError::UnsafeRepositoryMetadata {
path: marker,
reason: "Git worktree registry backlink mismatch".to_string(),
});
}
Ok(())
}
fn record_common_dir_ancestry(&mut self, common_dir: PathBuf) -> Result<(), GitReadError> {
for ancestor in common_dir.parent().into_iter().flat_map(Path::ancestors) {
let marker = ancestor.join(".git");
match std::fs::symlink_metadata(&marker) {
Ok(_) => {
let canonical_root = std::fs::canonicalize(ancestor)
.map_err(|error| invalid_metadata(&marker, error))?;
let already_known = self.roots.iter().any(|root| {
paths_equal(root, ancestor) || paths_equal(root, &canonical_root)
});
push_unique(&mut self.roots, ancestor.to_path_buf());
push_unique(&mut self.worktree_roots, ancestor.to_path_buf());
push_unique(&mut self.roots, canonical_root.clone());
push_unique(&mut self.worktree_roots, canonical_root);
if !already_known {
self.record_repository_marker(ancestor)?;
}
}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => return Err(invalid_metadata(&marker, error)),
}
}
Ok(())
}
}
@@ -0,0 +1,147 @@
use std::collections::BTreeSet;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use crate::errors::GitReadError;
use crate::git_config::path_is_within;
use super::super::RepositoryMetadataRouteKind;
use super::super::ResolvedRepositoryMetadata;
use super::super::directories_refer_to_same_location;
use super::super::helpers::canonical_existing_ancestors;
use super::super::helpers::invalid_metadata;
use super::super::helpers::paths_equal;
use super::super::repository_common_dir_for_candidate_root;
use super::RepositoryAuthority;
impl RepositoryAuthority {
pub(crate) fn canonical_command_cwd(&self, cwd: &Path) -> io::Result<PathBuf> {
let canonical = std::fs::canonicalize(cwd)?;
let inspection = self.route_boundaries.inspect_route(cwd)?;
if (inspection.touches_worktree || inspection.crosses_worktree)
&& !path_is_within(&canonical, &self.active_worktree_root)
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"refusing Git command cwd that no longer resolves within the selected worktree: {}",
cwd.display()
),
));
}
Ok(canonical)
}
pub(super) fn path_is_untrusted_for_executable_result(&self, path: &Path) -> io::Result<bool> {
let inspection = self.route_boundaries.inspect_route(path)?;
if inspection.touches_worktree || inspection.crosses_metadata {
return Ok(true);
}
for candidate in inspection.observed_paths {
if self.route_boundaries.contains_known_boundary(&candidate)? {
continue;
}
if self.has_related_repository_ancestor(&candidate)? {
return Ok(true);
}
}
Ok(false)
}
fn has_related_repository_ancestor(&self, path: &Path) -> io::Result<bool> {
let mut ancestors = path.ancestors().map(Path::to_path_buf).collect::<Vec<_>>();
ancestors.extend(canonical_existing_ancestors(path)?);
let mut seen = BTreeSet::new();
for ancestor in ancestors {
if !seen.insert(ancestor.clone()) {
continue;
}
let Some(common_dir) = repository_common_dir_for_candidate_root(&ancestor)? else {
continue;
};
for protected in &self.common_dirs {
if directories_refer_to_same_location(protected, &common_dir)? {
return Ok(true);
}
}
}
Ok(false)
}
pub(super) fn validate_metadata_routes(&self) -> Result<(), GitReadError> {
for snapshot in &self.metadata_snapshots {
self.validate_snapshot_routes(snapshot)?;
}
Ok(())
}
pub(super) fn validate_registered_worktree_routes(&self) -> Result<(), GitReadError> {
for registration in &self.registered_worktrees {
if self
.route_boundaries
.retained_route_is_untrusted(&registration.marker_route)
.map_err(|error| invalid_metadata(&registration.admin_dir.join("gitdir"), error))?
{
return Err(GitReadError::UnsafeRepositoryMetadata {
path: registration.admin_dir.join("gitdir"),
reason: "Git worktree registry route crosses a repository worktree".to_string(),
});
}
}
Ok(())
}
pub(super) fn validate_snapshot_routes(
&self,
snapshot: &ResolvedRepositoryMetadata,
) -> Result<(), GitReadError> {
for route in &snapshot.routes {
if route.kind == RepositoryMetadataRouteKind::StandardDirectory {
let root = route.spelling.parent().ok_or_else(|| {
GitReadError::InvalidRepositoryMetadata {
path: route.spelling.clone(),
reason: "Git metadata directory has no worktree parent".to_string(),
}
})?;
let expected = std::fs::canonicalize(root)
.map_err(|error| invalid_metadata(root, error))?
.join(".git");
if !paths_equal(&route.target, &expected) {
return Err(GitReadError::UnsafeRepositoryMetadata {
path: snapshot.marker.clone(),
reason: "nonstandard Git metadata directory".to_string(),
});
}
if self
.route_boundaries
.route_cancels_worktree_descendant(&route.spelling)
.map_err(|error| invalid_metadata(&route.spelling, error))?
{
return Err(GitReadError::UnsafeRepositoryMetadata {
path: snapshot.marker.clone(),
reason: "Git metadata route crosses a repository worktree".to_string(),
});
}
continue;
}
let spelling_crosses = self
.route_boundaries
.inspect_route(&route.spelling)
.map_err(|error| invalid_metadata(&route.spelling, error))?
.crosses_worktree;
let target_crosses = self
.route_boundaries
.inspect_route(&route.target)
.map_err(|error| invalid_metadata(&route.target, error))?
.crosses_worktree;
if spelling_crosses || target_crosses {
return Err(GitReadError::UnsafeRepositoryMetadata {
path: snapshot.marker.clone(),
reason: "Git metadata route crosses a repository worktree".to_string(),
});
}
}
Ok(())
}
}
@@ -0,0 +1,89 @@
use std::io;
use std::path::Path;
use std::path::PathBuf;
use crate::errors::GitReadError;
use crate::git_config::path_is_within;
use super::directories_refer_to_same_location;
use super::path_has_untrusted_root_identity_ancestor;
#[derive(Clone, Debug)]
pub(super) struct RegisteredWorktree {
pub(super) root: PathBuf,
pub(super) admin_dir: PathBuf,
pub(super) marker_route: PathBuf,
}
#[derive(Debug)]
pub(super) struct RegisteredWorktreeReadError {
pub(super) path: PathBuf,
pub(super) source: io::Error,
}
pub(super) fn registry_error(path: &Path, source: io::Error) -> RegisteredWorktreeReadError {
RegisteredWorktreeReadError {
path: path.to_path_buf(),
source,
}
}
pub(super) fn common_dir_is_within_untrusted_root(common_dir: &Path, roots: &[PathBuf]) -> bool {
for root in roots {
if directories_refer_to_same_location(common_dir, root).unwrap_or(false) {
continue;
}
if path_is_within(common_dir, root)
|| path_has_untrusted_root_identity_ancestor(common_dir, std::slice::from_ref(root))
{
return true;
}
}
false
}
pub(super) fn canonical_existing_ancestors(path: &Path) -> io::Result<Vec<PathBuf>> {
let mut canonical = Vec::new();
for ancestor in path.ancestors() {
match std::fs::canonicalize(ancestor) {
Ok(path) => canonical.push(path),
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(error) => return Err(error),
}
}
Ok(canonical)
}
pub(super) fn validated_logical_process_cwd(canonical_cwd: &Path) -> Option<PathBuf> {
let process_cwd = std::fs::canonicalize(std::env::current_dir().ok()?).ok()?;
if !paths_equal(&process_cwd, canonical_cwd) {
return None;
}
let logical_cwd = PathBuf::from(std::env::var_os("PWD")?);
if !logical_cwd.is_absolute() {
return None;
}
let canonical_logical_cwd = std::fs::canonicalize(&logical_cwd).ok()?;
paths_equal(&canonical_logical_cwd, canonical_cwd).then_some(logical_cwd)
}
pub(super) fn push_unique(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.iter().any(|existing| paths_equal(existing, &path)) {
paths.push(path);
}
}
pub(super) fn paths_equal(left: &Path, right: &Path) -> bool {
path_is_within(left, right) && path_is_within(right, left)
}
pub(super) fn invalid_metadata(path: &Path, error: io::Error) -> GitReadError {
GitReadError::InvalidRepositoryMetadata {
path: path.to_path_buf(),
reason: error.to_string(),
}
}
@@ -0,0 +1,137 @@
use std::io;
use std::path::Path;
use std::path::PathBuf;
use super::bytes_to_path;
use super::read_bounded_file;
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum CommonConfigAuthority {
Bare,
Worktree(PathBuf),
Unproven,
}
pub(crate) fn inspect_plain_common_config_authority(
common_dir: &Path,
) -> io::Result<CommonConfigAuthority> {
let config_path = common_dir.join("config");
let metadata = match std::fs::symlink_metadata(&config_path) {
Ok(metadata) => metadata,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
return Ok(CommonConfigAuthority::Unproven);
}
Err(error) => return Err(error),
};
if !metadata.is_file() || metadata.file_type().is_symlink() {
return Ok(CommonConfigAuthority::Unproven);
}
let bytes = read_bounded_file(
&config_path,
1024 * 1024,
"Git common config is too large for authority proof",
)?;
let config = match gix::config::File::from_bytes_no_includes(
&bytes,
gix::config::file::Metadata::default(),
gix::config::file::init::Options::default(),
) {
Ok(config) => config,
Err(_) => return Ok(CommonConfigAuthority::Unproven),
};
if config.sections_by_name("include").is_some()
|| config.sections_by_name("includeIf").is_some()
{
return Ok(CommonConfigAuthority::Unproven);
}
let bare = match unique_explicit_value(&config, "core", "bare") {
Ok(Some(value)) => match explicit_boolean(value.as_ref()) {
Some(value) => Some(value),
None => return Ok(CommonConfigAuthority::Unproven),
},
Ok(None) => None,
Err(()) => return Ok(CommonConfigAuthority::Unproven),
};
match unique_explicit_value(&config, "extensions", "worktreeConfig") {
Ok(Some(value)) if explicit_boolean(value.as_ref()) == Some(false) => {}
Ok(None) => {}
Ok(Some(_)) | Err(()) => return Ok(CommonConfigAuthority::Unproven),
}
let worktree = match unique_explicit_value(&config, "core", "worktree") {
Ok(Some(value)) => Some(value),
Ok(None) => None,
Err(()) => return Ok(CommonConfigAuthority::Unproven),
};
if let Some(worktree) = worktree {
if bare == Some(true) {
return Ok(CommonConfigAuthority::Unproven);
}
let worktree = bytes_to_path(&worktree)?;
if worktree.as_os_str().is_empty() || !worktree.is_absolute() {
return Ok(CommonConfigAuthority::Unproven);
}
#[cfg(windows)]
if worktree
.to_str()
.is_none_or(crate::path_authority::windows_authority_path_is_ambiguous)
{
return Ok(CommonConfigAuthority::Unproven);
}
return Ok(CommonConfigAuthority::Worktree(worktree));
}
Ok(if bare == Some(true) {
CommonConfigAuthority::Bare
} else {
CommonConfigAuthority::Unproven
})
}
fn unique_explicit_value(
config: &gix::config::File<'_>,
section_name: &str,
value_name: &str,
) -> Result<Option<Vec<u8>>, ()> {
let Some(sections) = config.sections_by_name(section_name) else {
return Ok(None);
};
let mut occurrences = 0usize;
let mut explicit_values = Vec::new();
for section in sections {
if section.header().subsection_name().is_some() {
return Err(());
}
occurrences += section
.value_names()
.filter(|name| {
let name: &str = name.as_ref();
name.eq_ignore_ascii_case(value_name)
})
.count();
explicit_values.extend(
section
.values(value_name)
.into_iter()
.map(|value| value.as_ref().to_vec()),
);
}
match (occurrences, explicit_values.len()) {
(0, 0) => Ok(None),
(1, 1) => Ok(explicit_values.pop()),
_ => Err(()),
}
}
fn explicit_boolean(value: &[u8]) -> Option<bool> {
if value.eq_ignore_ascii_case(b"true") {
Some(true)
} else if value.eq_ignore_ascii_case(b"false") {
Some(false)
} else {
None
}
}
@@ -0,0 +1,255 @@
use super::*;
#[test]
fn unreadable_metadata_is_invalid_not_a_proven_policy_crossing() {
let marker = PathBuf::from("repository/.git/commondir");
let error = super::helpers::invalid_metadata(
&marker,
io::Error::new(
io::ErrorKind::PermissionDenied,
"metadata marker is unreadable",
),
);
assert_eq!(
error,
crate::errors::GitReadError::InvalidRepositoryMetadata {
path: marker,
reason: "metadata marker is unreadable".to_string(),
}
);
assert_eq!(error.io_kind(), io::ErrorKind::InvalidData);
}
fn write_common_config(body: &[u8]) -> tempfile::TempDir {
let common = tempfile::tempdir().expect("common dir");
std::fs::write(common.path().join("config"), body).expect("write common config");
common
}
fn native_bare_value(config: &Path, includes: bool) -> io::Result<Option<bool>> {
let mut command = std::process::Command::new("git");
crate::safe_git::isolate_git_command_environment(&mut command);
command
.args(["config", "--file"])
.arg(config)
.arg(if includes {
"--includes"
} else {
"--no-includes"
})
.args(["--type=bool", "--get", "core.bare"]);
let output = command.output()?;
match output.status.code() {
Some(0) => Ok(Some(
String::from_utf8_lossy(&output.stdout).trim() == "true",
)),
Some(1) if output.stdout.is_empty() => Ok(None),
_ => Err(io::Error::other(format!(
"native core.bare query failed: {}",
String::from_utf8_lossy(&output.stderr)
))),
}
}
#[test]
fn plain_common_bare_parser_matches_native_boolean_syntax_and_is_stricter_on_duplicates() {
for (name, body, expected, native) in [
(
"explicit true",
b"[core]\n\tbare = true\n".as_slice(),
CommonConfigAuthority::Bare,
Some(true),
),
(
"implicit true",
b"[core]\n\tbare\n".as_slice(),
CommonConfigAuthority::Unproven,
Some(true),
),
(
"quoted true",
b"[core]\n\tbare = \"true\"\n".as_slice(),
CommonConfigAuthority::Bare,
Some(true),
),
(
"false",
b"[core]\n\tbare = false\n".as_slice(),
CommonConfigAuthority::Unproven,
Some(false),
),
(
"duplicate last true",
b"[core]\n\tbare = false\n\tbare = true\n".as_slice(),
CommonConfigAuthority::Unproven,
Some(true),
),
(
"duplicate last false",
b"[core]\n\tbare = true\n\tbare = false\n".as_slice(),
CommonConfigAuthority::Unproven,
Some(false),
),
(
"mixed implicit then explicit",
b"[core]\n\tbare\n\tbare = true\n".as_slice(),
CommonConfigAuthority::Unproven,
Some(true),
),
(
"mixed explicit then implicit",
b"[core]\n\tbare = false\n\tbare\n".as_slice(),
CommonConfigAuthority::Unproven,
Some(true),
),
(
"mixed case duplicate",
b"[core]\n\tbare = false\n\tBaRe = true\n".as_slice(),
CommonConfigAuthority::Unproven,
Some(true),
),
] {
let common = write_common_config(body);
assert_eq!(
inspect_plain_common_config_authority(common.path()).expect(name),
expected,
"authority result for {name}"
);
assert_eq!(
native_bare_value(&common.path().join("config"), false).expect(name),
native,
"native result for {name}"
);
}
}
#[test]
fn common_bare_proof_accepts_only_explicit_boolean_literals() {
for (name, body) in [
("empty", b"[core]\n\tbare =\n".as_slice()),
("yes", b"[core]\n\tbare = yes\n".as_slice()),
("on", b"[core]\n\tbare = on\n".as_slice()),
("one", b"[core]\n\tbare = 1\n".as_slice()),
("leading zero", b"[core]\n\tbare = 08\n".as_slice()),
(
"positive overflow",
b"[core]\n\tbare = 2147483648\n".as_slice(),
),
(
"negative overflow",
b"[core]\n\tbare = -2147483649\n".as_slice(),
),
(
"i64 max",
b"[core]\n\tbare = 9223372036854775807\n".as_slice(),
),
] {
let common = write_common_config(body);
assert_eq!(
inspect_plain_common_config_authority(common.path()).expect(name),
CommonConfigAuthority::Unproven,
"authority result for {name}"
);
}
}
#[test]
fn common_bare_proof_rejects_includes_worktree_config_and_relative_worktree() {
let include_target = tempfile::NamedTempFile::new().expect("included config");
std::fs::write(include_target.path(), "[core]\n\tbare = false\n")
.expect("write included config");
let common = write_common_config(
format!(
"[core]\n\tbare = true\n[include]\n\tpath = {}\n",
include_target.path().display()
)
.as_bytes(),
);
assert_eq!(
native_bare_value(&common.path().join("config"), false).expect("direct native bare"),
Some(true)
);
assert_eq!(
native_bare_value(&common.path().join("config"), true).expect("included native bare"),
Some(false)
);
assert_eq!(
inspect_plain_common_config_authority(common.path()).expect("include authority"),
CommonConfigAuthority::Unproven
);
for body in [
b"[core]\n\tbare = true\n[includeIf \"gitdir:**\"]\n\tpath = /tmp/ignored\n".as_slice(),
b"[core]\n\tbare = true\n[extensions]\n\tworktreeConfig = true\n".as_slice(),
b"[core]\n\tbare = true\n\tworktree = relative/path\n".as_slice(),
b"[core]\n\tbare = true\n[extensions]\n\tworktreeConfig\n".as_slice(),
b"[core]\n\tbare = true\n[extensions]\n\tworktreeConfig = false\n\tworktreeConfig\n"
.as_slice(),
b"[core]\n\tbare = true\n\tworktree\n".as_slice(),
b"[core]\n\tbare = true\n\tworktree = /tmp/one\n\tworktree\n".as_slice(),
b"[core \"unexpected\"]\n\tbare = true\n".as_slice(),
b"[extensions \"unexpected\"]\n\tworktreeConfig = false\n[core]\n\tbare = true\n"
.as_slice(),
] {
let common = write_common_config(body);
assert_eq!(
inspect_plain_common_config_authority(common.path()).expect("ambiguous authority"),
CommonConfigAuthority::Unproven
);
}
}
#[test]
fn common_config_absolute_worktree_is_returned_as_authority() {
let worktree = tempfile::tempdir().expect("worktree");
let common = write_common_config(
format!("[core]\n\tworktree = {}\n", worktree.path().display()).as_bytes(),
);
assert_eq!(
inspect_plain_common_config_authority(common.path()).expect("worktree authority"),
CommonConfigAuthority::Worktree(worktree.path().to_path_buf())
);
}
#[test]
fn common_config_rejects_contradictory_or_malformed_bare_with_absolute_worktree() {
let worktree = tempfile::tempdir().expect("worktree");
for (name, bare) in [("contradictory bare", "true"), ("malformed bare", "08")] {
let common = write_common_config(
format!(
"[core]\n\tbare = {bare}\n\tworktree = {}\n",
worktree.path().display()
)
.as_bytes(),
);
assert_eq!(
inspect_plain_common_config_authority(common.path()).expect(name),
CommonConfigAuthority::Unproven,
"authority result for {name}"
);
}
}
#[test]
fn windows_authority_path_grammar_rejects_ambiguous_primary_spellings() {
for path in [
r"C:\primary.\repo",
r"C:\primary \repo",
r"C:\primary:stream\repo",
r"C:\NUL\repo",
r"\\?\GLOBALROOT\Device\HarddiskVolume1\repo",
r"\??\C:\primary\repo",
r"C:\outside\..\primary",
] {
assert!(
crate::path_authority::windows_authority_path_is_ambiguous(path),
"ambiguous Windows authority path accepted: {path:?}"
);
}
for path in [r"C:\primary\repo", r"\\server\share\primary\repo"] {
assert!(
!crate::path_authority::windows_authority_path_is_ambiguous(path),
"ordinary Windows authority path rejected: {path:?}"
);
}
}