Contributions: reviewing changes — tcm-ts

21. Contributions: reviewing changes

A contribution is a branch proposed for merging into another, as on Unison Share: reviewed as a diff of definitions rather than text, commented on, and merged on the remote by history. Contributions live in the codebase you push to: a tcm serve server, or another codebase directory.

21.1 Proposing a change

Make the change on a branch, then contribute it:

$ tcm branch faster && tcm switch faster
$ tcm update                      # change lib.square, add lib.cube
$ tcm contribute --title "Faster squares" --description "Uses ** now."
pushed origin/faster: 3 names (new branch); sent 3 definition(s)
opened contribution #1 on origin
  review: tcm contribution view 1 · https://tcm.example.com:7737/forge/contributions/1#token=…
  • tcm contribute [remote] pushes the current branch (under its own name, or --as <name>) and opens a contribution of it into main (or --to <branch>). Without --title you're asked at a terminal; elsewhere the branch's name is the title.
  • Pushing again updates it: tcm contribute (or tcm push) on the same branch moves the open contribution to your latest changes.
  • From main itself there's nothing to propose: make a branch first.

21.2 Contributors

A project with contributors works as on Unison Share: nobody but its maintainer pushes main. Each contributor has a token of their own, made by the maintainer, and pushes contributor branches, under their handle, into the project itself (no fork):

$ tcm user add grace                            # the maintainer, with the server's token
added @grace on origin: they push branches under @grace/ and propose them
  their token (shown once; adding them again makes a new one): 8bS…
  they connect with: tcm remote add origin https://tcm.example.com --token 8bS…

$ tcm remote add origin https://tcm.example.com --token 8bS…   # grace
$ tcm pull && tcm branch cubes && tcm switch cubes
$ tcm contribute --title "Cubes"
pushed origin/@grace/cubes: 1 added, 0 changed, 0 removed, 0 renamed; sent 1 definition(s)
opened contribution #1 on origin
  • A contributor's branches are named for them: tcm push and tcm contribute push cubes as @grace/cubes, and --as spare as @grace/spare (a name already under @grace/ stays as it is). The server refuses them any other branch.
  • What they write is signed by their token: contributions, comments, closing and reopening are @grace's, whatever $TCM_AUTHOR says. The maintainer's token signs as $TCM_AUTHOR (or your user name).
  • Merging is the maintainer's, as are releases and the test results and lint findings a push sends (a contributor's leave them out: the maintainer's tests run on what's merged). A contributor closes and reopens only their own contributions.
  • tcm user says who you are on a remote; to the maintainer, it lists the contributors. tcm user rm grace stops grace's token working; tcm user add grace again gives grace a new one.
  • On a --public server anyone can pull, and read contributions and their review pages, without a token.

21.3 Reviewing

$ tcm contribution view 1
#1 Faster squares  open
  faster → main · opened by ada 2026-10-29 01:30 · 0 comment(s)

  Uses ** now.

  ✓ merges as a fast-forward

changes on faster since it left main (#2jut5etj)

  edited
    ~ lib.square  #tvtxd4fk → #kndlow5j
      - export const square = (x: number): number => x * x
      + export const square = (x: number): number => x ** 2

  updated because a dependency changed (code unchanged)
    ~ lib.sumSquares  #675kvzb7 → #d72molby  via lib.square

  added
    + lib.cube  #dzuntsd3 : (x: number) => number
  • tcm contribution lists the remote's open contributions (--all for merged and closed ones too).
  • tcm contribution view <id> shows the changes from where the branch left its target, the way tcm diff does: edits, what was re-hashed because of them (and why), what's added, removed and renamed. Renames and reformatting aren't changes. It also says whether it merges: as a fast-forward, cleanly (naming what both sides edited apart, which the merge merges as text), or with conflicts in which names. Merging one with definitions merged as text tries the merge first on a branch of its own, and refuses it if they don't typecheck merged.
  • tcm contribution comment <id> <text> [--about <name>] comments on the whole change, or on one definition.
  • Comments and merges are signed with $TCM_AUTHOR, or your user name; a contributor's, with their handle (above).

21.3.1 The review page

tcm serve's web UI has a page for each contribution, at the link tcm contribute prints (which signs the browser in with the token in its fragment, never sent to a server): its conversation, and its changes read along a review path (what's new before what uses it, then tests; what only re-hashed, folded), each definition's diff with its comments, whether it merges, and what the viewer may do: comment, close or reopen their own, merge (the maintainer). /forge/contributions lists them all (a site served beside it has every other path). With no token, on a public server, the pages only read.

21.4 Merging

tcm contribution merge <id> (or the page's button) merges it on the remote, by history:

  • a fast-forward if the target hasn't moved since the branch left it;
  • otherwise a three-way merge from their common ancestor, recorded with both as parents;
  • refused if what it brings doesn't typecheck: the code its branch reaches that the target didn't, typechecked on the remote, under each definition's recorded check profile, with the errors said. A server checks that what it receives is what it claims, not its types (sync): this is where a contribution's types are judged;
  • refused if the two changed the same names differently. Bring the target into the branch, settle the conflicts, and push again:
$ tcm contribution merge 3
#3 conflicts with main in lib.square: merge main into b (`tcm pull <remote> main` on that branch), resolve, push, and merge again
$ tcm switch b && tcm pull origin main      # merges origin/main into b
$ tcm resolve lib.square '#…'
$ tcm contribute                             # updated contribution #3
$ tcm contribution merge 3
merged #3 on origin as #mio7i2g5

Afterwards tcm pull on your main fast-forwards to the merge, and the contribution shows what the merge brought. tcm contribution close <id> closes one without merging; reopen opens it again.

built from c482a14e