docs/specs/2026-09-04-wiki-in-repo-design.md

v1.24.0
gitbay/docs/specs/2026-09-04-wiki-in-repo-design.md rendered · source · history · blame · raw

156 lines · 7243 bytes

  1# Wikis in the repository
  2
  3Ref #169, #170. Milestone v1.14.0. Two merge requests: path filters first,
  4because without them the second one makes the wiki worse to use than it is
  5today.
  6
  7## Problem
  8
  9A wiki is a companion bare repo at `<owner>/<name>.wiki.git`, created on first
 10push (`internal/sshd/sshd.go:359-362, 473-480`). It has no row in the store;
 11access derives from the parent.
 12
 13That buys one thing — prose edits stay out of the code repository's history,
 14its protected branches and its builds — and costs four:
 15
 16- **Invisible to the store.** Backup verification cannot distinguish a wiki
 17  from a leaked directory and prints so (`cmd/gitbayd/backup.go:270`). Quotas,
 18  `repo list`, search and the activity feed do not see it either.
 19- **Push is the only write path.** There is no `wiki edit` command and the web
 20  renders wikis read-only. It is the one capability that does not follow "the
 21  capability lands as a control command, then the surfaces render it".
 22- **`.wiki` is a reserved name suffix**, permanently
 23  (`internal/policy/names.go:62`).
 24- **A second clone URL** that cannot be discovered without knowing the
 25  convention.
 26
 27## Approach
 28
 29Pages move to `.gitbay/wiki/` on the default branch, beside `ci.yml` and
 30`CODEOWNERS`, which is already where repository-scoped gitbay metadata lives.
 31The companion path is removed rather than kept alongside: one wiki exists across
 3270 repositories on this instance, so a compatibility path would be permanent
 33cost for a single migration.
 34
 35Rejected: an orphan ref (`refs/wiki/main`) in the main repository. It keeps
 36prose off the code DAG and out of normal clones, but it is invisible to plain
 37git tooling, needs a custom refspec to fetch, and would need its own write
 38commands to be usable at all. The gain over a directory is that prose stays out
 39of `git log`; the cost is a wiki nobody can edit without forge-specific
 40instructions.
 41
 42## What this does and does not buy
 43
 44**Does:** one clone, one backup, one permission model, one history. Wiki edits
 45become reviewable through merge requests, approvals and CODEOWNERS for projects
 46that want that.
 47
 48**Does not:** web editing on every repository. `repo commit-file` is the command
 49behind the web editor, and it refuses repositories that require verified
 50signatures, because the server authors those commits unsigned and will not write
 51a commit the repository's own policy would reject
 52(`internal/control/commitfile.go:28-35`). `krz/gitbay` requires signed commits,
 53so its wiki stays push-only. That is not a regression — it is push-only today —
 54but the parity gain is conditional and should not be claimed otherwise.
 55
 56## Design
 57
 58### Phase 1 — path filters (#169)
 59
 60`ci.Job` gains `Paths` and `PathsIgnore`, each a list of globs matched against
 61the changed-file list from `gitutil.DiffFiles(dir, old, new)`, which already
 62exists (`internal/gitutil/merge.go:276`).
 63
 64**Matching.** `path.Match` alone is not enough: Go's `*` does not cross `/`, so
 65`.gitbay/wiki/**` matches `.gitbay/wiki/Home.md` but not
 66`.gitbay/wiki/sub/Page.md` — a filter that appears to work and quietly misses
 67nested files. Define it explicitly: a pattern ending in `/**` matches that
 68directory and everything beneath it at any depth, implemented as a prefix
 69check; every other pattern goes to `path.Match` against the full path. A test
 70must cover the nested case, since that is the one a reader will assume works.
 71
 72**Selection.** A job runs when `Paths` is empty or at least one changed file
 73matches one of its patterns. It is then skipped only when every changed file
 74matches at least one `PathsIgnore` pattern. A push touching one ignored file
 75and one other file runs the job.
 76
 77**Fail open.** A job runs whenever the filter cannot be evaluated: a new branch
 78with no diff base (`old` is empty or all zeros), a `DiffFiles` error, or a job
 79declaring neither key. A filter that silently skips CI when it cannot tell is
 80worse than no filter, because the failure is invisible.
 81
 82`QueueBranchBuilds` is shared with the merge path, which moves a ref without
 83reaching a hook (`internal/hookd/hookd.go:272-278`), so the old sha must reach
 84both callers. `u.Old` is already available at the hook call site
 85(`hookd.go:212`).
 86
 87Tag jobs are unaffected: a tag build has no meaningful diff base.
 88
 89### Phase 2 — the move (#170)
 90
 91**Storage.** `.gitbay/wiki/*.{md,org,markdown}` on `repo.DefaultBranch`.
 92`wikiExts` is unchanged.
 93
 94**Resolution.** `wikiPages` and `wikiHome` keep their logic; they read a tree at
 95`.gitbay/wiki` on the default branch instead of the root of the companion's
 96`main`. `wiki list` and `wiki show` keep their argv, their JSON fields and their
 97exit codes — only resolution moves, so no surface changes shape.
 98
 99`HasWiki` (`internal/httpd/web.go:336`) becomes "the default branch holds a
