forkctl 0.0.5

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

impl App {
    pub fn new_patch(&mut self, args: NewPatchRequest) -> Result<NewResult> {
        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)?;
        Ok(NewResult {
            phase: NewPhase::Created,
            patch: Some(patch.name),
            kind: Some(patch.kind),
            allowed_paths: patch.paths,
            verification: None,
        })
    }

    pub fn finish_new(&mut self) -> Result<NewResult> {
        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}"])?);
        pending.new_tip = Some(capture(&self.repo, "git", ["rev-parse", "HEAD"])?);
        self.write_pending(&pending)?;
        let verification = self.verify()?;
        Ok(NewResult {
            phase: NewPhase::Finished,
            patch: None,
            kind: None,
            allowed_paths: Vec::new(),
            verification: Some(verification),
        })
    }
}