mod app;
mod ledger;
mod manifest;
mod pattern;
mod process;
mod state;
use anyhow::{Context, Result, ensure};
use app::{App, NewPatchArgs};
use clap::{Args, Parser, Subcommand};
use manifest::PatchKind;
use std::env;
use std::path::PathBuf;
const DEFAULT_MANIFEST: &str = "patches/fork.json";
const INSTRUCTIONS: &str = include_str!("instructions.md");
#[derive(Parser)]
#[command(version, about = "Maintain a declared StGit downstream patch stack")]
struct Cli {
#[arg(long, global = true)]
manifest: Option<PathBuf>,
#[command(subcommand)]
operation: Operation,
}
#[derive(Subcommand)]
enum Operation {
Init,
Status {
#[arg(long)]
json: bool,
},
New(NewArgs),
Verify,
Rebase {
#[arg(long)]
onto: String,
},
Publish,
Instructions,
}
#[derive(Args)]
struct NewArgs {
#[arg(long)]
finish: bool,
name: Option<String>,
#[arg(long, value_enum)]
kind: Option<PatchKind>,
#[arg(long)]
purpose: Option<String>,
#[arg(long)]
upstream_status: Option<String>,
#[arg(long)]
drop_when: Option<String>,
#[arg(long = "path")]
paths: Vec<String>,
#[arg(long)]
export: Option<String>,
}
fn main() -> Result<()> {
let cli = Cli::parse();
if matches!(&cli.operation, Operation::Instructions) {
print!("{INSTRUCTIONS}");
return Ok(());
}
let manifest = cli
.manifest
.or_else(|| env::var_os("FORK_MANIFEST").map(PathBuf::from))
.unwrap_or_else(|| PathBuf::from(DEFAULT_MANIFEST));
let mut app = App::load(&manifest)?;
match cli.operation {
Operation::Init => app.init(),
Operation::Status { json } => app.status(json),
Operation::New(args) if args.finish => {
ensure!(
args.name.is_none()
&& args.kind.is_none()
&& args.purpose.is_none()
&& args.upstream_status.is_none()
&& args.drop_when.is_none()
&& args.paths.is_empty()
&& args.export.is_none(),
"--finish does not accept patch creation arguments"
);
app.finish_new()
}
Operation::New(args) => app.new_patch(NewPatchArgs {
name: args.name.context("patch name is required")?,
kind: args.kind.context("--kind is required")?,
purpose: args.purpose.context("--purpose is required")?,
upstream_status: args
.upstream_status
.context("--upstream-status is required")?,
drop_when: args.drop_when.context("--drop-when is required")?,
paths: {
ensure!(!args.paths.is_empty(), "at least one --path is required");
args.paths
},
export: args.export,
}),
Operation::Verify => app.verify(),
Operation::Rebase { onto } => app.rebase(&onto),
Operation::Publish => app.publish(),
Operation::Instructions => unreachable!("handled before loading repository state"),
}
}