use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PrReviewState {
Approved,
Pending,
ChangesRequested,
Draft,
Merged,
Closed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrStatus {
pub number: u32,
pub url: String,
pub review_state: PrReviewState,
}
const GH_TIMEOUT_MS: u64 = 5000;
pub fn derive_review_state(is_draft: bool, review_decision: &str) -> PrReviewState {
if is_draft {
return PrReviewState::Draft;
}
match review_decision {
"APPROVED" => PrReviewState::Approved,
"CHANGES_REQUESTED" => PrReviewState::ChangesRequested,
_ => PrReviewState::Pending,
}
}
pub async fn fetch_pr_status() -> Option<PrStatus> {
use std::process::Command;
let is_git = Command::new("git")
.args(["rev-parse", "--git-dir"])
.output()
.map(|o| o.status.success())
.unwrap_or(false);
if !is_git {
return None;
}
let branch_output = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
.ok()?;
let branch = String::from_utf8_lossy(&branch_output.stdout)
.trim()
.to_string();
let default_branch_output = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "origin/HEAD"])
.output()
.ok();
let default_branch = default_branch_output
.map(|o| {
String::from_utf8_lossy(&o.stdout)
.trim()
.trim_start_matches("origin/")
.to_string()
})
.unwrap_or_else(|| "main".to_string());
if branch == default_branch || branch == "main" || branch == "master" {
return None;
}
let output = Command::new("gh")
.args([
"pr",
"view",
"--json",
"number,url,reviewDecision,isDraft,headRefName,state",
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.trim().is_empty() {
return None;
}
let data: serde_json::Value = serde_json::from_str(&stdout).ok()?;
let number = data["number"].as_u64()? as u32;
let url = data["url"].as_str()?.to_string();
let is_draft = data["isDraft"].as_bool().unwrap_or(false);
let review_decision = data["reviewDecision"].as_str().unwrap_or("");
let head_ref = data["headRefName"].as_str().unwrap_or("");
let state = data["state"].as_str().unwrap_or("");
if head_ref == default_branch || head_ref == "main" || head_ref == "master" {
return None;
}
if state == "MERGED" || state == "CLOSED" {
return None;
}
Some(PrStatus {
number,
url,
review_state: derive_review_state(is_draft, review_decision),
})
}