git_policy_cli/shadow_git.rs
1//! What:
2//! Git commands run against a transaction's shadow repository,
3//! and its location and
4//! removal.
5//! Why:
6//! The shadow lives at `<git-common-dir>/cli-git/shadow/<transaction-id>`,
7//! derived from the
8//! transaction ID so recovery finds it before `prepared.json` exists.
9//! cli-git's own shadow
10//! commands remove every variable that would redirect Git elsewhere and point hooks at an
11//! absent directory,
12//! so no hook observes private shadow writes
13//! (`src/shadow-repository/shadow-refs.ts`,
14//! `runShadowGit`).
15//!
16//! In TS you'd write (pseudocode):
17//! ```ts
18//! await runShadowGit({ gitPath, shadowPath, args: ['rev-parse', 'HEAD'] });
19//! ```
20
21/// Debug diagnostics.
22use super::diagnostic_log::debug;
23/// Tree removal.
24use super::private_storage::remove_tree;
25/// The fail-closed recovery failure.
26use super::recovery_error::{RecoveryError, io_failure};
27/// One Git start.
28use super::transaction_git::GitRequest;
29/// `OsString` is owned operating-system text of raw bytes.
30use std::ffi::OsString;
31/// `Path`/`PathBuf` are borrowed/owned filesystem paths.
32use std::path::{Path, PathBuf};
33
34/// Variables that would redirect a shadow command to another repository,
35/// index or store.
36pub const REPOSITORY_REDIRECT_VARIABLES: [&str; 5] = [
37 "GIT_DIR",
38 "GIT_WORK_TREE",
39 "GIT_COMMON_DIR",
40 "GIT_OBJECT_DIRECTORY",
41 "GIT_INDEX_FILE",
42];
43
44/// Absent shadow directory named as the hooks path of cli-git's own shadow commands.
45pub const NO_HOOKS_DIRECTORY: &str = "no-hooks";
46
47/// What:
48/// The shadow repository path of a transaction.
49/// Why:
50/// `shadowRepositoryPath`:
51/// derived from the ID alone.
52///
53/// In TS you'd write (pseudocode):
54/// ```ts
55/// shadowRepositoryPath({ commonDir, transactionId }) // `${commonDir}/cli-git/shadow/${transactionId}`
56/// ```
57pub fn shadow_repository_path(common_dir: &Path, transaction_id: &str) -> PathBuf {
58 return common_dir
59 .join("cli-git")
60 .join("shadow")
61 .join(transaction_id);
62}
63
64/// What:
65/// A request running `arguments` against the shadow:
66/// `--git-dir=<shadow>`,
67/// hooks pointed
68/// at an absent directory,
69/// redirecting variables removed,
70/// in `cwd`.
71/// Why:
72/// Every cli-git shadow command looks the same;
73/// the caller adds input or variables.
74///
75/// In TS you'd write (pseudocode):
76/// ```ts
77/// runShadowGit({ gitPath, shadowPath, args, cwd })
78/// ```
79pub fn shadow_request<S: AsRef<std::ffi::OsStr>>(
80 shadow: &Path,
81 cwd: &Path,
82 arguments: &[S],
83) -> GitRequest {
84 // `mut` allows prefixing the shadow selection.
85 let mut git_dir: OsString = OsString::from("--git-dir=");
86 git_dir.push(shadow.as_os_str());
87 let mut hooks: OsString = OsString::from("core.hooksPath=");
88 hooks.push(shadow.join(NO_HOOKS_DIRECTORY).as_os_str());
89 let mut full: Vec<OsString> = vec![git_dir, OsString::from("-c"), hooks];
90 for argument in arguments {
91 full.push(argument.as_ref().to_os_string());
92 }
93 let mut request: GitRequest = GitRequest::new(cwd, full.as_slice());
94 request.without_prefix = true;
95 for name in REPOSITORY_REDIRECT_VARIABLES {
96 request.unset.push(OsString::from(name));
97 }
98 return request;
99}
100
101/// What:
102/// Remove a shadow repository,
103/// treating an absent one as removed.
104/// Why:
105/// `removeShadowRepository`:
106/// a shadow never outlives its transaction directory.
107///
108/// In TS you'd write (pseudocode):
109/// ```ts
110/// await rm(shadowPath, { recursive: true, force: true });
111/// ```
112pub fn remove_shadow_repository(shadow: &Path) -> Result<(), RecoveryError> {
113 if let Err(error) = remove_tree(shadow) {
114 return Err(io_failure("removing the shadow repository", shadow, &error));
115 }
116 debug(
117 "removeShadowRepository",
118 format!("removed shadow repository {}", shadow.display()).as_str(),
119 );
120 return Ok(());
121}
122
123/// Shadow request controls stay out of the release executable.
124#[cfg(test)]
125#[path = "shadow_git_tests.rs"]
126mod tests;