Skip to content

Commit ef75ea5

Browse files
jdclaude
andcommitted
feat(rust): port queue status to native Rust (Phase 1.7)
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
1 parent 742d92c commit ef75ea5

8 files changed

Lines changed: 1072 additions & 614 deletions

File tree

Cargo.lock

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/mergify-cli/src/main.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use mergify_config::simulate::SimulateOptions;
2727
use mergify_core::OutputMode;
2828
use mergify_core::StdioOutput;
2929
use mergify_queue::pause::PauseOptions;
30+
use mergify_queue::status::StatusOptions;
3031
use mergify_queue::unpause::UnpauseOptions;
3132

3233
fn main() -> ExitCode {
@@ -55,6 +56,7 @@ enum NativeCommand {
5556
CiQueueInfo,
5657
QueuePause(QueuePauseOpts),
5758
QueueUnpause(QueueUnpauseOpts),
59+
QueueStatus(QueueStatusOpts),
5860
}
5961

6062
struct ConfigSimulateOpts {
@@ -89,6 +91,14 @@ struct QueueUnpauseOpts {
8991
api_url: Option<String>,
9092
}
9193

94+
struct QueueStatusOpts {
95+
repository: Option<String>,
96+
token: Option<String>,
97+
api_url: Option<String>,
98+
branch: Option<String>,
99+
output_json: bool,
100+
}
101+
92102
/// Heuristic: does argv look like the user intended a native
93103
/// subcommand?
94104
///
@@ -104,7 +114,7 @@ fn looks_native(argv: &[String]) -> bool {
104114
(pair[0].as_str(), pair[1].as_str()),
105115
("config", "validate" | "simulate")
106116
| ("ci", "scopes-send" | "git-refs" | "queue-info")
107-
| ("queue", "pause" | "unpause"),
117+
| ("queue", "pause" | "unpause" | "status"),
108118
)
109119
})
110120
}
@@ -216,6 +226,18 @@ fn detect_native(argv: &[String]) -> Option<NativeCommand> {
216226
token,
217227
api_url,
218228
})),
229+
Subcommands::Queue(QueueArgs {
230+
repository,
231+
token,
232+
api_url,
233+
command: QueueSubcommand::Status(StatusCliArgs { branch, json }),
234+
}) => Some(NativeCommand::QueueStatus(QueueStatusOpts {
235+
repository,
236+
token,
237+
api_url,
238+
branch,
239+
output_json: json,
240+
})),
219241
}
220242
}
221243

@@ -294,6 +316,19 @@ fn run_native(cmd: NativeCommand) -> ExitCode {
294316
)
295317
.await
296318
}
319+
NativeCommand::QueueStatus(opts) => {
320+
mergify_queue::status::run(
321+
StatusOptions {
322+
repository: opts.repository.as_deref(),
323+
token: opts.token.as_deref(),
324+
api_url: opts.api_url.as_deref(),
325+
branch: opts.branch.as_deref(),
326+
output_json: opts.output_json,
327+
},
328+
&mut output,
329+
)
330+
.await
331+
}
297332
}
298333
});
299334

@@ -465,6 +500,8 @@ enum QueueSubcommand {
465500
Pause(PauseCliArgs),
466501
/// Unpause the merge queue for the repository.
467502
Unpause,
503+
/// Show merge queue status for the repository.
504+
Status(StatusCliArgs),
468505
}
469506

470507
#[derive(clap::Args)]
@@ -478,3 +515,14 @@ struct PauseCliArgs {
478515
#[arg(long = "yes-i-am-sure", default_value_t = false)]
479516
yes_i_am_sure: bool,
480517
}
518+
519+
#[derive(clap::Args)]
520+
struct StatusCliArgs {
521+
/// Filter the queue by branch name.
522+
#[arg(long, short = 'b')]
523+
branch: Option<String>,
524+
525+
/// Emit the raw API response as a single JSON document.
526+
#[arg(long, default_value_t = false)]
527+
json: bool,
528+
}

crates/mergify-queue/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ publish = false
1111

1212
[dependencies]
1313
mergify-core = { path = "../mergify-core" }
14+
chrono = { version = "0.4", default-features = false, features = ["clock"] }
15+
indexmap = "2"
1416
serde = { version = "1.0", features = ["derive"] }
17+
serde_json = "1.0"
1518
url = "2"
1619

1720
[dev-dependencies]
18-
serde_json = "1.0"
1921
temp-env = { version = "0.3", features = ["async_closure"] }
2022
tokio = { version = "1", default-features = false, features = ["macros", "rt", "time"] }
2123
wiremock = "0.6"

crates/mergify-queue/src/lib.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
//! Native Rust implementation of the `mergify queue` subcommands.
22
//!
3-
//! Phase 1.5 ports `pause` and `unpause` — two idempotent API
4-
//! calls that rest on the HTTP client added in 1.2b and the new
3+
//! Phase 1.5 ported `pause` and `unpause` — two idempotent API
4+
//! calls that rest on the HTTP client added in 1.2b and the
55
//! `put`/`delete_if_exists` methods added alongside this crate.
6-
//! `queue status` and `queue show` stay shimmed until their
7-
//! JSON-output contracts are locked (they carry considerable
8-
//! structured data and want careful schema work).
6+
//! Phase 1.7 ports `status`, the read-only command that fetches
7+
//! the merge-queue snapshot and renders it either as a JSON
8+
//! passthrough or as the human-friendly batch tree + waiting list.
9+
//! `queue show` stays shimmed until its conditions/checks tree
10+
//! ports next.
911
1012
pub mod auth;
1113
pub mod pause;
14+
pub mod status;
1215
pub mod unpause;

0 commit comments

Comments
 (0)