use std::path::PathBuf;
#[derive(Debug, clap::Subcommand)]
pub(crate) enum PrCmd {
Open(PrOpenArgs),
#[command(long_about = MERGE_LONG_ABOUT)]
Merge(PrMergeArgs),
QueueCheck(PrQueueCheckArgs),
#[command(long_about = CLEANUP_LONG_ABOUT)]
Cleanup(PrCleanupArgs),
}
const CLEANUP_LONG_ABOUT: &str = "\
Reclaim everything a merged pull request made obsolete.
Runs as the final step after merge confirmation, and refuses outright unless \
`gh pr view <n>` reports state MERGED. Five steps, one line of output each:
pr confirm the PR is MERGED
remote-branch delete origin/<head> when the remote still lists it
worktree end any session claim, then remove each worktree holding the head
local-branch delete the head branch and each `worktree-agent-*` at the head commit
prune `git worktree prune`, then `git fetch --prune origin`
Exits 0 only when every step succeeded; a step that could not do its job is a \
failure, never a warning. A worktree holding uncommitted or unpushed work is \
the one refusal — cleanup never passes `--force`, so it stops with a nonzero \
exit and leaves the work alone.
A merged PR makes its worktree's session claim obsolete, so cleanup ends that \
claim (record-only — it never kills a process it does not own) and proceeds.
--dry-run runs every read and prints the plan without issuing a single \
mutating command.";
const MERGE_LONG_ABOUT: &str = "\
Squash-merge a PR with its validated body as the landing commit message.
Reads the PR, re-validates its body with the same nine-field-and-footer check \
`tm pr open` runs, then merges with `--squash --delete-branch --subject \
\"<title> (#<n>)\" --body-file <tmp>`, so the validated body IS the squash \
commit message rather than a concatenation of the branch's raw commit messages.
Refuses with a one-line reason, exits non-zero, and calls no merge when:
- the PR body fails that validation
- the PR is a draft
- the PR carries a `do-not-merge` label (any case)
- the review decision is CHANGES_REQUESTED
- the PR has merge conflicts — `mergeable` CONFLICTING or `mergeStateStatus`
DIRTY (resolve them with `gh pr update-branch <n>`)
A mergeStateStatus of BEHIND is NOT a refusal — a behind branch merges fine here. \
Every other mergeStateStatus — BLOCKED, UNSTABLE, HAS_HOOKS, UNKNOWN — and every \
reviewDecision other than CHANGES_REQUESTED are left to `gh pr merge` to accept or \
reject.
With --auto the merge is queued instead of performed, and GitHub applies the \
supplied subject and body when auto-merge fires. Under a merge queue GitHub \
ignores the supplied subject and body entirely; this repo has no merge queue.";
#[derive(Debug, clap::Args)]
pub(crate) struct PrOpenArgs {
#[arg(long)]
pub(crate) title: String,
#[arg(long = "body-file")]
pub(crate) body_file: PathBuf,
#[arg(long)]
pub(crate) issue: Option<u64>,
#[arg(long)]
pub(crate) closes: bool,
#[arg(long, value_parser = clap::value_parser!(u8).range(1..=6))]
pub(crate) rung: Option<u8>,
#[arg(long, default_value = "main")]
pub(crate) base: String,
#[arg(long)]
pub(crate) head: Option<String>,
#[arg(long = "docs-only")]
pub(crate) docs_only: bool,
#[arg(long)]
pub(crate) minimal: bool,
#[arg(long)]
pub(crate) session: Option<String>,
#[arg(long)]
pub(crate) repo: Option<String>,
#[arg(long = "dry-run")]
pub(crate) dry_run: bool,
}
#[derive(Debug, clap::Args)]
pub(crate) struct PrMergeArgs {
pub(crate) pr: u64,
#[arg(long)]
pub(crate) auto: bool,
#[arg(long = "no-delete-branch")]
pub(crate) no_delete_branch: bool,
#[arg(long = "no-cleanup", visible_alias = "keep-worktree")]
pub(crate) no_cleanup: bool,
#[arg(long = "allow-failing", value_name = "CHECK")]
pub(crate) allow_failing: Vec<String>,
#[arg(long = "allow-no-checks")]
pub(crate) allow_no_checks: bool,
#[arg(long)]
pub(crate) repo: Option<String>,
}
#[derive(Debug, clap::Args)]
pub(crate) struct PrQueueCheckArgs {
pub(crate) pr: Option<u64>,
#[arg(long, default_value = "main")]
pub(crate) base: String,
#[arg(long)]
pub(crate) repo: Option<String>,
#[arg(long)]
pub(crate) json: bool,
}
#[derive(Debug, clap::Args)]
pub(crate) struct PrCleanupArgs {
pub(crate) pr: u64,
#[arg(long)]
pub(crate) repo: Option<String>,
#[arg(long = "dry-run")]
pub(crate) dry_run: bool,
}