Expand description
§vcs-gitlab — GitLab CLI guide
vcs-gitlab drives the GitLab CLI (glab) from Rust. Every operation is async,
runs inside an OS job (via processkit) so a glab subprocess is never
orphaned, and returns the structured processkit::Error instead of a stringly
exit. Commands ask for --output json and are deserialized into typed structs
(GitLab’s REST JSON, which glab passes through); the crate never scrapes
human-readable output.
The surface is the lean merge-request lifecycle — it mirrors vcs-github’s
shape but covers only auth, project view, and the MR lifecycle. The
vcs-forge facade unifies it with vcs-github and vcs-gitea.
Consumers code against the GitLabApi trait and substitute a fake in tests —
the real GitLab client only appears at the edges. See
Testing & mocking for the two seams (the mock feature →
MockGitLabApi, or a ScriptedRunner).
Requires the glab binary on PATH, authenticated via glab auth login.
⚠️ CLI surface tracks the installed
glab, not a frozen contract. The argv the code builds and the JSON shapes it parses are pinned by the hermetic tests (so a refactor can’t silently change them). The#[ignore]integration smoke tests additionally check, against the real binary in CI, thatglabintegrates at all —versionandauth_status. The create/merge/close lifecycle argv follows the documentedglabCLI but is not exercised end-to-end in CI (that needs a live, authenticated GitLab); confirm it against your installedglabif a flag ever drifts.
§Construction
use vcs_gitlab::GitLab;
use std::time::Duration;
let glab = GitLab::new(); // real job-backed runner
let glab = GitLab::new().default_timeout(Duration::from_secs(30)); // kill long runsGitLab::with_runner(runner) injects a fake ProcessRunner for tests.
glab.at(dir) returns a GitLabAt view whose project-scoped methods drop the
leading dir argument.
§Auth & version
let v = glab.version().await?; // String — "glab version 1.x …"
let authed = glab.auth_status().await?; // bool — true when `glab auth status` exits 0auth_status reads the exit code via probe: a timeout or unexpected failure
still surfaces as processkit::Error, never a silent false. Caveat: a
known glab bug can make
glab auth status exit 0 even when unauthenticated, so treat a true as
best-effort (the next API call is the real test); false/timeout are faithful.
§Project & merge requests
| Method | Runs | Returns |
|---|---|---|
repo_view(dir) | glab repo view --output json | Project |
mr_list(dir) | glab mr list --output json | Vec<MergeRequest> |
mr_view(dir, id) | glab mr view <id> --output json | MergeRequest |
mr_create(dir, spec) | glab mr create --title … --description … [--source-branch …] [--target-branch …] --yes | String (the MR URL) |
mr_merge(dir, id, strategy) | glab mr merge <id> --yes --auto-merge=false [--squash|--rebase] | () |
mr_ready(dir, id) | glab mr update <id> --ready | () |
mr_close(dir, id) | glab mr close <id> | () |
mr_checks(dir, id) | glab mr view <id> --output json (reads head_pipeline.status) | CiStatus |
MergeRequest carries iid (the project-scoped id the commands take), title,
state (GitLab’s "opened"/"closed"/"merged"/"locked"), source_branch,
target_branch, web_url, and draft.
for mr in glab.mr_list(repo).await? {
println!("!{} [{}] {} — {}", mr.iid, mr.state, mr.title, mr.web_url);
}
let url = glab
.mr_create(repo, MrCreate::new("Add streaming", "Implements …")
.source("feat/streaming").target("main"))
.await?;
glab.mr_merge(repo, 12, MergeStrategy::Squash).await?;MergeStrategy is Merge (glab’s default merge commit), Squash, or Rebase.
mr_merge passes --auto-merge=false so it merges immediately rather than
enabling glab’s default merge-when-pipeline-succeeds. CiStatus buckets the
pipeline into Passing / Failing / Pending / None.
mr_create takes an MrCreate spec — build it through MrCreate::new(title, body) and chain the optional .source(b) (--source-branch; None = the
current branch) / .target(b) (--target-branch; None = the project default)
setters. Public fields: title: String, body: String, source: Option<String>,
target: Option<String>.
§Issues & releases
| Method | Runs | Returns |
|---|---|---|
issue_list(dir) | glab issue list --per-page 100 --output json | Vec<Issue> |
issue_view(dir, number) | glab issue view <number> --output json | Issue |
issue_create(dir, title, body) | glab issue create --title … --description … --yes | String (the issue URL) |
release_list(dir) | glab release list --per-page 100 --output json | Vec<Release> |
release_view(dir, tag) | glab release view <tag> --output json | Release |
The list methods pin --per-page 100 (the GitLab API per-page max) so glab’s
default page size of 30 can’t silently truncate them; reach beyond 100 through
run. issue_create passes --yes to skip glab’s interactive submission prompt,
mirroring mr_create. release_view’s bare <tag> positional is flag-injection
guarded (a leading - or empty value is refused before any process spawns).
Issue carries number (the project-scoped iid GitLab’s issue commands take,
surfaced as number for cross-forge consistency with vcs-github), title,
state (GitLab’s "opened"/"closed"), body (GitLab’s description), and
url (GitLab’s web_url).
Release carries tag_name (the <tag> release_view takes), name (the
release title, may default to the tag), url (pulled off GitLab’s nested
_links.self — releases have no top-level web_url), and published_at (GitLab’s
released_at, ISO 8601, empty for an unpublished release).
for issue in glab.issue_list(repo).await? {
println!("#{} [{}] {}", issue.number, issue.state, issue.title);
}
let url = glab.issue_create(repo, "Flaky pipeline", "fails on …").await?;
for rel in glab.release_list(repo).await? {
println!("{} — {}", rel.tag_name, rel.url);
}§Escape hatch
run(args) / run_raw(args) (and the inherent run_args(&[&str]) /
run_raw_args) drive any unmodelled glab command; run returns trimmed stdout
and errors on a non-zero exit, run_raw hands back the captured ProcessResult.
§See also
- vcs-forge guide — the facade that unifies this with GitHub/Gitea.
- vcs-github guide — the broader-surfaced sibling this mirrors.
- Testing & mocking — the
mockfeature and theScriptedRunnerseam. - Process model & errors — OS-job containment, timeouts, and
the
Error/ProcessResultshapes. - crate docs — quickstart and crate-level docs.