Skip to main content

git_policy_cli/landing/
service_captures.rs

1//! What:
2//!  The service's memory of when the bytes of the commits it landed were captured, kept per worktree.
3//! Why:
4//!  Two commands that capture a file from one worktree at different moments hold different states of it, and the later
5//!  capture holds everything the earlier one did (`doc/decision/cli-git-concurrent-commits.md`, "Serial landing"). When the
6//!  service sends a commit back because a landed commit changed the same path, the client needs to know which of the two
7//!  captured later, and only the service saw the landed commit's request. The stamp is a reading of `CLOCK_MONOTONIC`, one
8//!  clock for every process of the host, so captures made by different wrapper processes compare. The memory lives in the
9//!  service process and starts empty with it, so a restart can only make a capture unknown, never misordered: a commit with
10//!  no entry is treated as captured by somebody else, and the client then merges instead of preferring either side. A reboot
11//!  restarts both the clock and the service together. Entries are kept per worktree, because the rule holds for captures of
12//!  one worktree only.
13//!
14//! In TS you'd write (pseudocode):
15//! ```ts
16//! memory.recordLanded(commonDir, worktree, commit, captured);
17//! const known = memory.remembered(commonDir, worktree); // what a commit sent back is told
18//! ```
19
20use crate::service_protocol::LandedCapture;
21use std::collections::{HashMap, VecDeque};
22use std::path::{Path, PathBuf};
23
24/// How many landed commits are remembered for one worktree; older ones are forgotten, which makes them unknown. Measured: in the
25///  service journal (1,085 landings of the `cli-git@` units, 2026-11-02T09:01:38Z to 2026-11-03T15:11:58Z) the busiest unit landed 15
26///  commits in its busiest ten minutes and 349 in all, so this many cover eight times the busiest ten minutes and over a third
27///  of everything the busiest worktree landed in the window. Kept as the coordinator's pick under the owner's delegation, open to the owner's veto (`doc/planning/cli-git-shipped-limits-audit.md`, "Sizes and ages of the service's own housekeeping").
28pub const REMEMBERED: usize = 128;
29
30type Key = (PathBuf, PathBuf);
31
32/// What:
33///  The memory of landed captures, owned by the serve loop that lands the commits and answers the clients.
34/// Why:
35///   Only that one thread records a landed commit and reads the memory to answer a commit sent back, so the memory is a plain
36///  value it holds and passes down (`&mut` to record, `&` to read), not a process-wide table behind a lock: nothing waits for
37///  it and nothing else can reach it.
38///
39/// In TS you'd write (pseudocode):
40/// ```ts
41/// const memory = new CaptureMemory(); memory.recordLanded(commonDir, worktree, commit, captured);
42/// ```
43#[derive(Debug, Default)]
44pub struct CaptureMemory {
45    /// The landed commits of each worktree, oldest first.
46    by_worktree: HashMap<Key, VecDeque<LandedCapture>>,
47}
48
49/// Methods over the memory.
50impl CaptureMemory {
51    /// What:
52    ///  Remembers a landed commit and the stamp of the request that made it, when the stamp is known.
53    /// Why:
54    ///   A request with no stamp (zero) cannot be ordered, so it is not remembered and its commit stays unknown.
55    ///
56    /// In TS you'd write (pseudocode):
57    /// ```ts
58    /// memory.recordLanded(commonDir, worktree, commit, captured)
59    /// ```
60    pub fn record_landed(&mut self, common_dir: &Path, worktree: &Path, commit: &str, captured: u64) {
61        if captured == 0 {
62            return;
63        }
64        let entries: &mut VecDeque<LandedCapture> = self.by_worktree.entry((common_dir.to_path_buf(), worktree.to_path_buf())).or_default();
65        entries.push_back(LandedCapture { commit: commit.to_string(), captured });
66        while entries.len() > REMEMBERED {
67            entries.pop_front();
68        }
69    }
70
71    /// What:
72    ///  The commits landed for a worktree that the service remembers, oldest first.
73    /// Why:
74    ///   The client matches them by name against the commits that changed a path since its base, so older ones do no harm.
75    ///
76    /// In TS you'd write (pseudocode):
77    /// ```ts
78    /// memory.remembered(commonDir, worktree)
79    /// ```
80    pub fn remembered(&self, common_dir: &Path, worktree: &Path) -> Vec<LandedCapture> {
81        return self.by_worktree.get(&(common_dir.to_path_buf(), worktree.to_path_buf())).map(|entries| { return entries.iter().cloned().collect(); }).unwrap_or_default();
82    }
83}
84
85/// Capture memory controls stay out of the release executable.
86#[cfg(test)]
87#[path = "service_captures_tests.rs"]
88mod tests;