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