.gitbay/wiki/Parity.org
384 lines · 19253 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. Anything whose input is a credential — secrets, mirror
19tokens, API tokens — stays SSH-only by design: the web dispatcher
20refuses =SSHOnly= commands outright. Session minting is not one of
21them: what a browser submits to ask for a login link is a username or
22an address, and the credential it gets back travels by mail.
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| close | yes | yes | yes |
44| close in favour of another | yes | yes | yes |
45| create | yes | yes | yes |
46| draft, ready | yes | yes | yes |
47| search title and body | yes | yes | yes |
48| create from a fork | yes | yes | yes |
49| retarget | yes | yes | yes |
50| milestone | yes | yes | yes |
51| labels | yes | yes | no |
52| filter by label | yes | yes | no |
53| request a review | yes | yes | yes |
54| choose body markup | yes | yes | yes |
55| stacked merge requests | yes | yes | yes |
56| revisions | yes | yes | yes |
57| range-diff | yes | no | yes |
58| merge gates | yes | yes | yes |
59
60Reviews and checks carry the time they last said something, and a
61=ci/<job>= check carries how long its build ran, so a merge request
62reads without opening the build. Statuses posted through =status set=
63have no build and report only the time. A merged or closed merge
64request names who resolved it and when; a row without that stamp — a
65=migrate= import, or one merged before krz/gitbay!137 with no
66=mr.merged= event to backfill from — says neither rather than
67inventing a time.
68
69A merge request whose target is another open merge request's source
70branch is stacked on it: =mr show= and the page say so both ways, and
71when the lower one merges, everything stacked on it is retargeted onto
72what it merged into with its reviews kept. A squash or rebase merge is
73refused while anything is stacked on the merge request, since it would
74rewrite the commits the stack builds on. All three surfaces show the
75stack both ways.
76
77A draft merge request is open but not asking: it does not merge, and it
78does not appear in anyone's review queue. Draft is a flag rather than a
79fifth state, so every =state = 'open'= rule still means what it did.
80=mr revisions= lists the heads a merge request has had; the web page
81lists them beside the reviews they staled, and the iOS client has a
82Revisions section. =mr range-diff= compares two heads: the iOS client
83shows it from a revision to the one before, as text; the web has no
84view. Batched review is not built.
85
86=mr review request --add <user>= asks a *particular* person, who then
87carries the merge request in their queue and is notified; =--remove=
88withdraws the ask. Without one the queue is computed from involvement —
89what you own, are granted, or reach through an org or team — so a
90collaborator with write access who has not touched a thread hears
91nothing until they do (krz/gitbay#145).
92
93Creating from a fork works anywhere the source can be typed as
94=owner/name:branch=. The web's source picker offers the branches of
95every fork the viewer can push to in that form, and the repository
96header has the fork control, so the whole path — fork, edit a file,
97propose — runs in a browser. Retargeting moves an open
98merge request onto another branch of the same repository and stales the
99reviews, since an approval was of the diff against the old branch.
100
101* Issues
102
103| capability | cli | web | ios |
104|---------------------+-----+-----+-----|
105| read, list, filter | yes | yes | yes |
106| filter by label, assignee, author, milestone | yes | yes | yes |
107| search title and body | yes | yes | yes |
108| create | yes | yes | yes |
109| comment | yes | yes | yes |
110| edit title and body | yes | yes | yes |
111| close and reopen | yes | yes | yes |
112| labels, assignees | yes | yes | yes |
113| milestone | yes | yes | yes |
114| milestone list | yes | yes | yes |
115| labels: list, colour | yes | yes | yes |
116| choose body markup | yes | yes | yes |
117| issue templates | yes | yes | yes |
118| milestone create, close, reopen | yes | no | yes |
119| org labels: set, list, remove | yes | list | yes |
120| org milestones: create, list, close, reopen | yes | list | yes |
121| closes across repositories | yes | yes | yes |
122
123Labels are created on the fly by =issue label --add= and =mr label
124--add=, and managed by =label list=, =label set <label> --color rrggbb=
125and =label remove=, which takes the label off every issue and merge
126request. One set serves both. The web paints the stored colour on every
127chip and derives one from the name when none is set. The set itself is
128at =/<owner>/<repo>/labels=, linked from the issue list: create,
129recolour and remove, dispatching the same commands.
130
131Org labels and milestones are managed on the CLI, the API and the iOS
132client's org screen; =/<org>/-/labels= and =/<org>/-/milestones= show
133them on the web. The repository
134label page's form exists for colour alone, and three org forms nobody
135asked for were not worth their handlers.
136
137Issue, MR and release bodies, and their comments, carry the markup they
138were written in — =--format md|org= on create, comment and edit, stored
139alongside the text so changing a preference later cannot reinterpret
140prose that already exists. Every surface *renders* the stored format, and
141every surface now offers the choice: the web's create forms, and the iOS
142composer on create, edit and comment. An edit starts on the format its
143body was stored in, since starting elsewhere would silently reinterpret
144it on the next save. Diff-line comments have no format column and are
145always markdown.
146
147* Repositories
148
149| capability | cli | web | ios |
150|-----------------------------+-----+-----+-----|
151| browse files | yes | yes | yes |
152| read a file | yes | yes | yes |
153| commit log | yes | yes | yes |
154| commit log at a ref | yes | yes | yes |
155| one commit with its patch | yes | yes | yes |
156| blame | yes | yes | yes |
157| search file contents | yes | yes | yes |
158| compare two refs | yes | yes | yes |
159| branches and tags | yes | yes | yes |
160| wiki (read) | yes | yes | yes |
161| download an archive | yes | yes | n/a |
162| edit a file | yes | yes | yes |
163| create | yes | yes | yes |
164| fork | yes | yes | yes |
165| pin | yes | yes | yes |
166| bookmark | yes | yes | yes |
167| bookmark list | yes | yes | yes |
168| watch, unwatch | yes | yes | yes |
169| mute | yes | no | yes |
170| settings, protection | yes | yes | yes |
171| default branch | yes | yes | yes |
172| merge requests only | yes | yes | yes |
173| protected tags | yes | yes | yes |
174| require codeowners | yes | yes | yes |
175| access grants | yes | no | yes |
176| effective access | yes | no | yes |
177| webhooks | yes | no | yes |
178| runners attach, list, detach | yes | yes | n/a |
179| import from a remote | yes | no | yes |
180| topics, website | yes | yes | yes |
181| visibility | yes | yes | yes |
182| archive (read-only flag) | yes | yes | yes |
183| release list, show | yes | yes | yes |
184| atom feeds | n/a | yes | n/a |
185| release create, edit | yes | yes | yes |
186| build list | yes | yes | yes |
187| build list filters (ref, status, job) | yes | yes | yes |
188| build show (one build) | yes | yes | yes |
189| build log | yes | yes | yes |
190| build jobs | yes | yes | yes |
191| build trigger | yes | yes | yes |
192| build cancel | yes | yes | yes |
193| job image (ci.yml) | yes | n/a | n/a |
194| dependency checks on/off | yes | yes | yes |
195| dependency status | yes | yes | yes |
196| delete, transfer | yes | no | no |
197| rename | yes | no | yes |
198| release delete | yes | yes | yes |
199| release asset add | yes | no | n/a |
200| release asset remove | yes | no | yes |
201| snippet create, edit, delete | yes | yes | yes |
202| snippet show, list | yes | yes | yes |
203| snippet file set, get, remove | yes | yes | yes |
204
205No row is web-only any more. Blame, file editing, log at a ref,
206archive, the public listing and the wiki were all in that state — a
207handler reading git or the store directly instead of dispatching a
208command — until krz/gitbay!99, !101 and !102 gave them commands.
209
210=build cancel= withdraws a queued build, or ends a running one: the
211server closes the runner's log session and the runner kills the step
212within a couple of seconds. Cancelling a duplicate of a commit that
213already passed the job puts that result back on the commit. The build
214page carries the button while a build is still cancellable.
215
216A pin and a bookmark are different things and are stored separately. A
217pin is private quick access to what you are working on, and drives the
218rail; a bookmark is public, says a repository is worth coming back to,
219and its count is the only popularity signal on the instance. Bookmarking
220needs read access only — it is something you do to someone else's
221repository — and a repository bookmarked while public and since made
222private drops out of the listing rather than leaking that it exists
223(krz/gitbay#146).
224
225The web's watch and pin controls write the store directly instead of
226dispatching =repo watch= and =repo pin=. That is why the web cannot
227mute: its toggle knows watching and default only.
228
229Dependency checks are off until a repository's admin turns them on: the
230check tells a public registry what the repository depends on. =repo deps
231status= lists what is behind; the repository's settings page renders the
232same report under the toggle — last check, last error, the tracking
233issue, and the rows. The issue the worker opens, rewrites and closes is
234still the copy every other surface reads.
235
236The wiki row covers reading. A wiki lives at =.gitbay/wiki/= on the
237default branch, so editing one is editing a file in the repository —
238a push, or =repo commit-file= — and there is no wiki-specific edit row
239to have parity on. The web editor reaches it on any repository that
240permits server-authored commits; one requiring verified signatures
241does not, because the server signs nothing on a user's behalf, so
242those wikis are push-only.
243
244=n/a= marks a capability deliberately absent from a surface rather than
245missing from it. Archive download is =n/a= on iOS: the read API carries
246a command's stdout as a JSON string, so a gzip stream cannot survive it,
247and the app has nowhere useful to put a tarball — the brief rules out
248local git, so there is no clone, checkout or build to feed. The web's
249route stays the way to get one. =release asset add= is =n/a= on iOS for
250the same reason from the other direction: the file arrives on stdin,
251which the API carries as a JSON string. Attaching a runner is =n/a= on
252iOS for a plainer reason: the key is generated by =gitbay-runner init=
253on the machine that will run builds, and that machine's terminal (the
254command =init= prints) or the settings page is where the paste happens.
255A phone has neither the key nor the runner.
256
257A README or wiki page's org renders per surface: the web through
258go-org, the iOS client through the shared OrgSwift package
259(=krz/org-swift=). OrgSwift is held to orgo's output by the
260=krz/org-conformance= corpus — golden renderings from orgo, itself
261diffed against Emacs =ox-html= — so constructs the iOS client used to
262approximate (tables, footnotes, timestamps, heading tags, nested lists)
263now render the way the reference does. go-org is not yet on the corpus.
264
265* Discovery
266
267| capability | cli | web | ios |
268|----------------------------------+-----+-----+-----|
269| search repositories | yes | yes | yes |
270| search issues and merge requests | yes | yes | yes |
271| browse all public repositories | yes | yes | yes |
272| profile page | yes | yes | yes |
273| profile about and links | yes | yes | yes |
274| activity feed | yes | yes | yes |
275| command reference | yes | no | n/a |
276
277=explore= is the listing without a query; =repo search= is the one with.
278=search= is both plus the title and body of every issue and merge
279request the caller can read, over FTS5; the web serves it at =/search=
280with a field in the rail, and an anonymous visitor gets the public rows
281from the same query. =issue list --search= and =mr list --search= narrow
282one repository. What someone types is quoted term by term, so an FTS5
283operator — =c++=, =AND=, a lone quote — is a word to match and never a
284syntax error. =repo grep= remains the per-repository file-contents
285search.
286
287About text renders as markdown or org-mode per the =about_format= it
288was stored with. The iOS client decodes and renders both,
289through the same OrgSwift path a README takes.
290
291=help= lists the command registry. Bare it is an index, one line per
292command, sorted. With a prefix (=help mr=) it adds each command's
293argument syntax, which is the only place flags are written down;
294=gitbay <cmd> --help= asks the server for the same thing, and =--json=
295returns ={path, summary, usage}=. No web page renders it, and a native
296client has no use for one (krz/gitbay#57).
297
298* Accounts
299
300| capability | cli | web | ios |
301|-----------------------------+-----+-----+-----|
302| SSH keys: list, add, remove | yes | yes | yes |
303| SSH key label | yes | yes | yes |
304| PGP keys: list, add, remove | yes | yes | yes |
305| email add and verify | yes | yes | yes |
306| email list, remove, primary | yes | yes | yes |
307| dashboard aggregate | yes | yes | yes |
308| notification inbox | yes | yes | yes |
309| activity mail on, off | yes | yes | yes |
310| watch writable repos | yes | yes | yes |
311| API token mint | yes | no | no |
312| account export bundle | yes | yes | n/a |
313| profile set | yes | yes | yes |
314| request a login link | n/a | yes | n/a |
315| account import bundle | yes | no | n/a |
316
317Notifications land in an inbox row per recipient whether or not the
318instance sends mail, and mail is the second half when SMTP is
319configured. =notifications list= reads it, unread by default;
320=notifications read <id>... | --all= clears it; the dashboard and the
321web rail carry the unread count. =repo watch= adds you to a
322repository's notifications, =repo mute= silences it, and =repo unwatch=
323returns you to the default from either — told about work you are part
324of, nothing more. =notifications settings watch on= widens that default
325to every repository you can write to, without a row per repository. A
326mute wins over owning the repository, having written the thread, or the
327preference.
328
329A login link is requested from the login page by username or verified
330address, and arrives by mail: it works once and expires in fifteen
331minutes. The row is =n/a= for the CLI because a terminal with a
332registered key runs =web login=, which mints a link directly and needs
333no mail. It exists because an account with no SSH key had no way into
334the web at all (krz/gitbay#155). The page offers the form only when the
335instance has SMTP configured; there is no separate switch. The response
336never says whether the account exists. It is =n/a= on iOS for the same
337reason it is on the CLI, from the other end: the app authenticates with
338a bearer token pasted at sign-in, so a one-time link into the web
339unlocks nothing it can use.
340
341The account export bundle is =n/a= on iOS on the archive-download
342argument above — a JSON bundle has nowhere useful to land on a phone,
343and the web route stays the way to get one.
344
345=profile set= carries description, website, about and links. It is not
346=SSHOnly= — nothing about a bio is a credential, and the JSON API runs
347it. The account settings page has the form, and so does the iOS client:
348=--link= replaces the whole set rather than appending, so a client sends
349every link it keeps on every save, and =--link ''= is how they are
350cleared.
351
352* Organizations
353
354| capability | cli | web | ios |
355|---------------------------+-----+-----+-----|
356| read members and teams | yes | yes | yes |
357| members add, remove, role | yes | yes | yes |
358| teams create, delete | yes | yes | yes |
359| team members | yes | yes | yes |
360| team repo grants | yes | yes | yes |
361| create, rename | yes | yes | yes |
362| delete | yes | n/a | n/a |
363| profile | yes | no | yes |
364
365* Pagination
366
367=issue list=, =mr list=, =repo list=, and =feed= take =--limit <n>=
368and =--cursor <c>=. Cursors are opaque; each page carries the next
369one. Without the flags a list stays complete, so existing scripts are
370unchanged. The web pages the issue and merge request lists at fifty
371with the same cursors; iOS pages with them too.
372
373* SSH only, by design
374
375Build secrets, mirror configuration and tokens, custom domain claims,
376API token minting, web session listing and revocation, deploy keys,
377account and instance administration. Deleting, transferring or renaming
378a repository is also CLI-only, as is deleting an organization and
379pruning merge request heads (=admin mr prune=): each removes or moves
380what clone URLs point at, and wants a typed command, not a button.
381
382These are the only rows where a =no= is intended. Everywhere else a
383=no= is work outstanding, and =n/a= means a surface cannot usefully
384carry the capability at all — see the archive note above.