Expand description
Mozilla’s Phabricator, via the moz-phab CLI. Not a general-purpose Phabricator source -
see the module name. moz-phab is the tool every Mozilla contributor already uses to submit
and apply patches; delegating the actual checkout to it, rather than reimplementing diff
fetching and application ourselves, was a deliberate pivot after an earlier from-scratch
implementation turned out to duplicate work moz-phab already does more robustly. Everything
below the “Checkout” heading was verified empirically against the real, live Mozilla
Phabricator and a real moz-phab install (not just read from its source), specifically
because the earlier from-scratch approach had exactly this kind of thing wrong.
Auth: POST form-encoded to {url}/api/{method} with api.token=.... Nested params flatten
to constraints[members][0]=... (see flatten_params). Token comes from, in order: config
token, token_cmd, then ~/.arcrc (hosts["{url}/api/"].token, which moz-phab already
writes, so it usually works with no setup). The same resolved token is passed to moz-phab
itself via MOZPHAB_PHABRICATOR_API_TOKEN, so both halves always agree on identity rather
than moz-phab silently falling back to its own (possibly different) ~/.arcrc lookup.
Queue: user.whoami -> project.search {constraints:{members:[me]}} (my groups, unless
include_groups is off) -> differential.revision.search {queryKey:"active", attachments:{reviewers:true}}, paginating on cursor.after. Bucketed locally (mirroring
Phabricator’s own DifferentialRevisionRequiredActionResultBucket, which Conduit doesn’t
expose - this part is transcribed from myqonly’s real, working
addon/services/phabricator-service.mjs):
- skip if
fields.authorPHIDis me - skip if my (or my group’s) reviewer entry has
status == "resigned" - require
fields.status.value == "needs-review" - require a reviewer entry of mine (or a group I’m in) with status in
blocking|rejected|rejected-older|added|commented(acceptedis deliberately excluded - nothing left to do)
Author PHIDs are resolved to usernames with one batched user.search {constraints:{phids:[...]}} call over every distinct author in the page, rather than showing
raw PHIDs in rq show/rq path.
Every per-revision Conduit lookup after the initial queue fetch is batched across all
actionable revisions rather than issued once per revision - a queue of N revisions used to cost
roughly N + N * stack_depth extra calls (one diffusion.repository.search per revision, plus
a edge.search/differential.revision.search pair per stack hop per revision), which is easy
to trip Phabricator’s rate limit on. Now:
- Repos: one
diffusion.repository.search {constraints:{phids:[...]}}over every distinctrepositoryPHIDamong actionable revisions (repo_refs_for). - Stacks:
resolve_stackswalks every actionable revision’s parent chain in lockstep, oneedge.search+differential.revision.searchpair per depth level covering every stack still in flight, instead of per revision. A 20-revision queue with 3-deep stacks now costs ~1 (repos) + 32 (stack levels) calls instead of ~20 + 20(1+2*3). - Diffstats:
diff_stats_forresolves every actionable revision’sdiffPHIDthrough one batcheddifferential.diff.searchcall, then fetches each resulting diff’s raw text viadifferential.getrawdiff- unlike the above, this one is one call per revision, sincegetrawdiffhas no batch form; seediff_stats_for’s own doc comment for why (and for why it’sgetrawdiff, not the more obvious-lookingdifferential.querydiffs).
fetch_queue fetches each Review’s diffstat itself (via diff_stats_for, above) rather than
rq show’s TUI fetching it lazily on expand, so the TUI never blocks on Conduit.
version is the comma-joined dateModified of every revision in the stack (walked via
edge.search, same as before), not diff ids - moz-phab re-resolves the live diff/base
itself on every invocation regardless of what we pass it, so this only needs to answer “has
anything about this stack changed since we last synced,” and dateModified answers that with
zero extra Conduit calls beyond the stack walk we’re already doing (resolve_stacks’ own
differential.revision.search calls already return it).
§Checkout
checkout_spec returns Checkout::ExternalCommand { program: "moz-phab", args: ["patch", "D<id>", "--apply-to", "base", "--yes", "--name", <key>], env }. moz-phab patch handles
everything the earlier implementation did by hand, and does it better:
- Walks the full dependency stack itself (confirmed live: patching a revision automatically discovered and applied its parent too, unprompted).
- Resolves the actual base commit itself, including a fallback our own
fields.refs-based base lookup never had: if the recorded base isn’t a public/landed commit (e.g. it belongs to another unlanded stack, or needs a git-cinnabar hg<->git translation), it walks forward to the latest landed ancestor and applies there instead (“Base revision … is not public … Applying the patch at … instead” - observed live againstmozilla-central). --yesfully suppresses its interactive “patch the full stack?” prompt.- Exits 1 on failure (a real patch-doesn’t-apply case was observed live), leaving the
workspace checked out at the resolved base with nothing applied - which is exactly the
Status::ApplyFailed“leave it for inspection” contract theVcsbackends already have.
Requires repository.callsign in .arcconfig, not just phabricator.uri - confirmed
live: moz-phab patch fails outright with “Failed to determine the Phabricator callsign for
this repository” without it, even though static analysis of moz-phab’s own source suggested
the callsign was only needed by submit. checkout_spec looks the callsign up itself
(diffusion.repository.search) and writes both fields to .git/.arcconfig - never the repo
root’s own .arcconfig, which might be absent, tracked, or (as seen live in a real non-central
repo) present but missing the callsign. .git/.arcconfig is checked first and never touches
tracked working-tree content; every canonical repo here is colocated with a real .git by
construction (tool-managed clones always are; jj canonical repos always are too, by our own
design choice), so this path needs no per-VCS fallback.
Known limitation, not yet handled: moz-phab defaults to the remote named origin and
warns (“Multiple remotes found. Defaulting to ‘origin’.”) if a repo has more than one. Every
tool-managed clone only ever has one remote, so this never bites the common path - but a
discovered workdir checkout with multiple/nonstandard remotes (common for Mozilla developers,
e.g. a central/try naming scheme instead of origin) may need git.remote set in their
own ~/.moz-phab-config, which this tool doesn’t configure on their behalf.
Whether moz-phab itself is installed and runnable is never checked by this module - only
checkout_spec’s ExternalCommand actually shells out to it, and testing this module
shouldn’t require moz-phab to be on PATH.
fetch_status: differential.revision.search {constraints:{ids:[...]}}. status.value of
published (landed) or abandoned is Lifecycle::Resolved; a revision id not found in the
response at all is also treated as Resolved, so a review that’s vanished (e.g. lost access)
doesn’t linger forever.
Structs§
Constants§
- NAME
- This source’s hardcoded id-namespace prefix - see
crate::state::ReviewKey.