Skip to main content

git_perf/
reset.rs

1use crate::git::git_interop::{create_new_write_ref, delete_reference, get_write_refs};
2use crate::status::gather_pending_status;
3use anyhow::{Context, Result};
4use std::io::{self, Write};
5
6/// Information about what will be reset
7#[derive(Debug)]
8pub struct ResetPlan {
9    /// References that will be deleted
10    pub refs_to_delete: Vec<String>,
11
12    /// Number of measurements that will be removed
13    pub measurement_count: usize,
14
15    /// Number of commits affected
16    pub commit_count: usize,
17}
18
19/// Reset (discard) pending measurements
20pub fn reset_measurements(dry_run: bool, force: bool) -> Result<()> {
21    // CRITICAL: Create a fresh write ref FIRST, before gathering refs to delete.
22    // This ensures that any concurrent measurements added during the reset operation
23    // will go to the new write ref and won't be accidentally deleted.
24    let new_write_ref = create_new_write_ref().context("Failed to create fresh write ref")?;
25
26    // Now gather the refs to delete (this will NOT include the new write ref we just created)
27    let plan = plan_reset(&new_write_ref)?;
28
29    // Check if there's anything to reset
30    if plan.refs_to_delete.is_empty() {
31        println!("No pending measurements to reset.");
32        return Ok(());
33    }
34
35    // Display plan
36    display_reset_plan(&plan)?;
37
38    // Get confirmation unless force or dry-run
39    if !dry_run && !force && !confirm_reset()? {
40        println!("Reset cancelled.");
41        return Ok(());
42    }
43
44    // Execute reset (unless dry-run)
45    if dry_run {
46        println!();
47        println!("Dry run - no changes made.");
48    } else {
49        execute_reset(&plan)?;
50        println!();
51        let ref_word = if plan.refs_to_delete.len() == 1 {
52            "ref"
53        } else {
54            "refs"
55        };
56        println!(
57            "Reset complete. {} write {} deleted.",
58            plan.refs_to_delete.len(),
59            ref_word
60        );
61    }
62
63    Ok(())
64}
65
66/// Plan what will be reset
67///
68/// The new_write_ref parameter is the ref we just created, which should NOT be deleted.
69fn plan_reset(new_write_ref: &str) -> Result<ResetPlan> {
70    // Get all write refs
71    let refs = get_write_refs()?;
72
73    // Filter out the new write ref we just created
74    let refs_to_delete: Vec<String> = refs
75        .into_iter()
76        .map(|(refname, _)| refname)
77        .filter(|refname| refname != new_write_ref)
78        .collect();
79
80    if refs_to_delete.is_empty() {
81        return Ok(ResetPlan {
82            refs_to_delete: vec![],
83            measurement_count: 0,
84            commit_count: 0,
85        });
86    }
87
88    // Count measurements for display using the existing status gathering logic
89    let status = gather_pending_status(false)?;
90
91    Ok(ResetPlan {
92        refs_to_delete,
93        measurement_count: status.measurement_count,
94        commit_count: status.commit_count,
95    })
96}
97
98/// Execute the reset plan
99fn execute_reset(plan: &ResetPlan) -> Result<()> {
100    // Delete all the old write refs
101    // The new write ref was already created before planning, so it won't be in this list
102    for ref_name in &plan.refs_to_delete {
103        delete_reference(ref_name)
104            .with_context(|| format!("Failed to delete reference: {}", ref_name))?;
105    }
106
107    Ok(())
108}
109
110/// Display what will be reset
111fn display_reset_plan(plan: &ResetPlan) -> Result<()> {
112    // Gather the full status to get measurement names
113    let status = gather_pending_status(false)?;
114
115    println!("Will reset:");
116    let commit_word = if plan.commit_count == 1 {
117        "commit"
118    } else {
119        "commits"
120    };
121    println!("  {} {} with measurements", plan.commit_count, commit_word);
122    let measurement_word = if status.measurement_names.len() == 1 {
123        "measurement"
124    } else {
125        "measurements"
126    };
127    println!(
128        "  {} unique {}",
129        status.measurement_names.len(),
130        measurement_word
131    );
132    println!();
133
134    if !status.measurement_names.is_empty() {
135        println!("Measurement names:");
136        let mut sorted_names: Vec<_> = status.measurement_names.iter().collect();
137        sorted_names.sort();
138        for name in sorted_names {
139            println!("  - {}", name);
140        }
141        println!();
142    }
143
144    Ok(())
145}
146
147/// Prompt user for confirmation
148fn confirm_reset() -> Result<bool> {
149    print!("Are you sure you want to discard these pending measurements? [y/N] ");
150    io::stdout().flush()?;
151
152    let mut input = String::new();
153    io::stdin().read_line(&mut input)?;
154
155    let response = input.trim().to_lowercase();
156    Ok(response == "y" || response == "yes")
157}