100non-empty `.gitbay/wiki/` tree". The web route `/{owner}/{repo}/wiki` is
101externally identical.
102
103**Writing.** A push, like any other file. `repo commit-file <owner/name>
104.gitbay/wiki/Page.md --ref <branch> --file -` is the existing command and the
105existing web editor path; no `wiki edit` is added, because it would duplicate
106one.
107
108**Removal.** The `.wiki` suffix branch and `runWikiGit` in
109`internal/sshd/sshd.go`; `wikiDir` in `internal/control/wiki.go` and
110`internal/httpd/wiki.go`; the reservation in `internal/policy/names.go:62` and
111the test asserting it; companion rename and delete in
112`internal/control/repo.go:396,452`; the special-case wording in
113`cmd/gitbayd/backup.go:270`.
114
115**Migration.** One repository, by hand, not a shipped command:
116
117```
118git bundle create gitbay-wiki-$(date +%F).bundle --all   # in a clone of the companion
119git subtree add --prefix=.gitbay/wiki <wiki-url> main
120```
121
122`git subtree add` preserves the wiki's history inside the repository's DAG
123rather than flattening it into one import commit. Verify pages render, keep the
124bundle, then remove the bare repo from the server.
125
126Add `paths-ignore: [".gitbay/wiki/**"]` to this repository's own heavy jobs in
127the same change, so the migration does not immediately demonstrate the problem
128phase 1 exists to prevent.
129
130## Tests
131
132Phase 1:
133- A job with `paths` matching a changed file runs; one matching nothing does not.
134- `paths-ignore` covering every changed file skips the job; covering some of
135  them does not.
136- A new branch runs every job.
137- A `DiffFiles` failure runs every job.
138- A job with neither key runs, unchanged from today.
139- Tag builds are unaffected.
140
141Phase 2:
142- `wiki list` and `wiki show` return the same JSON for a repository whose pages
143  are in `.gitbay/wiki/` as the old commands returned for a companion.
144- The web wiki tab renders, and reports no wiki when the directory is absent.
145- A repository with no `.gitbay/wiki/` reports no wiki rather than erroring.
146- Pushing to `<name>.wiki.git` is refused, since the route is gone.
147- A repository may now be named `something.wiki`.
148- `repo commit-file` writes a page on a repository that permits it, and is
149  refused on one requiring verified signatures.
150
151## Documentation
152
153CLAUDE.md's "the repo's own documentation lives in the wiki" stops being true of
154the storage and needs rewording. The wiki's Parity rows for wiki capabilities
155change, and the "SSH only, by design" list does not mention wikis, so it needs
156no edit.