Skip to main content

git_policy_cli/
landing_location.rs

1//! What:
2//!  Where a forwarded command's landing lock and real index live:
3//!  one `git rev-parse` that
4//!       reports the git directory,
5//!  the index Git would use,
6//!  and the transaction registry.
7//! Why:
8//!  An index writer coordinates with landings through the registry's landing lock and the real
9//!      index's lock.
10//!  A command outside a repository,
11//!  or one writing another index,
12//!  coordinates with
13//!      nothing (`src/index-lock/landing-location.ts`).
14//!
15//! In TS you'd write (pseudocode):
16//! ```ts
17//! const [gitDir, indexPath, registryRoot] = (await nanoSpawn(gitPath, [...globalArgs, 'rev-parse', ...])).stdout.split('\n');
18//! ```
19
20/// Running real Git for the location query.
21use super::transaction_git::{GitContext, GitOutput, GitRequest, run_git};
22/// `OsString` is owned operating-system text of raw bytes.
23use std::ffi::OsString;
24/// `Path` and `PathBuf` are borrowed and owned filesystem paths.
25use std::path::{Path, PathBuf};
26
27/// What:
28///  The landing lock and index paths of one repository selection.
29/// Why:
30///   `LandingLocation`;
31///  the real index is the worktree's `<git-dir>/index`.
32///
33/// In TS you'd write (pseudocode):
34/// ```ts
35/// type LandingLocation = { cwd: string; gitDir: string; indexPath: string; realIndexPath: string; registryRoot: string };
36/// ```
37#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct LandingLocation {
39    /// Per-worktree Git directory.
40    pub git_dir: PathBuf,
41    /// Index Git would write,
42    ///  honouring `GIT_INDEX_FILE`.
43    pub index_path: PathBuf,
44    /// The worktree's real index,
45    ///  `<git-dir>/index`.
46    pub real_index_path: PathBuf,
47    /// Transaction registry directory that holds the landing lock.
48    pub registry_root: PathBuf,
49}
50
51/// What:
52///  The arguments of the layout query,
53///  after the selection's global options.
54/// Why:
55///   One process reports every path the coordination needs,
56///  in a fixed order.
57///
58/// In TS you'd write (pseudocode):
59/// ```ts
60/// ['rev-parse', '--path-format=absolute', '--absolute-git-dir', '--git-path', 'index', '--git-path', 'cli-git-transactions']
61/// ```
62fn layout_arguments() -> Vec<OsString> {
63    return ["rev-parse", "--path-format=absolute", "--absolute-git-dir", "--git-path", "index", "--git-path", "cli-git-transactions"]
64        .into_iter()
65        .map(OsString::from)
66        .collect();
67}
68
69/// What:
70///  Split the layout output into its three fixed lines,
71///  refusing any other shape.
72/// Why:
73///   A missing line would point the lock at the wrong directory.
74///
75/// In TS you'd write (pseudocode):
76/// ```ts
77/// const [gitDir, indexPath, registryRoot] = stdout.split('\n'); if (!registryRoot) throw new TypeError(...);
78/// ```
79pub fn parse_layout(stdout: &[u8]) -> Result<LandingLocation, String> {
80    let text: String = String::from_utf8_lossy(stdout).into_owned();
81    let lines: Vec<&str> = text.split('\n').collect();
82    if lines.len() < 3 || lines[0].is_empty() || lines[1].is_empty() || lines[2].is_empty() {
83        return Err(format!("Unexpected rev-parse layout: {text:?}"));
84    }
85    let git_dir: PathBuf = PathBuf::from(lines[0]);
86    return Ok(LandingLocation {
87        real_index_path: git_dir.join("index"),
88        index_path: PathBuf::from(lines[1]),
89        registry_root: PathBuf::from(lines[2]),
90        git_dir,
91    });
92}
93
94/// What:
95///  Resolve the landing location for the global options of one command.
96/// Why:
97///   `resolveLandingLocation`:
98///  outside a repository the answer is nothing,
99///  not an error.
100///
101/// In TS you'd write (pseudocode):
102/// ```ts
103/// async function resolveLandingLocation({ gitPath, globalArgs }): Promise<LandingLocation | typeof LANDING_LOCATION_ABSENT>;
104/// ```
105pub fn resolve_landing_location(
106    context: &GitContext,
107    global_arguments: &[OsString],
108) -> Result<Option<LandingLocation>, String> {
109    // `mut` lets the request carry the layout arguments after the global options.
110    let mut arguments: Vec<OsString> = global_arguments.to_vec();
111    arguments.extend(layout_arguments());
112    let mut request: GitRequest = GitRequest::new(Path::new("."), arguments.as_slice());
113    request.without_prefix = true;
114    match run_git(context, &request) {
115        Ok(output) => return answer_of(&output),
116        Err(error) => return Err(error.to_string()),
117    }
118}
119
120/// What:
121///  The landing location a finished query answers with:
122///  none when Git refused,
123///  otherwise the parsed layout.
124/// Why:
125///   A refusal from Git means the command is not inside a repository.
126///
127/// In TS you'd write (pseudocode):
128/// ```ts
129/// if (error instanceof SubprocessError) return LANDING_LOCATION_ABSENT;
130/// ```
131fn answer_of(output: &GitOutput) -> Result<Option<LandingLocation>, String> {
132    if !output.succeeded() {
133        return Ok(None);
134    }
135    return parse_layout(output.stdout.as_slice()).map(Some);
136}
137
138/// Location controls stay out of the release executable.
139#[cfg(test)]
140#[path = "landing_location_tests.rs"]
141mod tests;