deb,config: the staged-tree budget replaces the size cap
The size cap was the wrong shape: an --incremental user asked for a
carried tree, and the invariant that matters is which trees survive —
the newest successful one (the next run's automatic fork base) and the
newest one (what a failed run is re-forked from explicitly). The cap
is gone, with the tree measurement and manifest field it needed; the
retention planner now works on the records' outcomes alone and fills a
budget: staged_trees staged trees per target, the newer ones first,
the pair always among them. When the budget cannot hold the pair (one
staged tree configured, the newest build failed and an older
successful tree present) only one of the two is successful and that
one is kept; with no successful tree at all, the most recent one is.
The budget is a setting of the nested config file:
deb:
incremental:
staged_trees: 2
(default 2; 0 clamps to 1 with a warning — the newest successful tree
always survives). The fork-base selection no longer truncates the
candidate list to the budget before ranking it: with a budget of one
and a failed newest tree, that truncation hid the successful record
and forked the failed one instead.
This commit is contained in:
@@ -111,35 +111,6 @@ pub(crate) fn format_bytes(bytes: u64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a human size into bytes: an integer with an optional unit
|
||||
/// suffix — `B`, `K`, `M`, `G`, `T` (optionally `iB`, case-insensitive),
|
||||
/// the binary multiples ccache's configuration uses (`20G` = 20 GiB). A
|
||||
/// bare number is bytes. `None` on anything else (empty, negative,
|
||||
/// overflow, unknown unit).
|
||||
pub(crate) fn parse_size(text: &str) -> Option<u64> {
|
||||
let text = text.trim();
|
||||
let split = text
|
||||
.rfind(|c: char| c.is_ascii_digit())
|
||||
.map(|idx| idx + 1)
|
||||
.unwrap_or(0);
|
||||
let (digits, unit) = text.split_at(split);
|
||||
let power = match unit.trim().to_ascii_lowercase().as_str() {
|
||||
"" | "b" => 0,
|
||||
"k" | "kib" => 1,
|
||||
"m" | "mib" => 2,
|
||||
"g" | "gib" => 3,
|
||||
"t" | "tib" => 4,
|
||||
_ => return None,
|
||||
};
|
||||
if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
digits
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|value| value.checked_mul(1u64.checked_shl(10 * power)?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -188,32 +159,4 @@ mod tests {
|
||||
// this size; the mount-crossing walk keeps it out of the totals.
|
||||
assert_eq!(format_bytes(140_737_477_959_680), "128.0 TiB");
|
||||
}
|
||||
|
||||
/// Unit spellings and their byte values: ccache-style binary
|
||||
/// multiples, optional B/iB decorations, case-insensitive.
|
||||
#[test]
|
||||
fn parse_size_reads_the_unit_suffixes() {
|
||||
assert_eq!(parse_size("0"), Some(0));
|
||||
assert_eq!(parse_size("512"), Some(512));
|
||||
assert_eq!(parse_size("512B"), Some(512));
|
||||
assert_eq!(parse_size("512 b"), Some(512));
|
||||
assert_eq!(parse_size("10K"), Some(10 * 1024));
|
||||
assert_eq!(parse_size("512m"), Some(512 * 1024 * 1024));
|
||||
assert_eq!(parse_size("20G"), Some(20 * 1024 * 1024 * 1024));
|
||||
assert_eq!(parse_size("2TiB"), Some(2 * 1024 * 1024 * 1024 * 1024));
|
||||
assert_eq!(parse_size(" 20G "), Some(20 * 1024 * 1024 * 1024));
|
||||
}
|
||||
|
||||
/// Garbage is an error, not a guess: empty, negative, junk units and
|
||||
/// overflow all parse to None.
|
||||
#[test]
|
||||
fn parse_size_rejects_garbage() {
|
||||
assert_eq!(parse_size(""), None);
|
||||
assert_eq!(parse_size(" "), None);
|
||||
assert_eq!(parse_size("G"), None);
|
||||
assert_eq!(parse_size("-1"), None);
|
||||
assert_eq!(parse_size("20GB"), None);
|
||||
assert_eq!(parse_size("20 kg"), None);
|
||||
assert_eq!(parse_size("999999999999999999999999T"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user