6.1 KiB · text History d1406da

gg — command-line client

Speaks /api/mobile/v1, the same surface the Android app in ../android uses. The two are kept in step deliberately: a capability that exists on the phone exists here, and a field that changes shape breaks both at once rather than drifting apart.

cargo build --release   # target/release/gg
cargo test

Signing in

Mint a token on the web under Settings → API tokens with the Mobile app bundle ticked, then either paste the pairing URI the QR encodes:

gg login 'gitgud://pair?base=https://forge.example.com&token=ggp_…'

or give the two fields:

gg login --url https://forge.example.com        # prompts for the token, unechoed
gg login --url https://forge.example.com --token-stdin < token.txt

Credentials land in ~/.config/gitgud/config.toml, mode 0600. GITGUD_URL and GITGUD_TOKEN override the file; --url and --token override both.

Which repository

Commands that need one take --repo owner/name. Without it, the git remote of the current directory is used when it points at this forge — so inside a checkout, gg ci list just works. gg login --default-repo owner/name sets a fallback for everywhere else.

Commands

gg whoami                        who the token belongs to, and its scopes
gg repos [--sort pushed|name|ci] repositories this token can reach
gg repo [owner/name]             counts, permissions, latest run

gg ci list [--limit N]           recent workflow runs
gg ci show [N] [--steps]         one run: jobs, steps, artifacts
gg ci logs [N] [--job JOB]       a job's logs, grouped by step
gg ci rerun N [--job JOB]        re-queue a run, or one job of it
gg ci cancel N                   cancel a queued or running run
gg ci watch [N]                  poll until it settles; exits non-zero on failure
gg ci workflows                  workflows that can be run by hand, with their inputs
gg ci dispatch WORKFLOW [--ref REF] [-f NAME=VALUE …] [--watch]
                                 run one (workflow_dispatch); prints the new run

gg issues list [--state open|closed|all] [--search TEXT]
gg issue show N [--no-comments]
gg issue comment N --body TEXT   (or --body-file PATH, or piped stdin)
gg issue close|reopen N

gg pulls list [--state open|closed|merged|all]
gg pull show N
gg pull comment N --body TEXT
gg pull review N --state approve|request-changes|comment [--body TEXT]
gg pull merge N [--message TEXT] [--override-rules]
gg pull approve-runs N           let a fork's waiting workflow runs start
gg pull reject-runs N            cancel them instead

gg wiki list | gg wiki show SLUG
gg packages list | gg package show FORMAT NAME | gg package usage FORMAT

gg dashboard [--refresh SECS]    interactive CI dashboard

--json on any command prints the server’s response verbatim instead of a table, which is the intended way to script against it. (gg ci dispatch --json prints the new run and doesn’t watch it.)

CI statuses

Statuses read as the Android app’s do. Besides queued, running, blocked, success, failure, cancelled and skipped:

mark status what
… waiting held by its concurrency group
⚑ needs approval a fork pull request’s run, waiting for someone with write access (gg pull approve-runs N)
⊘ superseded cancelled because a newer run or job in its concurrency group replaced it — dim, not red

gg ci show lists why each job is waiting, skipped or cancelled, a re-run job’s attempt, and says when a dynamic matrix is still a placeholder; such a job is replaced by its legs (new ids) when it expands, so gg ci logs on one that’s gone says so. gg ci watch keeps watching anything that hasn’t finished — including a run waiting for approval, which it points out once. gg pull show lists a fork’s runs waiting for approval.

gg ci dispatch takes inputs as strings, true/false for a boolean; the server checks them against what the workflow declares and the CLI prints its reason per input when it refuses them.

Dashboard

gg dashboard opens runs, jobs and logs over one repository.

j/k, ↑/↓   move          Enter  open the run / the job's logs
Tab        switch pane   l      logs for the selected job
r          refresh now   Esc    back a level
R          re-run        C      cancel
q          quit

Write actions are capitalized so neither is a neighbour of a navigation key — re-running someone else’s release because you reached for r is not a recoverable mistake.

Packaging

CI builds a .deb and an .rpm on every push to master and publishes both to this forge’s own registries — gg installs from the same place the server package does:

# Debian / Ubuntu (trixie, forky)
sudo apt install gg

# Fedora / RHEL / openSUSE
sudo dnf install gg

The workflow prints the repository lines to add first; see the cli job in .forgejo/workflows/build.yml.

Both packages come from metadata in Cargo.toml, and both build by hand — a packaging bug should be reproducible without pushing a commit:

cargo deb            # target/debian/gg_<version>_<arch>.deb
cargo generate-rpm   # target/generate-rpm/gg-<version>-<release>.<arch>.rpm

cli/Dockerfile is what CI drives. It compiles the crate twice, once on Debian trixie and once on EL9, because a Rust binary is linked against the glibc of whatever built it — one binary packaged for both families would install cleanly and then die with version GLIBC_2.41 not found. It also runs cargo fmt --check, cargo clippy -D warnings and cargo test on the way past, so a failing tree produces no package.

docker buildx build --target packages \
  --output type=local,dest=./cli-dist --file cli/Dockerfile .

The build context is the repository root, not cli/: the copyright file in both packages is the repo’s LICENSE.md.

Layout

path what
src/cli.rs the clap command tree
src/api/ HTTP client and the wire types, mirroring the server’s JSON module
src/commands/ one module per noun, plus repo inference and body input
src/output.rs colour, the CI status vocabulary, tables, relative times
src/tui/ the dashboard