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