Skip to content

feat(glab): add GitLab CLI (glab) command support#314

Open
tnucera wants to merge 1 commit intortk-ai:developfrom
tnucera:feat/glab-support
Open

feat(glab): add GitLab CLI (glab) command support#314
tnucera wants to merge 1 commit intortk-ai:developfrom
tnucera:feat/glab-support

Conversation

@tnucera
Copy link
Copy Markdown

@tnucera tnucera commented Mar 4, 2026

Summary

Adds token-optimized output for glab (GitLab CLI) commands, mirroring gh_cmd.rs patterns adapted for GitLab-specific differences. Integrated into the post-#826 module structure (src/cmds/git/).

Subcommands: mr (list/view/create/merge/approve/diff/note/update), issue (list/view), ci (list/status/trace), release (list/view), api (passthrough). Unhandled subcommands forwarded to native glab.

glab vs gh adaptations

Aspect gh glab
Notation #42 !42
States OPEN/MERGED/CLOSED opened/merged/closed
Author author.login author.username
URL field url web_url
Body field body description
Merge check mergeable merge_status
CI status statusCheckRollup head_pipeline.status

Token savings

Command Strategy Savings
glab mr list JSON -> compact text with state icons up to 96%
glab mr view JSON -> essential fields + filtered markdown up to 93%
glab issue list JSON -> compact text with state icons up to 80%
glab issue view JSON -> essential fields + filtered markdown up to 80%
glab ci list JSON -> status icons + ref up to 82%
glab ci status Text keyword parsing with raw fallback up to 82%
glab ci trace Strip ANSI, section markers, runner boilerplate 40-60%
glab release list Tab parser (old glab) + raw fallback (glab 1.82+) 20-25%
glab release view Strip SOURCES block, image lines, markdown noise 20-60%
glab api, glab * Passthrough tracked

Savings tested empirically (Apr 2026 smoke tests). Unit tests assert a floor of ≥60% on JSON filters and ≥20-30% on text filters.

