Skip to main content

git_policy_cli/
standards_review_append.rs

1//! What:
2//!  How the service adds a review entry to the repository's live shadow log, which every worktree's service instance
3//!  appends to at the same time: one whole-entry write on an append-mode descriptor, once per commit, creating the
4//!  directory and the file when missing.
5//! Why:
6//!  The live log is an untracked file in the common repository directory (`cli-git/standards-review-shadow.md`), and no
7//!  lock may be taken. One write of the whole entry on an `O_APPEND` descriptor lands at the end of the file in one step,
8//!  so two instances (threads or processes, each with its own descriptor) cannot interleave their entries. Nothing else
9//!  edits this file, so the protection a tracked, session-edited log needed is gone: the per-worktree spool of delivered
10//!  entries, the bounded repair that appended again an entry another writer's whole-file rewrite had lost, and the rule
11//!  that the service never creates the log. A write the operating system cuts short (a signal, a full disk) is
12//!  continued with the rest of the same entry, so an entry is whole unless the disk refuses it.
13//!
14//! In TS you'd write (pseudocode):
15//! ```ts
16//! if (!(await readLog(log)).includes(sha12)) await appendFile(log, text); // one write on an 'a' handle
17//! ```
18
19use super::private_storage::create_private_directory_all;
20use super::standards_review_entry::SHORT_SHA_LENGTH;
21use std::io::Write;
22use std::path::Path;
23
24/// What a delivery did.
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum Delivery {
27    /// The entry was appended.
28    Appended,
29    /// The log already names this commit, so nothing was added.
30    AlreadyThere,
31}
32
33/// The text the log is searched for: the first characters of the commit name, which survive a reflow of the entry.
34fn token(sha: &str) -> String {
35    return sha.chars().take(SHORT_SHA_LENGTH).collect();
36}
37
38/// Whether the log names the commit already; a log that cannot be read names nothing.
39pub fn names_commit(log: &Path, sha: &str) -> bool {
40    return std::fs::read_to_string(log).is_ok_and(|text| return text.contains(token(sha).as_str()));
41}
42
43/// What:
44///  Append `text` to the log in one write on an append-mode descriptor, creating the log and its directory when missing.
45/// Why:
46///   The kernel positions an `O_APPEND` write at the end of the file and writes it whole, so concurrent appenders never
47///   interleave; `write_all` continues a cut-short write with the rest of the same entry. A final link is refused, and a
48///   created file is private because entries carry commit subjects.
49///
50/// In TS you'd write (pseudocode):
51/// ```ts
52/// await mkdir(dirname(log), { recursive: true, mode: 0o700 }); await appendFile(log, text, { mode: 0o600 });
53/// ```
54fn append_whole(log: &Path, text: &str) -> std::io::Result<()> {
55    if let Some(directory) = log.parent() {
56        create_private_directory_all(directory)?;
57    }
58    // `mut` allows the builder to be configured step by step.
59    let mut options: std::fs::OpenOptions = std::fs::OpenOptions::new();
60    options.append(true).create(true);
61    #[cfg(unix)]
62    {
63        // The trait adds `.mode` and `.custom_flags` to open options on Unix.
64        use std::os::unix::fs::OpenOptionsExt;
65        options.mode(super::private_storage::PRIVATE_FILE_MODE).custom_flags(libc::O_NOFOLLOW);
66    }
67    let mut file: std::fs::File = options.open(log)?;
68    return file.write_all(text.as_bytes());
69}
70
71/// What:
72///  Deliver one entry for `sha` to `log`: nothing if the log already names the commit, otherwise append it whole.
73/// Why:
74///   Keeping the commit-name test first makes a repeated item (a crash after the append, before the item was settled)
75///   add nothing. A log that does not exist yet names nothing; any other read failure is reported, because appending
76///   after it could repeat an entry.
77///
78/// In TS you'd write (pseudocode):
79/// ```ts
80/// await deliver({ log, sha, text })
81/// ```
82pub fn deliver(log: &Path, sha: &str, text: &str) -> Result<Delivery, String> {
83    let current: String = match std::fs::read_to_string(log) {
84        Ok(found) => found,
85        Err(error) if error.kind() == std::io::ErrorKind::NotFound => String::new(),
86        Err(error) => return Err(format!("reading {} failed: {error}", log.display())),
87    };
88    if current.contains(token(sha).as_str()) {
89        return Ok(Delivery::AlreadyThere);
90    }
91    append_whole(log, text).map_err(|error| return format!("appending to {} failed: {error}", log.display()))?;
92    return Ok(Delivery::Appended);
93}
94
95/// Controls stay out of the release executable.
96#[cfg(test)]
97#[path = "standards_review_append_tests.rs"]
98mod tests;