.gitbay/wiki/Users.org
1113 lines · 55248 bytes
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* Organizations
370
371Orgs share the owner namespace with users and own repositories at
372=org/repo=. By default members get write on all org repos; org admins
373get repo admin, create repos under the org, and manage membership. An
374org's existence and member list are public; its teams are visible to
375members, and anyone else asking is refused rather than told the org
376does not exist.
377
378#+begin_src sh
379gitbay org create krz
380gitbay org members add krz alice [--role admin]
381gitbay org show krz
382gitbay org rename krz newname # clone URLs change
383gitbay org delete krz --yes # only when it owns no repositories
384#+end_src
385
386Large orgs scope access with teams: set what plain membership implies,
387then grant per-repo roles through named teams (org admins always keep
388admin; the default =write= keeps the simple model):
389
390#+begin_src sh
391gitbay org settings members-role krz none # write | read | none
392gitbay org team create krz core-devs
393gitbay org team add krz core-devs alice bob # org members only
394gitbay org team grant krz core-devs krz/gitbay write
395gitbay org team show krz core-devs # members + grants
396gitbay org team revoke / remove / delete ...
397#+end_src
398
399* Issues
400
401Anyone who can read a repository can file and comment. Closing/reopening
402is for the author or anyone with write; labels and assignees need write.
403
404#+begin_src sh
405gitbay issue create --title "it breaks" [--body "..." | --file -]
406 [--label bug]... [--milestone v1.0] [--assignee alice]... # these need write
407gitbay issue list [--state open|closed|all]
408gitbay issue show 4
409gitbay issue comment 4 --message "same here"
410gitbay issue react 4 +1 [--comment 12] [--remove] # the same on mr
411gitbay issue edit 4 --title "better title" [--body|--file -] # author or write
412gitbay issue close 4 / reopen 4
413gitbay issue label 4 --add bug --remove wontfix
414gitbay issue assign 4 --add alice
415gitbay issue milestone 4 v1.0 # or "none" to clear
416#+end_src
417
418A reaction is one of =+1 -1 laugh hooray confused heart rocket eyes=,
419given by name or as the emoji itself, on an issue or merge request or on
420one of its comments (=show= prints comment ids). Anyone who may comment
421may react; reacting twice or removing an absent reaction does nothing.
422=show= carries the counts and which are yours. A reaction sends no
423notification and appears in no activity feed.
424
425Inside a clone, the repository is inferred from the =origin= remote —
426that is why no =owner/name= appears above. Anywhere else, pass it as the
427first argument. Long text: =--body= inline, =--file -= from stdin, or
428neither on a terminal and =$EDITOR= opens.
429
430A wiki is the =.gitbay/wiki/= directory on the default branch — no
431separate repository, the same rendering pipeline as READMEs (markdown
432and org). Editing it is editing a file: a push, =repo commit-file=, or
433the web editor on a repository that permits server-authored commits (one
434requiring signed commits is push-only, since the server signs nothing):
435
436#+begin_src sh
437mkdir -p .gitbay/wiki
438# add Home.md (or .org), more pages, images; relative links between
439# pages just work on the web at /you/project/wiki
440git add .gitbay/wiki && git commit -m "wiki" && git push
441#+end_src
442
443Pages may sit in subfolders. A nested page is named by its path —
444=.gitbay/wiki/Guide/Install.md= is =/you/project/wiki/Guide/Install= and
445=wiki show you/project Guide/Install= — and the sidebar groups it under
446its folder. A relative link or image on a nested page resolves from the
447page's own folder first, then from the top of the wiki, so a link to
448=Home= still reaches the top-level page. SVG images render.
449
450Releases anchor notes and binary assets to a pushed tag (write access;
451assets stream over SSH, capped by the instance's =max_asset_bytes=).
452While a release exists its tag can be neither deleted nor moved, whether
453or not the tag is protected: delete the release first.
454
455#+begin_src sh
456gitbay release create v1.0 --title "First light" [--notes|--file -|$EDITOR]
457gitbay release asset add v1.0 tool-linux-amd64 < dist/tool-linux-amd64
458gitbay release asset get v1.0 tool-linux-amd64 > tool # or the web download link
459gitbay release list / show v1.0 / delete v1.0 --yes
460#+end_src
461
462The web shows them under the repository's =releases= tab with rendered
463notes, sha256 sums, and download links; writers add and remove assets
464there (a file upload, up to =max_asset_bytes=; removal asks for the file
465name). Feed readers subscribe at
466=/you/project/releases.atom=; commits are at =/you/project/log.atom=
467(the default branch) or =/you/project/log.atom/<ref>=, and an owner's
468activity on their public repositories at =/you/activity.atom=. The
469pages carry the discovery link.
470
471Commit messages act on issues when the commits land on the default
472branch (direct push, MR merge, =repo commit-file= or the web editor):
473=closes/fixes/resolves #4= closes the
474issue with a linking comment, and a bare =#4= leaves a reference
475comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when
476you hold write there with a key whose scope reaches it; otherwise it
477stays a plain link. A deploy key closes nothing outside the repository
478it is bound to, but naming that repository by full path works as the
479bare form does. The comment left
480on the closed issue links the closing commit by its repository path, so
481closing a public repository's issue from a private one names the private
482repository there. A bare =owner/name#N= links and does nothing.
483
484Milestones group issues and MRs toward a release (write access to
485manage, attach with =issue milestone= / =mr milestone=; progress shows
486on the web at =/owner/name/milestones=, where writers create, close and
487reopen them):
488
489#+begin_src sh
490gitbay milestone create v1.0 --description "first release" --due 2027-01-01
491gitbay milestone list [--state open|closed|all]
492gitbay milestone close v1.0 / reopen v1.0
493#+end_src
494
495An org holds labels and milestones every repository under it sees
496beside its own. =issue label --add=, =mr label --add=, =issue
497milestone= and =mr milestone= resolve the org's row first; a repository
498cannot create a label or milestone with a name its org holds. Creating
499an org label or milestone whose name repositories under the org already
500use folds them in: their issues and merge requests move to the org's
501row. Org admins manage them, on the CLI or at =/<org>/-/labels= and
502=/<org>/-/milestones=; counts span the repositories you can read.
503
504#+begin_src sh
505gitbay org label set acme bug --color cf222e
506gitbay org label list acme / remove acme bug
507gitbay org milestone create acme v2 --due 2027-03-01
508gitbay org milestone list acme [--state open|closed|all]
509gitbay org milestone close acme v2 / reopen acme v2
510#+end_src
511
512On the web: =/acme/-/labels= and =/acme/-/milestones=, read-only.
513
514Issue templates: commit =.gitbay/issue-template.md= (and optional
515=issue-template-<name>.md= variants) to the default branch. =gitbay
516issue create= prefills =$EDITOR= with the default template, the web
517form prefills its textarea, and =gitbay issue templates= lists them.
518
519Lists narrow the same way on every surface: =issue list --label bug
520--assignee bob --author alice --milestone v1= (or =--milestone none=),
521=mr list --label bug --author bob --milestone v1=; the web's issue and
522merge request lists take the same names as query parameters, and each
523active filter shows with a link that drops it.
524
525A query spans repositories, and a saved one keeps it under a name:
526
527#+begin_src sh
528gitbay query save mine is:open assignee:@me
529gitbay query save triage 'repo:krz/*' is:issue is:open no:label
530gitbay query save v2 owner:krz label:bug 'label:needs review' milestone:v2
531gitbay query list / show mine / remove mine
532gitbay query run mine [--limit 20] [--cursor <c>] # issues and MRs
533gitbay issue list --query mine # only the issues
534gitbay mr list --q 'owner:krz is:open author:@me' # a query written out
535gitbay query pin mine # on the dashboard; unpin
536#+end_src
537
538The terms: =repo:owner/name=, =repo:owner/glob*= (=*= in the name
539only), =repo:*=, =owner:name=; =is:open=, =is:closed=, =is:merged=,
540=is:issue=, =is:mr=; =label:x= (repeat it: every label must be there),
541=no:label=; =milestone:x=, =no:milestone=; =assignee:user=,
542=author:user=, either as =@me= for whoever runs the query. Anything
543without a colon is text matched against title and body, as =search=
544does. Every term narrows, except that several =repo:= and =owner:=
545terms widen the scope to any of them; with none the query covers every
546repository you can read, and only those — someone else's private
547repository is neither a row nor part of a count. Merge requests have no
548assignees, so =assignee:= means issues. A term that does not parse
549exits 2 and names itself. =@me= is resolved when the query runs, and
550the saved text is the query in a canonical order.
551
552Rows come newest first, each naming its repository, paged as ={items,
553next}= with fifty to a page unless =--limit= says otherwise.
554An account keeps at most 50 saved queries, 10 of them pinned; past
555either, =query save= or =query pin= exits 2 until one is removed or
556unpinned. =dashboard --json= carries each pinned query's count and first five
557rows under =queries=; the web dashboard shows them, and
558=/you/-/queries= lists your saved queries with a page per query.
559
560Labels take a colour: =gitbay label set bug --color cf222e=; =label
561list= shows each with its colour and how many issues and merge requests
562carry it, and =label remove= takes one off all of them. =issue label
563--add= and =mr label --add= still create a colourless label on the fly.
564
565* Merge requests
566
567#+begin_src sh
568gitbay mr create --source feature --target main --title "add thing"
569gitbay mr create other/upstream --source you/fork:feature --target main --title "..."
570gitbay mr list / show 4 / diff 4
571gitbay mr checkout 4 # local branch mr/4 from the MR head
572gitbay mr review 4 --approve # or --request-changes / --comment
573gitbay mr label 4 --add bug --remove wontfix
574gitbay mr merge 4 [--strategy ff|merge|squash|rebase]
575gitbay mr merge 4 --when-ready # merge once the gates pass; --cancel dequeues
576gitbay mr close 4
577#+end_src
578
579A merge request closed without merging can name the one that carries
580its change forward: =mr close 4 --by 7= records it and both pages show
581it, and =mr edit 4 --superseded-by 7|none= sets or clears it
582afterwards, refused on anything but a closed merge request.
583
584The web list shows each request's combined check state and comment
585count alongside its title, so open work needing attention stands out
586without opening it.
587
588Semantics worth knowing:
589
590- the MR head lives in the *target* repository as
591 =refs/merge-requests/N/head= (fetchable by any reader), so an MR
592 survives deletion of its source branch or fork. It is never removed
593 on merge or close; after a history rewrite an instance admin can
594 drop it with =admin mr prune=.
595- force-pushing the source updates the MR and marks existing reviews
596 stale — unless the diff is the one they reviewed. A rebase onto a
597 target that moved on changes every sha and nothing about the change,
598 so fresh reviews follow it to the new head; a push that changes the
599 diff stales them.
600- default strategy: fast-forward when possible, else a merge commit.
601 Squash makes one commit authored by the MR author, committed by the
602 merger. Rebase replays a linear range preserving authors; it refuses
603 ranges containing merge commits, and when fast-forward is possible it
604 *is* one (original commits and signatures land untouched).
605- on =require_signed_commits= branches only fast-forwards of fully
606 verified commits merge; everything server-created is refused with
607 instructions to rebase locally. =gitbay mr rebase <n>= is those
608 instructions: in a clone of the target it replays the source branch
609 onto the target and force-pushes it, then =mr merge <n>= fast-forwards.
610 The replay is local, so the commits carry your signature and not the
611 server's — it holds no key. A branch living in a fork is refused, with
612 the repository to run it in; rebase it there.
613
614Repo admins can gate merges (=repo settings ...=): =require-approvals
615<n>= (fresh, non-author approvals; each reviewer's latest review is
616their stance, and a fresh request-changes blocks), =require-resolved=
617(no open review threads), =require-checks= (all statuses green), and
618=require-codeowners= (an approval from an owner of every owned changed
619file). Owners come from a =CODEOWNERS= file on the target branch, root
620or =.gitbay/=, gitignore-style patterns, last match wins. The toggle is
621the opt-in, so a repository can carry the file as documentation of who
622to ask without it gating merges; with it on and no file on the target
623branch, the merge is refused and says so. It does not wait on
624=require-approvals=.
625
626=mr show= carries a =gates= block with where the merge request stands
627against all of them — approvals counted and required, owners still
628outstanding per file, open threads, checks, and whether a fast-forward
629is possible — and the merge request page shows the same block. =mr
630merge= names every unmet gate at once rather than the first. =mr
631review= says when a verdict is advisory, which it is from anyone
632without write access: the gates do not count it.
633
634=mr merge <n> --when-ready= queues the merge and merges once the gates
635pass, or at once if they already do. It is tried again when a status
636is reported on the head, a review is submitted, a thread is resolved,
637the request is marked ready, and the source branch is pushed; a push
638by someone who can write to the target keeps it queued, and the new
639head has to pass on its own. The merge is made as the user who queued
640it, with their rights checked at that moment. Anything the queuer can
641fix (an unmet gate, a conflict, a branch behind a
642=require_signed_commits= target that needs a rebase) leaves it queued
643with the reason shown on =mr show= and the page.
644
645The queue is bound to the credential it was made with. It is dequeued,
646with the reason on the request's timeline, when:
647
648- the queuer loses write access or their account is disabled;
649- the SSH key or API token it was queued with is removed, revoked,
650 expires, or no longer has full scope (a merge queued from the web
651 rests on the account alone);
652- someone who cannot write to the target pushes to the source branch
653 or retargets the request;
654- the source branch is deleted, or the request is closed.
655
656An expiring key or token cannot queue a merge; use one without an
657expiry, or the web. =mr merge <n> --cancel= dequeues without closing.
658=merge= and =squash= are refused at queue time on a
659=require_signed_commits= repository.
660
661Those gates apply to =mr merge=. A direct push to a protected branch
662passes none of them until =require-mr on=: then an existing protected
663branch refuses every push, including =repo commit-file= and the web
664editor, and the server's merge is its only writer. Creating the branch
665is still a push, since there is nothing to open a merge request against
666yet.
667
668A merge request whose target is another open merge request's source
669branch is stacked on it: =mr create= says so, =mr show= carries
670=stacked_on= and =stacked=, and merging the lower one retargets the
671upper onto =main= with its reviews kept. See [[Stacked-MRs][Stacked
672merge requests]].
673
674Review threads anchor to diff lines:
675
676#+begin_src sh
677gitbay mr diff-comment 4 --path main.go --line 12 --message "use log here"
678gitbay mr diff-comment 4 --reply 7 --message "done" # join thread 7
679gitbay mr threads 4 # threads with staleness
680gitbay mr resolve 4 7 / unresolve 4 7
681#+end_src
682
683Threads render inline on the MR page. A force-push marks them stale
684(shown under "threads on earlier revisions") rather than guessing new
685anchors; =mr show= reports the unresolved count. Resolving is for the
686thread author, the MR author, or anyone with write.
687
688A thread can span lines: =--start-line 12 --line 13= anchors it to
689lines 12 and 13 of the new file. A fenced =suggestion= block in the
690comment proposes replacement lines for that range; an empty block
691proposes deleting it.
692
693#+begin_src sh
694gitbay mr diff-comment 4 --path main.go --start-line 12 --line 13 --file - < suggestion.md
695gitbay mr apply-suggestion 4 9 # commit thread 9's suggestion
696#+end_src
697
698where =suggestion.md= is
699
700#+begin_example
701one call does both
702```suggestion
703 log.Printf("starting %s", name)
704```
705#+end_example
706
707The page and =mr threads= show a suggestion as the lines it replaces
708and the lines it proposes; =mr threads --json= carries it as
709=suggestion= with the path, the line range, the commit and blob it was
710made against, the original and replacement text, =outdated= with a
711reason, and =apply= (=server= or =local=). A suggestion is outdated
712once the lines it replaces differ at the head from what it was made
713against, or the file is renamed or deleted; it cannot be applied then.
714
715Applying commits the replacement to the source branch as the applying
716user, with a message naming the merge request and thread, and resolves
717the thread if the applier could resolve it by hand (the thread author,
718the MR author, or a writer of the target); otherwise the thread stays
719open and the output says so. It is a push: only the source branch's
720writers can apply (for a merge request from a fork, the fork's
721writers), the branch's protection, =require-mr= and the owner's storage
722quota apply, and a queued merge treats it as a push by the applier. The web's Apply suggestion button and =mr
723apply-suggestion= commit it on the server. The server cannot sign, so
724where the source or target requires signed commits (=apply= is
725=local=) the CLI fetches the source branch into the clone it runs in,
726builds the commit without touching the working tree, signs it with
727=git commit-tree -S= under your git signing configuration, pushes it,
728and resolves the thread; the page shows that command.
729
730* CI builds
731
732A =.gitbay/ci.yml= in the repo runs jobs on every branch push:
733
734#+begin_src yaml
735jobs:
736 test:
737 steps:
738 - go test ./...
739#+end_src
740
741A config holds at most ten jobs of fifty steps each, each step under
7424096 bytes, in a file under 64 KiB; a longer step runs a script from the
743repository (=- sh ci/publish.sh=). A config past a cap, or otherwise
744unparseable, records a failed =ci/config= status on the commit and
745queues nothing, so look there when a push builds nothing.
746
747Each job becomes a build (=build list=, =build log=, the builds tab on
748the web) and a =ci/<job>= commit status, which =repo settings
749require-checks= can gate merges on. =repo settings
750require-contexts= names statuses the gate waits for until they report,
751and turns the gate on. =build list= takes =--ref=,
752=--status= and =--job= to narrow the listing, combinable; the builds
753tab reads the same flags from its =?ref=, =?status= and =?job= query
754parameters and groups the result into one row per commit. =build show=
755names a failed build's step and how long it ran, and =build log= takes
756=--step <n>|failed= and =--tail <lines>=. Steps run
757with =sh -c= on the instance's runner, stopping at the first failure;
758a broken config surfaces as a failed =ci/config= status. Environment:
759=GITBAY_REPO=,
760=GITBAY_SHA=, =GITBAY_REF=, =GITBAY_JOB=, =CI=true=, and =GITBAY_SSH=,
761the instance's ssh destination as the build reaches it: on the forge's
762own runner, =git@169.254.1.2=, pasta's address for the host, with
763=:port= when the instance's ssh is not on 22; from any other runner,
764the destination that runner polls (its =-remote=, such as
765=git@gitbay.org=). Use it as =ssh://$GITBAY_SSH/owner/name.git= or
766=ssh ssh://$GITBAY_SSH …=, which work in either form. A job that
767talks back to the instance — a release asset, a comment, a push to a
768pages branch — uses =$GITBAY_SSH= with a key it holds as a secret;
769the build's container has no key of its own. On the forge's own runner a
770build from a fork's merge request cannot reach the instance at all, and
771reaches the internet only over HTTPS, HTTP and DNS (CI page). Two things about that
772container: a secret with newlines (a private key) arrives intact, and
773=ssh= expands =~= from the passwd entry, =/root=, not from =$HOME=,
774which is the build home — so keep an ssh config in the workspace and
775pass it with =ssh -F= (and =GIT_SSH_COMMAND="ssh -F ..."= for git),
776which also works on a runner with no container, where writing to
777=~/.ssh= would edit that machine's own configuration.
778
779Secrets: =repo secret set <owner/name> <NAME>= reads the value from
780stdin (never argv) and injects it into the repo's builds as =$NAME=;
781=repo secret list= shows names only, and the value is never echoed
782back. Anyone with write access can read a secret from inside a build,
783so scope them accordingly.
784
785Schedules: a job with =schedule: "17 11,23 * * *"= (five-field cron,
786server-local time; lists, ranges, and steps supported) runs on its cron
787against the default branch instead of on push. A default-branch push
788registers or updates the schedule. A job with =tags: "v*"= runs when a
789matching tag is pushed — and only then; =schedule= and =tags= are
790mutually exclusive. =build trigger <owner/name> <job>= queues any job
791immediately, and =build cancel <owner/name> <n>= withdraws one, queued
792or running: a running build stops at the runner within seconds and the
793log says who cancelled it. Both need write access.
794
795What each push shape queues, with dedupe, path filters, schedules and
796the reaper together, is one table on [[CI][CI]].
797
798** Your own runner
799
800Builds run on runners attached to the repository. An instance need not
801offer any: install =gitbay-runner= on a machine of yours and attach it.
802
803#+begin_src sh
804brew install krz/tap/gitbay-runner # or a binary from the release
805gitbay-runner init -remote git@gitbay.org
806#+end_src
807
808=init= generates a key under =~/.config/gitbay-runner/=, writes
809=config.toml= beside it, and prints the public key with the command to
810attach it:
811
812#+begin_src sh
813gitbay repo runner add owner/name < ~/.config/gitbay-runner/id_ed25519.pub
814#+end_src
815
816or paste the key under Runners on the repository's settings page. Then
817=brew services start krz/tap/gitbay-runner=, or run =gitbay-runner= with
818no arguments; it reads the config file, and any flag overrides it.
819
820What it builds: every build for the repositories it is attached to,
821with the repository's secrets, and nothing else. Merge requests from
822forks are untrusted and wait unless the runner runs with =-untrusted=,
823which is only sensible with =-isolation podman -image <ref>= (see
824[[Admin][Admin]]). Attach one runner to several repositories by repeating
825=repo runner add=; run several runners on one account by running =init=
826on each machine. =repo runner list= shows each attached key, when it
827last polled and the build it holds; =repo runner remove <fingerprint>=
828detaches one (the key stays on your account; =keys remove= drops it).
829A runner key reaches only the runner protocol and read-only git, so a
830build step that reads it off disk cannot administer your account.
831
832* Large files (LFS)
833
834Standard Git LFS works over both transports with no setup beyond the
835usual =git lfs track=. SSH remotes authenticate through
836=git-lfs-authenticate= (deploy keys included: ro keys can download, rw
837keys upload); anonymous HTTPS clones of public repositories can fetch
838LFS objects with no credentials. Objects are verified against their
839sha256 on upload and capped at 512MB by default.
840
841* Pages
842
843On instances with =[pages] domain= set, a =pages= branch in any public
844repo is served as a static site: the repo named =pages= at
845=https://<you>.<domain>/=, every other repo at
846=https://<you>.<domain>/<repo>/=. Push HTML to publish; a CI job can
847build and push the branch for automatic deploys. Sites run on a
848separate origin — your scripts work, and the forge's cookies are out of
849reach.
850
851A repo can also serve its pages branch on a domain you own.
852=repo domain add <owner/name> <domain>= claims it and prints a DNS TXT
853challenge (=_gitbay-challenge.<domain>=); create the record, run
854=repo domain verify=, then point the domain's A/AAAA records at the
855instance (DNS-only if the domain sits behind a proxying provider — the
856instance issues its own certificates). Claims are exclusive per
857instance; unverified claims serve nothing and expire after 7 days.
858=repo domain list= reports pending/verified/expired.
859
860* Snippets
861
862A snippet is one or more named text files you own outside any
863repository, for a log or a fragment shared by URL. Create one from a
864file on stdin; the reply is the id and the URL:
865
866#+begin_src sh
867gitbay snippet create build.log --description "failing build" < build.log
868gitbay snippet file set <id> notes.txt < notes.txt # add or replace a file
869gitbay snippet file get <id> build.log > build.log
870gitbay snippet edit <id> --visibility public
871gitbay snippet list # yours
872gitbay snippet list <owner> # their public ones
873gitbay snippet delete <id>
874#+end_src
875
876Visibility is =public= (listed on your page), =unlisted= (anyone with
877the URL, listed nowhere; the default) or =private= (you alone; not
878found to everyone else). Files are text, valid UTF-8, each under the
879instance's =max_snippet_bytes=, at most 64 per snippet. A snippet keeps
880at least one file. There is no history: setting a file replaces it.
881
882On the web, =/<you>/-/snippets= lists yours, each snippet page renders
883its files with a raw link per file, and the same page creates, edits
884and deletes through the commands above.
885
886* Browser sessions
887
888=gitbay web login= mints a one-time URL; the session it opens ends
889after twelve hours without a request, and after seven days in any case.
890=gitbay web sessions list= shows each of yours by a short id with its
891creation, expiry and last use, and =gitbay web sessions revoke <id>=
892or =--all= ends them from the terminal, which is where a lost laptop is
893handled.
894
895Actions that create a credential or grant access (adding a key, token
896or email, org and repository roles, transfers, a repository's
897visibility) ask you to sign in again
898when your web sign-in is older than 15 minutes. The form shows a "Sign
899in again" link and the login returns to the page. Idle renewal does not
900extend this window.
901
902=web theme set light= or =dark= fixes the web UI's colour scheme for
903your account; =system=, the default, follows the browser's own
904preference. =web theme show= prints it. The account page has the same
905control under Appearance.
906
907=web diff set split= draws diffs side by side on the merge request,
908commit and compare pages; =unified=, the default, keeps one column.
909=web diff show= prints it, and the account page has the same control
910under Appearance. =?layout=split= or =?layout=unified= on a diff page
911overrides the setting for that request. In a narrow window the split
912layout stacks the old line over the new one, as a unified diff does.
913Each column's line numbers open a comment on their own side.
914
915* Notifications
916
917When the instance has SMTP configured, activity mails you as well as
918filing the inbox row: someone opens an issue or MR on your repository,
919comments where you are a participant (author, commenter, reviewer, or
920mentioned), reviews, closes, merges, or assigns you.
921=notifications settings mail off= keeps the inbox and stops that mail
922(login links are not activity and still arrive); the account page has
923the same switch.
924=notifications settings watch on= makes you a recipient of every issue
925and merge request on the repositories you can write to, as if you had
926run =repo watch= on each: it is consulted when a notice is delivered,
927so a grant or a revoke needs no watch row, and an explicit watch or
928mute on a repository still wins. Off by default; the account page has
929this switch too. Writing =@name= in an issue, merge request or
930comment files "mentioned you" in that account's inbox and makes them a
931participant of the thread, provided they can read the repository and
932have not muted it; watchers are not told about a mention addressed to
933someone else.
934You are never mailed about your own actions, and only verified primary
935addresses receive anything. Delivery retries on relay failure.
936
937=notifications settings reply on= lets you answer that mail: issue and
938merge request mail then carries a =Reply-To= address, and replying to
939it posts your reply as a comment on the thread, as you. Off by
940default; the account page has the switch when the instance reads
941replies, and turning it on is refused where it does not
942(=[mail.inbound]= off). A reply is posted only when it comes from one
943of your verified addresses, you can still comment on the thread, and
944the mail it answers is less than thirty days old. Quoted text (lines
945starting with =>=, the "On … wrote:" line and what follows it, the
946header block Outlook quotes) and everything after a =-- = signature
947line are removed, and the rest is stored as markdown. HTML-only mail is
948not accepted; send plain text or the usual plain-and-HTML pair. A
949refused reply gets no answer: nothing is mailed back. Each reply
950address names you, so do not forward notification mail you would not
951want answered from your own address.
952
953Push is the same activity again, delivered to a phone: the iOS app
954registers a device, and =notifications settings push off= silences it
955the way =mail off= silences mail, without deregistering anything.
956=notifications device list= shows what is registered (token shown
957truncated); =notifications device remove <id>= drops one by hand, from
958the CLI or the account page. A device is also dropped on its own the
959moment Apple reports the token dead, so an app deleted from a phone
960stops costing anything without you having to notice. Push only works
961when the instance operator has configured it: on an instance with
962=[push] enabled = false=, =notifications device add= refuses rather
963than registering a device nothing can deliver to.
964
965Push notifications carry the notice in full: a private repository's
966name and the issue or merge request number reach Apple and can appear
967on a lock screen, the same as any other text a phone shows in a
968notification. This is deliberate, not an oversight — weigh it against
969what you keep in a private repository before registering a device.
970
971* Web vocabulary
972
973Sign in / Log out, Search, sentence-case headings and buttons, product
974name lowercase.
975
976* Output rules
977
978=--json= is the contract; the plain output is for a person at a
979terminal, and follows these rules so every noun reads the same way.
980
981- A list command prints one row per item, tab-separated, no header.
982 Columns run identifier, state, then description; a trailing column
983 may carry a word (=due 2027-01-01=, =via team=). Piped, a timestamp in
984 a row is RFC3339 to the second, UTC. Under =--json= nothing is
985 touched.
986- An empty list prints nothing on stdout and =nothing to list= on
987 stderr.
988- A mutation prints one line: verb, object, identifier
989 (=created krz/gitbay#7=). A second line appears only for something
990 to copy: a URL, a token shown once.
991- A bad invocation prints =usage:= and the command's registered usage,
992 the same text =help <noun>= shows. Exit 2.
993- A refusal says who may and what to do instead: =only admins of acme
994 can manage teams; ask one to add you=. Exit 4. A thing that does not
995 exist, or that you may not know exists, is exit 3.
996- stdout is the result; stderr is everything else: progress, a note
997 that output was truncated, errors.
998- Verbs: =create= and =delete= for things with their own identity
999 (repository, issue, release, team), =add= and =remove= for attaching
1000 something to them (a key, a member, a label on an issue), =set= for
1001 a value, =revoke= for a credential, =show= and =list= for reads.
1002
1003** At a terminal
1004
1005The =gitbay= CLI sends a leading =--term=<cols>[,<option>]...= argument on
1006the SSH command line when stdout is a terminal (OpenSSH's multiplexed
1007sessions, which the CLI uses, do not forward a session's =SetEnv=).
1008The argument goes first: the server reads =--term=<v>= only as the
1009first argument, and ignores it over HTTP. =color= is the one option
1010the server acts on; it ignores options it does not know. The server
1011then prints:
1012
1013- lists padded and fitted to the width (the title or description
1014 column is cut with =…= first; a title or description column blank on
1015 most rows is held to a third of the width), with no trailing
1016 whitespace. A list has a dim header row only when a column is a
1017 number or a size, which would not say what it counts without one. The
1018 next page is a =Next page= line on stderr with the command that
1019 fetches it (piped output keeps a =next\t<cursor>= row instead);
1020- colour by meaning: green for open, success, approved, verified; magenta
1021 for merged; red for failures, =private=, and bad or untrusted
1022 signatures; dim for closed, pending, archived, unsigned; yellow only
1023 for what waits on you (an unverified address); references (=#12=,
1024 =krz/gitbay=, SHAs) dim. A marker piped output keeps as its own
1025 trailing cell (=[archived]=, =primary=) joins the state at a terminal:
1026 =public, archived=;
1027- every list and show drawn as a screen: a header block of =Label:=
1028 lines (labels dim), the description or notes, sections titled =Title
1029 (n)= in bold blue, and a legend under a rule of the commands that
1030 apply now, grouped and chosen from the item's state and your access.
1031 Rows have no header (unless a column is a number or a size) and no
1032 indent: a dim reference, a state glyph, the text, then dim facts
1033 joined by =·=. The glyphs are =✓= passed, =✗= failed or blocked, =◐=
1034 still going, =●= waiting on you (your review requested, an issue
1035 assigned to you, an unread notification, a pending account, an
1036 overdue milestone, the session's own key), =○= closed or draft. A
1037 section cut short ends with =+n more= and the command for the rest.
1038 Below 80 columns the legend's groups stack; a suggested command is
1039 never cut, so it may run past the width. =dashboard= opens with what
1040 waits on you; for admins a =Problems= line counts background
1041 failures, and =admin stats= has the detail. =repo commit= prints its
1042 screen, then the diff. Secrets never appear on a screen, and a
1043 webhook URL shows its scheme and host only. =wiki show= piped prints
1044 the page source verbatim;
1045- errors on stderr after a red =error:=, a mistyped flag with the flag
1046 it is closest to (=did you mean --state?=), and a usage line wrapped
1047 to the width between its bracketed groups, one alternative per line;
1048- help with flag descriptions, defaults, and examples; =gitbay --help=
1049 groups commands under WORK, REPOSITORIES, YOU and INSTANCE;
1050 =help --json= adds =flags= and =examples=.
1051
1052=NO_COLOR=, =TERM=dumb= and =--no-color= drop the colour. =show=,
1053=diff= and =log= at a terminal go through =$GITBAY_PAGER=, else
1054=$PAGER=, else =less= (with =LESS=FRX= when =LESS= is unset); an empty
1055=GITBAY_PAGER= turns paging off. Paging never applies to =--follow=,
1056=--json=, or piped output.
1057
1058Inside a clone of a repository on the instance, the CLI adds
1059=here=<owner/name>= to =--term=, so a command the output suggests leaves
1060that repository out, as the CLI would infer it. An instance that does
1061not know the option ignores it.
1062
1063=GITBAY_TERM=off= stops the CLI sending =--term= at all, for an
1064instance older than v1.36.0, which refuses the argument as an unknown
1065command.
1066
1067Two more options say what the terminal can show. =truecolor= (sent when
1068=COLORTERM= is =truecolor= or =24bit=) puts a dot in each label's own
1069colour beside its hex in =label list=. =links= (sent in iTerm2, WezTerm,
1070Ghostty, VS Code, kitty and VTE terminals, or when =GITBAY_LINKS=1=;
1071=GITBAY_LINKS=0= stops it) makes references in lists open their page,
1072as OSC 8 hyperlinks. A terminal outside that list would print the
1073escape's text, so it is not sent there by default. =GITBAY_TERM=basic=
1074sends only the width and colour, for an instance older than the release
1075that added these options, which turns any option it does not know into
1076plain output.
1077
1078Stock ssh without the CLI's multiplexing gets the plain output unless
1079it passes the same leading argument or sets the environment variable
1080sshd is told to accept. OpenSSH parses options after the host, so the
1081argument goes after =--=:
1082
1083#+begin_src sh
1084ssh git@gitbay.org -- --term=120,color issue list krz/gitbay
1085ssh -o SetEnv=GITBAY_TERM=120,color git@gitbay.org issue list krz/gitbay
1086#+end_src
1087
1088* Scripting
1089
1090Every read command takes =--json= and emits one envelope:
1091={"protocol_version": 1, "data": ...}=. stdout is data, stderr is
1092messages. Exit codes are stable: 0 ok, 1 failure, 2 usage, 3 not found,
10934 denied, 5 server/protocol error. Usage means the arguments were wrong;
1094a refusal — a name already taken, a state that does not allow the change
1095— is a failure. Nothing ever prompts; destructive commands take =--yes=.
1096
1097For HTTP automation see [[API]].
1098
1099* CLI setup
1100
1101#+begin_src sh
1102gitbay remote add myforge forge.example.org [--port n] [--user u] --default
1103gitbay remote list
1104gitbay init [name] [--private] # git init + repo create + origin, in one step
1105#+end_src
1106
1107Configuration lives at =~/.config/gitbay/config.toml=. The CLI shells
1108out to your real =ssh=, so =~/.ssh/config=, the agent, and hardware keys
1109all apply. It shares one connection per instance through a control
1110socket under =~/.ssh=: the first command in five minutes pays the
1111handshake and the rest ride it. =no_multiplex = true= on an instance in
1112the config turns that off. Man pages: =gitbay man --dir <dir>=; completions:
1113=gitbay completion bash|zsh|fish=.