Key design choices

  • runner::run_filtered + RunOptions (mirrors gh_cmd.rs) via a local run_glab_json<F>() helper — unified exit-code propagation, tracking, and JSON-fallback-to-raw
  • resolved_command("glab") everywhere (Windows .cmd wrapper compat, mirrors gh_cmd.rs)
  • has_output_flag() guard at top of run() — passthrough when user passes -F/--output/--json
  • should_passthrough_view() for --web, --comments on mr/issue view
  • Unified mr_action() for merge/approve/note/update with extract_identifier_and_extra_args (handles glab mr note -m "msg" 42)
  • -R/--repo and -g/--group declared at clap level, appended to args (not prepended — avoids breaking subcommand dispatch)
  • Non-JSON fallback: when glab returns plain text (e.g. empty results), prints raw instead of failing
  • ci status: text-based parsing (glab doesn't support -F json for this subcommand), with raw fallback for unrecognized output
  • ci trace: strip ANSI codes, GitLab CI section markers, runner/git/artifact boilerplate; preserve build output and errors
  • release view: strip SOURCES archive URLs, image lines, HTML comments, horizontal rules; collapse blank lines
  • glab api: passthrough by design — API responses vary and filtering would destroy values
  • Display: plain text tags ([open]/[merged]/[closed]), functional icons only, aligned with fix: remove decorative emojis from CLI output (#511) #558
  • Markdown filtering copied into glab_cmd.rs (shared-module refactor deferred to a separate PR)

Files changed

  • src/cmds/git/glab_cmd.rs (new) — all handlers + 54 unit tests (auto-discovered via automod::dir!)
  • src/main.rsCommands::Glab variant with -R/-g flags, routing, is_operational_command
  • src/discover/rules.rs — glab rewrite pattern + RtkRule
  • src/discover/registry.rs"GitLab" => 200 in category_avg_tokens + 5 classification/rewrite tests
  • tests/fixtures/glab_mr_list_raw.json, glab_issue_list_raw.json, glab_release_list_raw.txt, glab_release_view_raw.txt, glab_ci_trace_raw.txt

Closes #851, closes #1085. Partially addresses #122.

Test plan

  • cargo fmt --all --check — clean
  • cargo clippy --all-targets — 0 new warnings
  • cargo test --all — 1665 passed, 6 ignored
  • Manual smoke tests against a live GitLab instance (2 projects, with -R cross-project targeting)
  • Tested: mr list/view/diff, issue list, ci list/status, release list/view, api passthrough, ultra-compact mode, -F json passthrough guard, empty results fallback, 404 error propagation

@tnucera tnucera force-pushed the feat/glab-support branch 2 times, most recently from 861d24d to fab7a76 Compare March 4, 2026 13:53
@tnucera
Copy link
Copy Markdown
Author

tnucera commented Mar 4, 2026

Manual smoke test results

Tested against a live GitLab instance with multiple projects.

Without -R (local repo context)

Command Result Notes
rtk glab mr list Compact table with state icons
rtk glab mr view <N> JSON extraction + markdown filtering
rtk glab mr diff <N> Diff compaction via git::compact_diff
rtk glab ci status Current branch pipeline status
rtk glab ci list 10 pipelines with status icons
rtk glab ci view Passthrough (interactive TUI, cannot capture)
rtk glab pipeline list Alias for ci list, works correctly
rtk glab issue list Graceful fallback when no issues (plain text)
rtk glab api /projects/<id> JSON schema filtering
rtk glab repo view Passthrough for unknown subcommands

With -R owner/repo (cross-project)

Command Result Notes
rtk glab -R owner/repo mr list Correct project targeting
rtk glab -R owner/repo mr view <N> Correct MR from target project
rtk glab -R owner/repo mr diff <N> Diff from target project
rtk glab -R owner/repo ci list Pipelines from target project
rtk glab -R owner/repo ci status Correct error when no pipeline on branch
rtk glab -R owner/repo issue list Issues from target project
rtk glab -R owner/repo issue view <N> Full issue details from target project

With -g group (cross-group)

Command Result Notes
rtk glab -g group mr list MRs from all projects in group (20+, cap applied)
rtk glab -g group issue list Issues from all projects in group (plain text fallback)

Action commands (tested on a dedicated test project)

Command Result Notes
rtk glab mr create --source-branch ... --title ... ok created !2 <url> — MR created (verified with mr view)
rtk glab mr note <N> -m "..." ok noted !1 — comment added
rtk glab mr update <N> --title "..." ok updated !1 — title changed (verified with mr view)
rtk glab mr approve <N> 401 self-approve restriction propagated correctly (GitLab limitation)
rtk glab mr merge <N> --yes ok merged !1 — MR merged (verified with mr view → state: merged)

Edge cases — empty project (0 MRs, 0 issues, 0 pipelines)

Command Result Notes
rtk glab -R owner/repo mr list Header only, no crash
rtk glab -R owner/repo issue list Graceful plain text fallback
rtk glab -R owner/repo ci list Header only, no crash
rtk glab -R owner/repo mr view 1 glab 404 error propagated, exit code 1

Hook rewrite tests (rtk-rewrite.sh)

Input command Rewritten to Result
glab mr list rtk glab mr list
glab ci status rtk glab ci status
glab issue view 42 rtk glab issue view 42
glab pipeline list rtk glab pipeline list
glab api /projects rtk glab api /projects
glab auth login (not rewritten)
glab repo clone foo (not rewritten)

Token savings observed

Command Savings
glab mr list 49-99% (varies with result count)
glab mr view 77-94%
glab mr diff 36-98% (varies with diff size)
glab ci list 76-95%
glab api ~86%

Bugs found and fixed during testing

  1. Non-JSON fallback: glab returns plain text (not JSON) when there are no results (e.g. empty issue list). Added graceful fallback in all JSON-parsing handlers.
  2. -R flag not parsed by clap: -R/--repo and -g/--group were not declared in the Commands::Glab clap definition, causing the command to fall through to run_fallback. Fixed by adding them as explicit #[arg] fields (same pattern as git -C).
  3. mr_view/issue_view not forwarding extra args: Functions that extract a number from args[0] were not passing remaining args (including -R) to glab. Added the missing for arg in &args[1..] loop.
  4. ci view TUI crash: glab ci view launches an interactive TUI (tcell/tview) that panics when stdout is captured. Moved to passthrough (inherited stdio).
  5. mr_action wrong subcommand: mr note and mr update passed the confirmation label ("noted", "updated") as the glab subcommand instead of the actual command ("note", "update"). Split into separate subcmd and label parameters.

@tnucera tnucera force-pushed the feat/glab-support branch 2 times, most recently from eb8302d to 74194ba Compare March 4, 2026 14:20
@FlorianBruniaux
Copy link
Copy Markdown
Collaborator

Hi @tnucera, good structural work. Hook rule, registry entry, and CHANGELOG are all present, making this the most complete of the CLI integration PRs on that front. Two things to address:

  1. discover/registry.rs declares savings_pct: 82.0 and subcmd savings for mr (87%), issue (80%), but there are no count_tokens() assertions in glab_cmd.rs verifying these numbers. Please add tests with JSON fixture inputs for at least mr_list and issue_list.

  2. Throughout glab_cmd.rs, output is written to stdout piece-by-piece inside the formatting loop (print!("{}", line)) while also being accumulated into filtered for tracking. Building the full string first and printing once at the end (the pattern used in gh_cmd.rs) is cleaner and avoids the two representations diverging.

Missing tee integration is also worth noting, though it's not a blocker for a first merge.

@pszymkowiak
Copy link
Copy Markdown
Collaborator

Note: this PR adds rules to hooks/rtk-rewrite.sh, but since #241 (rtk rewrite), all rewrite logic lives in Rust (src/discover/rules.rs + src/discover/registry.rs). The bash hook now just calls rtk rewrite "$cmd".

Please remove the changes to rtk-rewrite.sh and add your glab patterns to rules.rs / registry.rs instead.

@tnucera tnucera force-pushed the feat/glab-support branch from 74194ba to 7fe56d5 Compare March 6, 2026 16:06
@tnucera
Copy link
Copy Markdown
Author

tnucera commented Mar 6, 2026

Review feedback addressed

Rebased on upstream/master (includes PR #241 rtk rewrite) and addressed both reviews:

@FlorianBruniaux

  1. count_tokens() tests with JSON fixtures — Added 7 new tests in glab_cmd.rs:

    • test_mr_list_token_savings / test_issue_list_token_savings — verify ≥60% savings with realistic 10-item JSON fixtures
    • test_mr_list_format / test_issue_list_format — verify output contains expected icons and IDs
    • test_mr_list_ultra_compact — verify compact mode
    • test_format_mr_list_invalid_json / test_format_issue_list_invalid_json — edge cases
    • Fixtures: tests/fixtures/glab_mr_list_raw.json and tests/fixtures/glab_issue_list_raw.json
  2. Refactored output building — All handlers (mr_list, mr_view, issue_list, issue_view, ci_list, ci_status) now build the full filtered string first, then print!("{}", filtered) once at the end. Extracted format_mr_list_from_json() and format_issue_list_from_json() as pure functions (no I/O) for testability.

@pszymkowiak

  1. Removed hooks/rtk-rewrite.sh changes — Accepted upstream version (thin delegator from PR feat: rtk rewrite — single source of truth for LLM hook rewrites #241).
  2. Added glab patterns to rules.rs — Pattern r"^glab\s+(mr|issue|ci|pipeline|api)" + RtkRule with rewrite_prefixes: &["glab"], savings 82% (mr: 87%, ci: 82%, issue: 80%).
  3. Updated registry.rs — Added "GitLab" => 200 in category_avg_tokens + 6 tests (test_classify_glab_mr, test_classify_glab_ci, test_classify_glab_issue, test_classify_glab_pipeline, test_rewrite_glab_mr_list, test_rewrite_glab_ci_status).

Quality gates

cargo fmt --all --check  ✅
cargo clippy --all-targets  ✅ (0 new warnings)
cargo test --all  ✅ 739 passed, 2 ignored

Live smoke tests (private GitLab instance)

Command Result
rtk glab mr list ✅ Compact MR table with state icons
rtk glab mr view <N> ✅ JSON extraction + markdown filtering
rtk glab mr diff <N> ✅ Diff compaction
rtk glab issue list ✅ Graceful fallback (0 issues)
rtk glab ci list ✅ Header only (0 pipelines)
rtk rewrite "glab mr list"rtk glab mr list ✅ rules.rs integration works

@tnucera tnucera force-pushed the feat/glab-support branch 3 times, most recently from 0153790 to a4dfe04 Compare March 6, 2026 17:31
@tnucera
Copy link
Copy Markdown
Author

tnucera commented Mar 6, 2026

Polish pass on glab_cmd.rs (post-audit, same commit)

Two fixes to align with gh_cmd.rs patterns:

  1. run_api → passthrough: API responses are now passed through unchanged (like gh_cmd.rs:1204-1209). Converting JSON to a schema destroyed values and forced Claude to re-fetch — defeating the purpose. Removed json_cmd import.

  2. ci_status fallback: Replaced raw.clone() with early return printing &raw directly — avoids a wasteful allocation on unrecognized formats.

Net: -33 lines, 739 tests passing.

@tnucera tnucera force-pushed the feat/glab-support branch 2 times, most recently from c8c8aad to 64bc295 Compare March 18, 2026 14:34
@tnucera
Copy link
Copy Markdown
Author

tnucera commented Mar 18, 2026

Hey! Quick update on this PR:

  1. Rebased on latest master (0.30.1) — no conflicts
  2. Aligned with PR fix: remove decorative emojis from CLI output (#511) #558 conventions — all decorative emojis removed ([open]/[merged]/[closed] instead of 🟢/🟣/🔴, plain text headers, etc.). Functional icons (✅/❌) preserved.
  3. PR description updated to reflect the current state — please re-read it for the full picture

All quality gates pass locally: cargo fmt clean, cargo clippy 0 new warnings, cargo test 986 passed. Tested manually against a live GitLab instance.

@FlorianBruniaux @pszymkowiak Is there anything else needed to get this merged? Happy to address any feedback.

@jbdelhommeau
Copy link
Copy Markdown

We need it ! Thank you @tnucera

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 20, 2026

CLA assistant check
All committers have signed the CLA.

@pszymkowiak
Copy link
Copy Markdown
Collaborator

Hi! Two things needed before we can review:

  1. Retarget to develop — this PR targets master, but all PRs should target develop. You can change the base branch in the PR settings (right sidebar).
  2. Sign the CLA — if not already done, please sign at https://cla-assistant.io/rtk-ai/rtk

Thanks!

@aeppling
Copy link
Copy Markdown
Contributor

Hey

We are cleaning up the codebase and improving the project structure for better onboarding. As part of this effort, PR #826 reorganizes src/ from a flat layout into subfolders.

No logic changes — only file moves and import path updates.

What you need to do

Rebase your branch on develop when receiving this comment:

git fetch origin && git rebase origin/develop

Git detects renames automatically. If you get import conflicts, update the paths:

use crate::git;        // now: use crate::cmds::git::git;
use crate::tracking;   // now: use crate::core::tracking;
use crate::config;     // now: use crate::core::config;
use crate::init;       // now: use crate::hooks::init;
use crate::gain;       // now: use crate::analytics::gain;

Need help rebasing? Tag @aeppling

@tnucera tnucera changed the base branch from master to develop March 27, 2026 08:25
@tnucera tnucera force-pushed the feat/glab-support branch from 64bc295 to f4d9867 Compare March 27, 2026 09:02
@tnucera
Copy link
Copy Markdown
Author

tnucera commented Mar 27, 2026

Rebased on develop (post-refactoring #826) and addressed all review feedback:

  • Retargeted to develop (@pszymkowiak)
  • Removed rtk-rewrite.sh changes — glab patterns added to rules.rs / registry.rs instead (@pszymkowiak)
  • count_tokens() tests added with JSON fixtures for mr_list and issue_list (>=60% savings verified) (@FlorianBruniaux)
  • Output build refactored — all handlers build full string first, print once (@FlorianBruniaux)
  • Adapted to feat(refacto-codebase-onboarding): partie 1 - folders and technical docs #826 module structureglab_cmd.rs lives in src/cmds/git/ (@aeppling)
  • Code review fixes: resolved_command() for Windows compat, has_output_flag() guard, should_passthrough_view() for --web/--comments, unified mr_action with proper identifier extraction, consistent from_str JSON parsing
  • CLA signed

Quality gates: cargo fmt clean, cargo clippy 0 new warnings, cargo test 1161 passed.
Tested against a live GitLab instance with -R cross-project targeting.

@tnucera tnucera force-pushed the feat/glab-support branch 2 times, most recently from d2e028f to ff9bf9d Compare March 27, 2026 10:22
@tnucera tnucera force-pushed the feat/glab-support branch 2 times, most recently from da25fde to 80e950b Compare March 27, 2026 10:44
@tnucera tnucera force-pushed the feat/glab-support branch from 80e950b to a3aa5a8 Compare April 20, 2026 08:40
@tnucera
Copy link
Copy Markdown
Author

tnucera commented Apr 20, 2026

Hi @pszymkowiak, @FlorianBruniaux, @aeppling 👋

Just rebased this PR on latest develop — it was conflicting after the big refactors that landed recently (automod::dir!, Result<()>Result<i32> exit-code contract, PATTERNS/RULES merge in discover/rules.rs). All three conflicts are resolved and glab_cmd.rs has been migrated to the new exit-code contract.

Current status:

  • cargo fmt --all --check clean
  • cargo clippy --all-targets — 0 new warnings
  • cargo test --all — 1657 passed, 6 ignored, 0 failed
  • ✅ End-to-end smoke tests against a live GitLab instance covering mr createviewnoteupdatediffmerge — all handlers working, 76–96% token savings on read operations

What's missing to get this merged? 🙏

Add token-optimized filters for glab CLI commands:
- mr list/view/create/merge/approve/diff/note/update
- issue list/view
- ci list/status/trace (strip ANSI, section markers, runner boilerplate)
- release list/view (strip SOURCES block, markdown noise)
- api passthrough
- Markdown body filtering (HTML comments, badges, blank lines)
- Graceful JSON/plain-text fallback for empty results
- -R/--repo and -g/--group flag forwarding at clap level
- resolved_command() for Windows compat
- has_output_flag() / should_passthrough_view() guards
- Rewrite rules and classify/rewrite tests for rtk discover

Adapted to post-refactoring src/cmds/git/ module structure.

Token savings: mr 87%, ci 82%, issue 80% (avg 82%)

Closes rtk-ai#851
@tnucera tnucera force-pushed the feat/glab-support branch from a3aa5a8 to 4ea78b0 Compare April 20, 2026 09:34
@tnucera
Copy link
Copy Markdown
Author

tnucera commented Apr 20, 2026

Quick follow-up to tighten things before review:

  • Refactored glab_cmd.rs to use runner::run_filtered + RunOptions + a local run_glab_json<F> helper, matching gh_cmd.rs architecture (net -221 LOC, unified tracking and exit-code flow)
  • Added 8 edge-case tests (null nested fields, empty JSON arrays for mr/issue/ci, missing description, non-English CI trace fallback, no-SOURCES release view) — 54 unit tests total
  • Fixed a latent bug in ci status fallback: now correctly preserves raw output when no English status keyword is recognized (previously the raw was silently replaced by a " "-prefixed version)
  • Updated PR description: corrected test counts (1665 passed, 54 unit tests) and file list post-rebase

cargo fmt --all && cargo clippy --all-targets && cargo test --all all clean. Smoke tested end-to-end against a live GitLab instance (full mr create → view → note → update → diff → merge cycle). Ready for review 🙏

@aeppling
Copy link
Copy Markdown
Contributor

aeppling commented Apr 20, 2026

Hey @tnucera , thanks for following our guidelines and codebase architecture updates.

Here is a review on filter quality, to ensure no important signal lost by aggressive filtering.
This is a main concern for us because loss of signal = retry = more token sent

Could you please verify those to ensure we do not integrate invalid filter ?

Filters quality

mr_view drops source/target branch info

The format_mr_view output has: state, title, author, merge_status, pipeline, URL, description. But it omits source_branch/target_branch. An LLM working on a merge workflow needs to know which branch is being merged into which. The gh_cmd.rs format_pr_view similarly doesn't show branches, but GitHub's PR model makes this less critical (the URL encodes it). For GitLab MRs, the branch info is often the most actionable signal.

mr_view drops labels and reviewers

Labels (e.g., bug, enhancement, blocked) and reviewer assignments are decision-relevant for an LLM asked to review/merge/triage MRs. The format_mr_list also drops labels — less critical there since it's a summary, but mr view is the detailed view.

Suggestions

pipeline_icon uses emoji for non-ultra-compact mode

"✅" and "❌" are multi-byte characters. The gh_cmd.rs may uses text tags [pass]/[fail] for non-compact mode.

Tech docs may be interesting for others contributors

This is a new integration and some technical documentation in the cmds/git/README.md could be interesting for futur contributors.
If you could check against others components of cmds/git/ if anything should be documented to explain any important architecture specifications, this could help futur contributors on this !

Thanks

For contributing to RKT and adding more coverage !

Others maintainers already reviewed, awaiting for their approvals since i've only reviewed on the surface :)
Should be released soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants