Contributions: reviewing changes — tcm-ts

Contributions: reviewing changes

A contribution is a branch proposed for merging into another, as on Unison Share. It's 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.

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] [--to main] [--as name] [--title t] [--description d] pushes the current branch (under its own name, or --as) and opens a contribution of it into main (or --to). 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 there's nothing to propose: make a branch first.

Contributors

A project with contributors works as on Unison Share: only the maintainer pushes main. Each contributor gets a token from 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.
  • Their token signs what they write: 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 pushes leave those out: the maintainer's tests run on what's merged. A contributor closes and reopens only their own contributions.
  • tcm user [--remote origin] 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.

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 [--all]
the remote's open contributions (--all: merged and closed too)
tcm contribution view <id>
its changes and whether it merges
tcm contribution comment <id> <text> [--about <name>]
comment on the whole change, or on one definition
tcm contribution merge <id>
merge it on the remote
tcm contribution close <id> / reopen <id>
close it without merging, or open it again

Each takes --remote <name> (default origin).

  • view shows the changes since the branch left its target, as tcm diff does: edits, what re-hashed because of them (and why), and what's added, removed and renamed. Renames and reformatting aren't changes.
  • It says whether it merges: as a fast-forward, cleanly (naming definitions both sides edited apart, which the merge merges as text), or with conflicts in which names.
  • Comments and merges are signed with $TCM_AUTHOR, or your user name; a contributor's, with their handle (above).

The review page

tcm serve's web UI has a page for each contribution, at the link tcm contribute prints. The link signs the browser in with the token in its fragment, which is never sent to a server. The page has:

  • the conversation;
  • the changes 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;
  • what you may do: comment, close or reopen your own, or 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 are read-only.

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. Definitions both sides edited apart are merged as text, and the merge is refused if they don't typecheck merged.
  • Refused if what it brings doesn't typecheck, with the errors. The code its branch reaches that the target didn't is typechecked on the remote, under each definition's recorded check profile. This is where a contribution's types are judged: receiving a push checks only that code is what it claims (sync).
  • Refused if both 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.

Built from c482a14e