docs/specs/2026-09-19-profile-about-repo-design.md

v1.34.0
gitbay/docs/specs/2026-09-19-profile-about-repo-design.md rendered · source · history · blame · raw

223 lines · 8433 bytes

  1# Profile about text in a repository
  2
  3Closes #236.
  4
  5The `about` text on a user or org profile is a column on `users`/`orgs`,
  6set through `profile set --about` and rendered by `aboutHTML`. It is the
  7only long-form, version-worthy prose on the instance that is not a file
  8in a repository. This moves it into one.
  9
 10Links, description and website stay where they are. The issue lists
 11links as a "consider"; moving them buys nothing the DB columns do not
 12already give, and a second file or a front-matter parser is cost without
 13a return.
 14
 15## Where it lives
 16
 17`profile/README.md` or `profile/README.org` on the default branch of a
 18repository named `.gitbay` under the owner's namespace:
 19
 20```
 21cmc/.gitbay
 22└── profile/
 23    └── README.org
 24```
 25
 26Resolution order is the wiki's `wikiExts`: `.md`, `.org`, `.markdown`;
 27the first that exists wins. There are no store rows for the file —
 28access derives from the parent repository, exactly as
 29`internal/control/wiki.go` states for wiki pages.
 30
 31A dot-repo rather than the GitHub-style `cmc/cmc` because `.gitbay` is
 32not single-purpose: it is the place later per-owner configuration
 33(issue templates, org defaults) goes, and a leading dot is the signal
 34that it is infrastructure rather than a project.
 35
 36### Reading
 37
 38A helper in `internal/control/profile.go`:
 39
 40```go
 41// ownerAbout returns the about text and its format for an owner, read
 42// from profile/README.* on the default branch of <owner>/.gitbay.
 43// Everything missing — the repo, the branch, the file — is an empty
 44// about, as is a repo the caller cannot read.
 45func ownerAbout(c *Ctx, owner string) (text, format string)
 46```
 47
 48It resolves `<owner>/.gitbay` through the same access check every other
 49read takes, so a repository the caller cannot read yields no about. The
 50blob read is capped at `maxCommitFileBytes` (1MB).
 51
 52`ProfileOut.About` and `ProfileOut.AboutFormat` keep their JSON names
 53and meanings; only the source changes. `AboutFormat` is `org` for a
 54`.org` file and `md` otherwise. The API contract and the iOS client are
 55untouched.
 56
 57One field is added: `about_path`, the repository-relative path the text
 58was read from, empty when there is no about. The web needs it to link to
 59the file rather than guess its extension, and every other client gets
 60the same pointer.
 61
 62`aboutHTML` in `internal/httpd/web.go` becomes a direct
 63`renderReadme(name, raw)` call — there is a filename to dispatch on now,
 64so the stored-format indirection goes away.
 65
 66## Naming
 67
 68`policy.namePat` requires a leading alphanumeric, so `.gitbay` is an
 69invalid repository name today:
 70
 71```go
 72var namePat = regexp.MustCompile(`^\.?[a-z0-9][a-z0-9._-]{0,61}$`)
 73```
 74
 75One optional leading dot, same 63-character ceiling. `ValidateName`
 76keeps refusing `.`, `..` and a `.git` suffix, and gains an exact-`.git`
 77refusal — the suffix rule only fires for names longer than four
 78characters.
 79
 80Relaxing the pattern rather than whitelisting the one name `.gitbay` is
 81the smaller change, and it gives owners `.dotfiles` and the like for
 82free.
 83
 84## A first commit into an empty repository
 85
 86`gitutil.CommitFileChange` resolves the target branch and fails when it
 87does not exist, so committing the first file into a freshly created
 88`.gitbay` is impossible today. Both the web's create button and the
 89backfill need it to work.
 90
 91An unresolvable branch becomes a root commit **only when the repository
 92has no refs at all**. Anywhere else it stays the error it is now — a
 93typo'd branch name in a repository with history must not silently start
 94an orphan branch.
 95
 96This also makes `repo commit-file` work on a repository created but
 97never pushed to, which is the same gap seen from the CLI.
 98
 99## Writing
