Skip to main content

review_queue/source/
mod.rs

1//! The `ReviewSource` abstraction: sources say *what* to check out, the `vcs` module decides
2//! *how*. See the design plan for the GitHub/moz-phab implementations; this module currently
3//! only has the shared types and trait.
4
5use std::path::Path;
6
7use anyhow::Result;
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10
11pub mod diffstat;
12pub mod github;
13pub mod moz_phab;
14
15/// `{source}/{id}` identity for a review; re-exported here since sources produce it.
16pub use crate::state::ReviewKey;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Lifecycle {
20    Open,
21    /// Landed/closed/abandoned/merged: safe to remove the workspace once clean.
22    Resolved,
23}
24
25/// Serialized into `state.json` (as part of `ReviewEntry`) so a review's kind survives between
26/// `sync` and a later on-demand `rq fetch`.
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub enum ReviewKind {
29    Direct,
30    Group(String),
31}
32
33/// A repo referenced by a review, before it's resolved to a canonical local repo. Serialized into
34/// `state.json` (as part of `ReviewEntry`) so `rq fetch`/the TUI can resolve it later without
35/// re-querying the source.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct RepoRef {
38    /// Candidate clone URLs (mirrors, ssh/https variants) - matched against discovered workdir
39    /// repos and the tool-managed clone registry after normalization.
40    pub urls: Vec<String>,
41    pub display_name: String,
42}
43
44#[derive(Debug, Clone)]
45pub struct Review {
46    pub key: ReviewKey,
47    pub title: String,
48    pub author: String,
49    pub url: String,
50    pub repo: RepoRef,
51    /// Diff ID / head SHA (or comma-joined stack of diff IDs for Phabricator). Used to detect
52    /// updates that require re-syncing the workspace.
53    pub version: String,
54    pub kind: ReviewKind,
55    /// Diffstat (same summary format `git diff --stat`/`jj diff --stat` print), fetched
56    /// best-effort as part of the same `fetch_queue()` call that built this `Review` - `None` if
57    /// the source couldn't get one (no active diff, a Conduit/API hiccup, etc). Carried straight
58    /// into `state.json`'s `ReviewEntry` so `rq show`'s TUI can show it with no further network
59    /// calls of its own.
60    pub diff_stat: Option<String>,
61    /// The PR description / Phabricator revision summary (the commit message body - the title is
62    /// carried separately in `title`). Carried into `state.json` alongside `diff_stat` so the TUI
63    /// can show it with no network calls of its own.
64    pub description: Option<String>,
65    /// This review's ancestors within its stack, bottom-most first, excluding itself and any
66    /// already-landed ones (those are part of the base). Empty for a review that isn't stacked.
67    pub ancestors: Vec<ReviewKey>,
68}
69
70#[derive(Debug, Clone)]
71pub struct Patch {
72    pub title: String,
73    /// `"Name <email>"`, ready to pass straight to `git commit --author`/`jj commit --author`.
74    pub author: String,
75    pub message: String,
76    pub diff: String,
77}
78
79#[derive(Debug, Clone)]
80pub enum Checkout {
81    /// e.g. GitHub: fetch `refspec` from the canonical repo's own origin, expect it to resolve
82    /// to `commit`. The git backend only ever needs `refspec`/`commit`; `fork`, when present,
83    /// is used solely by the jj backend, which tracks the PR head's own remote+branch rather
84    /// than importing an anonymous ref (see `vcs::jj`).
85    Ref {
86        refspec: String,
87        commit: String,
88        fork: Option<ForkRef>,
89    },
90    /// Apply `patches` bottom-to-top on top of `base` (or the canonical repo's default branch
91    /// tip if `base` is unset). No current source produces this - `moz_phab` delegates the whole
92    /// apply step to the `moz-phab` CLI instead (see `ExternalCommand`) - but it's kept as a
93    /// generic extension point for a future source that fetches raw diffs itself.
94    Patches {
95        base: Option<String>,
96        patches: Vec<Patch>,
97    },
98    /// Delegate the entire checkout to an external command, run with the given `env` added and
99    /// `cwd` set to the workspace. The workspace starts at the canonical repo's default branch
100    /// tip (same starting point as `Patches` with `base: None`) - the command is expected to
101    /// move it wherever it needs to itself (`moz-phab patch --apply-to base` resolves and checks
102    /// out the revision's actual base on its own, including cases a source's own Conduit calls
103    /// can't cheaply replicate, like an unlanded base needing a git-cinnabar hg/git translation -
104    /// confirmed against real Mozilla Phabricator, not just moz-phab's source). A nonzero exit
105    /// leaves the workspace in place for inspection, same as a failed `Patches` application.
106    ExternalCommand {
107        program: String,
108        args: Vec<String>,
109        env: Vec<(String, String)>,
110    },
111}
112
113/// The PR head's own remote+branch, e.g. `https://github.com/alice/firefox` + `feature-x`.
114/// `None` when the source fork has been deleted, in which case the jj backend falls back to a
115/// raw fetch-and-import of `refspec`/`commit` instead.
116#[derive(Debug, Clone)]
117pub struct ForkRef {
118    /// Name to register the remote under in the canonical jj repo. Callers should use `"origin"`
119    /// instead of registering a redundant remote when this normalizes to the canonical repo's
120    /// own origin URL (a same-repo branch PR).
121    pub remote_name: String,
122    pub remote_url: String,
123    pub branch: String,
124}
125
126#[async_trait]
127pub trait ReviewSource: Send + Sync {
128    /// Instance name from config, e.g. "moz" - distinguishes multiple configured instances of
129    /// the same source type and forms the `source` half of a `ReviewKey`.
130    fn name(&self) -> &str;
131
132    async fn fetch_queue(&self) -> Result<Vec<Review>>;
133
134    /// `canonical_repo` is the already-resolved local repo this review's workspace will be built
135    /// from - most sources ignore it, but one that needs to prepare local repo state first (e.g.
136    /// `moz_phab` writing `.git/.arcconfig` so `moz-phab` can find the right Phabricator
137    /// instance) needs it before it can build the `Checkout`.
138    async fn checkout_spec(&self, review: &Review, canonical_repo: &Path) -> Result<Checkout>;
139
140    /// Refresh the lifecycle of reviews that are no longer in the queue (e.g. changes were
141    /// requested, or the reviewer was removed) so `sync` knows whether to keep the workspace.
142    async fn fetch_status(&self, ids: &[String]) -> Result<Vec<(String, Lifecycle)>>;
143
144    /// Whether `message` (a commit message from a workspace built for a stack containing
145    /// `review`) is the commit that carries `review`'s patch. Lets a stack workspace be
146    /// positioned at one member's patch. Sources that don't stack reviews never need this.
147    fn is_commit_for(&self, _review: &ReviewKey, _message: &str) -> bool {
148        false
149    }
150}