romele / git-trace public
62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 16m ago
doc/patch-workflow.md
# GNU patch workflow
Patch sets are first-class: submit via the web form or pipe
`git format-patch --stdout` over HTTP with a token:
```sh
git format-patch --stdout origin/master | \
curl -u user:token -H 'Content-Type: text/plain' \
--data-binary @- https://host/owner/repo/patches/new?base=master
```
The mbox is parsed (cover letter, per-patch author/date/subject),
shown with a series summary and combined diff. Maintainers can run a
dry-run check or apply the series entirely in the object store: a
scratch `GIT_INDEX_FILE` is seeded from the base tip, each patch is
`git apply --cached`, committed with the original author via
`commit-tree`, and the ref moves with `update-ref` compare-and-swap —
no work tree is ever checked out.
## Revisions
Patch sets iterate like on a mailing list: every submission lands in
`patch_revisions` with a monotonically increasing version.
`git format-patch -v2` auto-attaches to an existing open set when
exactly one by the same author carries the same title; `?set=N` (API)
or the *submit new version* button (web) attach explicitly. Only the
author (or a repo admin) may add revisions, and only while the set is
open. The view offers a `v1 v2 v3` timeline — older revisions stay
browsable read-only while check/apply/materialize always operate on
the latest, and title + cover letter follow it.
## Mail-in: `git send-email` through a real MTA
`git-trace` does not implement SMTP — a mail transfer agent does that
part (TLS, queues, SPF/DKIM). The forge ships a pipe-alias endpoint
that reads one RFC822 message on stdin:
```sh
# Postfix /etc/aliases (one alias per repo, or scripted per project)
patches-demo: "|/usr/bin/git-trace mailrecv alice demo"
```
Senders authenticate with a token in a header — either bare or
`user:token` (`git-trace mktoken alice mail` creates one), and
`git send-email` carries it via `--add-header`:
```sh
git send-email --to=patches-demo@host \
--add-header="X-Git-Trace-Token: <token>" master
```
Without a token, `From:` must match a registered `users.email` — that
fallback trusts the MTA to have enforced SPF/DKIM upstream. A sender
with no repository read access is dropped. The token header is
stripped before anything is stored, so credentials never land in
`patch_revisions.mbox`.
Numbered series (`[PATCH n/m]`) collect in `mail_items` keyed by the
thread root (`Message-ID` of the cover, or `In-Reply-To` of the
parts); when parts `1..m` are all present the thread is assembled —
cover first — and submitted through the same core as the web/API
path, so `[PATCH v2 n/m]` auto-attaches as a new revision exactly
like an HTTP submission. A mail whose body already is an mbox
(`format-patch` output pasted inline) submits directly; anything else
must contain a `diff --git` hunk. An assembled thread leaves a
tombstone, so an MTA redelivery cannot open a duplicate set;
incomplete threads are garbage-collected after 7 days. Rejected mail
exits non-zero with a reason on stderr — the MTA bounces it back to
the sender.
## Convergence: web MRs ↔ GNU patch sets
The two workflows are the same object seen from different tools:
- **web → terminal**: every MR exports `/mr/N.patch` (a real
`git am`-ready mbox built from the merge base) and `/mr/N.diff`;
the MR page shows the one-liner to apply it locally.
- **terminal → web**: a maintainer can *materialize* a submitted patch
set — the series is applied to a synthetic branch
`refs/heads/psets/<id>` (compare-and-swap create, base tip never
moves) and a linked merge request is opened automatically, so the
patch flows through the normal review/merge path.
- Each page cross-links the other representation; comments live in
the shared `comments` table under either parent type.