git_policy_cli/
shadow_create.rs1use super::shadow_git::{REPOSITORY_REDIRECT_VARIABLES, shadow_repository_path};
23use super::capture_invocation::{InvocationCapture, InvocationLayout};
25use super::shadow_skeleton::create_shadow_skeleton;
27use super::shadow_config::{format_shadow_config, read_repository_format, repository_format_from_snapshot};
29use super::shadow_links::link_shadow_entries;
31use super::conclusion_copy::copy_conclusion_state;
33use super::shadow_refs::write_shadow_refs;
34use super::private_storage::create_private_directory;
36use super::transaction_git::{GitContext, GitFailure, GitRequest, run_git_checked};
38use std::path::{Path, PathBuf};
40use std::ffi::OsString;
42
43fn overwrite_private(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
54 std::fs::write(path, bytes)?;
55 return set_private_file(path);
56}
57
58#[cfg(unix)]
68fn set_private_file(path: &Path) -> std::io::Result<()> {
69 use std::os::unix::fs::PermissionsExt;
70 return std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
71}
72
73#[cfg(windows)]
76fn set_private_file(_path: &Path) -> std::io::Result<()> {
77 return Ok(());
78}
79
80fn create_parent(shadow: &Path) -> std::io::Result<()> {
90 let parent: &Path = shadow.parent().unwrap_or(shadow);
91 if let Some(grandparent) = parent.parent() {
92 ensure_private_directory(grandparent)?;
93 }
94 return ensure_private_directory(parent);
95}
96
97pub fn ensure_private_directory(directory: &Path) -> std::io::Result<()> {
108 match create_private_directory(directory) {
109 Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists && directory.is_dir() => return Ok(()),
110 other => return other,
111 }
112}
113
114fn init_with_git(
124 context: &GitContext,
125 layout: &InvocationLayout,
126 shadow: &Path,
127) -> Result<(), GitFailure> {
128 let arguments: Vec<String> = vec![
129 String::from("init"),
130 String::from("--bare"),
131 String::from("--quiet"),
132 String::from("--template="),
133 format!("--object-format={}", layout.object_format),
134 String::from("--ref-format=reftable"),
135 shadow.display().to_string(),
136 ];
137 let mut init: GitRequest = GitRequest::new(layout.common_dir.as_path(), &arguments);
138 init.without_prefix = true;
139 init.unset = REPOSITORY_REDIRECT_VARIABLES.iter().map(|name| { return OsString::from(*name); }).collect();
140 run_git_checked(context, &init)?;
141 return Ok(());
142}
143
144pub fn create_shadow_repository(
155 context: &GitContext,
156 cwd: &Path,
157 capture: &InvocationCapture,
158 transaction_id: &str,
159 transaction_directory: &Path,
160) -> Result<PathBuf, GitFailure> {
161 let layout = &capture.layout;
162 let shadow: PathBuf = shadow_repository_path(layout.common_dir.as_path(), transaction_id);
163 create_parent(shadow.as_path())
164 .map_err(|error| { return GitFailure(format!("creating {} failed: {error}", shadow.display())); })?;
165 if layout.ref_format == super::transaction_journal::RefFormat::Reftable {
166 init_with_git(context, layout, shadow.as_path())?;
168 } else {
169 create_shadow_skeleton(shadow.as_path())
170 .map_err(|error| { return GitFailure(format!("creating {} failed: {error}", shadow.display())); })?;
171 }
172 set_private_directory_mode(shadow.as_path())
173 .map_err(|error| { return GitFailure(format!("protecting {} failed: {error}", shadow.display())); })?;
174 let format = match layout.config_snapshot.as_ref().and_then(repository_format_from_snapshot) {
175 Some(from_snapshot) => from_snapshot,
176 None => read_repository_format(context, layout.common_dir.as_path())?,
177 };
178 let config_text: String =
179 format_shadow_config(&format, layout.common_dir.as_path(), layout.repository_root.as_path());
180 overwrite_private(shadow.join("config").as_path(), config_text.as_bytes())
181 .map_err(|error| { return GitFailure(format!("writing the shadow config failed: {error}")); })?;
182 overwrite_private(
183 shadow.join("objects").join("info").join("alternates").as_path(),
184 format!("{}\n", layout.object_directory.display()).as_bytes(),
185 )
186 .map_err(|error| { return GitFailure(format!("writing the shadow alternates failed: {error}")); })?;
187 link_shadow_entries(shadow.as_path(), layout.common_dir.as_path(), layout.git_dir.as_path())
188 .map_err(|error| { return GitFailure(format!("linking the shadow entries failed: {error}")); })?;
189 copy_conclusion_state(
190 context,
191 cwd,
192 layout.git_dir.as_path(),
193 shadow.as_path(),
194 transaction_directory,
195 layout.ref_format,
196 )?;
197 write_shadow_refs(
198 context,
199 cwd,
200 shadow.as_path(),
201 layout.ref_format,
202 layout.target_ref.as_str(),
203 &layout.symbolic_head,
204 &capture.base,
205 )?;
206 return Ok(shadow);
207}
208
209#[cfg(unix)]
219fn set_private_directory_mode(path: &Path) -> std::io::Result<()> {
220 use std::os::unix::fs::PermissionsExt;
221 return std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700));
222}
223
224#[cfg(windows)]
227fn set_private_directory_mode(_path: &Path) -> std::io::Result<()> {
228 return Ok(());
229}
230
231#[cfg(test)]
233#[path = "shadow_create_tests.rs"]
234mod tests;