feat(rust): port queue pause and unpause to native Rust#1352
Conversation
|
This pull request is part of a Mergify stack:
|
Merge ProtectionsYour pull request matches the following merge protections and will not be merged until they are valid. 🔴 ⛓️ Depends-On RequirementsWaiting for
This rule is failing.Requirement based on the presence of
🔴 👀 Review RequirementsWaiting for
This rule is failing.
🔴 🔎 ReviewsWaiting for
This rule is failing.
🟢 🤖 Continuous IntegrationWonderful, this rule succeeded.
🟢 Enforce conventional commitWonderful, this rule succeeded.Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
🟢 📕 PR descriptionWonderful, this rule succeeded.
|
253835c to
1ae9f28
Compare
103418f to
9889d7e
Compare
Revision history
|
9889d7e to
549a14d
Compare
1ae9f28 to
1670fa1
Compare
1670fa1 to
8afcb9c
Compare
8afcb9c to
2eeb6ff
Compare
ed2b8b5 to
6b1d62e
Compare
2eeb6ff to
7e85a94
Compare
The Rust binary now serves ``mergify queue status`` natively. The Python implementation (``mergify_cli/queue/cli.py:status`` plus the batch/scope/topology helpers it depended on) is removed in the same PR — the port-and-delete rule we adopted in #1322 keeps a single live copy of every command. ## What ports ``mergify queue status [-r REPO] [-t TOKEN] [-u URL] [-b BRANCH] [--json]``: 1. Resolves repository / token / API URL via the shared ``mergify_queue::auth`` resolver introduced in #1352. 2. Fetches ``GET /v1/repos/<repo>/merge-queue/status``, optionally with ``?branch=<branch>`` (URL-encoded via ``url::form_urlencoded::byte_serialize``). 3. With ``--json``: pretty-prints the raw response. The schema is Mergify's API contract, not this CLI's, so we deserialize into ``serde_json::Value`` and emit verbatim — unknown fields and future schema additions survive the round trip. 4. Without ``--json``: deserializes into a typed ``StatusView`` that uses ``#[serde(default)] Option<…>`` for every field the Mergify API has historically treated as optional/nullable (matches the port checklist from #1357), then renders a header, an optional pause indicator, the batch tree (grouped by scope when there is more than one), and the waiting-PR list. Status icons (``● ◑ ◌ ✓ ✗ ◎ ⏳ ↻ ⏰ ❄``) and relative times (``5m ago`` / ``~1h``) match the Python implementation. ## Rendering departures Python used Rich's ``Tree`` for batches. The Rust port emits flat indented text instead — same data, simpler rendering. Both are line-oriented and round-trip cleanly through pipes; the fancy box-drawing was visual sugar that didn't survive piping anyway. ## Tests (24 new, in mergify-queue::status) - ``build_path`` covers no-branch, branch, and URL-encoding of a branch name with slashes + spaces (e.g. ``feature/foo bar`` becomes ``feature%2Ffoo+bar`` in the query). - ``relative_time`` covers seconds / minutes / hours / days, future prefix, and graceful empty-string return on a malformed timestamp (matches Python's "degrade rather than fail"). - ``topological_sort`` covers parents-before-children ordering and tolerance of ``parent_ids`` that reference missing batches. - ``group_by_scope`` covers the ``[]`` → ``"default"`` fallback and multi-scope batches appearing under each scope they claim. - ``status_icon`` covers known + unknown codes. - End-to-end wiremock tests: empty queue, paused queue, batches + waiting PRs, multi-scope grouping, ``?branch=…`` query threading, JSON-passthrough preserving an ``extra_field``, and tolerance of a response that omits all optional fields. ## Wiring - ``crates/mergify-queue/Cargo.toml``: adds ``chrono`` (relative time math), ``indexmap`` (scope groups in insertion order), promotes ``serde_json`` from dev to runtime (used for the ``serde_json::Value`` passthrough). - ``crates/mergify-cli/src/main.rs``: registers the ``status`` subcommand under ``QueueSubcommand``, threads the ``--branch``/``--json`` flags, dispatches to ``mergify_queue::status::run``. Adds ``status`` to ``looks_native``. - ``mergify_cli/queue/api.py``: removes ``QueueStatusResponse``, ``QueueBatch``, ``QueuePause``, ``QueueChecksSummary``, ``QueueBatchStatus``, ``QueuePullRequest``, ``QueuePullRequestAuthor``, and ``get_queue_status`` — all now Rust-native. ``QueuePullResponse`` and friends stay for the still-shimmed ``queue show`` (next phase). - ``mergify_cli/queue/cli.py``: removes the ``@queue.command status`` block and the helpers it owned (``STATUS_STYLES``, ``_status_text``, ``_batch_label``, ``_pr_label``, ``_topological_sort``, ``_group_batches_by_scope``, ``_print_batches``, ``_print_waiting_prs``). ``_relative_time`` stays — ``show`` still uses it. - ``mergify_cli/tests/queue/test_cli.py``: deletes ``TestStatusCommand``, ``TestTopologicalSort``, and the ``_invoke_status`` helper. ``TestRelativeTime`` stays. Workspace: 138 Rust tests green, 590 Python tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Change-Id: I8cebcd325f05173dfa41083da2ec6516a6ec3a3f
7e85a94 to
9478efd
Compare
|
@jd this pull request is now in conflict 😩 |
8485cd1 to
31c58ff
Compare
There was a problem hiding this comment.
Pull request overview
Ports mergify queue pause / mergify queue unpause from the Python shim to native Rust, wiring them into the Rust mergify binary and removing the legacy Python implementations. This extends mergify-core’s HTTP client to support the required PUT and an idempotent DELETE-with-404-handling flow.
Changes:
- Add a new
mergify-queueRust crate implementingqueue pauseandqueue unpause(with unit tests) and dispatch these commands fromcrates/mergify-cli. - Extend
mergify-core::HttpClientwithput()anddelete_if_exists()(returningDeletedvsNotFound). - Remove Python
pause/unpauseclick commands + API helpers and delete the corresponding Python CLI tests; adjust skill validation to recognize Rust-native queue commands.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| mergify_cli/tests/queue/test_skill.py | Allows skill validation to treat pause/unpause as Rust-native queue commands. |
| mergify_cli/tests/queue/test_cli.py | Removes Python-level tests for pause/unpause now that the implementation moved to Rust. |
| mergify_cli/queue/cli.py | Deletes the Python click implementations of queue pause/queue unpause. |
| mergify_cli/queue/api.py | Removes Python API helper functions/types for pausing/unpausing the queue. |
| crates/mergify-queue/src/pause.rs | New Rust implementation of queue pause, including confirmation flow and tests. |
| crates/mergify-queue/src/unpause.rs | New Rust implementation of queue unpause, including 404 “not paused” behavior and tests. |
| crates/mergify-queue/src/lib.rs | Exposes the new queue subcommand modules. |
| crates/mergify-queue/Cargo.toml | Introduces the new mergify-queue crate and its dependencies. |
| crates/mergify-core/src/lib.rs | Re-exports DeleteOutcome from the HTTP module. |
| crates/mergify-core/src/http.rs | Adds put() and delete_if_exists() plus the supporting status-only execution path. |
| crates/mergify-cli/src/main.rs | Adds clap parsing + native dispatch for queue pause and queue unpause. |
| crates/mergify-cli/Cargo.toml | Adds the new mergify-queue crate as a dependency. |
| Cargo.lock | Updates the lockfile for the new crate/dependency graph changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
31c58ff to
01e7be5
Compare
01e7be5 to
dd391d3
Compare
Two queue commands in one PR — both are idempotent one-shot API
calls that share the same auth + repository resolution. Pause
exercises the new PUT method; unpause exercises the new
DELETE-with-status-check method.
PUTs ``{"reason": "..."}`` to
``/v1/repos/<repo>/merge-queue/pause``, prints a confirmation line
with the reason and timestamp.
Safety rails match Python:
- ``--yes-i-am-sure`` skips confirmation outright.
- Interactive (TTY): prompts "Proceed? [y/N]". Anything other than
``y``/``yes`` aborts as a generic error.
- Non-interactive without the flag: refuses with INVALID_STATE
(exit 7), matching Python's
``raise SystemExit(ExitCode.INVALID_STATE)``.
``--reason`` has a 255-char cap enforced by clap's ``value_parser``
— bad input exits 2.
DELETEs the same path. On 404 the API is telling us the queue
wasn't paused, so the command prints "Queue is not currently
paused" and exits MERGIFY_API_ERROR (matches Python). On 2xx it
prints "Queue resumed." and exits 0.
Two new methods on ``mergify_core::HttpClient``:
- ``put<B, T>(path, body) -> Result<T, CliError>`` — mirror of
``post``, different verb.
- ``delete_if_exists(path) -> Result<DeleteOutcome, CliError>`` —
returns ``Deleted`` on 2xx, ``NotFound`` on 404, errors on any
other non-success status. Lets commands like ``unpause`` give
a friendlier 404 message without parsing error strings.
Auth resolution (token / api-url / repository) goes through the
shared ``mergify_core::auth`` module added earlier in the stack —
no per-crate copy of the resolvers in ``mergify-queue``.
5 new unit tests in the queue crate:
- ``parse_reason`` accepts short strings and rejects > 255 chars
- ``run`` pauses and prints the API-returned reason + timestamp
- ``run`` prints "Queue resumed" on 2xx
- ``run`` errors with MERGIFY_API_ERROR on 404 carrying the
"not currently paused" message
End-to-end smoke tested three paths:
``queue pause --reason X -r owner/repo`` → exit 8 (missing token),
``queue unpause -r owner/repo`` → exit 8 (missing token),
``echo n | queue pause --reason X`` → exit 7 (non-TTY, no --sure).
The Python ``queue pause`` / ``queue unpause`` implementations and
their tests are removed in the same PR — the Rust binary now owns
both commands end-to-end. Binary: 8.4 MB → 8.5 MB. 56 Rust tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change-Id: Idba6fa38caf403fd5f4184cda462b5f7c1eb3ebf
Reviewer feedback (#1352): hardcoding the set of Rust-native queue subcommands in ``mergify_cli/tests/queue/test_skill.py`` made the skill-reference validation drift-prone — a port PR that forgot to update ``NATIVE_QUEUE_COMMANDS`` would silently pass even though the skill was referencing a command the binary couldn't handle. Two changes make the binary the single source of truth: 1. ``crates/mergify-cli/src/main.rs`` factors the ``(group, subcommand)`` pairs into a top-level ``NATIVE_COMMANDS`` const. ``looks_native`` iterates that const instead of a `match` arm. A new hidden flag ``--list-native-commands`` (intercepted before clap or the shim) prints one ``<group> <subcommand>`` pair per line and exits ``0``. 2. ``test_skill.py`` queries the installed ``mergify`` binary via ``subprocess.run([…, "--list-native-commands"])`` to discover the native set, replacing the ``NATIVE_QUEUE_COMMANDS`` frozenset. The test skips cleanly when the binary isn't on ``PATH`` (rare; ``uv run pytest`` installs it first). The result: the next port PR adds an entry to ``NATIVE_COMMANDS`` in main.rs as part of its normal wiring, and ``test_skill.py`` picks it up automatically. No parallel list to maintain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Change-Id: I74502fe8affcc58f26eaaa9d058668eb36fec83b
dd391d3 to
9d19f3e
Compare
Two queue commands in one PR — both are idempotent one-shot API
calls that share the same auth + repository resolution. Pause
exercises the new PUT method; unpause exercises the new
DELETE-with-status-check method.
PUTs
{"reason": "..."}to/v1/repos/<repo>/merge-queue/pause, prints a confirmation linewith the reason and timestamp.
Safety rails match Python:
--yes-i-am-sureskips confirmation outright.y/yesaborts as a generic error.(exit 7), matching Python's
raise SystemExit(ExitCode.INVALID_STATE).--reasonhas a 255-char cap enforced by clap'svalue_parser— bad input exits 2.
DELETEs the same path. On 404 the API is telling us the queue
wasn't paused, so the command prints "Queue is not currently
paused" and exits MERGIFY_API_ERROR (matches Python). On 2xx it
prints "Queue resumed." and exits 0.
Two new methods on
mergify_core::HttpClient:put<B, T>(path, body) -> Result<T, CliError>— mirror ofpost, different verb.delete_if_exists(path) -> Result<DeleteOutcome, CliError>—returns
Deletedon 2xx,NotFoundon 404, errors on anyother non-success status. Lets commands like
unpausegivea friendlier 404 message without parsing error strings.
Auth resolution (token / api-url / repository) goes through the
shared
mergify_core::authmodule added earlier in the stack —no per-crate copy of the resolvers in
mergify-queue.5 new unit tests in the queue crate:
parse_reasonaccepts short strings and rejects > 255 charsrunpauses and prints the API-returned reason + timestamprunprints "Queue resumed" on 2xxrunerrors with MERGIFY_API_ERROR on 404 carrying the"not currently paused" message
End-to-end smoke tested three paths:
queue pause --reason X -r owner/repo→ exit 8 (missing token),queue unpause -r owner/repo→ exit 8 (missing token),echo n | queue pause --reason X→ exit 7 (non-TTY, no --sure).The Python
queue pause/queue unpauseimplementations andtheir tests are removed in the same PR — the Rust binary now owns
both commands end-to-end. Binary: 8.4 MB → 8.5 MB. 56 Rust tests.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Depends-On: #1380