.gitbay/wiki/Parity.org
489 lines · 25049 bytes
1#+title: Parity
2
3Which surface can do what. The CLI is meant to be the complete
4interface: every capability exists over SSH, and the other surfaces
5dispatch the same control commands rather than reimplementing them, so
6they cannot drift. This page is updated in the merge request that
7changes a row.
8
9Three surfaces now: the CLI over SSH, the web, and the iOS client
10(krz/gitbay-ios). A =no= in the cli column is a defect, not a
11preference — it means some surface reached around the registry and
12stranded a capability where only it can reach.
13
14* Rule
15
16A capability lands over SSH first. If it belongs to the
17triage/review/respond loop, it lands on the web in the same merge
18request. Nothing is held back from a surface any more (#234): the
19registry has no flag for it, and what a caller may do is the account's
20rights narrowed by the scope of the key or token it arrived with,
21decided in one place. A =no= in the web or ios column is a page nobody
22has built yet, not a refusal.
23
24Rows are one page or one action each. Grouped rows hide gaps, twice
25now: "browse, log, blame, search" read as covered while blame had no
26command at all, and "build list, log" read as covered while the job
27list a trigger can name had none (krz/gitbay#50), leaving the picker
28browser-only and the iOS build screen unable to say more than the log.
29
30* Merge requests
31
32| capability | cli | web | ios |
33|------------------------+-----+-----+-----|
34| read, diff, commits | yes | yes | yes |
35| review and check times | yes | yes | yes |
36| who resolved it, when | yes | yes | yes |
37| comment | yes | yes | yes |
38| edit title and body | yes | yes | yes |
39| review (approve etc.) | yes | yes | yes |
40| resolve a thread | yes | yes | yes |
41| comment on a diff line | yes | yes | yes |
42| merge (all strategies) | yes | yes | yes |
43| merge when ready, cancel | yes | yes | no |
44| close | yes | yes | yes |
45| close in favour of another | yes | yes | yes |
46| create | yes | yes | yes |
47| draft, ready | yes | yes | yes |
48| search title and body | yes | yes | yes |
49| create from a fork | yes | yes | yes |
50| retarget | yes | yes | yes |
51| milestone | yes | yes | yes |
52| labels | yes | yes | yes |
53| filter by label | yes | yes | yes |
54| request a review | yes | yes | yes |
55| choose body markup | yes | yes | yes |
56| preview body markup | n/a | yes | no |
57| stacked merge requests | yes | yes | yes |
58| revisions | yes | yes | yes |
59| range-diff | yes | yes | yes |
60| merge gates | yes | yes | yes |
61
62Reviews and checks carry the time they last said something, and a
63=ci/<job>= check carries how long its build ran, so a merge request
64reads without opening the build. Statuses posted through =status set=
65have no build and report only the time. A merged or closed merge
66request names who resolved it and when; a row without that stamp — a
67=migrate= import, or one merged before krz/gitbay!137 with no
68=mr.merged= event to backfill from — says neither rather than
69inventing a time.
70
71A merge request whose target is another open merge request's source
72branch is stacked on it: =mr show= and the page say so both ways, and
73when the lower one merges, everything stacked on it is retargeted onto
74what it merged into with its reviews kept. A squash or rebase merge is
75refused while anything is stacked on the merge request, since it would
76rewrite the commits the stack builds on. All three surfaces show the
77stack both ways.
78
79A draft merge request is open but not asking: it does not merge, and it
80does not appear in anyone's review queue. Draft is a flag rather than a
81fifth state, so every =state = 'open'= rule still means what it did.
82=mr revisions= lists the heads a merge request has had; the web page
83lists them beside the reviews they staled, and the iOS client has a
84Revisions section. =mr range-diff= compares two heads: the iOS client shows it from a
85revision to the one before, as text; the web renders the same view, with
86a "compare to previous" link on each revision after the first. Batched
87review — draft diff comments held with =mr comment --pending= and sent
88together with =--comment=/=--discard= or a verdict — is built and the
89web uses it: composing review comments before publishing them is the same
90round trip as the CLI's =--pending= flag.
91
92=mr review request --add <user>= asks a *particular* person, who then
93carries the merge request in their queue and is notified; =--remove=
94withdraws the ask. Without one the queue is computed from involvement —
95what you own, are granted, or reach through an org or team — so a
96collaborator with write access who has not touched a thread hears
97nothing until they do (krz/gitbay#145).
98
99Creating from a fork works anywhere the source can be typed as
100=owner/name:branch=. The web's source picker offers the branches of
101every fork the viewer can push to in that form, and the repository
102header has the fork control, so the whole path — fork, edit a file,
103propose — runs in a browser. Retargeting moves an open
104merge request onto another branch of the same repository and stales the
105reviews, since an approval was of the diff against the old branch.
106
107* Issues
108
109| capability | cli | web | ios |
110|---------------------+-----+-----+-----|
111| read, list, filter | yes | yes | yes |
112| filter by label, assignee, author, milestone | yes | yes | yes |
113| search title and body | yes | yes | yes |
114| create | yes | yes | yes |
115| comment | yes | yes | yes |
116| edit title and body | yes | yes | yes |
117| close and reopen | yes | yes | yes |
118| labels, assignees | yes | yes | yes |
119| milestone | yes | yes | yes |
120| milestone list | yes | yes | yes |
121| labels: list, colour | yes | yes | yes |
122| choose body markup | yes | yes | yes |
123| preview body markup | n/a | yes | no |
124| issue templates | yes | yes | yes |
125| milestone create, close, reopen | yes | no | yes |
126| org labels: set, list, remove | yes | list | yes |
127| org milestones: create, list, close, reopen | yes | list | yes |
128| closes across repositories | yes | yes | yes |
129
130Labels are created on the fly by =issue label --add= and =mr label
131--add=, and managed by =label list=, =label set <label> --color rrggbb=
132and =label remove=, which takes the label off every issue and merge
133request. One set serves both. The web paints the stored colour on every
134chip and derives one from the name when none is set. The set itself is
135at =/<owner>/<repo>/labels=, linked from the issue list: create,
136recolour and remove, dispatching the same commands.
137
138Org labels and milestones are managed on the CLI, the API and the iOS
139client's org screen; =/<org>/-/labels= and =/<org>/-/milestones= show
140them on the web. The repository
141label page's form exists for colour alone, and three org forms nobody
142asked for were not worth their handlers.
143
144Issue, MR and release bodies, and their comments, carry the markup they
145were written in — =--format md|org= on create, comment and edit, stored
146alongside the text so changing a preference later cannot reinterpret
147prose that already exists. Every surface *renders* the stored format, and
148every surface now offers the choice: the web's create forms, and the iOS
149composer on create, edit and comment. An edit starts on the format its
150body was stored in, since starting elsewhere would silently reinterpret
151it on the next save. Diff-line comments have no format column and are
152always markdown.
153
154Every web form that takes markup has a Preview button beside its own
155submit: issue and merge request create, their edit and comment boxes,
156release create and edit, and the file editor on
157a path the forge renders. It posts to the form's own action, which
158renders the draft and hands the page back without writing, so what you
159see is the rendering the thread will show, autolinks included. It is a
160round trip rather than a live preview because the instance serves no
161JavaScript, the same way the blob page's rendered/source toggle works.
162The row is =n/a= on the CLI: a terminal has no form to preview, and
163=issue show= already renders. Diff-line comments are left out — they
164sit inside the diff, where a page-level preview has nowhere to go.
165
166The iOS client resolves no autolinks: =#N= and =owner/name#N= render as
167text in its threads, so a preview there will show the app's rendering
168rather than the one the web page shows.
169
170* Repositories
171
172| capability | cli | web | ios |
173|-----------------------------+-----+-----+-----|
174| browse files | yes | yes | yes |
175| read a file | yes | yes | yes |
176| render a README | yes | yes | yes |
177| commit log | yes | yes | yes |
178| commit log at a ref | yes | yes | yes |
179| one commit with its patch | yes | yes | yes |
180| blame | yes | yes | yes |
181| search file contents | yes | yes | yes |
182| compare two refs | yes | yes | yes |
183| branches and tags | yes | yes | yes |
184| wiki (read) | yes | yes | yes |
185| download an archive | yes | yes | n/a |
186| edit a file | yes | yes | yes |
187| preview an edited markup file | n/a | yes | no |
188| create | yes | yes | yes |
189| fork | yes | yes | yes |
190| pin | yes | yes | yes |
191| bookmark | yes | yes | yes |
192| bookmark list | yes | yes | yes |
193| bookmarks on your profile | n/a | yes | no |
194| watch, unwatch | yes | yes | yes |
195| mute | yes | yes | yes |
196| settings, protection | yes | yes | yes |
197| default branch | yes | yes | yes |
198| merge requests only | yes | yes | yes |
199| protected tags | yes | yes | yes |
200| require codeowners | yes | yes | yes |
201| require contexts | yes | yes | no |
202| access grants | yes | no | yes |
203| effective access | yes | no | yes |
204| webhooks | yes | no | yes |
205| runners attach, list, detach | yes | yes | n/a |
206| import from a remote | yes | no | yes |
207| topics, website | yes | yes | yes |
208| visibility | yes | yes | yes |
209| archive (read-only flag) | yes | yes | yes |
210| release list, show | yes | yes | yes |
211| atom feeds | n/a | yes | n/a |
212| release create, edit | yes | yes | yes |
213| preview release notes | n/a | yes | no |
214| build list | yes | yes | yes |
215| build list paging (limit, cursor) | yes | yes | no |
216| build row names its commit | yes | yes | no |
217| build list filters (ref, status, job) | yes | yes | yes |
218| build show (one build) | yes | yes | yes |
219| build log | yes | yes | yes |
220| build log follow (until it ends) | yes | yes | no |
221| build failed step, duration | yes | yes | no |
222| build log one step | yes | yes | no |
223| build log tail | yes | no | no |
224| build jobs | yes | yes | yes |
225| build trigger | yes | yes | yes |
226| build cancel | yes | yes | yes |
227| job image (ci.yml) | yes | n/a | n/a |
228| dependency checks on/off | yes | yes | yes |
229| dependency status | yes | yes | yes |
230| delete, transfer | yes | no | no |
231| rename | yes | no | yes |
232| release delete | yes | yes | yes |
233| release asset add | yes | no | n/a |
234| release asset remove | yes | no | yes |
235| snippet create, edit, delete | yes | yes | yes |
236| snippet show, list | yes | yes | yes |
237| snippet file set, get, remove | yes | yes | yes |
238
239No row is web-only any more. Blame, file editing, log at a ref,
240archive, the public listing and the wiki were all in that state — a
241handler reading git or the store directly instead of dispatching a
242command — until krz/gitbay!99, !101 and !102 gave them commands.
243
244=build cancel= withdraws a queued build, or ends a running one: the
245server closes the runner's log session and the runner kills the step
246within a couple of seconds. Cancelling a duplicate of a commit that
247already passed the job puts that result back on the commit. The build
248page carries the button while a build is still cancellable.
249
250A pin and a bookmark are different things and are stored separately. A
251pin is private quick access to what you are working on, and drives the
252rail; a bookmark is public, says a repository is worth coming back to,
253and its count is the only popularity signal on the instance. Bookmarking
254needs read access only — it is something you do to someone else's
255repository — and a repository bookmarked while public and since made
256private drops out of the listing rather than leaking that it exists
257(krz/gitbay#146).
258
259The web's watch and pin controls dispatch =repo pin=/=repo unpin= and
260=repo watch=/=repo mute=/=repo unwatch=, the same commands the CLI runs
261(krz/gitbay#261). The single watch button cycles default, watching and
262muted.
263
264Dependency checks are off until a repository's admin turns them on: the
265check tells a public registry what the repository depends on. =repo deps
266status= lists what is behind; the repository's settings page renders the
267same report under the toggle — last check, last error, the tracking
268issue, and the rows. The issue the worker opens, rewrites and closes is
269still the copy every other surface reads.
270
271The wiki row covers reading. A wiki lives at =.gitbay/wiki/= on the
272default branch, so editing one is editing a file in the repository —
273a push, or =repo commit-file= — and there is no wiki-specific edit row
274to have parity on. The web editor reaches it on any repository that
275permits server-authored commits; one requiring verified signatures
276does not, because the server signs nothing on a user's behalf, so
277those wikis are push-only.
278
279=n/a= marks a capability deliberately absent from a surface rather than
280missing from it. Archive download is =n/a= on iOS: the read API carries
281a command's stdout as a JSON string, so a gzip stream cannot survive it,
282and the app has nowhere useful to put a tarball — the brief rules out
283local git, so there is no clone, checkout or build to feed. The web's
284route stays the way to get one. =release asset add= is =n/a= on iOS for
285the same reason from the other direction: the file arrives on stdin,
286which the API carries as a JSON string. Attaching a runner is =n/a= on
287iOS for a plainer reason: the key is generated by =gitbay-runner init=
288on the machine that will run builds, and that machine's terminal (the
289command =init= prints) or the settings page is where the paste happens.
290A phone has neither the key nor the runner.
291
292A README or wiki page's org renders per surface: the web through
293go-org, the iOS client through the shared OrgSwift package
294(=krz/org-swift=). OrgSwift is held to orgo's output by the
295=krz/org-conformance= corpus — golden renderings from orgo, itself
296diffed against Emacs =ox-html= — so constructs the iOS client used to
297approximate (tables, footnotes, timestamps, heading tags, nested lists)
298now render the way the reference does. go-org is not yet on the corpus.
299
300* Discovery
301
302| capability | cli | web | ios |
303|----------------------------------+-----+-----+-----|
304| search repositories | yes | yes | yes |
305| search issues and merge requests | yes | yes | yes |
306| browse all public repositories | yes | yes | yes |
307| profile page | yes | yes | yes |
308| profile sections as tabs | n/a | yes | n/a |
309| profile about and links | yes | yes | yes |
310| profile about as a file | yes | yes | yes |
311| activity feed | yes | yes | yes |
312| command reference | yes | no | n/a |
313
314=explore= is the listing without a query; =repo search= is the one with.
315=search= is both plus the title and body of every issue and merge
316request the caller can read, over FTS5; the web serves it at =/search=
317with a field in the rail, and an anonymous visitor gets the public rows
318from the same query. =issue list --search= and =mr list --search= narrow
319one repository. What someone types is quoted term by term, so an FTS5
320operator — =c++=, =AND=, a lone quote — is a word to match and never a
321syntax error. =repo grep= remains the per-repository file-contents
322search.
323
324The about text is =profile/README.{md,org,markdown}= on the default
325branch of =<owner>/.gitbay=, resolved in that order, so the extension
326picks the renderer rather than a stored format. =profile show= reports
327it as =about=, =about_format= and =about_path=. It reads with the
328repository's own access, so a private =.gitbay= is a profile with no
329about text to anyone but its owner and the admins. A repository whose
330name starts with a dot stays out of =explore= and off the profile's
331repository list. On the web a profile is tabs: =/<owner>= is About —
332the text, the year of squares and a log of the newest thirty events —
333then =/<owner>/-/repositories=, =/<owner>/-/bookmarks=,
334=/<owner>/-/snippets=, and =/<owner>/-/people= for an organization's
335members and teams. A tab nobody may open is not offered and its URL is
336a 404: bookmarks are the viewer's own, only a user has snippets, and
337people is the org admin panel. The log counts what the graph over it
338counts — a user's own events, an organization's repositories' — on
339public repositories only; =activity.atom= is the feed that goes back
340further. The CLI's =profile show= is unchanged and still returns all of
341it at once. The iOS client decodes and renders both formats,
342through the same OrgSwift path a README takes.
343
344=help= lists the command registry. Bare it is an index, one line per
345command, sorted. With a prefix (=help mr=) it adds each command's
346argument syntax, which is the only place flags are written down;
347=gitbay <cmd> --help= asks the server for the same thing, and =--json=
348returns ={path, summary, usage}=. No web page renders it, and a native
349client has no use for one (krz/gitbay#57).
350
351* Accounts
352
353| capability | cli | web | ios |
354|-----------------------------+-----+-----+-----|
355| SSH keys: list, add, remove | yes | yes | yes |
356| SSH key label | yes | yes | yes |
357| SSH key expiry and last use | yes | no | no |
358| PGP keys: list, add, remove | yes | yes | yes |
359| email add and verify | yes | yes | yes |
360| email list, remove, primary | yes | yes | yes |
361| dashboard aggregate | yes | yes | yes |
362| notification inbox | yes | yes | yes |
363| activity mail on, off | yes | yes | yes |
364| watch writable repos | yes | yes | yes |
365| push device add | yes | yes | yes |
366| push device list | yes | yes | yes |
367| push device remove | yes | yes | yes |
368| activity push on, off | yes | yes | yes |
369| web colour scheme | yes | yes | n/a |
370| API token mint | yes | yes | no |
371| API token list, revoke | yes | yes | no |
372| API token revoke with what it created | yes | no | no |
373| account export bundle | yes | yes | n/a |
374| profile set | yes | yes | yes |
375| write the profile about | yes | yes | yes |
376| request a login link | n/a | yes | n/a |
377| account import bundle | yes | no | n/a |
378
379Notifications land in an inbox row per recipient whether or not the
380instance sends mail, and mail is the second half when SMTP is
381configured. =notifications list= reads it, unread by default;
382=notifications read <id>... | --all= clears it; the dashboard and the
383web rail carry the unread count. =repo watch= adds you to a
384repository's notifications, =repo mute= silences it, and =repo unwatch=
385returns you to the default from either — told about work you are part
386of, nothing more. =notifications settings watch on= widens that default
387to every repository you can write to, without a row per repository. A
388mute wins over owning the repository, having written the thread, or the
389preference.
390
391Push is the third leg beside inbox and mail, delivered to Apple devices
392an account has registered. =notifications device add= runs on every
393surface like every other command, but no web page offers a form for
394it, because only the iOS app can produce an APNs device token — a
395browser has no way to ask Apple for one. =device list= and =device
396remove= have no such limit and are yes on the web like the rest.
397
398A login link is requested from the login page by username or verified
399address, and arrives by mail: it works once and expires in fifteen
400minutes. The row is =n/a= for the CLI because a terminal with a
401registered key runs =web login=, which mints a link directly and needs
402no mail. It exists because an account with no SSH key had no way into
403the web at all (krz/gitbay#155). The page offers the form only when the
404instance has SMTP configured; there is no separate switch. The response
405never says whether the account exists. It is =n/a= on iOS for the same
406reason it is on the CLI, from the other end: the app authenticates with
407a bearer token pasted at sign-in, so a one-time link into the web
408unlocks nothing it can use.
409
410The account export bundle is =n/a= on iOS on the archive-download
411argument above — a JSON bundle has nowhere useful to land on a phone,
412and the web route stays the way to get one.
413
414=profile set= carries description, website and links; the JSON API runs
415it like any other write. The account settings page has the form, and so
416does the iOS client: =--link= replaces the whole set rather than
417appending, so a client sends every link it keeps on every save, and
418=--link ''= is how they are cleared.
419
420The about text is not among those flags. It is a file, written by a push
421or =repo commit-file= like any other file, which is why writing it is
422yes on every surface: anything that can commit a file can write it. The
423settings page points at the file and offers to create =<owner>/.gitbay=
424when there is none.
425
426* Administration
427
428| capability | cli | web | ios |
429|---------------------------------+-----+-----+-----|
430| account list, filter by state | yes | yes | no |
431| account show | yes | no | no |
432| promote, demote | yes | yes | no |
433| disable, enable | yes | yes | no |
434| account create, delete | yes | no | no |
435| invite | yes | no | no |
436| worker queues | yes | yes | no |
437| runners | yes | no | no |
438| repository list, archive, visibility | yes | no | no |
439| audit log | yes | no | no |
440| instance statistics | yes | no | no |
441
442=/admin/users= carries the account list with the command's state
443filter and cursor, and promote, demote, disable and enable per row;
444demote and disable ask for the username to be typed. The rest is a
445page nobody has built yet rather than a refusal — see below. Account
446creation and deletion stay on the command line on purpose: one takes a
447key, the other cannot be undone.
448
449* Organizations
450
451| capability | cli | web | ios |
452|---------------------------+-----+-----+-----|
453| read members and teams | yes | yes | yes |
454| members add, remove, role | yes | yes | yes |
455| teams create, delete | yes | yes | yes |
456| team members | yes | yes | yes |
457| team repo grants | yes | yes | yes |
458| create, rename | yes | yes | yes |
459| delete | yes | n/a | n/a |
460| profile | yes | no | yes |
461
462* Pagination
463
464=issue list=, =mr list=, =repo list=, =feed= and =build list= take
465=--limit <n>= and =--cursor <c>=. Cursors are opaque; each page carries
466the next one. Without the flags a list stays as it was, so existing
467scripts are unchanged — for =build list= that means the newest fifty
468matching builds, which is what the cursor now reaches past. The web
469pages the issue and merge request lists at fifty and the builds list at
470thirty with the same cursors; iOS pages with them too.
471
472* CLI only, for now
473
474Build secrets, mirror configuration and tokens, custom domain claims,
475web session listing and revocation, deploy keys, and instance
476administration have no page yet. Until #234 these were
477refused outright on the other surfaces; the refusal is gone, so each is
478now a page waiting to be built rather than a rule. A credential still
479travels on stdin wherever it is set, since argv is world-readable in
480/proc and the audit log keeps flag values.
481
482Deleting or transferring a repository stays CLI-only on purpose, as
483does deleting an organization and pruning merge request heads (=admin
484mr prune=): each removes or moves what clone URLs point at, and wants a
485typed command rather than a button. Renaming is the exception: the iOS
486client offers it, and the web has no page yet.
487
488=n/a= means a surface cannot usefully carry the capability at all —
489see the archive note above.