forkctl 0.0.4

Control audited StGit downstream patch stacks
use super::App;
use crate::manifest::{Patch, PatchKind};
use crate::process::{capture, run};
use crate::state::PendingOperation;
use anyhow::{Context, Result, ensure};
use std::ffi::OsString;

pub struct NewPatchArgs {
    pub name: String,
    pub kind: PatchKind,
    pub purpose: String,
    pub upstream_status: String,
    pub drop_when: String,
    pub paths: Vec<String>,
    pub export: Option<String>,
}

impl App {
    pub fn new_patch(&mut self, args: NewPatchArgs) -> Result<()> {
        self.verify()?;
        let patch = Patch {
            name: args.name,
            kind: args.kind,
            purpose: args.purpose,
            upstream_status: args.upstream_status,
            drop_when: args.drop_when,
            paths: args.paths,
            export: args.export,
        };
        ensure!(
            !self
                .manifest
                .patches
                .iter()
                .any(|candidate| candidate.name == patch.name),
            "patch already exists: {}",
            patch.name
        );
        let insertion = self.manifest.insertion_index(patch.kind);
        let mut next_manifest = self.manifest.clone();
        next_manifest.patches.insert(insertion, patch.clone());
        next_manifest.validate(&self.repo, &self.manifest_path)?;

        let mut pending = self.create_recovery(PendingOperation::New)?;
        self.write_pending(&pending)?;
        if insertion == 0 {
            run(&self.repo, "stg", ["pop", "--all"])?;
        } else {
            run(
                &self.repo,
                "stg",
                ["goto", self.manifest.patches[insertion - 1].name.as_str()],
            )?;
        }
        run(
            &self.repo,
            "stg",
            [
                OsString::from("new"),
                OsString::from("--message"),
                OsString::from(patch.message()),
                OsString::from(&patch.name),
            ],
        )?;
        run(&self.repo, "stg", ["push", "--all"])?;

        self.manifest = next_manifest;
        self.write_manifest()?;
        let ledger = self.write_ledger()?;
        self.stage_and_refresh_bookkeeping(&[self.manifest_path.clone(), ledger])?;

        pending.new_base = Some(capture(&self.repo, "stg", ["id", "{base}"])?);
        self.write_pending(&pending)?;
        println!(
            "forkctl: created empty {} patch {}; add implementation only under: {}",
            match patch.kind {
                PatchKind::Source => "source",
                PatchKind::Tooling => "tooling",
            },
            patch.name,
            patch.paths.join(", ")
        );
        println!(
            "forkctl: refresh the patch, restore {}, then run forkctl new --finish",
            self.manifest.bookkeeping_patch
        );
        Ok(())
    }

    pub fn finish_new(&mut self) -> Result<()> {
        self.require_clean()?;
        self.require_declared_branch()?;
        let mut pending = self
            .read_pending()?
            .context("no forkctl new operation is pending")?;
        ensure!(
            pending.operation == PendingOperation::New,
            "pending operation is not new"
        );
        ensure!(
            capture(&self.repo, "stg", ["series", "--unapplied", "--count"])? == "0",
            "apply all patches before finishing new"
        );
        let top = capture(&self.repo, "stg", ["top"])?;
        ensure!(
            top == self.manifest.bookkeeping_patch,
            "top patch is {top}, expected {}",
            self.manifest.bookkeeping_patch
        );
        let exports = self.write_exports()?;
        let ledger = self.write_ledger()?;
        let paths = std::iter::once(self.manifest_path.clone())
            .chain(std::iter::once(ledger))
            .chain(exports)
            .collect::<Vec<_>>();
        self.stage_and_refresh_bookkeeping(&paths)?;
        pending.new_base = Some(capture(&self.repo, "stg", ["id", "{base}"])?);
        self.write_pending(&pending)?;
        self.verify()?;
        println!("forkctl: new patch is complete and structurally verified");
        Ok(())
    }
}