git-trace

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.