git-utils: fix Windows repository authority checks

This commit is contained in:
Chris Bookholt
2026-07-01 22:27:54 -07:00
parent bf70395505
commit 9c0e48eba6
7 changed files with 149 additions and 23 deletions
@@ -103,7 +103,7 @@ fn push_unique_spelling(paths: &mut Vec<PathBuf>, path: PathBuf) {
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)?;
collect_symlink_route_hops(path, /*depth*/ 0, &mut seen, &mut hops)?;
Ok(hops)
}
@@ -121,8 +121,8 @@ fn collect_symlink_route_hops(
}
for ancestor in path.ancestors() {
match std::fs::symlink_metadata(ancestor) {
Ok(_) => match std::fs::read_link(ancestor) {
Ok(target) => {
Ok(_) => match read_link_if_symlink(ancestor) {
Ok(Some(target)) => {
let parent = ancestor
.parent()
.ok_or_else(|| invalid_data("authority symlink has no parent"))?;
@@ -143,7 +143,7 @@ fn collect_symlink_route_hops(
});
collect_symlink_route_hops(&projected, depth + 1, seen, hops)?;
}
Err(error) if error.kind() == io::ErrorKind::InvalidInput => {}
Ok(None) => {}
Err(error) => return Err(error),
},
Err(error)
@@ -157,8 +157,30 @@ fn collect_symlink_route_hops(
Ok(())
}
fn read_link_if_symlink(path: &Path) -> io::Result<Option<PathBuf>> {
match std::fs::read_link(path) {
Ok(target) => Ok(Some(target)),
Err(error) if read_link_error_means_not_symlink(&error) => Ok(None),
Err(error) => Err(error),
}
}
fn read_link_error_means_not_symlink(error: &io::Error) -> bool {
#[cfg(windows)]
{
// Win32 reports this for ordinary files and directories passed to
// `std::fs::read_link`; it is the Windows equivalent of EINVAL here.
const ERROR_NOT_A_REPARSE_POINT: i32 = 4390;
error.raw_os_error() == Some(ERROR_NOT_A_REPARSE_POINT)
}
#[cfg(not(windows))]
{
error.kind() == io::ErrorKind::InvalidInput
}
}
fn project_through_longest_existing_ancestor(path: &Path) -> io::Result<PathBuf> {
project_path(path, 0)
project_path(path, /*symlink_depth*/ 0)
}
fn project_path(path: &Path, symlink_depth: usize) -> io::Result<PathBuf> {
@@ -219,3 +241,7 @@ fn resolve_literal_path(path: impl AsRef<Path>, base: &Path) -> PathBuf {
pub(super) fn invalid_data(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, message)
}
#[cfg(test)]
#[path = "route_walker_tests.rs"]
mod tests;
@@ -0,0 +1,18 @@
use pretty_assertions::assert_eq;
use super::read_link_if_symlink;
#[test]
fn ordinary_paths_are_not_treated_as_failed_symlink_reads() {
let fixture = tempfile::tempdir().expect("fixture");
let directory = fixture.path().join("directory");
let file = fixture.path().join("file");
std::fs::create_dir(&directory).expect("directory");
std::fs::write(&file, b"ordinary file").expect("file");
assert_eq!(
read_link_if_symlink(&directory).expect("ordinary directory"),
None
);
assert_eq!(read_link_if_symlink(&file).expect("ordinary file"), None);
}