use crate::shadow_git::{REPOSITORY_REDIRECT_VARIABLES, shadow_repository_path};
use crate::capture_invocation::{InvocationCapture, InvocationLayout};
use crate::shadow_skeleton::create_shadow_skeleton;
use crate::shadow_config::{format_shadow_config, read_repository_format, repository_format_from_snapshot};
use crate::shadow_links::link_shadow_entries;
use crate::conclusion_copy::copy_conclusion_state;
use crate::shadow_refs::write_shadow_refs;
use crate::private_storage::create_private_directory;
use crate::inner_git_words::record_words;
use crate::transaction_git::{GitContext, GitFailure, GitRequest, run_git_checked};
use std::path::{Path, PathBuf};
use std::ffi::OsString;
fn overwrite_private(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
std::fs::write(path, bytes)?;
return set_private_file(path);
}
#[cfg(unix)]
fn set_private_file(path: &Path) -> std::io::Result<()> {
use std::os::unix::fs::PermissionsExt;
return std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
}
#[cfg(windows)]
fn set_private_file(_path: &Path) -> std::io::Result<()> {
return Ok(());
}
fn create_parent(shadow: &Path) -> std::io::Result<()> {
let parent: &Path = shadow.parent().unwrap_or(shadow);
if let Some(grandparent) = parent.parent() {
ensure_private_directory(grandparent)?;
}
return ensure_private_directory(parent);
}
pub fn ensure_private_directory(directory: &Path) -> std::io::Result<()> {
match create_private_directory(directory) {
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists && directory.is_dir() => return Ok(()),
other => return other,
}
}
fn init_with_git(
context: &GitContext,
layout: &InvocationLayout,
shadow: &Path,
) -> Result<(), GitFailure> {
let arguments: Vec<String> = vec![
String::from("init"),
String::from("--bare"),
String::from("--template="),
format!("--object-format={}", layout.object_format),
String::from("--ref-format=reftable"),
shadow.display().to_string(),
];
let mut init: GitRequest = GitRequest::new(layout.common_dir.as_path(), &arguments);
init.without_prefix = true;
init.unset = REPOSITORY_REDIRECT_VARIABLES.iter().map(|name| { return OsString::from(*name); }).collect();
let made = run_git_checked(context, &init)?;
record_words(&["init", "--bare", "--template=", "--ref-format=reftable"], &made);
return Ok(());
}
pub fn create_shadow_repository(
context: &GitContext,
cwd: &Path,
capture: &InvocationCapture,
transaction_id: &str,
transaction_directory: &Path,
) -> Result<PathBuf, GitFailure> {
let shadow: PathBuf = shadow_repository_path(capture.layout.common_dir.as_path(), transaction_id);
create_parent(shadow.as_path())
.map_err(|error| { return GitFailure(format!("creating {} failed: {error}", shadow.display())); })?;
create_shadow_repository_at(context, cwd, capture, shadow.as_path(), transaction_directory)?;
return Ok(shadow);
}
pub fn create_shadow_repository_at(
context: &GitContext,
cwd: &Path,
capture: &InvocationCapture,
shadow: &Path,
transaction_directory: &Path,
) -> Result<(), GitFailure> {
return create_shadow_repository_carrying(context, cwd, capture, shadow, transaction_directory, true);
}
pub fn create_shadow_repository_carrying(
context: &GitContext,
cwd: &Path,
capture: &InvocationCapture,
shadow: &Path,
transaction_directory: &Path,
carry_conclusion: bool,
) -> Result<(), GitFailure> {
let layout = &capture.layout;
if layout.ref_format == crate::transaction_journal::RefFormat::Reftable {
init_with_git(context, layout, shadow)?;
} else {
create_shadow_skeleton(shadow)
.map_err(|error| { return GitFailure(format!("creating {} failed: {error}", shadow.display())); })?;
}
set_private_directory_mode(shadow)
.map_err(|error| { return GitFailure(format!("protecting {} failed: {error}", shadow.display())); })?;
let format = match layout.config_snapshot.as_ref().and_then(repository_format_from_snapshot) {
Some(from_snapshot) => from_snapshot,
None => read_repository_format(context, layout.common_dir.as_path())?,
};
let config_text: String =
format_shadow_config(&format, layout.common_dir.as_path(), layout.repository_root.as_path());
overwrite_private(shadow.join("config").as_path(), config_text.as_bytes())
.map_err(|error| { return GitFailure(format!("writing the shadow config failed: {error}")); })?;
overwrite_private(
shadow.join("objects").join("info").join("alternates").as_path(),
format!("{}\n", layout.object_directory.display()).as_bytes(),
)
.map_err(|error| { return GitFailure(format!("writing the shadow alternates failed: {error}")); })?;
link_shadow_entries(shadow, layout.common_dir.as_path(), layout.git_dir.as_path())
.map_err(|error| { return GitFailure(format!("linking the shadow entries failed: {error}")); })?;
if carry_conclusion {
copy_conclusion_state(
context,
cwd,
layout.git_dir.as_path(),
shadow,
transaction_directory,
layout.ref_format,
)?;
}
write_shadow_refs(
context,
cwd,
shadow,
layout.ref_format,
layout.target_ref.as_str(),
&layout.symbolic_head,
&capture.base,
)?;
return Ok(());
}
#[cfg(unix)]
fn set_private_directory_mode(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_mode(_path: &Path) -> std::io::Result<()> {
return Ok(());
}
#[cfg(test)]
#[path = "shadow_create_tests.rs"]
mod tests;