.gitbay/wiki/Users.org

main
gitbay/.gitbay/wiki/Users.org rendered · source · history · blame · raw

1133 lines · 56241 bytes

22 symbols in this file
   1#+title: gitbay user guide
   2
   3Everything here works from stock OpenSSH — replace =gitbay= with
   4=ssh git@<host>= in any command. The CLI adds convenience (instance
   5profiles, repo inference, =$EDITOR=), nothing more. Two things differ
   6over bare ssh:
   7
   8- ssh joins its arguments with spaces and the server tokenizes the
   9  result, so a value with a space in it needs a second layer of quotes:
  10  =--description "'a widget'"=, not =--description "a widget"=, which
  11  arrives as two arguments.
  12- =--help= is not understood. =ssh git@<host> help= lists every command
  13  the server knows, and =help <noun>= (=help repo=, =help mr=) prints
  14  the usage of each command under that noun.
  15
  16* Installing the CLI
  17
  18#+begin_src sh
  19go install gitbay.org/gitbay/cmd/gitbay@latest   # any platform with Go
  20
  21brew tap krz/tap https://gitbay.org/krz/homebrew-tap.git
  22brew install krz/tap/gitbay                      # Homebrew (macOS/Linux)
  23#+end_src
  24
  25Or build from source: =go build ./cmd/gitbay= in a clone of
  26=https://gitbay.org/krz/gitbay.git=.
  27
  28* Getting an account
  29
  30How you join depends on the instance's registration mode. On instances
  31with web accounts enabled, =/register= offers the same signup as a
  32browser form (paste your SSH public key); everything below works from
  33the terminal alone:
  34
  35- closed :: an admin creates your account on the host and registers your
  36  first SSH key. Nothing for you to do but hand over your public key.
  37- invite :: you receive a single-use code by email. With the SSH key you
  38  want to use:
  39  #+begin_src sh
  40  ssh git@<host> register --username you --invite <code>
  41  #+end_src
  42  Your account is active immediately; the invited address is your
  43  verified email.
  44- open ::
  45  #+begin_src sh
  46  ssh git@<host> register --username you --email you@example.org
  47  #+end_src
  48  A verification code arrives by mail. Until you run
  49  =ssh git@<host> email verify <code>=, the account is pending: you can
  50  run =whoami= and the email commands, and nothing else — no git, no
  51  repos.
  52
  53* SSH keys
  54
  55New to SSH keys? [[SSH-keys]] makes one in two commands.
  56
  57Your key is your identity; there are no passwords anywhere. The SSH
  58username is always =git= — the key alone determines who you are.
  59
  60#+begin_src sh
  61gitbay auth keys list
  62gitbay auth keys add --scope git < ~/.ssh/ci_key.pub   # key on stdin
  63gitbay auth keys add --label laptop < ~/.ssh/id_ed25519.pub
  64gitbay auth keys add --scope git --ttl 90d < ~/.ssh/ci_key.pub
  65gitbay auth keys label SHA256:... "work laptop"
  66gitbay auth keys remove SHA256:...
  67#+end_src
  68
  69A key's label is the comment on its =authorized_keys= line unless
  70=--label= gives one; =keys label= renames a key, and with no text
  71clears the name. Labels are one line of up to 64 bytes.
  72
  73=--ttl 90d= (or any Go duration, =720h=) makes a key stop
  74authenticating after that long; =repo deploy-key add= takes the same
  75flag. An expiring key cannot create credentials: tokens, keys, login
  76links. =keys list= shows when each key was last used and when it
  77expires, so a key nobody uses is easy to spot.
  78
  79Removing a key closes every connection it opened, including the CLI's
  80shared one; removing the key the current command runs on ends that
  81command's connection too.
  82
  83Scopes: =full= (default; git plus every control command), =git= (git
  84transport only — right for automation keys, which then cannot touch
  85issues, settings, or your account), or =runner= (the CI runner's
  86protocol plus read-only git, for the key a =gitbay-runner= host holds;
  87see [[Admin]]).
  88
  89A key belongs to exactly one account instance-wide. Registering a key
  90someone else already holds is refused without telling you whose it is.
  91
  92* Verified commits
  93
  94The commit badge is driven by the *author* email and the signing key:
  95=verified= means the signature is valid, the key is registered to an
  96account, and the author email is a verified address on that account.
  97
  98For OpenPGP signing (git's default):
  99#+begin_src sh
 100gpg --armor --export you@example.org | gitbay auth pgp add
 101#+end_src
 102
 103For SSH signing (=git config gpg.format ssh=): sign with any key
 104registered on your account; your verified addresses act as the principal
 105set. No separate registration step.
 106
 107Add and verify additional addresses with =email add <address>= /
 108=email verify <code>= (requires the instance to have SMTP; otherwise an
 109admin can assert an address for you). =email list= shows them. =email
 110remove <address>= drops one, with two refusals: the primary stays until
 111=email primary <address>= names another verified address, and the last
 112verified address stays, since activation, login links and commit
 113identity all resolve through verified addresses. Removing a verified
 114address re-evaluates signature states, so a commit authored from it
 115shows as =signed_email_mismatch= afterwards. A removed address is free
 116for any account to claim.
 117
 118The states you will see, in decreasing order of trust: =verified=,
 119=signed_unknown_key= (valid signature, key not registered here — register
 120it and history upgrades retroactively), =signed_email_mismatch= (real
 121key, author line claims someone else), =signed_key_expired= /
 122=signed_key_revoked=, =bad_signature=, =unsigned=. Server-created commits
 123(web edits, merge commits) are always =unsigned= — the server holds no
 124signing key on principle.
 125
 126=repo bookmark <owner/name>= saves a repository to come back to and
 127=repo unbookmark= drops it; =repo bookmarks= lists yours, each with how
 128many people have bookmarked it. The web has the control on the
 129repository header, the count in the facts bar, and the list at
 130=/bookmarks=. Bookmarks are public and counted; pins are private and
 131drive the rail. Read access is all a bookmark needs.
 132
 133A job's result is a property of the commit's *tree*, not its sha: a
 134rebase onto a base that touched nothing the branch did gives every
 135commit a new sha and the same tree, and a job that already passed for
 136that tree is not run again — the new commit gets the earlier result as
 137its =ci/<job>= status, naming the build it came from. Scheduled and tag
 138jobs are never reused this way; their trigger is the clock or the tag.
 139
 140* Profiles
 141
 142A profile is what =/{owner}= shows: a one-line description, a website, a
 143set of links, long-form about text, the repositories you can see, org
 144membership, and a year of activity. Users and orgs have the same fields.
 145Repository rows carry the listing metadata too — topics, license,
 146default branch, last commit — so a client renders a profile listing the
 147way =/{owner}= does. The web dispatches this command rather than
 148assembling the page itself.
 149
 150#+begin_src sh
 151gitbay profile show               # your own
 152gitbay profile show alice
 153gitbay profile set --description "builds small tools" --website https://alice.example
 154#+end_src
 155
 156The about text is not a field. It is =profile/README.md= or
 157=profile/README.org= on the default branch of =<owner>/.gitbay=, a
 158repository you own like any other, written by a push or by
 159=repo commit-file=:
 160
 161#+begin_src sh
 162gitbay repo create alice/.gitbay
 163gitbay repo commit-file alice/.gitbay profile/README.org \
 164       --ref main --file - < about.org
 165#+end_src
 166
 167The extension picks the renderer; =.md=, =.org= and =.markdown= are
 168resolved in that order, so a =README.md= beside a =README.org= wins.
 169=profile show= reports the text as =about=, its format as
 170=about_format=, and the file it came from as =about_path=.
 171
 172Access follows the repository: a private =.gitbay= keeps the about text
 173to you and the admins. A repository whose name starts with a dot is
 174infrastructure rather than a project, so it stays out of =explore= and
 175off the profile's repository list — =repo list= still shows it, and it
 176is reachable at its own URL.
 177
 178Up to five links, each =label|url= or a bare url, http(s) only. Passing
 179=--link= replaces the whole set; a single empty one clears it:
 180
 181#+begin_src sh
 182gitbay profile set --link "Mastodon|https://fosstodon.example/@alice" \
 183                   --link https://alice.example/now
 184gitbay profile set --link ""      # clear
 185#+end_src
 186
 187Every field follows the same rule as the rest of the CLI: a flag you
 188leave out is untouched, and ='' clears the one you name. Org profiles
 189work the same way and need org admin:
 190
 191#+begin_src sh
 192gitbay org profile krz --description "software and experiments"
 193gitbay org profile krz            # no flags shows it
 194#+end_src
 195
 196An org's about text works the same way, in =<org>/.gitbay=.
 197
 198The settings page edits description, website and links, and points at the
 199about file — with a button that creates =<owner>/.gitbay= and its first
 200README when you have none, so the file editor has a branch to open. The
 201JSON API runs =profile set= like any other write command.
 202
 203* Repositories
 204
 205#+begin_src sh
 206gitbay repo create you/project [--private]
 207gitbay repo clone you/project
 208gitbay repo list
 209gitbay repo show you/project
 210gitbay repo log you/project --limit 20      # commits with signature states
 211gitbay repo fork other/project [--name mine]
 212gitbay repo rename you/project tool          # clone URLs change
 213gitbay repo delete you/project --yes
 214#+end_src
 215
 216=repo show= also reports your own state on the repository — =watch=
 217(=watching= or =muted=; =repo unwatch= clears either) and =bookmarked= —
 218and =fork_of= when it is a
 219fork whose parent you can read, so a client draws a toggle rather than
 220two blind buttons. Absent means none.
 221
 222On the web, the repository's settings page carries access, webhooks,
 223rename, transfer and delete, and =/new= imports from a remote. Delete and
 224transfer ask for the repository's path to be typed. Large imports belong
 225on the CLI, where progress is visible.
 226
 227Pushing is SSH-only. Public repositories are anonymously readable over
 228HTTPS (and =git://= where enabled); private repositories exist only over
 229SSH and answer "not found" to everyone without access.
 230
 231Access and settings (owner or =admin= grant):
 232#+begin_src sh
 233gitbay repo access grant you/project alice write    # read | write | admin
 234gitbay repo access revoke you/project alice
 235gitbay repo access list you/project                 # everyone who can reach it, role, and via what
 236gitbay repo settings protect you/project main       # no force-push, no delete
 237gitbay repo settings require-mr you/project on      # protected branches: merge requests only
 238gitbay repo settings protect-tag you/project 'v*'   # matching tags: created once, never moved or deleted
 239gitbay repo settings default-branch you/project trunk # HEAD, and what the web shows
 240gitbay repo settings require-signed you/project on  # every commit must verify
 241gitbay repo settings git-daemon you/project on      # expose over git://
 242gitbay repo topics add you/project cli forge        # free-form tags, shown on the web
 243gitbay repo search forge                            # find repos by name/description/topic
 244gitbay repo grep you/project "some string"          # literal git grep over the default branch
 245gitbay repo symbols you/project Dispatch            # where a name is defined, from the symbol index
 246gitbay repo pin you/project                         # pin to your web dashboard
 247gitbay repo unpin you/project
 248gitbay repo archive you/project                     # read-only: pushes and issue/MR
 249gitbay repo unarchive you/project                   #   writes refused, browsing intact
 250#+end_src
 251
 252A push to the default branch queues a symbol index of its tree, built in
 253the background after the push completes; a tree that is already indexed
 254is not indexed again. =repo symbols <owner/name> [--kind k] <query>=
 255lists definitions whose name starts with the query (two characters
 256at least), exact matches
 257first, case-sensitive before case-insensitive, as name, kind, path and
 258line; =--limit= and =--cursor= page it, and without them it stops at
 259200 rows. A Go method is listed as =Type.Method= and also matches on
 260=Method=. Only the default branch is indexed: =--ref= accepts another
 261ref only when its tree is the indexed one. The index follows read
 262access: a repository you cannot read has no symbols either.
 263
 264What is indexed:
 265
 266| language                       | kinds                                                     |
 267|--------------------------------+-----------------------------------------------------------|
 268| Go (parsed)                    | function, method, type, const, var                        |
 269| Swift                          | function, class, struct, enum, interface (protocol), type |
 270| Rust                           | function, struct, enum, interface (trait), type, module, const, macro |
 271| Python                         | function, method, class                                   |
 272| JavaScript, TypeScript         | function, class, interface, type, enum, const             |
 273| C and C++ headers (=.h=, =.hpp=) | function (prototype), struct, enum, class, type, macro  |
 274| shell (=.sh=, =.bash=, =.zsh=) | function                                                  |
 275| org, Markdown                  | section (a heading)                                       |
 276
 277Languages other than Go are matched a line at a time, so unusual
 278definition shapes are missed. Files over 1 MiB, anything under
 279=vendor/= or =node_modules/=, =*_gen.go=, =*.pb.go=, Go files marked
 280=Code generated ... DO NOT EDIT.=, and =*.min.js= are skipped, and a
 281name longer than 256 bytes is dropped. One run stops at 200,000
 282symbols, 32 MiB of names and paths, or two minutes, and publishes what
 283it found as a partial index that says which bound it reached.
 284
 285A new index is written while the previous one stays in use, and
 286replaces it in one step. A run that cannot build an index records why
 287and leaves the previous index current; the same tree is tried again
 288once after an hour, or when a push changes the tree. A repository that
 289has not been pushed to since the index existed has none until its next
 290push to the default branch. =admin symbols reindex <owner/name>=
 291rebuilds one regardless.
 292
 293On the web, a file viewed at the indexed tree (the default branch's
 294head, or any commit with the same tree) links each name the index
 295holds: to its definition when there is one, to the results page at
 296=/{owner}/{repo}/symbols?q=<name>= when there are several. The file's
 297own definitions are listed above its source.
 298
 299Import from another forge — git data first, then optionally the issue
 300and PR history from GitHub or any Forgejo instance such as Codeberg
 301(issues keep state/labels/comments; PRs land as closed or merged MRs
 302with their discussion; originals are attributed inline since foreign
 303authors have no local account; re-running resumes where it stopped):
 304#+begin_src sh
 305gitbay repo import you/mirror --from https://github.com/you/repo.git \
 306    [--private] [--token-stdin]        # token on stdin, never in the URL
 307gitbay repo import-issues you/mirror --from you/repo --token-stdin
 308gitbay repo import-issues you/mirror --from you/repo \
 309    --api-base https://codeberg.org/api/v1    # Forgejo: the site's /api/v1
 310#+end_src
 311
 312=repo import= fetches over http and https only, from an address that
 313passes the same check as a webhook target; a =git://= URL is refused,
 314so use the repository's https URL.
 315
 316Sourcehut has no API of that shape; =repo import= takes its git data
 317and the todo.sr.ht tracker is not read.
 318
 319Moving between gitbay instances (no lock-in): run on the TARGET, with
 320your key registered on both sides. Profile, repos with settings,
 321issues, MRs, and comments replay with attribution; git data mirrors
 322client-side through your own key. Keys never transfer and emails
 323arrive unverified — trust is per-instance. Re-running resumes.
 324Push-blocking policies (require-signed, protected branches) are
 325deferred and printed for you to re-apply after the data lands.
 326=gitbay auth export= alone doubles as a user-level backup.
 327
 328#+begin_src sh
 329gitbay migrate --from old-instance.example [--from-port 22]
 330#+end_src
 331
 332Mirroring keeps a foreign remote in sync during a gradual migration
 333(repo admin; https remotes; the token is stored server-side for the
 334recurring sync and never echoed back):
 335
 336#+begin_src sh
 337gitbay repo mirror add you/project https://github.com/you/project.git \
 338    --direction push --token-stdin    # propagate after every local push
 339gitbay repo mirror add you/copy https://github.com/them/theirs.git \
 340    --direction pull                  # follow upstream; local pushes refused
 341gitbay repo mirror list               # sync status and last error, per mirror
 342gitbay repo mirror sync / remove <id>
 343#+end_src
 344
 345Markdown and org files render on the web when opened, the way a README
 346does on the repository page, with relative links resolved against the
 347file's directory; =source= in the file's action bar (or =?view=source=)
 348shows the text instead. Other files show the text with highlighting.
 349
 350TeX math renders as MathML wherever markdown or org renders: READMEs,
 351files, wiki pages, issue, merge request and release bodies, comments,
 352and their previews. Markdown takes =$…$= inline and =$$…$$= for display,
 353either inline or with the =$$= lines on their own. A =$= followed by a
 354space, or a closing =$= preceded by a space or followed by a digit, is a
 355dollar sign, so =$5 and $10= stays prose; =\$= is always one. Org takes
 356=$…$=, =$$…$$=, =\(…\)=, =\[…\]= and =\begin{…}…\end{…}=. Code spans
 357and blocks are left alone. The supported TeX is a subset (letters,
 358numbers, operators, scripts, =\frac=, =\sqrt=, Greek and common symbols,
 359=\left=/=\right=, accents, =\text=, the =\math…= fonts, matrices,
 360=cases= and =aligned=; the full list heads =internal/texmath/texmath.go=).
 361Anything outside it, including macros, colours and links, shows as
 362source, as does an expression over 8 KiB or nested more than 64 deep,
 363and so does all math in a document after its first 1000 expressions or
 364256 KiB of TeX. A =$$= line opens display math only when a line ending
 365in =$$= follows before a blank line. MathML typed as raw HTML is
 366stripped like any other markup the sanitizer does not allow. The CLI
 367shows the source.
 368
 369* Deleting your account
 370
 371#+begin_src sh
 372gitbay auth delete --confirm <username>   # mails a link to your primary address
 373gitbay auth delete --cancel               # withdraw it before the link is opened
 374#+end_src
 375
 376The same is at the bottom of =/settings=. Nothing changes until the
 377mailed link is opened (it works for 24 hours, and needs a verified
 378address). Opening it disables the account at once and deletes it seven
 379days later: its repositories, snippets, keys, addresses and tokens go.
 380Issues, merge requests, comments and reviews it wrote on other people's
 381repositories stay, under the name =ghost=. Signing in during the seven
 382days — on the web, or any command over SSH with a full-scope key —
 383cancels; git-, read-, runner-scoped keys and API tokens are refused and
 384cannot cancel. The only admin of an organization is refused until
 385another admin exists or the organization is deleted. Export first
 386(=gitbay auth export=) to keep a copy. The username is free again after
 387the purge.
 388
 389* Organizations
 390
 391Orgs share the owner namespace with users and own repositories at
 392=org/repo=. By default members get write on all org repos; org admins
 393get repo admin, create repos under the org, and manage membership. An
 394org's existence and member list are public; its teams are visible to
 395members, and anyone else asking is refused rather than told the org
 396does not exist.
 397
 398#+begin_src sh
 399gitbay org create krz
 400gitbay org members add krz alice [--role admin]
 401gitbay org show krz
 402gitbay org rename krz newname     # clone URLs change
 403gitbay org delete krz --yes       # only when it owns no repositories
 404#+end_src
 405
 406Large orgs scope access with teams: set what plain membership implies,
 407then grant per-repo roles through named teams (org admins always keep
 408admin; the default =write= keeps the simple model):
 409
 410#+begin_src sh
 411gitbay org settings members-role krz none   # write | read | none
 412gitbay org team create krz core-devs
 413gitbay org team add krz core-devs alice bob # org members only
 414gitbay org team grant krz core-devs krz/gitbay write
 415gitbay org team show krz core-devs          # members + grants
 416gitbay org team revoke / remove / delete ...
 417#+end_src
 418
 419* Issues
 420
 421Anyone who can read a repository can file and comment. Closing/reopening
 422is for the author or anyone with write; labels and assignees need write.
 423
 424#+begin_src sh
 425gitbay issue create --title "it breaks" [--body "..." | --file -]
 426    [--label bug]... [--milestone v1.0] [--assignee alice]...  # these need write
 427gitbay issue list [--state open|closed|all]
 428gitbay issue show 4
 429gitbay issue comment 4 --message "same here"
 430gitbay issue react 4 +1 [--comment 12] [--remove]  # the same on mr
 431gitbay issue edit 4 --title "better title" [--body|--file -]  # author or write
 432gitbay issue close 4 / reopen 4
 433gitbay issue label 4 --add bug --remove wontfix
 434gitbay issue assign 4 --add alice
 435gitbay issue milestone 4 v1.0                 # or "none" to clear
 436#+end_src
 437
 438A reaction is one of =+1 -1 laugh hooray confused heart rocket eyes=,
 439given by name or as the emoji itself, on an issue or merge request or on
 440one of its comments (=show= prints comment ids). Anyone who may comment
 441may react; reacting twice or removing an absent reaction does nothing.
 442=show= carries the counts and which are yours. A reaction sends no
 443notification and appears in no activity feed.
 444
 445Inside a clone, the repository is inferred from the =origin= remote —
 446that is why no =owner/name= appears above. Anywhere else, pass it as the
 447first argument. Long text: =--body= inline, =--file -= from stdin, or
 448neither on a terminal and =$EDITOR= opens.
 449
 450A wiki is the =.gitbay/wiki/= directory on the default branch — no
 451separate repository, the same rendering pipeline as READMEs (markdown
 452and org). Editing it is editing a file: a push, =repo commit-file=, or
 453the web editor on a repository that permits server-authored commits (one
 454requiring signed commits is push-only, since the server signs nothing):
 455
 456#+begin_src sh
 457mkdir -p .gitbay/wiki
 458# add Home.md (or .org), more pages, images; relative links between
 459# pages just work on the web at /you/project/wiki
 460git add .gitbay/wiki && git commit -m "wiki" && git push
 461#+end_src
 462
 463Pages may sit in subfolders. A nested page is named by its path —
 464=.gitbay/wiki/Guide/Install.md= is =/you/project/wiki/Guide/Install= and
 465=wiki show you/project Guide/Install= — and the sidebar groups it under
 466its folder. A relative link or image on a nested page resolves from the
 467page's own folder first, then from the top of the wiki, so a link to
 468=Home= still reaches the top-level page. SVG images render.
 469
 470Releases anchor notes and binary assets to a pushed tag (write access;
 471assets stream over SSH, capped by the instance's =max_asset_bytes=).
 472While a release exists its tag can be neither deleted nor moved, whether
 473or not the tag is protected: delete the release first.
 474
 475#+begin_src sh
 476gitbay release create v1.0 --title "First light" [--notes|--file -|$EDITOR]
 477gitbay release asset add v1.0 tool-linux-amd64 < dist/tool-linux-amd64
 478gitbay release asset get v1.0 tool-linux-amd64 > tool   # or the web download link
 479gitbay release list / show v1.0 / delete v1.0 --yes
 480#+end_src
 481
 482The web shows them under the repository's =releases= tab with rendered
 483notes, sha256 sums, and download links; writers add and remove assets
 484there (a file upload, up to =max_asset_bytes=; removal asks for the file
 485name). Feed readers subscribe at
 486=/you/project/releases.atom=; commits are at =/you/project/log.atom=
 487(the default branch) or =/you/project/log.atom/<ref>=, and an owner's
 488activity on their public repositories at =/you/activity.atom=. The
 489pages carry the discovery link.
 490
 491Commit messages act on issues when the commits land on the default
 492branch (direct push, MR merge, =repo commit-file= or the web editor):
 493=closes/fixes/resolves #4= closes the
 494issue with a linking comment, and a bare =#4= leaves a reference
 495comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when
 496you hold write there with a key whose scope reaches it; otherwise it
 497stays a plain link. A deploy key closes nothing outside the repository
 498it is bound to, but naming that repository by full path works as the
 499bare form does. The comment left
 500on the closed issue links the closing commit by its repository path, so
 501closing a public repository's issue from a private one names the private
 502repository there. A bare =owner/name#N= links and does nothing.
 503
 504Milestones group issues and MRs toward a release (write access to
 505manage, attach with =issue milestone= / =mr milestone=; progress shows
 506on the web at =/owner/name/milestones=, where writers create, close and
 507reopen them):
 508
 509#+begin_src sh
 510gitbay milestone create v1.0 --description "first release" --due 2027-01-01
 511gitbay milestone list [--state open|closed|all]
 512gitbay milestone close v1.0 / reopen v1.0
 513#+end_src
 514
 515An org holds labels and milestones every repository under it sees
 516beside its own. =issue label --add=, =mr label --add=, =issue
 517milestone= and =mr milestone= resolve the org's row first; a repository
 518cannot create a label or milestone with a name its org holds. Creating
 519an org label or milestone whose name repositories under the org already
 520use folds them in: their issues and merge requests move to the org's
 521row. Org admins manage them, on the CLI or at =/<org>/-/labels= and
 522=/<org>/-/milestones=; counts span the repositories you can read.
 523
 524#+begin_src sh
 525gitbay org label set acme bug --color cf222e
 526gitbay org label list acme / remove acme bug
 527gitbay org milestone create acme v2 --due 2027-03-01
 528gitbay org milestone list acme [--state open|closed|all]
 529gitbay org milestone close acme v2 / reopen acme v2
 530#+end_src
 531
 532On the web: =/acme/-/labels= and =/acme/-/milestones=, read-only.
 533
 534Issue templates: commit =.gitbay/issue-template.md= (and optional
 535=issue-template-<name>.md= variants) to the default branch. =gitbay
 536issue create= prefills =$EDITOR= with the default template, the web
 537form prefills its textarea, and =gitbay issue templates= lists them.
 538
 539Lists narrow the same way on every surface: =issue list --label bug
 540--assignee bob --author alice --milestone v1= (or =--milestone none=),
 541=mr list --label bug --author bob --milestone v1=; the web's issue and
 542merge request lists take the same names as query parameters, and each
 543active filter shows with a link that drops it.
 544
 545A query spans repositories, and a saved one keeps it under a name:
 546
 547#+begin_src sh
 548gitbay query save mine is:open assignee:@me
 549gitbay query save triage 'repo:krz/*' is:issue is:open no:label
 550gitbay query save v2 owner:krz label:bug 'label:needs review' milestone:v2
 551gitbay query list / show mine / remove mine
 552gitbay query run mine [--limit 20] [--cursor <c>]   # issues and MRs
 553gitbay issue list --query mine                     # only the issues
 554gitbay mr list --q 'owner:krz is:open author:@me'  # a query written out
 555gitbay query pin mine                              # on the dashboard; unpin
 556#+end_src
 557
 558The terms: =repo:owner/name=, =repo:owner/glob*= (=*= in the name
 559only), =repo:*=, =owner:name=; =is:open=, =is:closed=, =is:merged=,
 560=is:issue=, =is:mr=; =label:x= (repeat it: every label must be there),
 561=no:label=; =milestone:x=, =no:milestone=; =assignee:user=,
 562=author:user=, either as =@me= for whoever runs the query. Anything
 563without a colon is text matched against title and body, as =search=
 564does. Every term narrows, except that several =repo:= and =owner:=
 565terms widen the scope to any of them; with none the query covers every
 566repository you can read, and only those — someone else's private
 567repository is neither a row nor part of a count. Merge requests have no
 568assignees, so =assignee:= means issues. A term that does not parse
 569exits 2 and names itself. =@me= is resolved when the query runs, and
 570the saved text is the query in a canonical order.
 571
 572Rows come newest first, each naming its repository, paged as ={items,
 573next}= with fifty to a page unless =--limit= says otherwise.
 574An account keeps at most 50 saved queries, 10 of them pinned; past
 575either, =query save= or =query pin= exits 2 until one is removed or
 576unpinned. =dashboard --json= carries each pinned query's count and first five
 577rows under =queries=; the web dashboard shows them, and
 578=/you/-/queries= lists your saved queries with a page per query.
 579
 580Labels take a colour: =gitbay label set bug --color cf222e=; =label
 581list= shows each with its colour and how many issues and merge requests
 582carry it, and =label remove= takes one off all of them. =issue label
 583--add= and =mr label --add= still create a colourless label on the fly.
 584
 585* Merge requests
 586
 587#+begin_src sh
 588gitbay mr create --source feature --target main --title "add thing"
 589gitbay mr create other/upstream --source you/fork:feature --target main --title "..."
 590gitbay mr list / show 4 / diff 4
 591gitbay mr checkout 4              # local branch mr/4 from the MR head
 592gitbay mr review 4 --approve      # or --request-changes / --comment
 593gitbay mr label 4 --add bug --remove wontfix
 594gitbay mr merge 4 [--strategy ff|merge|squash|rebase]
 595gitbay mr merge 4 --when-ready      # merge once the gates pass; --cancel dequeues
 596gitbay mr close 4
 597#+end_src
 598
 599A merge request closed without merging can name the one that carries
 600its change forward: =mr close 4 --by 7= records it and both pages show
 601it, and =mr edit 4 --superseded-by 7|none= sets or clears it
 602afterwards, refused on anything but a closed merge request.
 603
 604The web list shows each request's combined check state and comment
 605count alongside its title, so open work needing attention stands out
 606without opening it.
 607
 608Semantics worth knowing:
 609
 610- the MR head lives in the *target* repository as
 611  =refs/merge-requests/N/head= (fetchable by any reader), so an MR
 612  survives deletion of its source branch or fork. It is never removed
 613  on merge or close; after a history rewrite an instance admin can
 614  drop it with =admin mr prune=.
 615- force-pushing the source updates the MR and marks existing reviews
 616  stale — unless the diff is the one they reviewed. A rebase onto a
 617  target that moved on changes every sha and nothing about the change,
 618  so fresh reviews follow it to the new head; a push that changes the
 619  diff stales them.
 620- default strategy: fast-forward when possible, else a merge commit.
 621  Squash makes one commit authored by the MR author, committed by the
 622  merger. Rebase replays a linear range preserving authors; it refuses
 623  ranges containing merge commits, and when fast-forward is possible it
 624  *is* one (original commits and signatures land untouched).
 625- on =require_signed_commits= branches only fast-forwards of fully
 626  verified commits merge; everything server-created is refused with
 627  instructions to rebase locally. =gitbay mr rebase <n>= is those
 628  instructions: in a clone of the target it replays the source branch
 629  onto the target and force-pushes it, then =mr merge <n>= fast-forwards.
 630  The replay is local, so the commits carry your signature and not the
 631  server's — it holds no key. A branch living in a fork is refused, with
 632  the repository to run it in; rebase it there.
 633
 634Repo admins can gate merges (=repo settings ...=): =require-approvals
 635<n>= (fresh, non-author approvals; each reviewer's latest review is
 636their stance, and a fresh request-changes blocks), =require-resolved=
 637(no open review threads), =require-checks= (all statuses green), and
 638=require-codeowners= (an approval from an owner of every owned changed
 639file). Owners come from a =CODEOWNERS= file on the target branch, root
 640or =.gitbay/=, gitignore-style patterns, last match wins. The toggle is
 641the opt-in, so a repository can carry the file as documentation of who
 642to ask without it gating merges; with it on and no file on the target
 643branch, the merge is refused and says so. It does not wait on
 644=require-approvals=.
 645
 646=mr show= carries a =gates= block with where the merge request stands
 647against all of them — approvals counted and required, owners still
 648outstanding per file, open threads, checks, and whether a fast-forward
 649is possible — and the merge request page shows the same block. =mr
 650merge= names every unmet gate at once rather than the first. =mr
 651review= says when a verdict is advisory, which it is from anyone
 652without write access: the gates do not count it.
 653
 654=mr merge <n> --when-ready= queues the merge and merges once the gates
 655pass, or at once if they already do. It is tried again when a status
 656is reported on the head, a review is submitted, a thread is resolved,
 657the request is marked ready, and the source branch is pushed; a push
 658by someone who can write to the target keeps it queued, and the new
 659head has to pass on its own. The merge is made as the user who queued
 660it, with their rights checked at that moment. Anything the queuer can
 661fix (an unmet gate, a conflict, a branch behind a
 662=require_signed_commits= target that needs a rebase) leaves it queued
 663with the reason shown on =mr show= and the page.
 664
 665The queue is bound to the credential it was made with. It is dequeued,
 666with the reason on the request's timeline, when:
 667
 668- the queuer loses write access or their account is disabled;
 669- the SSH key or API token it was queued with is removed, revoked,
 670  expires, or no longer has full scope (a merge queued from the web
 671  rests on the account alone);
 672- someone who cannot write to the target pushes to the source branch
 673  or retargets the request;
 674- the source branch is deleted, or the request is closed.
 675
 676An expiring key or token cannot queue a merge; use one without an
 677expiry, or the web. =mr merge <n> --cancel= dequeues without closing.
 678=merge= and =squash= are refused at queue time on a
 679=require_signed_commits= repository.
 680
 681Those gates apply to =mr merge=. A direct push to a protected branch
 682passes none of them until =require-mr on=: then an existing protected
 683branch refuses every push, including =repo commit-file= and the web
 684editor, and the server's merge is its only writer. Creating the branch
 685is still a push, since there is nothing to open a merge request against
 686yet.
 687
 688A merge request whose target is another open merge request's source
 689branch is stacked on it: =mr create= says so, =mr show= carries
 690=stacked_on= and =stacked=, and merging the lower one retargets the
 691upper onto =main= with its reviews kept. See [[Stacked-MRs][Stacked
 692merge requests]].
 693
 694Review threads anchor to diff lines:
 695
 696#+begin_src sh
 697gitbay mr diff-comment 4 --path main.go --line 12 --message "use log here"
 698gitbay mr diff-comment 4 --reply 7 --message "done"     # join thread 7
 699gitbay mr threads 4                                     # threads with staleness
 700gitbay mr resolve 4 7 / unresolve 4 7
 701#+end_src
 702
 703Threads render inline on the MR page. A force-push marks them stale
 704(shown under "threads on earlier revisions") rather than guessing new
 705anchors; =mr show= reports the unresolved count. Resolving is for the
 706thread author, the MR author, or anyone with write.
 707
 708A thread can span lines: =--start-line 12 --line 13= anchors it to
 709lines 12 and 13 of the new file. A fenced =suggestion= block in the
 710comment proposes replacement lines for that range; an empty block
 711proposes deleting it.
 712
 713#+begin_src sh
 714gitbay mr diff-comment 4 --path main.go --start-line 12 --line 13 --file - < suggestion.md
 715gitbay mr apply-suggestion 4 9       # commit thread 9's suggestion
 716#+end_src
 717
 718where =suggestion.md= is
 719
 720#+begin_example
 721one call does both
 722```suggestion
 723	log.Printf("starting %s", name)
 724```
 725#+end_example
 726
 727The page and =mr threads= show a suggestion as the lines it replaces
 728and the lines it proposes; =mr threads --json= carries it as
 729=suggestion= with the path, the line range, the commit and blob it was
 730made against, the original and replacement text, =outdated= with a
 731reason, and =apply= (=server= or =local=). A suggestion is outdated
 732once the lines it replaces differ at the head from what it was made
 733against, or the file is renamed or deleted; it cannot be applied then.
 734
 735Applying commits the replacement to the source branch as the applying
 736user, with a message naming the merge request and thread, and resolves
 737the thread if the applier could resolve it by hand (the thread author,
 738the MR author, or a writer of the target); otherwise the thread stays
 739open and the output says so. It is a push: only the source branch's
 740writers can apply (for a merge request from a fork, the fork's
 741writers), the branch's protection, =require-mr= and the owner's storage
 742quota apply, and a queued merge treats it as a push by the applier. The web's Apply suggestion button and =mr
 743apply-suggestion= commit it on the server. The server cannot sign, so
 744where the source or target requires signed commits (=apply= is
 745=local=) the CLI fetches the source branch into the clone it runs in,
 746builds the commit without touching the working tree, signs it with
 747=git commit-tree -S= under your git signing configuration, pushes it,
 748and resolves the thread; the page shows that command.
 749
 750* CI builds
 751
 752A =.gitbay/ci.yml= in the repo runs jobs on every branch push:
 753
 754#+begin_src yaml
 755jobs:
 756  test:
 757    steps:
 758      - go test ./...
 759#+end_src
 760
 761A config holds at most ten jobs of fifty steps each, each step under
 7624096 bytes, in a file under 64 KiB; a longer step runs a script from the
 763repository (=- sh ci/publish.sh=). A config past a cap, or otherwise
 764unparseable, records a failed =ci/config= status on the commit and
 765queues nothing, so look there when a push builds nothing.
 766
 767Each job becomes a build (=build list=, =build log=, the builds tab on
 768the web) and a =ci/<job>= commit status, which =repo settings
 769require-checks= can gate merges on. =repo settings
 770require-contexts= names statuses the gate waits for until they report,
 771and turns the gate on. =build list= takes =--ref=,
 772=--status= and =--job= to narrow the listing, combinable; the builds
 773tab reads the same flags from its =?ref=, =?status= and =?job= query
 774parameters and groups the result into one row per commit. =build show=
 775names a failed build's step and how long it ran, and =build log= takes
 776=--step <n>|failed= and =--tail <lines>=. Steps run
 777with =sh -c= on the instance's runner, stopping at the first failure;
 778a broken config surfaces as a failed =ci/config= status. Environment:
 779=GITBAY_REPO=,
 780=GITBAY_SHA=, =GITBAY_REF=, =GITBAY_JOB=, =CI=true=, and =GITBAY_SSH=,
 781the instance's ssh destination as the build reaches it: on the forge's
 782own runner, =git@169.254.1.2=, pasta's address for the host, with
 783=:port= when the instance's ssh is not on 22; from any other runner,
 784the destination that runner polls (its =-remote=, such as
 785=git@gitbay.org=). Use it as =ssh://$GITBAY_SSH/owner/name.git= or
 786=ssh ssh://$GITBAY_SSH …=, which work in either form. A job that
 787talks back to the instance — a release asset, a comment, a push to a
 788pages branch — uses =$GITBAY_SSH= with a key it holds as a secret;
 789the build's container has no key of its own. On the forge's own runner a
 790build from a fork's merge request cannot reach the instance at all, and
 791reaches the internet only over HTTPS, HTTP and DNS (CI page). Two things about that
 792container: a secret with newlines (a private key) arrives intact, and
 793=ssh= expands =~= from the passwd entry, =/root=, not from =$HOME=,
 794which is the build home — so keep an ssh config in the workspace and
 795pass it with =ssh -F= (and =GIT_SSH_COMMAND="ssh -F ..."= for git),
 796which also works on a runner with no container, where writing to
 797=~/.ssh= would edit that machine's own configuration.
 798
 799Secrets: =repo secret set <owner/name> <NAME>= reads the value from
 800stdin (never argv) and injects it into the repo's builds as =$NAME=;
 801=repo secret list= shows names only, and the value is never echoed
 802back. Anyone with write access can read a secret from inside a build,
 803so scope them accordingly.
 804
 805Schedules: a job with =schedule: "17 11,23 * * *"= (five-field cron,
 806server-local time; lists, ranges, and steps supported) runs on its cron
 807against the default branch instead of on push. A default-branch push
 808registers or updates the schedule. A job with =tags: "v*"= runs when a
 809matching tag is pushed — and only then; =schedule= and =tags= are
 810mutually exclusive. =build trigger <owner/name> <job>= queues any job
 811immediately, and =build cancel <owner/name> <n>= withdraws one, queued
 812or running: a running build stops at the runner within seconds and the
 813log says who cancelled it. Both need write access.
 814
 815What each push shape queues, with dedupe, path filters, schedules and
 816the reaper together, is one table on [[CI][CI]].
 817
 818** Your own runner
 819
 820Builds run on runners attached to the repository. An instance need not
 821offer any: install =gitbay-runner= on a machine of yours and attach it.
 822
 823#+begin_src sh
 824brew install krz/tap/gitbay-runner        # or a binary from the release
 825gitbay-runner init -remote git@gitbay.org
 826#+end_src
 827
 828=init= generates a key under =~/.config/gitbay-runner/=, writes
 829=config.toml= beside it, and prints the public key with the command to
 830attach it:
 831
 832#+begin_src sh
 833gitbay repo runner add owner/name < ~/.config/gitbay-runner/id_ed25519.pub
 834#+end_src
 835
 836or paste the key under Runners on the repository's settings page. Then
 837=brew services start krz/tap/gitbay-runner=, or run =gitbay-runner= with
 838no arguments; it reads the config file, and any flag overrides it.
 839
 840What it builds: every build for the repositories it is attached to,
 841with the repository's secrets, and nothing else. Merge requests from
 842forks are untrusted and wait unless the runner runs with =-untrusted=,
 843which is only sensible with =-isolation podman -image <ref>= (see
 844[[Admin][Admin]]). Attach one runner to several repositories by repeating
 845=repo runner add=; run several runners on one account by running =init=
 846on each machine. =repo runner list= shows each attached key, when it
 847last polled and the build it holds; =repo runner remove <fingerprint>=
 848detaches one (the key stays on your account; =keys remove= drops it).
 849A runner key reaches only the runner protocol and read-only git, so a
 850build step that reads it off disk cannot administer your account.
 851
 852* Large files (LFS)
 853
 854Standard Git LFS works over both transports with no setup beyond the
 855usual =git lfs track=. SSH remotes authenticate through
 856=git-lfs-authenticate= (deploy keys included: ro keys can download, rw
 857keys upload); anonymous HTTPS clones of public repositories can fetch
 858LFS objects with no credentials. Objects are verified against their
 859sha256 on upload and capped at 512MB by default.
 860
 861* Pages
 862
 863On instances with =[pages] domain= set, a =pages= branch in any public
 864repo is served as a static site: the repo named =pages= at
 865=https://<you>.<domain>/=, every other repo at
 866=https://<you>.<domain>/<repo>/=. Push HTML to publish; a CI job can
 867build and push the branch for automatic deploys. Sites run on a
 868separate origin — your scripts work, and the forge's cookies are out of
 869reach.
 870
 871A repo can also serve its pages branch on a domain you own.
 872=repo domain add <owner/name> <domain>= claims it and prints a DNS TXT
 873challenge (=_gitbay-challenge.<domain>=); create the record, run
 874=repo domain verify=, then point the domain's A/AAAA records at the
 875instance (DNS-only if the domain sits behind a proxying provider — the
 876instance issues its own certificates). Claims are exclusive per
 877instance; unverified claims serve nothing and expire after 7 days.
 878=repo domain list= reports pending/verified/expired.
 879
 880* Snippets
 881
 882A snippet is one or more named text files you own outside any
 883repository, for a log or a fragment shared by URL. Create one from a
 884file on stdin; the reply is the id and the URL:
 885
 886#+begin_src sh
 887gitbay snippet create build.log --description "failing build" < build.log
 888gitbay snippet file set <id> notes.txt < notes.txt   # add or replace a file
 889gitbay snippet file get <id> build.log > build.log
 890gitbay snippet edit <id> --visibility public
 891gitbay snippet list                                  # yours
 892gitbay snippet list <owner>                          # their public ones
 893gitbay snippet delete <id>
 894#+end_src
 895
 896Visibility is =public= (listed on your page), =unlisted= (anyone with
 897the URL, listed nowhere; the default) or =private= (you alone; not
 898found to everyone else). Files are text, valid UTF-8, each under the
 899instance's =max_snippet_bytes=, at most 64 per snippet. A snippet keeps
 900at least one file. There is no history: setting a file replaces it.
 901
 902On the web, =/<you>/-/snippets= lists yours, each snippet page renders
 903its files with a raw link per file, and the same page creates, edits
 904and deletes through the commands above.
 905
 906* Browser sessions
 907
 908=gitbay web login= mints a one-time URL; the session it opens ends
 909after twelve hours without a request, and after seven days in any case.
 910=gitbay web sessions list= shows each of yours by a short id with its
 911creation, expiry and last use, and =gitbay web sessions revoke <id>=
 912or =--all= ends them from the terminal, which is where a lost laptop is
 913handled.
 914
 915Actions that create a credential or grant access (adding a key, token
 916or email, org and repository roles, transfers, a repository's
 917visibility) ask you to sign in again
 918when your web sign-in is older than 15 minutes. The form shows a "Sign
 919in again" link and the login returns to the page. Idle renewal does not
 920extend this window.
 921
 922=web theme set light= or =dark= fixes the web UI's colour scheme for
 923your account; =system=, the default, follows the browser's own
 924preference. =web theme show= prints it. The account page has the same
 925control under Appearance.
 926
 927=web diff set split= draws diffs side by side on the merge request,
 928commit and compare pages; =unified=, the default, keeps one column.
 929=web diff show= prints it, and the account page has the same control
 930under Appearance. =?layout=split= or =?layout=unified= on a diff page
 931overrides the setting for that request. In a narrow window the split
 932layout stacks the old line over the new one, as a unified diff does.
 933Each column's line numbers open a comment on their own side.
 934
 935* Notifications
 936
 937When the instance has SMTP configured, activity mails you as well as
 938filing the inbox row: someone opens an issue or MR on your repository,
 939comments where you are a participant (author, commenter, reviewer, or
 940mentioned), reviews, closes, merges, or assigns you.
 941=notifications settings mail off= keeps the inbox and stops that mail
 942(login links are not activity and still arrive); the account page has
 943the same switch.
 944=notifications settings watch on= makes you a recipient of every issue
 945and merge request on the repositories you can write to, as if you had
 946run =repo watch= on each: it is consulted when a notice is delivered,
 947so a grant or a revoke needs no watch row, and an explicit watch or
 948mute on a repository still wins. Off by default; the account page has
 949this switch too. Writing =@name= in an issue, merge request or
 950comment files "mentioned you" in that account's inbox and makes them a
 951participant of the thread, provided they can read the repository and
 952have not muted it; watchers are not told about a mention addressed to
 953someone else.
 954You are never mailed about your own actions, and only verified primary
 955addresses receive anything. Delivery retries on relay failure.
 956
 957=notifications settings reply on= lets you answer that mail: issue and
 958merge request mail then carries a =Reply-To= address, and replying to
 959it posts your reply as a comment on the thread, as you. Off by
 960default; the account page has the switch when the instance reads
 961replies, and turning it on is refused where it does not
 962(=[mail.inbound]= off). A reply is posted only when it comes from one
 963of your verified addresses, you can still comment on the thread, and
 964the mail it answers is less than thirty days old. Quoted text (lines
 965starting with =>=, the "On … wrote:" line and what follows it, the
 966header block Outlook quotes) and everything after a =-- = signature
 967line are removed, and the rest is stored as markdown. HTML-only mail is
 968not accepted; send plain text or the usual plain-and-HTML pair. A
 969refused reply gets no answer: nothing is mailed back. Each reply
 970address names you, so do not forward notification mail you would not
 971want answered from your own address.
 972
 973Push is the same activity again, delivered to a phone: the iOS app
 974registers a device, and =notifications settings push off= silences it
 975the way =mail off= silences mail, without deregistering anything.
 976=notifications device list= shows what is registered (token shown
 977truncated); =notifications device remove <id>= drops one by hand, from
 978the CLI or the account page. A device is also dropped on its own the
 979moment Apple reports the token dead, so an app deleted from a phone
 980stops costing anything without you having to notice. Push only works
 981when the instance operator has configured it: on an instance with
 982=[push] enabled = false=, =notifications device add= refuses rather
 983than registering a device nothing can deliver to.
 984
 985Push notifications carry the notice in full: a private repository's
 986name and the issue or merge request number reach Apple and can appear
 987on a lock screen, the same as any other text a phone shows in a
 988notification. This is deliberate, not an oversight — weigh it against
 989what you keep in a private repository before registering a device.
 990
 991* Web vocabulary
 992
 993Sign in / Log out, Search, sentence-case headings and buttons, product
 994name lowercase.
 995
 996* Output rules
 997
 998=--json= is the contract; the plain output is for a person at a
 999terminal, and follows these rules so every noun reads the same way.
1000
1001- A list command prints one row per item, tab-separated, no header.
1002  Columns run identifier, state, then description; a trailing column
1003  may carry a word (=due 2027-01-01=, =via team=). Piped, a timestamp in
1004  a row is RFC3339 to the second, UTC. Under =--json= nothing is
1005  touched.
1006- An empty list prints nothing on stdout and =nothing to list= on
1007  stderr.
1008- A mutation prints one line: verb, object, identifier
1009  (=created krz/gitbay#7=). A second line appears only for something
1010  to copy: a URL, a token shown once.
1011- A bad invocation prints =usage:= and the command's registered usage,
1012  the same text =help <noun>= shows. Exit 2.
1013- A refusal says who may and what to do instead: =only admins of acme
1014  can manage teams; ask one to add you=. Exit 4. A thing that does not
1015  exist, or that you may not know exists, is exit 3.
1016- stdout is the result; stderr is everything else: progress, a note
1017  that output was truncated, errors.
1018- Verbs: =create= and =delete= for things with their own identity
1019  (repository, issue, release, team), =add= and =remove= for attaching
1020  something to them (a key, a member, a label on an issue), =set= for
1021  a value, =revoke= for a credential, =show= and =list= for reads.
1022
1023** At a terminal
1024
1025The =gitbay= CLI sends a leading =--term=<cols>[,<option>]...= argument on
1026the SSH command line when stdout is a terminal (OpenSSH's multiplexed
1027sessions, which the CLI uses, do not forward a session's =SetEnv=).
1028The argument goes first: the server reads =--term=<v>= only as the
1029first argument, and ignores it over HTTP. =color= is the one option
1030the server acts on; it ignores options it does not know. The server
1031then prints:
1032
1033- lists padded and fitted to the width (the title or description
1034  column is cut with =…= first; a title or description column blank on
1035  most rows is held to a third of the width), with no trailing
1036  whitespace. A list has a dim header row only when a column is a
1037  number or a size, which would not say what it counts without one. The
1038  next page is a =Next page= line on stderr with the command that
1039  fetches it (piped output keeps a =next\t<cursor>= row instead);
1040- colour by meaning: green for open, success, approved, verified; magenta
1041  for merged; red for failures, =private=, and bad or untrusted
1042  signatures; dim for closed, pending, archived, unsigned; yellow only
1043  for what waits on you (an unverified address); references (=#12=,
1044  =krz/gitbay=, SHAs) dim. A marker piped output keeps as its own
1045  trailing cell (=[archived]=, =primary=) joins the state at a terminal:
1046  =public, archived=;
1047- every list and show drawn as a screen: a header block of =Label:=
1048  lines (labels dim), the description or notes, sections titled =Title
1049  (n)= in bold blue, and a legend under a rule of the commands that
1050  apply now, grouped and chosen from the item's state and your access.
1051  Rows have no header (unless a column is a number or a size) and no
1052  indent: a dim reference, a state glyph, the text, then dim facts
1053  joined by =·=. The glyphs are =✓= passed, =✗= failed or blocked, =◐=
1054  still going, =●= waiting on you (your review requested, an issue
1055  assigned to you, an unread notification, a pending account, an
1056  overdue milestone, the session's own key), =○= closed or draft. A
1057  section cut short ends with =+n more= and the command for the rest.
1058  Below 80 columns the legend's groups stack; a suggested command is
1059  never cut, so it may run past the width. =dashboard= opens with what
1060  waits on you; for admins a =Problems= line counts background
1061  failures, and =admin stats= has the detail. =repo commit= prints its
1062  screen, then the diff. Secrets never appear on a screen, and a
1063  webhook URL shows its scheme and host only. =wiki show= piped prints
1064  the page source verbatim;
1065- errors on stderr after a red =error:=, a mistyped flag with the flag
1066  it is closest to (=did you mean --state?=), and a usage line wrapped
1067  to the width between its bracketed groups, one alternative per line;
1068- help with flag descriptions, defaults, and examples; =gitbay --help=
1069  groups commands under WORK, REPOSITORIES, YOU and INSTANCE;
1070  =help --json= adds =flags= and =examples=.
1071
1072=NO_COLOR=, =TERM=dumb= and =--no-color= drop the colour. =show=,
1073=diff= and =log= at a terminal go through =$GITBAY_PAGER=, else
1074=$PAGER=, else =less= (with =LESS=FRX= when =LESS= is unset); an empty
1075=GITBAY_PAGER= turns paging off. Paging never applies to =--follow=,
1076=--json=, or piped output.
1077
1078Inside a clone of a repository on the instance, the CLI adds
1079=here=<owner/name>= to =--term=, so a command the output suggests leaves
1080that repository out, as the CLI would infer it. An instance that does
1081not know the option ignores it.
1082
1083=GITBAY_TERM=off= stops the CLI sending =--term= at all, for an
1084instance older than v1.36.0, which refuses the argument as an unknown
1085command.
1086
1087Two more options say what the terminal can show. =truecolor= (sent when
1088=COLORTERM= is =truecolor= or =24bit=) puts a dot in each label's own
1089colour beside its hex in =label list=. =links= (sent in iTerm2, WezTerm,
1090Ghostty, VS Code, kitty and VTE terminals, or when =GITBAY_LINKS=1=;
1091=GITBAY_LINKS=0= stops it) makes references in lists open their page,
1092as OSC 8 hyperlinks. A terminal outside that list would print the
1093escape's text, so it is not sent there by default. =GITBAY_TERM=basic=
1094sends only the width and colour, for an instance older than the release
1095that added these options, which turns any option it does not know into
1096plain output.
1097
1098Stock ssh without the CLI's multiplexing gets the plain output unless
1099it passes the same leading argument or sets the environment variable
1100sshd is told to accept. OpenSSH parses options after the host, so the
1101argument goes after =--=:
1102
1103#+begin_src sh
1104ssh git@gitbay.org -- --term=120,color issue list krz/gitbay
1105ssh -o SetEnv=GITBAY_TERM=120,color git@gitbay.org issue list krz/gitbay
1106#+end_src
1107
1108* Scripting
1109
1110Every read command takes =--json= and emits one envelope:
1111={"protocol_version": 1, "data": ...}=. stdout is data, stderr is
1112messages. Exit codes are stable: 0 ok, 1 failure, 2 usage, 3 not found,
11134 denied, 5 server/protocol error. Usage means the arguments were wrong;
1114a refusal — a name already taken, a state that does not allow the change
1115— is a failure. Nothing ever prompts; destructive commands take =--yes=.
1116
1117For HTTP automation see [[API]].
1118
1119* CLI setup
1120
1121#+begin_src sh
1122gitbay remote add myforge forge.example.org [--port n] [--user u] --default
1123gitbay remote list
1124gitbay init [name] [--private]   # git init + repo create + origin, in one step
1125#+end_src
1126
1127Configuration lives at =~/.config/gitbay/config.toml=. The CLI shells
1128out to your real =ssh=, so =~/.ssh/config=, the agent, and hardware keys
1129all apply. It shares one connection per instance through a control
1130socket under =~/.ssh=: the first command in five minutes pays the
1131handshake and the rest ride it. =no_multiplex = true= on an instance in
1132the config turns that off. Man pages: =gitbay man --dir <dir>=; completions:
1133=gitbay completion bash|zsh|fish=.