git-ents.gitmain
⌘K
foforge
commit 8cb2e93
fix: compile git-toolchain on Windows

The crate unconditionally used unix-only file APIs (PermissionsExt, os::unix::fs::symlink), breaking the workspace build on the Windows CI job. Gate the executable-bit and symlink handling behind cfg helpers and restrict the unix-specific test module to cfg(unix).

Assisted-by: Claude:claude-opus-4-8

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/git-toolchain/src/lib.rs @@ -24,7 +24,6 @@ use std::collections::HashMap; use std::fs; use std::io::{Seek as _, SeekFrom, Write as _}; -use std::os::unix::fs::PermissionsExt as _; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::FromStr as _; @@ -672,12 +671,10 @@ return Ok(Some((oid, EntryMode::from(EntryKind::Link)))); } let bytes = fs::read(path).map_err(|error| Error::Io(path.to_owned(), error))?; - let executable = fs::metadata(path) + let perms = fs::metadata(path) .map_err(|error| Error::Io(path.to_owned(), error))? - .permissions() - .mode() - & 0o111 - != 0; + .permissions(); + let executable = is_executable(&perms); let oid = collector.insert(gix::objs::Kind::Blob, bytes)?; let kind = if executable { EntryKind::BlobExecutable @@ -699,6 +696,47 @@ Ok(()) } +/// Whether any of the file's execute bits are set. Windows has no execute +/// permission, so imported files never carry the executable mode there. +#[cfg(unix)] +fn is_executable(perms: &fs::Permissions) -> bool { + use std::os::unix::fs::PermissionsExt as _; + perms.mode() & 0o111 != 0 +} + +#[cfg(not(unix))] +fn is_executable(_perms: &fs::Permissions) -> bool { + false +} + +/// Create a symlink at `path` pointing to `target`. +#[cfg(unix)] +fn symlink(target: &str, path: &Path) -> std::io::Result<()> { + std::os::unix::fs::symlink(target, path) +} + +#[cfg(not(unix))] +fn symlink(target: &str, path: &Path) -> std::io::Result<()> { + std::os::windows::fs::symlink_file(target, path) +} + +/// Set the executable mode on `path`. A no-op on platforms without unix +/// permission bits. +#[cfg(unix)] +fn set_executable(path: &Path) -> Result<(), Error> { + use std::os::unix::fs::PermissionsExt as _; + let mut perms = fs::metadata(path) + .map_err(|error| Error::Io(path.to_owned(), error))? + .permissions(); + perms.set_mode(0o755); + fs::set_permissions(path, perms).map_err(|error| Error::Io(path.to_owned(), error)) +} + +#[cfg(not(unix))] +fn set_executable(_path: &Path) -> Result<(), Error> { + Ok(()) +} + /// Walk `tree` back onto disk under `dest`, recursing into subdirectories /// before returning — the export side of [`build_tree`]. fn write_tree_to_disk(odb: &gix::odb::Handle, tree: ObjectId, dest: &Path) -> Result<(), Error> { @@ -726,8 +764,7 @@ .data .to_str() .map_err(|_error| Error::NotUtf8(path.clone()))?; - std::os::unix::fs::symlink(target, &path) - .map_err(|error| Error::Io(path.clone(), error))?; + symlink(target, &path).map_err(|error| Error::Io(path.clone(), error))?; } EntryKind::BlobExecutable | EntryKind::Blob => { let mut blob_buf = Vec::new(); @@ -736,12 +773,7 @@ .map_err(|error| git_store::Error::Object(error.to_string()))?; fs::write(&path, blob.data).map_err(|error| Error::Io(path.clone(), error))?; if entry.mode.is_executable() { - let mut perms = fs::metadata(&path) - .map_err(|error| Error::Io(path.clone(), error))? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&path, perms) - .map_err(|error| Error::Io(path.clone(), error))?; + set_executable(&path)?; } } EntryKind::Commit => { @@ -870,7 +902,7 @@ } } -#[cfg(test)] +#[cfg(all(test, unix))] mod tests { #![allow( clippy::unwrap_used, @@ -879,6 +911,8 @@ reason = "unit test" )] + use std::os::unix::fs::PermissionsExt as _; + use git_store::test_support::repo; use super::*;