Skip to main content

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;