use std::time::Instant;
use anyhow::Result;
use objects::object::State;
use refs::Head;
use repo::Repository;
use serde::Serialize;
use crate::{
cli::{Cli, commands::snapshot::resolve_attribution, should_output_json},
config::UserConfig,
};
#[derive(Serialize)]
struct CollapseOutput {
change_id: String,
content_hash: String,
collapsed_count: usize,
intent: Option<String>,
message: String,
}
pub fn cmd_collapse(
cli: &Cli,
states: Vec<String>,
into: String,
confidence: Option<f32>,
) -> Result<()> {
let repo = Repository::open(cli.repo.as_ref().unwrap_or(&std::env::current_dir()?))?;
let json = should_output_json(cli, Some(repo.config()));
if states.is_empty() {
return Err(anyhow::anyhow!("No states specified to collapse"));
}
let started = Instant::now();
if !json {
eprintln!("Collapsing {} states: resolving state ids...", states.len());
}
let mut resolved_states = Vec::new();
for state_spec in &states {
let change_id = repo
.resolve_state(state_spec)?
.ok_or_else(|| anyhow::anyhow!("State not found: {}", state_spec))?;
let state = repo
.store()
.get_state(&change_id)?
.ok_or_else(|| anyhow::anyhow!("State not found: {}", state_spec))?;
resolved_states.push(state);
}
let first_state = &resolved_states[0];
let last_state = &resolved_states[resolved_states.len() - 1];
if !json {
eprintln!("Using final tree from {}", last_state.change_id.short());
}
let user_config = UserConfig::load_default()?;
let attribution = resolve_attribution(&repo, &user_config)?;
let mut new_state =
State::new_collapse_of(last_state.tree, first_state.parents.clone(), attribution);
new_state = new_state.with_intent(into.clone());
if let Some(provenance) = repo.get_state_provenance_root(last_state)? {
new_state = new_state.with_provenance(provenance);
}
if let Some(conf) = confidence {
new_state = new_state.with_confidence(conf);
}
repo.store().put_state(&new_state)?;
if !json {
eprintln!("Writing collapsed state {}...", new_state.change_id.short());
}
match repo.refs().read_head()? {
Head::Attached { ref thread } => {
repo.refs().set_thread(thread, &new_state.change_id)?;
}
Head::Detached { .. } => {
repo.refs().write_head(&Head::Detached {
state: new_state.change_id,
})?;
}
}
let source_ids: Vec<_> = resolved_states.iter().map(|s| s.change_id).collect();
repo.oplog()
.record_collapse(&source_ids, &new_state.change_id)?;
let output = CollapseOutput {
change_id: new_state.change_id.short(),
content_hash: new_state.compute_hash().short(),
collapsed_count: resolved_states.len(),
intent: Some(into),
message: format!(
"Collapsed {} states into {}",
resolved_states.len(),
new_state.change_id.short()
),
};
if json {
println!("{}", serde_json::to_string(&output)?);
} else {
println!(
"{} in {:.1}s",
output.message,
started.elapsed().as_secs_f32()
);
}
Ok(())
}