Skip to content

Commit

Permalink
working_copy: implement symlinks on Windows with a helper function
Browse files Browse the repository at this point in the history
  • Loading branch information
gulbanana committed Feb 4, 2024
1 parent 99f0e7f commit 37a43a3
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 19 deletions.
45 changes: 45 additions & 0 deletions lib/src/file_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#![allow(missing_docs)]

use std::fs::{self, File};
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Component, Path, PathBuf};
use std::{io, iter};

Expand All @@ -29,6 +33,14 @@ pub struct PathError {
pub error: io::Error,
}

#[derive(Debug, Error)]
pub enum SymlinkError {
#[error("Developer Mode not enabled")]
DeveloperModeNotEnabledError,
#[error("Symlink creation failed")]
InternalError(#[source] std::io::Error),
}

pub(crate) trait IoResultExt<T> {
fn context(self, path: impl AsRef<Path>) -> Result<T, PathError>;
}
Expand Down Expand Up @@ -134,6 +146,39 @@ pub fn persist_content_addressed_temp_file<P: AsRef<Path>>(
}
}

/// Symlinks are always available on UNIX, but on Windows they require admin or
/// Developer Mode
#[cfg(windows)]
pub fn try_symlink<P: AsRef<Path>, Q: AsRef<Path>>(
original: P,
link: Q,
) -> Result<(), SymlinkError> {
const ERROR_PRIVILEGE_NOT_HELD: i32 = 1314;

if Path::is_dir(original.as_ref()) {
symlink_dir(original, link)
} else {
symlink_file(original, link)
}
.map_err(|err| {
if let Some(ERROR_PRIVILEGE_NOT_HELD) = err.raw_os_error() {
SymlinkError::DeveloperModeNotEnabledError
} else {
SymlinkError::InternalError(err)
}
})
}

/// Symlinks are always available on UNIX, but on Windows they require admin or
/// Developer Mode
#[cfg(unix)]
pub fn try_symlink<P: AsRef<Path>, Q: AsRef<Path>>(
original: P,
link: Q,
) -> Result<(), SymlinkError> {
symlink(original, link).map_err(SymlinkError::InternalError)
}

#[cfg(test)]
mod tests {
use std::io::Write;
Expand Down
29 changes: 10 additions & 19 deletions lib/src/local_working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ use std::fs::{File, Metadata, OpenOptions};
use std::io::{Read, Write};
use std::ops::Range;
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Sender};
Expand All @@ -46,6 +44,7 @@ use crate::backend::{
};
use crate::commit::Commit;
use crate::conflicts::{self, materialize_tree_value, MaterializedTreeValue};
use crate::file_util::try_symlink;
#[cfg(feature = "watchman")]
use crate::fsmonitor::watchman;
use crate::fsmonitor::FsmonitorKind;
Expand Down Expand Up @@ -1177,24 +1176,16 @@ impl TreeState {
Ok(FileState::for_file(executable, size, &metadata))
}

#[cfg_attr(windows, allow(unused_variables))]
fn write_symlink(&self, disk_path: &Path, target: String) -> Result<FileState, CheckoutError> {
#[cfg(windows)]
{
println!("ignoring symlink at {}", disk_path.display());
}
#[cfg(unix)]
{
let target = PathBuf::from(&target);
symlink(&target, disk_path).map_err(|err| CheckoutError::Other {
message: format!(
"Failed to create symlink from {} to {}",
disk_path.display(),
target.display()
),
err: err.into(),
})?;
}
let target = PathBuf::from(&target);
try_symlink(&target, disk_path).map_err(|err| CheckoutError::Other {
message: format!(
"Failed to create symlink from {} to {}",
disk_path.display(),
target.display()
),
err: err.into(),
})?;
let metadata = disk_path
.symlink_metadata()
.map_err(|err| checkout_error_for_stat_error(err, disk_path))?;
Expand Down

0 comments on commit 37a43a3

Please sign in to comment.