use std::error::Error;
use std::io::ErrorKind;
use std::path::Path;
use std::process::Command;
use serde::Deserialize;
pub const GH: &str = "gh";
#[derive(Debug, Deserialize)]
struct Commit {
oid: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrInfo {
base_ref_oid: String,
head_ref_oid: String,
merge_commit: Option<Commit>,
}
#[derive(Debug, PartialEq)]
pub enum Plan {
Refs { since: String, until: String },
Patch { base: Option<String> },
}
pub fn plan(
info: &PrInfo,
is_local: impl Fn(&str) -> bool,
is_ancestor: impl Fn(&str, &str) -> bool,
) -> Plan {
let base = info.base_ref_oid.as_str();
if !is_local(base) {
return Plan::Patch { base: None };
}
let merged = info.merge_commit.as_ref().map(|c| c.oid.as_str());
let until = if is_local(&info.head_ref_oid) {
Some(info.head_ref_oid.as_str())
} else {
merged.filter(|merge| is_ancestor(base, merge))
};
match until {
Some(until) => Plan::Refs {
since: base.to_string(),
until: until.to_string(),
},
None => Plan::Patch {
base: Some(base.to_string()),
},
}
}
fn run(program: &str, dir: &Path, args: &[&str]) -> Result<Vec<u8>, Box<dyn Error>> {
let output = Command::new(program)
.args(args)
.current_dir(dir)
.output()
.map_err(|e| match e.kind() {
ErrorKind::NotFound => format!(
"--pr requires the GitHub CLI (`{program}`) installed and authenticated: \
https://cli.github.com"
),
_ => format!("cannot run `{program}`: {e}"),
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("`{program} {}` failed: {}", args.join(" "), stderr.trim()).into());
}
Ok(output.stdout)
}
pub fn info(program: &str, dir: &Path, number: u64) -> Result<PrInfo, Box<dyn Error>> {
let number = number.to_string();
let fields = "baseRefOid,headRefOid,mergeCommit";
let json = run(program, dir, &["pr", "view", &number, "--json", fields])?;
Ok(serde_json::from_slice(&json)
.map_err(|e| format!("unexpected answer from `{program} pr view`: {e}"))?)
}
pub fn patch(program: &str, dir: &Path, number: u64) -> Result<Vec<u8>, Box<dyn Error>> {
run(
program,
dir,
&["pr", "diff", &number.to_string(), "--color=never"],
)
}
#[cfg(test)]
#[path = "pr_test.rs"]
mod tests;