use crate::conclusion_cleanup::{CONCLUSION_STATE_FILES, SEQUENCER_DIRECTORY};
use std::path::{Path, PathBuf};
pub const PRIVATE_SHADOW_ENTRIES: [&str; 13] = [
"HEAD",
"refs",
"packed-refs",
"reftable",
"logs",
"index",
"config",
"config.worktree",
"objects",
"COMMIT_EDITMSG",
"cli-git",
"cli-git-transactions",
"cli-git-transaction",
];
pub fn is_private_shadow_entry(name: &str) -> bool {
return PRIVATE_SHADOW_ENTRIES.contains(&name)
|| CONCLUSION_STATE_FILES.contains(&name)
|| name == SEQUENCER_DIRECTORY
|| name.ends_with(".lock");
}
fn remove_entry(path: &Path) -> std::io::Result<()> {
return match std::fs::symlink_metadata(path) {
Ok(metadata) if metadata.is_dir() => std::fs::remove_dir_all(path),
Ok(_) => std::fs::remove_file(path),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(error),
};
}
fn share_entry(source: &Path, destination: &Path, directory: bool) -> std::io::Result<()> {
#[cfg(unix)]
{
let _ = directory;
return std::os::unix::fs::symlink(source, destination);
}
#[cfg(windows)]
{
if directory {
return std::os::windows::fs::symlink_dir(source, destination);
}
return std::fs::copy(source, destination).map(|_| ());
}
}
fn link_directory_entries(
source_directory: &Path,
shadow_directory: &Path,
skip: impl Fn(&str) -> bool,
) -> std::io::Result<()> {
for entry_result in std::fs::read_dir(source_directory)? {
let entry: std::fs::DirEntry = entry_result?;
let name: String = entry.file_name().to_string_lossy().into_owned();
if skip(name.as_str()) {
continue;
}
let destination: PathBuf = shadow_directory.join(&name);
remove_entry(destination.as_path())?;
share_entry(
entry.path().as_path(),
destination.as_path(),
entry.file_type()?.is_dir(),
)?;
}
return Ok(());
}
fn regular_file_exists(path: &Path) -> std::io::Result<bool> {
return match std::fs::symlink_metadata(path) {
Ok(metadata) => Ok(metadata.is_file()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error),
};
}
pub fn link_shadow_entries(shadow: &Path, common_dir: &Path, git_dir: &Path) -> std::io::Result<()> {
let per_worktree_sparse: PathBuf = git_dir.join("info").join("sparse-checkout");
let mixed_info: bool =
git_dir != common_dir && regular_file_exists(per_worktree_sparse.as_path())?;
link_directory_entries(common_dir, shadow, |name| {
return is_private_shadow_entry(name) || (mixed_info && name == "info");
})?;
let worktree_config: PathBuf = git_dir.join("config.worktree");
if regular_file_exists(worktree_config.as_path())? {
std::fs::copy(&worktree_config, shadow.join("config.worktree"))?;
}
if !mixed_info {
return Ok(());
}
let shadow_info: PathBuf = shadow.join("info");
remove_entry(shadow_info.as_path())?;
std::fs::create_dir(&shadow_info)?;
set_private_directory(shadow_info.as_path())?;
let shared_info: PathBuf = common_dir.join("info");
match link_directory_entries(shared_info.as_path(), shadow_info.as_path(), |name| {
return name == "sparse-checkout";
}) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error),
}
std::fs::copy(&per_worktree_sparse, shadow_info.join("sparse-checkout"))?;
return Ok(());
}
#[cfg(unix)]
fn set_private_directory(path: &Path) -> std::io::Result<()> {
use std::os::unix::fs::PermissionsExt;
return std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700));
}
#[cfg(windows)]
fn set_private_directory(_path: &Path) -> std::io::Result<()> {
return Ok(());
}
#[cfg(test)]
#[path = "shadow_links_tests.rs"]
mod tests;