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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! What:
//! The reference locks a stopped Git child may have left behind, judged and reported once the child and what it started
//! are gone.
//! Why:
//! Git removes its `.lock` files when it ends normally or on a catchable signal, but a child that is killed, or that is
//! in an uninterruptible call when the stop arrives, cannot. On 2026-11-03 two pushes were stopped at their deadline;
//! about 37 seconds later, in the same instant, empty `refs/remotes/origin/*.lock` files appeared for exactly their two
//! tracking refs, with no pid file beside them, and every later `git fetch` of those refs failed with "Unable to create
//! ... File exists". The service had started those pushes without `core.lockfilePid`, so no pid file was written. That
//! the creators were the stopped pushes, released by the kernel late, is the inference that fits these facts; nothing
//! observed the processes themselves.
//! Releases 0.5.23 to 0.5.26 removed two kinds of file after the stop: a lock whose pid file named an ended member of the
//! stopped tree, and an empty lock without a pid file that was born after the child started and stayed empty for a settle
//! time. A cross-vendor review of 2026-11-03T11:33:50-05:00 (finding S-1) showed that neither is an ownership proof: a
//! live writer of another Git that does not write pid files holds its lock empty with no descriptor open, for as long as
//! its transaction lasts, and a pid file older than a lock names an earlier holder, so both rules could delete the lock
//! of a Git that is running, and two writers could then update one reference. The sweep removes nothing on those rules.
//! It hands the repository to the same judgement that runs before every fetch and push (`stale_ref_locks.rs`), which
//! removes a reference lock only on a proof and through a conditional removal, and it says what it found and which
//! command clears a lock it will not remove. The judgement is given one proof of its own, which a container's view of the
//! machine cannot give it: the numbers of the processes of the stopped tree that have ended. A pid file born at or after a
//! lock that was itself born after the child started, naming one of them, names a process the wrapper started and saw end, in
//! its own namespace (the container gate found the sweep leaving its own child's locks without it).
//! Locks of other protocols (the index, the shallow file, object and pack locks) are not touched or mentioned.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! const ledger = LockLedger.beforeStart(command); const tree = await stop(child);
//! for (const lock of ledger.sweep(tree)) log(`removed ${lock}`);
//! ```
/// The diagnostic log of the wrapper.
use cratewarn;
/// The Git directories a command works in.
use crateroots_of_command;
/// How reference locks are judged, and what the judge knows of the wrapper's own stopped child.
use crate;
/// The processes of the stopped tree and whether each still runs.
use crate;
/// The judgement and conditional removal of the reference locks below some Git directories.
use crateclear_stale_ref_locks;
/// Paths of the repository.
use ;
/// The command whose children are stopped.
use Command;
/// The wall clock the kernel stamps files with.
use SystemTime;
/// Tag used for diagnostics from this module.
const TAG: &str = "cli-git";
/// What every Git lock file's name ends with.
pub const LOCK_SUFFIX: &str = ".lock";
/// What the pid file of a lock ends with: the lock's name without `.lock`, then this.
pub const PID_LOCK_SUFFIX: &str = "~pid.lock";
/// What a pid file's text starts with, before the number.
const PID_TEXT_PREFIX: &str = "pid ";
/// What:
/// Where a command's Git child can leave reference locks.
/// Why:
/// The sweep after a stop judges the locks of this repository, the one the child worked in; the time taken before the
/// child started tells a lock born in its life from an older one.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type LockLedger = { roots: string[]; since: Date; judge?: Judge };
/// ```
/// The process number a pid file names, when its text is `pid <number>`.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// Number(readFileSync(path, 'utf8').trim().replace(/^pid /, ''))
/// ```
pub
/// Lock-sweep behavior is controlled in the test module.