loopflow 0.12.6

Run steps and flows with coding agents
Documentation
use std::path::Path;
use std::process::Command;

use crate::engine::git::{
    current_branch, delete_local_branch, delete_remote_branch, is_clean, worktree_remove,
};
use crate::engine::worktrees::{list_worktrees, main_repo_root};

use crate::ops::error::{OpsError, OpsResult};
use crate::ops::progress::Progress;

#[derive(Debug, Clone)]
pub struct AbandonOptions {
    pub branch: Option<String>,
    pub force: bool,
}

pub fn abandon_branch(
    repo: &Path,
    options: &AbandonOptions,
    progress: &impl Progress,
) -> OpsResult<()> {
    if options.branch.is_none() && crate::ops::task::abandon_task_pr(repo, options.force, progress)?
    {
        return Ok(());
    }
    let main_repo = main_repo_root(repo).unwrap_or_else(|_| repo.to_path_buf());
    let branch = match options.branch.as_deref() {
        Some(branch) => branch.to_string(),
        None => {
            current_branch(repo)?.ok_or_else(|| OpsError::Message("not on a branch".to_string()))?
        }
    };

    let worktree_path = find_worktree_path(&main_repo, &branch)?
        .ok_or_else(|| OpsError::Message(format!("no worktree found for '{}'", branch)))?;

    if !is_clean(&worktree_path)? {
        if !options.force {
            return Err(OpsError::Message(
                "uncommitted changes; use --force".to_string(),
            ));
        }
        progress.error("Abandoning worktree with uncommitted changes");
    }

    if !options.force {
        let confirmed = progress.confirm(&format!(
            "Abandon branch '{}' (close PR, delete remote, remove worktree)?",
            branch
        ));
        if !confirmed {
            return Err(OpsError::Message("aborted".to_string()));
        }
    }

    progress.status("Closing PR...");
    let _ = Command::new("gh")
        .arg("pr")
        .arg("close")
        .arg(&branch)
        .current_dir(&main_repo)
        .status();

    progress.status("Deleting remote branch...");
    let _ = delete_remote_branch(&main_repo, "origin", &branch);

    progress.status("Removing worktree...");
    let _ = worktree_remove(&main_repo, &worktree_path);

    progress.status("Deleting local branch...");
    let _ = delete_local_branch(&main_repo, &branch);

    Ok(())
}

fn find_worktree_path(main_repo: &Path, branch: &str) -> OpsResult<Option<std::path::PathBuf>> {
    let worktrees = list_worktrees(main_repo)?;
    Ok(worktrees
        .into_iter()
        .find(|wt| wt.branch.as_deref() == Some(branch))
        .map(|wt| wt.path))
}