100
101There is no about-specific write command, for the reason the wiki has
102none: the content is a file, and the file is written the way files are
103written.
104
105- `profile set` loses `--about`, `--about-format` and `--file`, and
106  loses `ReadsStdin`.
107- `org profile` loses the same three flags.
108- Authoring is a push, or
109  `repo commit-file cmc/.gitbay profile/README.org --ref main --file -`.
110
111### The web
112
113The About textarea on the account page is replaced by a line naming the
114file and linking to it, with a button that creates `<owner>/.gitbay`
115and commits a starter `profile/README.md` when the repository does not
116exist yet. Editing then happens in the repository file editor that
117already exists.
118
119The alternative — keeping the textarea and dispatching
120`repo commit-file` from it — needs an auto-create path and has a stale
121file problem: moving the format picker from md to org leaves a
122`README.md` that keeps winning resolution, and `commit-file` cannot
123delete it, so the handler needs a second dispatch to `file remove`. The
124pointer is smaller and matches how a wiki page is edited.
125
126The account form's `profile` case keeps `--description`, `--website`
127and `--link`, and stops sending `--about-format` and stdin. The Preview
128button on that form goes with the textarea; the repository file editor
129has its own.
130
131## Access and visibility
132
133The about renders to whoever can read `<owner>/.gitbay`. A private
134`.gitbay` means the about is visible to the owner and admins only. That
135is the parent-derived rule already in force for wiki pages, not a new
136one.
137
138The backfill creates the repository **public**, so no about that was
139world-readable becomes hidden by the move.
140
141Repositories whose name starts with `.` are filtered out of:
142
143- the profile page's repository list (`ProfileOut.Repos`), and
144- `explore`.
145
146They stay in `repo list`, which is the owner's own inventory, and stay
147reachable at their URL. Hiding the repository is what a dot-repo buys
148over `cmc/cmc`; without the filter the move trades one visible
149single-purpose repository for another.
150
151## Migration
152
153A SQL migration cannot write git objects, so the move is two pieces
154that ship together. `gitbayd` runs `MigrateUp` at startup, so a backfill
155that reads the columns must not run after a migration that drops them —
156hence the holding table.
157
158**Migration 0058** copies every owner with a non-empty about into a
159holding table, then drops the columns:
160
161```sql
162CREATE TABLE profile_about_backfill (
163  owner_kind   TEXT NOT NULL,
164  owner_id     INTEGER NOT NULL,
165  about        TEXT NOT NULL,
166  about_format TEXT NOT NULL,
167  PRIMARY KEY (owner_kind, owner_id)
168);
169INSERT INTO profile_about_backfill ...  -- users, then orgs
170ALTER TABLE users DROP COLUMN about;    -- and about_format
171ALTER TABLE orgs  DROP COLUMN about;    -- and about_format
172```
173
174The down migration re-adds the columns empty and drops the table.
175
176**`gitbayd admin migrate-profile-about`**, in the shape of
177`adminMigrateCommitRefsCmd` in `cmd/gitbayd/adminusers.go`, drains the
178table. Per row: create `<owner>/.gitbay` public if it does not exist,
179`gitutil.CommitFileChange` the README at the recorded format, delete
180the row. Idempotent — an owner who already has the file is skipped and
181their row deleted.
182
183Commit identity is the owner's primary verified email when they have
184one, otherwise `<name>@users.noreply.<host>`. Orgs have no email and
185always take the fallback.
186
187A later release drops the emptied holding table.
188
189## Testing
190
191Unit:
192
193- `policy`: `.gitbay` and `.dotfiles` accepted; `.`, `..`, `.git` and
194  `x.git` still refused; the 63-character ceiling holds with the dot.
195- `ownerAbout`: `.md` wins over `.org`; a missing repo, a missing
196  branch and a missing file each give an empty about; a repo the caller
197  cannot read gives an empty about.
198- `gitutil.CommitFileChange`: the first commit into an empty repository
199  succeeds and reads back; the second takes the parented path; an
200  unknown branch in a repository with history is still an error.
201
202e2e, new `e2e/profileabout_test.go`:
203
204- a committed `profile/README.md` shows on `profile show` and on the
205  web profile page;
206- a private `.gitbay` hides the about from an outsider while the owner
207  still sees it;
208- dot-repos do not appear in `explore` or in a profile's repository
209  list, and do appear in `repo list`;
210- `profile set --about` is refused as an unknown flag.
211
212e2e for the backfill in the shape of `e2e/commentmigrate_test.go`:
213a row in the holding table becomes a repository with the file, and a
214second run is a no-op.
215
216`TestStdinCommandsReadStdin` already polices `profile set` dropping
217`ReadsStdin`.
218
219## Documentation
220
221- `.gitbay/wiki/Parity` — the profile row.
222- `.gitbay/wiki/Users` — the profile section: where the about lives and
223  how to write it.