a73x

c98c1ef6

Add reject verdict to patch review

a73x   2026-03-21 19:28

When a reviewer uses the "reject" verdict, the patch is reviewed and
then automatically closed with an archived ref, combining two steps
into one. This adds the Reject variant to ReviewVerdict and updates
all match arms, serialization, and the error message for valid verdicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

diff --git a/src/event.rs b/src/event.rs
index 86804c1..74ea16d 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -107,4 +107,5 @@ pub enum ReviewVerdict {
    Approve,
    RequestChanges,
    Comment,
    Reject,
}
diff --git a/src/lib.rs b/src/lib.rs
index ce4f804..223d0e4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -354,9 +354,10 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> {
                    "approve" => ReviewVerdict::Approve,
                    "request-changes" => ReviewVerdict::RequestChanges,
                    "comment" => ReviewVerdict::Comment,
                    "reject" => ReviewVerdict::Reject,
                    _ => {
                        return Err(git2::Error::from_str(
                            "verdict must be: approve, request-changes, or comment",
                            "verdict must be: approve, request-changes, comment, or reject",
                        )
                        .into());
                    }
diff --git a/src/patch.rs b/src/patch.rs
index a043c55..20e23de 100644
--- a/src/patch.rs
+++ b/src/patch.rs
@@ -305,10 +305,11 @@ pub fn review(
        patch.revisions.last().map(|r| r.number).unwrap_or(1)
    };

    let is_reject = verdict == ReviewVerdict::Reject;
    let author = get_author(repo)?;
    let event = Event {
        timestamp: chrono::Utc::now().to_rfc3339(),
        author,
        author: author.clone(),
        action: Action::PatchReview {
            verdict,
            body: body.to_string(),
@@ -317,6 +318,24 @@ pub fn review(
        clock: 0,
    };
    dag::append_event(repo, &ref_name, &event, &sk)?;

    // A reject verdict also closes the patch
    if is_reject {
        let close_event = Event {
            timestamp: chrono::Utc::now().to_rfc3339(),
            author,
            action: Action::PatchClose {
                reason: Some(format!("Rejected: {}", body)),
            },
            clock: 0,
        };
        dag::append_event(repo, &ref_name, &close_event, &sk)?;
        // Archive the ref
        if ref_name.starts_with("refs/collab/patches/") {
            state::archive_patch_ref(repo, &id)?;
        }
    }

    Ok(())
}

diff --git a/src/state.rs b/src/state.rs
index 5521693..205e99f 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -34,6 +34,7 @@ fn serialize_verdict<S: serde::Serializer>(v: &ReviewVerdict, s: S) -> Result<S:
        ReviewVerdict::Approve => "approve",
        ReviewVerdict::RequestChanges => "request-changes",
        ReviewVerdict::Comment => "comment",
        ReviewVerdict::Reject => "reject",
    };
    s.serialize_str(str_val)
}
@@ -44,6 +45,7 @@ fn deserialize_verdict<'de, D: serde::Deserializer<'de>>(d: D) -> Result<ReviewV
        "approve" => Ok(ReviewVerdict::Approve),
        "request-changes" => Ok(ReviewVerdict::RequestChanges),
        "comment" => Ok(ReviewVerdict::Comment),
        "reject" => Ok(ReviewVerdict::Reject),
        other => Err(serde::de::Error::custom(format!(
            "unknown verdict: {}",
            other
diff --git a/src/status.rs b/src/status.rs
index 62b3598..022226d 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -68,6 +68,7 @@ pub fn compute(repo: &Repository) -> Result<ProjectStatus, Error> {
                ReviewVerdict::Approve => patches_approved += 1,
                ReviewVerdict::RequestChanges => patches_changes_requested += 1,
                ReviewVerdict::Comment => {}
                ReviewVerdict::Reject => {}
            }
        }
    }