use super::receipt::{OpKind, OpReceipt, PlanSummary};
use crate::git::GitRepo;
use anyhow::Result;
use colored::Colorize;
use std::path::PathBuf;
pub struct Transaction {
receipt: OpReceipt,
git_dir: PathBuf,
workdir: PathBuf,
snapshotted: bool,
finished: bool,
quiet: bool,
}
impl Transaction {
pub fn begin(kind: OpKind, repo: &GitRepo, quiet: bool) -> Result<Self> {
let op_id = super::generate_op_id();
let git_dir = repo.git_dir()?.to_path_buf();
let workdir = repo.workdir()?.to_path_buf();
let trunk = repo.trunk_branch()?;
let head_branch = repo.current_branch()?;
let receipt = OpReceipt::new(
op_id,
kind,
workdir.to_string_lossy().to_string(),
trunk,
head_branch,
);
Ok(Self {
receipt,
git_dir,
workdir,
snapshotted: false,
finished: false,
quiet,
})
}
#[allow(dead_code)]
pub fn op_id(&self) -> &str {
&self.receipt.op_id
}
pub fn plan_branch(&mut self, repo: &GitRepo, branch: &str) -> Result<()> {
let oid = repo.branch_commit(branch).ok();
self.receipt.add_local_ref(branch, oid.as_deref());
Ok(())
}
pub fn plan_branches(&mut self, repo: &GitRepo, branches: &[String]) -> Result<()> {
for branch in branches {
self.plan_branch(repo, branch)?;
}
Ok(())
}
pub fn plan_remote_branch(&mut self, repo: &GitRepo, remote: &str, branch: &str) -> Result<()> {
let remote_ref = format!("{}/{}", remote, branch);
let oid = repo.rev_parse(&remote_ref).ok();
self.receipt.add_remote_ref(remote, branch, oid.as_deref());
Ok(())
}
pub fn set_plan_summary(&mut self, summary: PlanSummary) {
self.receipt.plan_summary = summary;
}
pub fn snapshot(&mut self) -> Result<()> {
if self.snapshotted {
return Ok(());
}
for entry in &self.receipt.local_refs {
if let Some(oid) = &entry.oid_before {
super::create_backup_ref(&self.workdir, &self.receipt.op_id, &entry.branch, oid)?;
}
}
self.receipt.save(&self.git_dir)?;
self.snapshotted = true;
if !self.quiet {
self.print_snapshot_info();
}
Ok(())
}
fn print_snapshot_info(&self) {
let count = self
.receipt
.local_refs
.iter()
.filter(|r| r.oid_before.is_some())
.count();
if count > 0 {
println!(
" {} Backup refs created: {}",
"▸".dimmed(),
format!("refs/stax/backups/{}/*", self.receipt.op_id).dimmed()
);
}
}
pub fn record_after(&mut self, repo: &GitRepo, branch: &str) -> Result<()> {
let oid = repo.branch_commit(branch)?;
self.receipt.update_local_ref_after(branch, &oid);
Ok(())
}
#[allow(dead_code)]
pub fn record_all_after(&mut self, repo: &GitRepo) -> Result<()> {
let branches: Vec<String> = self
.receipt
.local_refs
.iter()
.map(|r| r.branch.clone())
.collect();
for branch in branches {
if let Ok(oid) = repo.branch_commit(&branch) {
self.receipt.update_local_ref_after(&branch, &oid);
}
}
Ok(())
}
pub fn record_remote_after(&mut self, remote: &str, branch: &str, local_oid: &str) {
self.receipt
.update_remote_ref_after(remote, branch, local_oid);
}
pub fn finish_ok(mut self) -> Result<()> {
self.receipt.mark_success();
self.receipt.save(&self.git_dir)?;
self.finished = true;
Ok(())
}
pub fn finish_err(
mut self,
message: &str,
failed_step: Option<&str>,
failed_branch: Option<&str>,
) -> Result<()> {
self.receipt
.mark_failed(message, failed_step, failed_branch);
self.receipt.save(&self.git_dir)?;
self.finished = true;
if !self.quiet {
self.print_recovery_hint();
}
Ok(())
}
fn print_recovery_hint(&self) {
println!();
println!("{}", "Your repo is recoverable via:".yellow());
println!(" {}", "stax undo".cyan());
}
pub fn kind(&self) -> &OpKind {
&self.receipt.kind
}
#[allow(dead_code)]
pub fn is_snapshotted(&self) -> bool {
self.snapshotted
}
}
impl Drop for Transaction {
fn drop(&mut self) {
if self.snapshotted && !self.finished {
self.receipt
.mark_failed("Transaction dropped without finishing", None, None);
let _ = self.receipt.save(&self.git_dir);
}
}
}
pub fn print_plan(_kind: &OpKind, summary: &PlanSummary, quiet: bool) {
if quiet {
return;
}
if summary.branches_to_rebase > 0 {
println!(
" {} About to rebase {} {}",
"▸".dimmed(),
summary.branches_to_rebase.to_string().cyan(),
if summary.branches_to_rebase == 1 {
"branch"
} else {
"branches"
}
);
}
if summary.branches_to_push > 0 {
println!(
" {} Will force-push {} {}",
"▸".dimmed(),
summary.branches_to_push.to_string().cyan(),
if summary.branches_to_push == 1 {
"branch"
} else {
"branches"
}
);
}
for desc in &summary.description {
println!(" {} {}", "▸".dimmed(), desc);
}
}