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
//! What:
//! The landing lease:
//! a capability a running wrapper passes to the
//! Git it starts,
//! so a nested wrapper invocation from `rebase --exec` proceeds
//! under its ancestor instead of waiting for it.
//! Why:
//! Without a lease a nested invocation would wait for the landing lock its own
//! ancestor holds,
//! forever.
//! Both wrappers format and validate the same lease
//! (`src/index-lock/landing-lease.ts`).
//! The wrapper runs no external hook, so it passes no preparation lease any more.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! if (await hasValidLandingLease({ environment, lockDirectory })) { /* skip the landing lock */ }
//! ```
/// Record readers and the ordered writer.
use super::json_record::{ObjectWriter, parse_object, string_field};
/// Owner records of published locks.
use super::owner_lock_record::{PublishedOwner, read_owner_lock_record};
/// `OsString` is owned operating-system text of raw bytes.
use std::ffi::OsString;
/// `Path`/`PathBuf` are borrowed/owned filesystem paths.
use std::path::Path;
/// Environment variable carrying the landing lease to a forwarded index writer.
pub const LANDING_LEASE_VARIABLE: &str = "CLI_GIT_LANDING_LEASE";
/// What:
/// Format a landing lease naming the held lock directory and its owner token.
/// Why:
/// `formatLandingLease`:
/// `JSON.stringify({ lockDirectory, token })`.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// formatLandingLease({ lockDirectory, token })
/// ```
pub fn format_landing_lease(lock_directory: &str, token: &str) -> String {
// `mut` allows appending one field at a time.
let mut writer: ObjectWriter = ObjectWriter::new();
writer
.string("lockDirectory", lock_directory)
.string("token", token);
return writer.finish();
}
/// What:
/// The value of one variable in an injected environment,
/// when present and not empty.
/// Why:
/// An empty lease is no lease,
/// as `(lease === undefined) || (lease === '')`.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const lease = environment[name]; if (!lease) return undefined;
/// ```
fn nonempty_variable(environment: &[(OsString, OsString)], name: &str) -> Option<String> {
// The last entry wins, as a process environment has one value per name.
let mut found: Option<&OsString> = None;
for (key, value) in environment {
if key == name {
found = Some(value);
}
}
let text: &str = found?.to_str()?;
if text.is_empty() {
return None;
}
return Some(String::from(text));
}
/// What:
/// Whether the environment carries a lease for `lock_directory` that its owner still holds.
/// Why:
/// The incumbent's `hasValidLandingLease`:
/// the lease must name exactly this lock,
/// and the
/// lock's current owner token must be the lease's token.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// async function hasValidLandingLease({ environment, lockDirectory }): Promise<boolean>;
/// ```
pub fn has_valid_landing_lease(
environment: &[(OsString, OsString)],
lock_directory: &Path,
) -> bool {
let Some(lease) = nonempty_variable(environment, LANDING_LEASE_VARIABLE) else {
return false;
};
let Some(value) = parse_object(lease.as_str()) else {
return false;
};
let (Some(named), Some(token)) = (
string_field(&value, "lockDirectory"),
string_field(&value, "token"),
) else {
return false;
};
// The incumbent compares the texts exactly, so a differently spelled path is another lock.
if lock_directory.to_str() != Some(named) {
return false;
}
match read_owner_lock_record(lock_directory) {
Ok(PublishedOwner::Owner(record)) => return record.token == token,
Ok(PublishedOwner::Busy) | Err(_) => return false,
}
}
/// What:
/// The owner token a landing lease in the environment carries.
/// Why:
/// A nested landing holds the ancestor's lock by that token, never by the lease text around it.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// JSON.parse(environment.CLI_GIT_LANDING_LEASE).token
/// ```
pub fn landing_lease_token(environment: &[(OsString, OsString)]) -> Option<String> {
let lease: String = nonempty_variable(environment, LANDING_LEASE_VARIABLE)?;
let value = parse_object(lease.as_str())?;
return string_field(&value, "token").map(String::from);
}
/// Lease formatting and validation controls stay out of the release executable.
#[cfg(test)]
#[path = "transaction_lease_tests.rs"]
mod tests;