Skip to main content

verbs/
git_projection_io_plan.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Pure Git projection import/export/sync summary strings (no Git I/O).
3//!
4//! Owns plain-text commit and ref listing summaries for `bridge git export` /
5//! `sync git` human output. Styling, destination I/O, and RecoveryAdvice
6//! stay CLI-owned.
7
8/// Plain commits summary for export/sync: total plus newly-written vs already-in-sync.
9///
10/// Examples:
11/// - `0 total`
12/// - `5 total (already in sync)` when `newly == 0` and `total > 0`
13/// - `3 total (3 newly written)` when everything is new
14/// - `5 total (2 newly written, 3 already in sync)` when mixed
15pub fn export_commits_summary(total: usize, newly: usize) -> String {
16    let already = total.saturating_sub(newly);
17    let breakdown = if total == 0 {
18        String::new()
19    } else if newly == 0 {
20        " (already in sync)".to_string()
21    } else if already == 0 {
22        format!(" ({newly} newly written)")
23    } else {
24        format!(" ({newly} newly written, {already} already in sync)")
25    };
26    format!("{total} total{breakdown}")
27}
28
29/// One exported ref fact for plain listing (name + tip hex, any length).
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct ExportedRefSummaryFact<'a> {
32    pub name: &'a str,
33    /// Full tip hex (or any string); truncated to 7 chars for display.
34    pub tip_hex: &'a str,
35}
36
37/// Plain branches/tags summary: count, then `name shorttip · …`.
38///
39/// Empty refs yields just the count string (e.g. `"0"`).
40pub fn exported_refs_summary(refs: &[ExportedRefSummaryFact<'_>]) -> String {
41    let count = refs.len();
42    if refs.is_empty() {
43        return count.to_string();
44    }
45    let listing = refs
46        .iter()
47        .map(|r| {
48            let short_tip: String = r.tip_hex.chars().take(7).collect();
49            format!("{} {short_tip}", r.name)
50        })
51        .collect::<Vec<_>>()
52        .join(" · ");
53    format!("{count}   {listing}")
54}
55
56/// Short tip hex for display (first 7 characters).
57pub fn short_git_tip(tip_hex: &str) -> String {
58    tip_hex.chars().take(7).collect()
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn export_commits_summary_variants() {
67        assert_eq!(export_commits_summary(0, 0), "0 total");
68        assert_eq!(export_commits_summary(5, 0), "5 total (already in sync)");
69        assert_eq!(export_commits_summary(3, 3), "3 total (3 newly written)");
70        assert_eq!(
71            export_commits_summary(5, 2),
72            "5 total (2 newly written, 3 already in sync)"
73        );
74    }
75
76    #[test]
77    fn exported_refs_summary_lists_and_empty() {
78        assert_eq!(exported_refs_summary(&[]), "0");
79        let refs = [
80            ExportedRefSummaryFact {
81                name: "main",
82                tip_hex: "af25b9d1234",
83            },
84            ExportedRefSummaryFact {
85                name: "spike-ok",
86                tip_hex: "7f1002c",
87            },
88        ];
89        assert_eq!(
90            exported_refs_summary(&refs),
91            "2   main af25b9d · spike-ok 7f1002c"
92        );
93        assert_eq!(short_git_tip("abcdef0123"), "abcdef0");
94    }
95}