use clap::{Args, Subcommand};
use octocode::indexer;
#[derive(Args, Debug)]
pub struct BranchArgs {
#[command(subcommand)]
pub command: BranchCommand,
}
#[derive(Subcommand, Debug)]
pub enum BranchCommand {
List,
Info {
name: Option<String>,
},
Delete {
name: String,
},
Prune {
#[arg(long)]
dry_run: bool,
},
}
pub async fn execute(args: &BranchArgs) -> Result<(), anyhow::Error> {
let current_dir = std::env::current_dir()?;
match &args.command {
BranchCommand::List => {
let manifests = indexer::branch::list_indexed_branches(¤t_dir)?;
if manifests.is_empty() {
println!("No branch delta indexes found.");
println!("Index a branch by checking it out and running 'octocode index'.");
return Ok(());
}
println!("Indexed branch deltas:\n");
for manifest in &manifests {
let age = chrono::Utc::now().timestamp() - manifest.indexed_at;
let age_str = if age < 3600 {
format!("{}m ago", age / 60)
} else if age < 86400 {
format!("{}h ago", age / 3600)
} else {
format!("{}d ago", age / 86400)
};
println!(
" {} ({} changed, {} deleted) [base: {}, indexed {}]",
manifest.branch_name,
manifest.changed_paths.len(),
manifest.deleted_paths.len(),
manifest.base_branch,
age_str,
);
}
println!("\n{} branch(es) indexed.", manifests.len());
}
BranchCommand::Info { name } => {
let branch_name = match name {
Some(n) => n.clone(),
None => indexer::branch::get_current_branch(¤t_dir).ok_or_else(|| {
anyhow::anyhow!("Not on a branch (detached HEAD?). Specify a branch name.")
})?,
};
let (_, manifest) = indexer::branch::resolve_branch_state(¤t_dir, &branch_name)?;
match manifest {
Some(m) => {
println!("Branch: {}", m.branch_name);
println!("Base branch: {}", m.base_branch);
println!(
"Base commit: {}",
&m.base_commit[..12.min(m.base_commit.len())]
);
println!(
"Branch commit: {}",
&m.branch_commit[..12.min(m.branch_commit.len())]
);
println!("Changed files: {}", m.changed_paths.len());
for path in &m.changed_paths {
println!(" M {}", path);
}
if !m.deleted_paths.is_empty() {
println!("Deleted files: {}", m.deleted_paths.len());
for path in &m.deleted_paths {
println!(" D {}", path);
}
}
let dt = chrono::DateTime::from_timestamp(m.indexed_at, 0)
.map(|d| d.format("%Y-%m-%d %H:%M:%S UTC").to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("Indexed at: {}", dt);
}
None => {
println!("No delta index found for branch '{}'.", branch_name);
println!("Check out the branch and run 'octocode index' to create one.");
}
}
}
BranchCommand::Delete { name } => {
indexer::branch::delete_branch_index(¤t_dir, name)?;
println!("Deleted branch delta index for '{}'.", name);
}
BranchCommand::Prune { dry_run } => {
if !indexer::git_utils::GitUtils::is_git_repo_root(¤t_dir) {
return Err(anyhow::anyhow!("Not in a git repository root."));
}
let pruned = indexer::branch::prune_branches(¤t_dir, ¤t_dir, *dry_run)?;
if pruned.is_empty() {
println!("No stale branch indexes to prune.");
} else if *dry_run {
println!("Would prune {} branch index(es):", pruned.len());
for name in &pruned {
println!(" {}", name);
}
println!("\nRun without --dry-run to actually delete.");
} else {
println!("Pruned {} branch index(es):", pruned.len());
for name in &pruned {
println!(" {}", name);
}
}
}
}
Ok(())
}