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. | |