Skip to main content

git_perf/
status.rs

1use crate::git::git_interop::{
2    create_consolidated_pending_read_branch, get_commit_details, get_commits_with_notes,
3    get_notes_for_commit,
4};
5use crate::serialization::deserialize;
6use anyhow::Result;
7use std::collections::HashSet;
8
9/// Information about pending measurements
10#[derive(Debug)]
11pub struct PendingStatus {
12    /// Total number of commits with pending measurements
13    pub commit_count: usize,
14
15    /// Total number of measurements across all commits
16    pub measurement_count: usize,
17
18    /// Unique measurement names found in pending writes
19    pub measurement_names: HashSet<String>,
20
21    /// Per-commit breakdown (if detailed)
22    pub per_commit: Option<Vec<CommitMeasurements>>,
23}
24
25/// Measurements for a specific commit
26#[derive(Debug)]
27pub struct CommitMeasurements {
28    /// Commit SHA
29    pub commit: String,
30
31    /// Commit title
32    pub title: String,
33
34    /// Measurement names in this commit
35    pub measurement_names: Vec<String>,
36
37    /// Number of measurements in this commit
38    pub count: usize,
39}
40
41/// Display pending measurement status
42pub fn show_status(detailed: bool) -> Result<()> {
43    // 1. Check if there are any pending measurements
44    let status = gather_pending_status(detailed)?;
45
46    // 2. Display results
47    display_status(&status, detailed)?;
48
49    Ok(())
50}
51
52/// Gather information about pending measurements
53pub fn gather_pending_status(detailed: bool) -> Result<PendingStatus> {
54    // Create a consolidated read branch that includes ONLY pending writes
55    // (not the remote branch). After a successful push, the write refs are deleted,
56    // so this branch only contains measurements that haven't been pushed yet.
57    let pending_guard = create_consolidated_pending_read_branch()?;
58
59    // Get the temporary ref name from the guard
60    let pending_ref = pending_guard.ref_name();
61
62    // Efficiently get commits that have notes in the pending branch
63    // These are all commits with pending (unpushed) measurements
64    let pending_commits = get_commits_with_notes(pending_ref)?;
65
66    let mut commit_count = 0;
67    let mut measurement_count = 0;
68    let mut all_measurement_names = HashSet::new();
69    let mut per_commit = if detailed { Some(Vec::new()) } else { None };
70
71    for commit_sha in &pending_commits {
72        let note_lines = get_notes_for_commit(pending_ref, commit_sha)?;
73        if note_lines.is_empty() {
74            continue;
75        }
76
77        // Deserialize measurements from note
78        let note_text = note_lines.join("\n");
79        let measurements = deserialize(&note_text);
80
81        if measurements.is_empty() {
82            continue;
83        }
84
85        commit_count += 1;
86        measurement_count += measurements.len();
87
88        // Collect measurement names
89        let measurement_names: Vec<String> = measurements.iter().map(|m| m.name.clone()).collect();
90
91        for name in &measurement_names {
92            all_measurement_names.insert(name.clone());
93        }
94
95        // Store per-commit details if requested
96        if let Some(ref mut per_commit_vec) = per_commit {
97            // Get commit details (title, author)
98            let commit_details = get_commit_details(std::slice::from_ref(commit_sha))?;
99            if let Some(commit_info) = commit_details.first() {
100                per_commit_vec.push(CommitMeasurements {
101                    commit: commit_sha.clone(),
102                    title: commit_info.title.clone(),
103                    measurement_names: measurement_names.clone(),
104                    count: measurements.len(),
105                });
106            }
107        }
108    }
109
110    Ok(PendingStatus {
111        commit_count,
112        measurement_count,
113        measurement_names: all_measurement_names,
114        per_commit,
115    })
116}
117
118/// Display status information to stdout
119fn display_status(status: &PendingStatus, detailed: bool) -> Result<()> {
120    if status.commit_count == 0 {
121        println!("No pending measurements.");
122        println!("(use \"git perf add\" or \"git perf measure\" to add measurements)");
123        return Ok(());
124    }
125
126    println!("Pending measurements:");
127    let commit_word = if status.commit_count == 1 {
128        "commit"
129    } else {
130        "commits"
131    };
132    println!(
133        "  {} {} with measurements",
134        status.commit_count, commit_word
135    );
136    let measurement_word = if status.measurement_names.len() == 1 {
137        "measurement"
138    } else {
139        "measurements"
140    };
141    println!(
142        "  {} unique {}",
143        status.measurement_names.len(),
144        measurement_word
145    );
146    println!();
147
148    if !status.measurement_names.is_empty() {
149        println!("Measurement names:");
150        let mut sorted_names: Vec<_> = status.measurement_names.iter().collect();
151        sorted_names.sort();
152        for name in sorted_names {
153            println!("  - {}", name);
154        }
155        println!();
156    }
157
158    if detailed {
159        if let Some(ref per_commit) = status.per_commit {
160            println!("Per-commit breakdown:");
161            for commit_info in per_commit {
162                let short_sha = if commit_info.commit.len() >= 12 {
163                    &commit_info.commit[..12]
164                } else {
165                    &commit_info.commit
166                };
167                let meas_word = if commit_info.count == 1 {
168                    "measurement"
169                } else {
170                    "measurements"
171                };
172                println!(
173                    "  {} ({} {}) - {}",
174                    short_sha, commit_info.count, meas_word, commit_info.title
175                );
176                for name in &commit_info.measurement_names {
177                    println!("    - {}", name);
178                }
179            }
180            println!();
181        }
182    }
183
184    println!("(use \"git perf reset\" to discard pending measurements)");
185    println!("(use \"git perf push\" to publish measurements)");
186
187    Ok(())
188}