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;