Skip to main content

git_policy_cli/
sequencer_marker.rs

1//! What:
2//!  Proof, without a Git process, that a sequencer marker such as `MERGE_HEAD` names nothing.
3//! Why:
4//!  A commit asks `git rev-parse --verify --quiet <marker>` for each of three markers,
5//!  and in almost every commit none exists.
6//!  On the files ref storage that question reads a fixed list of places,
7//!  so when every one of them is empty the answer is "absent" without a process;
8//!  any entry in any place, of any kind or content,
9//!  says "maybe present" and the caller asks Git,
10//!  which alone decides whether the entry resolves (a dangling marker still counts as a conclusion).
11//!
12//! In TS you'd write (pseudocode):
13//! ```ts
14//! const maybePresent = names.map((name) => lookupPlaces(layout, name).some(exists) || packedRefs.includes(name));
15//! ```
16
17/// Where one worktree's ref files live and what format they are in.
18use super::ref_files::RefFilesLayout;
19/// The ref storage backend.
20use super::transaction_journal::RefFormat;
21/// Borrowed and owned filesystem paths.
22use std::path::{Path, PathBuf};
23
24/// The most `packed-refs` bytes this check reads;
25///  a bigger file is left to Git.
26const PACKED_REFS_LIMIT: u64 = 64 * 1024 * 1024;
27
28/// What:
29///  The paths `git rev-parse --verify <name>` looks at for a loose entry,
30///  in Git's order.
31/// Why:
32///   Git's lookup rules try the name itself (a pseudoref in the worktree's Git directory),
33///  then `refs/`, `refs/tags/`, `refs/heads/`, `refs/remotes/` and `refs/remotes/<name>/HEAD`
34///  in the common directory.
35///
36/// In TS you'd write (pseudocode):
37/// ```ts
38/// [join(gitDir, name), join(commonDir, 'refs', name), join(commonDir, 'refs/tags', name), ...]
39/// ```
40fn lookup_paths(layout: &RefFilesLayout<'_>, name: &str) -> Vec<PathBuf> {
41    return vec![
42        layout.git_dir.join(name),
43        layout.common_dir.join(format!("refs/{name}")),
44        layout.common_dir.join(format!("refs/tags/{name}")),
45        layout.common_dir.join(format!("refs/heads/{name}")),
46        layout.common_dir.join(format!("refs/remotes/{name}")),
47        layout.common_dir.join(format!("refs/remotes/{name}/HEAD")),
48    ];
49}
50
51/// What:
52///  Whether something exists at `path`,
53///  where anything but a clean "nothing here" counts as existing.
54/// Why:
55///   A link, a directory, a file of any content and an unreadable parent all send the caller to Git.
56///
57/// In TS you'd write (pseudocode):
58/// ```ts
59/// await lstat(path).then(() => true, (error) => error.code !== 'ENOENT')
60/// ```
61fn something_is_at(path: &Path) -> bool {
62    return match std::fs::symlink_metadata(path) {
63        Ok(_) => true,
64        Err(error) => error.kind() != std::io::ErrorKind::NotFound,
65    };
66}
67
68/// What:
69///  The bytes of `packed-refs`,
70///  nothing when there is no such file,
71///  or `None` when the file cannot be read as a whole.
72/// Why:
73///   A missing `packed-refs` holds no ref;
74///  an unreadable or oversized one is not proof of anything.
75///
76/// In TS you'd write (pseudocode):
77/// ```ts
78/// await readFile(join(commonDir, 'packed-refs')).catch((e) => (e.code === 'ENOENT' ? Buffer.alloc(0) : undefined))
79/// ```
80fn packed_refs_bytes(common_dir: &Path) -> Option<Vec<u8>> {
81    let path: PathBuf = common_dir.join("packed-refs");
82    let metadata: std::fs::Metadata = match std::fs::metadata(&path) {
83        Ok(found) => found,
84        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Some(Vec::new()),
85        Err(_) => return None,
86    };
87    if !metadata.is_file() || metadata.len() > PACKED_REFS_LIMIT {
88        return None;
89    }
90    return match std::fs::read(&path) {
91        Ok(bytes) => Some(bytes),
92        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Some(Vec::new()),
93        Err(_) => None,
94    };
95}
96
97/// What:
98///  Whether `needle` occurs anywhere in `haystack`.
99/// Why:
100///   Any occurrence of the marker's name in `packed-refs` is a hit,
101///  a superset of the lines that name one of the lookup spellings;
102///  the extra hits only cost a Git process.
103///
104/// In TS you'd write (pseudocode):
105/// ```ts
106/// haystack.includes(needle)
107/// ```
108fn contains_bytes(haystack: &[u8], needle: &[u8]) -> bool {
109    if needle.is_empty() {
110        return true;
111    }
112    return haystack.windows(needle.len()).any(|window| { return window == needle; });
113}
114
115/// What:
116///  For each name, whether a marker of that name may exist:
117///  `false` is proof that `git rev-parse --verify --quiet <name>` exits non-zero.
118/// Why:
119///   The files backend resolves a name only through a loose entry at one of six paths
120///  or a line of `packed-refs`; reftable storage and anything unreadable answer "maybe".
121///
122/// In TS you'd write (pseudocode):
123/// ```ts
124/// names.map((name) => layout.refFormat !== 'files' || maybePresent(layout, name))
125/// ```
126pub fn markers_maybe_present(layout: &RefFilesLayout<'_>, names: &[&str]) -> Vec<bool> {
127    if layout.ref_format != RefFormat::Files {
128        return vec![true; names.len()];
129    }
130    let packed: Option<Vec<u8>> = packed_refs_bytes(layout.common_dir);
131    let mut answers: Vec<bool> = Vec::with_capacity(names.len());
132    for name in names {
133        let loose: bool = lookup_paths(layout, name).iter().any(|path| { return something_is_at(path.as_path()); });
134        let packed_hit: bool = match &packed {
135            Some(bytes) => contains_bytes(bytes.as_slice(), name.as_bytes()),
136            None => true,
137        };
138        answers.push(loose || packed_hit);
139    }
140    return answers;
141}
142
143/// Sequencer marker controls stay out of the release executable.
144#[cfg(test)]
145#[path = "sequencer_marker_tests.rs"]
146mod tests;