Skip to main content

Crate vcs_forge

Crate vcs_forge 

Source
Expand description

vcs-forge — one PR/MR lifecycle across GitHub, GitLab, and Gitea.

A forge-agnostic facade over the vcs-github, vcs-gitlab, and vcs-gitea wrappers: it dispatches the common forge operations (auth, repo view, the PR/MR lifecycle, issues, releases) to whichever CLI backs the handle and parses each one’s output into forge-agnostic DTOs (ForgePr, ForgeIssue, ForgeRelease, ForgeRepo, …) — so a tool can target the forge instead of one specifically. It is the gh/glab/tea analogue of how vcs-core’s Repo sits over git and jj.

Unlike a repository, a forge has no filesystem marker (.git/.jj) to detect — it’s identified by the remote host — so a Forge is constructed explicitly (Forge::github / Forge::gitlab / Forge::gitea), optionally guided by ForgeKind::from_remote_url applied to a remote URL the caller already holds.

§The surface

  • Forge — the cwd-bound, forge-agnostic handle. Operations run against the bound directory (cwd); the CLI infers the repository from that directory’s git remote. Forge::github / gitlab / gitea build over the real job-backed runner; at re-binds the cwd, sharing the client; kind reports which forge drives it.
  • ForgeApi — the object-safe trait the common surface lives on. Hold a Box<dyn ForgeApi> / &dyn ForgeApi to code against the operations without naming the ProcessRunner generic. Every method mirrors the like-named inherent method on Forge; the trait adds nothing but the &dyn boundary.
  • ForgeKindGitHub / GitLab / Gitea. Its pure, best-effort from_remote_url classifies the public SaaS hosts (github.com, gitlab.com, gitea.com, codeberg.org, and proper subdomains) with an anchored match — a lookalike like gitlab.com.attacker.net and a self-hosted instance on an arbitrary domain both return None (pick the kind yourself).
  • Unified DTOsForgePr (+ ForgePrState), ForgeIssue (+ ForgeIssueState), ForgeRelease, ForgeRepo, CiStatus; the inputs PrCreate (open-a-PR spec: new(title, body) then .source(branch) / .target(branch), defaulting to the current branch and repo default) and MergeStrategy (Merge / Squash / Rebase). Each normalises the three CLIs’ shapes — e.g. GitLab’s iid becomes number, and OPEN / opened / open all read as one state. Some fields are best-effort: draft, and the body/url not present on lean list output.
  • Operation groups — auth (auth_status); the repo (repo_view); the PR/MR lifecycle (pr_list / pr_view / pr_create / pr_merge / pr_mark_ready / pr_close / pr_checks); issues (issue_list / issue_view / issue_create); releases (release_list / release_view). List ops cap at 100 — drop to the wrapped client for more.
  • Capability gapstea has no current-repo view, draft toggle, checks command, or single-release view, so on a Gitea handle repo_view, pr_mark_ready, pr_checks, and release_view return Error::Unsupported without spawning. Classify it with Error::is_unsupported.

The wrappers are re-exported (vcs_forge::vcs_github / vcs_gitlab / vcs_gitea) so anything beyond the portable intersection — a forge-specific op, or one the facade marks Unsupported — is one constructor away without a new dependency.

§Recipes

Read the open PRs — depend on the trait so the same code takes a real handle or a test double:

use vcs_forge::{Forge, ForgeApi};
let forge = Forge::github("."); // or ::gitlab(".") / ::gitea(".")
for pr in forge.pr_list().await? {
    println!("#{} [{:?}] {}", pr.number, pr.state, pr.title);
}

Open a PR/MR with PrCreate — the facade maps source/target to each CLI’s own flags, and returns the CLI’s success output (a URL on GitHub/GitLab):

use vcs_forge::{Forge, ForgeApi, PrCreate};
let spec = PrCreate::new("Add widget", "Closes #12").source("feature");
let out = forge.pr_create(spec).await?;

§Testing

The facade trait has no mock featuremockall can’t process the macro-generated ForgeApi signatures. Test the real dispatch instead: build a Forge over an explicit client wrapping a fake runner — e.g. Forge::for_github(cwd, GitHub::with_runner(ScriptedRunner::new())) (likewise for_gitlab / for_gitea) — and script the canned CLI output, exercising the argv-building and DTO parsing end to end. The cross-cutting testing patterns live in vcs-testkit’s guide.

§In-depth guide

Beyond this page, this crate ships a full how-to guide — rendered on docs.rs from docs/. See the guide module.

Re-exports§

pub use vcs_gitea;
pub use vcs_github;
pub use vcs_gitlab;

Modules§

guide
vcs-forge — the forge facade

Structs§

CancellationTokencancellation
A token which can be used to signal a cancellation request to one or more tasks.
Forge
A cwd-bound, forge-agnostic handle. Operations run against the bound directory (cwd); the CLI infers the repository from that directory’s git remote. Use at for a sibling handle bound elsewhere.
ForgeIssue
An issue, unified across the three forges.
ForgePr
A pull request (GitHub) / merge request (GitLab) / pull request (Gitea), unified across the three forges.
ForgeRelease
A release, unified across the three forges. (Gitea’s tea always lists — it has no single-release view — so release_view is Unsupported there.)
ForgeRepo
A repository (GitHub) / project (GitLab), unified. (Gitea’s tea has no current-repo view, so repo_view is Unsupported there.)
PrCreate
Options for pr_create — the unified open-a-PR/MR spec, mapped to each CLI’s own flags (gh --head/--base, glab --source-branch/--target-branch, tea --head/--base).

Enums§

CiStatus
The coarse CI status for a PR/MR, bucketed into the four states a caller acts on. GitHub aggregates its per-check buckets into this; GitLab maps its pipeline status; Gitea’s tea has no checks command, so pr_checks is Unsupported there.
Error
An error from a Forge operation.
ForgeIssueState
The normalised state of a ForgeIssue, unifying GitHub’s OPEN/CLOSED, GitLab’s opened/closed, and Gitea’s open/closed. An unknown state reads as Open — a state we don’t model is treated as live, never silently as resolved.
ForgeKind
Which forge backs a Forge handle.
ForgePrState
The normalised state of a ForgePr, unifying GitHub’s OPEN/CLOSED/ MERGED, GitLab’s opened/closed/locked/merged, and Gitea’s open/closed (+ a merged flag).
MergeStrategy
How pr_merge merges — mapped to each CLI’s own merge-strategy flag.

Traits§

ForgeApi
The forge-agnostic common surface of Forge, as a trait — so a consumer can hold a Box<dyn ForgeApi> / &dyn ForgeApi and code against the operations without naming the ProcessRunner generic.

Type Aliases§

Result
Result specialised to the facade Error.