patchette

USAGE.md

Using Patchette🔗

Patchette snapshots your branches into a patches repo, as one folder of patch files per branch, and rebuilds those exact branches from the patches, commit for commit, in any clone of the upstream. You keep working in your own repo, with plain git; patchette only reads it, and writes the branches back when you ask.

Every session below is real: the test suite runs each one, and checks that patchette still prints exactly what's shown. They all start from the same place: a home dir with an upstream repo at ~/upstream/frob, and your clone of it at ~/src/frob, with main checked out, carrying two branches of your own work, topic/fancy and docs-polish.

Setting up🔗

The patches live in a repo of their own, beside your clone. Make one, then pair your clone with it, so patchette knows where to look:

~$ cd src/frob
~/src/frob$ git init -b main ../frob-patches
Initialized empty Git repository in ~/src/frob-patches/.git/
~/src/frob$ patchette pair ../frob-patches
paired: '~/src/frob' -> patches dir '../frob-patches'
~/src/frob$ patchette pair
paired: patches dir is '../frob-patches' (relative to '~/src/frob')

Pairing is the one thing patchette keeps in your repo: a single value, in .git/patchette, never pushed. Delete that file to un-pair.

Taking snapshots🔗

Work on your branches however you like; then export them, and commit the result in the patches repo. Export takes the upstream commit your branches are based on, then the branches:

~$ cd src/frob
~/src/frob$ git init -q -b main ../frob-patches
~/src/frob$ patchette pair ../frob-patches
paired: '~/src/frob' -> patches dir '../frob-patches'
~/src/frob$ patchette export origin/main topic/fancy docs-polish
export: wrote ~/src/frob-patches/topic¦fancy (8 patches)
export: wrote ~/src/frob-patches/docs-polish (1 patches)
~/src/frob$ ls ../frob-patches/docs-polish
STACK
polish-the-readme.patch
~/src/frob$ git -C ../frob-patches add .
~/src/frob$ git -C ../frob-patches commit -q -m "first snapshot"

Each branch becomes a folder: a STACK file saying where it's based and what's in it, and one patch file per commit.

The payoff comes when your patches change. Here, a commit on docs-polish gets reworked; export again, and the patches repo shows what changed about the change:

~$ cd src/frob
~/src/frob$ git init -q -b main ../frob-patches
~/src/frob$ patchette pair ../frob-patches
paired: '~/src/frob' -> patches dir '../frob-patches'
~/src/frob$ patchette export origin/main docs-polish
export: wrote ~/src/frob-patches/docs-polish (1 patches)
~/src/frob$ git -C ../frob-patches add .
~/src/frob$ git -C ../frob-patches commit -q -m "first snapshot"
~/src/frob$ git switch -q docs-polish
~/src/frob$ sed -i 's/be kind to/be gentle with/' README.md
~/src/frob$ git commit -q -a --amend --no-edit
~/src/frob$ patchette export origin/main docs-polish
export: wrote ~/src/frob-patches/docs-polish (1 patches)
~/src/frob$ git -C ../frob-patches diff
diff --git a/docs-polish/STACK b/docs-polish/STACK
index d187142..eb8cce4 100644
--- a/docs-polish/STACK
+++ b/docs-polish/STACK
@@ -1,5 +1,5 @@
 ref refs/heads/docs-polish
 base 5b0130cfd2153bcdb4a6d7ec356e7d805018afc9
-tip 485a9d568a1be975c873793612c74733df10dd30
+tip c8e5de76c06ca994c681de041257be8b01b28969
 series
 polish-the-readme
diff --git a/docs-polish/polish-the-readme.patch b/docs-polish/polish-the-readme.patch
index 0373218..1cf464c 100644
--- a/docs-polish/polish-the-readme.patch
+++ b/docs-polish/polish-the-readme.patch
@@ -1,13 +1,13 @@
 parents BASE
 author Ada Downstream <ada@downstream.example> 1700000660 +0000
-committer Ada Downstream <ada@downstream.example> 1700000660 +0000
+committer Ada Downstream <ada@downstream.example> 1700086400 +0000
 
 ----
 > Polish the readme
 ----
 
 diff --git a/README.md b/README.md
-index a330cd687e7fdede54bbe0aca5df47c06d2e8b64..c195933372242cdbb3c014665ebcff2bb3fb4449 100644
+index a330cd687e7fdede54bbe0aca5df47c06d2e8b64..4487040f0eaebfa924f8b6011a62557137d3d03d 100644
 --- a/README.md
 +++ b/README.md
 @@ -3,3 +3,5 @@
@@ -15,4 +15,4 @@ index a330cd687e7fdede54bbe0aca5df47c06d2e8b64..c195933372242cdbb3c014665ebcff2b
  
  More upstream docs.
 +
-+Downstream notes: be kind to the machinery.
++Downstream notes: be gentle with the machinery.

Commit that with a message saying why it changed, and the history of your patches tells its own story.

Checking a snapshot🔗

verify rebuilds each branch from its patches, off to the side, and checks it comes out exactly as it was recorded. It never touches your branches. Name branches to check just those:

~$ cd src/frob
~/src/frob$ git init -q -b main ../frob-patches
~/src/frob$ patchette pair ../frob-patches
paired: '~/src/frob' -> patches dir '../frob-patches'
~/src/frob$ patchette export origin/main topic/fancy docs-polish
export: wrote ~/src/frob-patches/topic¦fancy (8 patches)
export: wrote ~/src/frob-patches/docs-polish (1 patches)
~/src/frob$ patchette verify
verify: ok: refs/heads/docs-polish reproduces recorded tip 485a9d568a1be975c873793612c74733df10dd30
verify: ok: refs/heads/topic/fancy reproduces recorded tip cc261dc9f0b878a78e2b80aeb60de397205f61ad
~/src/frob$ patchette verify topic/fancy
verify: ok: refs/heads/topic/fancy reproduces recorded tip cc261dc9f0b878a78e2b80aeb60de397205f61ad

Rebuilding branches in a fresh clone🔗

The patches repo is enough to bring your branches back, exactly, in a clone that never had them. Pair the new clone, and import:

~$ cd src/frob
~/src/frob$ git init -q -b main ../frob-patches
~/src/frob$ patchette pair ../frob-patches
paired: '~/src/frob' -> patches dir '../frob-patches'
~/src/frob$ patchette export origin/main topic/fancy docs-polish
export: wrote ~/src/frob-patches/topic¦fancy (8 patches)
export: wrote ~/src/frob-patches/docs-polish (1 patches)
~/src/frob$ cd ..
~/src$ git clone -q ~/upstream/frob frob2
~/src$ cd frob2
~/src/frob2$ patchette pair ../frob-patches
paired: '~/src/frob2' -> patches dir '../frob-patches'
~/src/frob2$ patchette import
import: created refs/heads/docs-polish at 485a9d568a1be975c873793612c74733df10dd30
import: created refs/heads/topic/fancy at cc261dc9f0b878a78e2b80aeb60de397205f61ad
~/src/frob2$ git rev-parse topic/fancy
cc261dc9f0b878a78e2b80aeb60de397205f61ad
~/src/frob2$ git -C ../frob rev-parse topic/fancy
cc261dc9f0b878a78e2b80aeb60de397205f61ad

Editing patches by hand🔗

Patch files are plain text, and fine to edit directly. Then rebuild the branch from them. import won't replace a branch that already exists unless you say -f, and even then, not the branch you have checked out: switch away first.

~$ cd src/frob
~/src/frob$ git init -q -b main ../frob-patches
~/src/frob$ patchette pair ../frob-patches
paired: '~/src/frob' -> patches dir '../frob-patches'
~/src/frob$ patchette export origin/main topic/fancy
export: wrote ~/src/frob-patches/topic¦fancy (8 patches)
~/src/frob$ sed -i 's/^> The hum is load-bearing.$/> The hum is load-bearing, and tuned./' ../frob-patches/topic¦fancy/teach-gizmo-to-hum.patch
~/src/frob$ patchette import topic/fancy
patchette: 'refs/heads/topic/fancy' already exists in '~/src/frob'; refusing to touch it (-f updates it instead)
[exit status: 1]
~/src/frob$ git switch -q topic/fancy
~/src/frob$ patchette import -f topic/fancy
patchette: 'refs/heads/topic/fancy' is checked out in '~/src/frob'; refusing to move it under that checkout, even with -f
[exit status: 1]
~/src/frob$ git switch -q main
~/src/frob$ patchette import -f topic/fancy
import: updated refs/heads/topic/fancy to 57d05b16d9ad81fd92af19d210f0884dc9745e01 (previous tip cc261dc9f0b878a78e2b80aeb60de397205f61ad remains in its reflog)
import: note: replayed tip differs from recorded tip cc261dc9f0b878a78e2b80aeb60de397205f61ad (patches edited since the snapshot?)
~/src/frob$ patchette verify topic/fancy
verify: MISMATCH: refs/heads/topic/fancy replays to 57d05b16d9ad81fd92af19d210f0884dc9745e01, but records tip cc261dc9f0b878a78e2b80aeb60de397205f61ad
verify:   either the snapshot was unfaithful, or the patches were edited since (patches repo is ahead of its snapshot)
[exit status: 1]
~/src/frob$ patchette export origin/main topic/fancy
export: wrote ~/src/frob-patches/topic¦fancy (8 patches)
~/src/frob$ patchette verify topic/fancy
verify: ok: refs/heads/topic/fancy reproduces recorded tip 57d05b16d9ad81fd92af19d210f0884dc9745e01

verify fails right after the edit: the patches no longer match the snapshot they were taken as. Exporting the rebuilt branch takes a new snapshot of it, and they agree again.

Without pairing🔗

Pairing is a convenience. From anywhere, --subject names your repo and --patches the patches repo; each one, given, wins over the pairing.

~$ git init -q -b main src/frob-patches
~$ patchette export --subject=src/frob --patches=src/frob-patches origin/main docs-polish
export: wrote ~/src/frob-patches/docs-polish (1 patches)
~$ patchette verify --subject=src/frob --patches=src/frob-patches
verify: ok: refs/heads/docs-polish reproduces recorded tip 485a9d568a1be975c873793612c74733df10dd30

When something's wrong🔗

~$ cd src/frob
~/src/frob$ patchette verify
patchette: --subject/--patches not given, and '~/src/frob' is not paired; run 'patchette pair <patches-dir>' there, or pass the flags
[exit status: 1]
~/src/frob$ cd /tmp
/tmp$ patchette verify --subject="$HOME/src/frob"
patchette: --subject/--patches not given, and the cwd is not inside a git repository
[exit status: 1]
/tmp$ cd ~/src/frob
~/src/frob$ mkdir ../frob-patches
~/src/frob$ patchette pair ../frob-patches
paired: '~/src/frob' -> patches dir '../frob-patches'
~/src/frob$ patchette export origin/main docs-polish
export: wrote ~/src/frob-patches/docs-polish (1 patches)
~/src/frob$ patchette verify docs-polish --patches=../frob-patches
patchette: no branch folder for '--patches=../frob-patches' in '~/src/frob-patches'
[exit status: 1]
~/src/frob$ patchette verify --patches=
verify: ok: refs/heads/docs-polish reproduces recorded tip 485a9d568a1be975c873793612c74733df10dd30
~/src/frob$ patchette export
patchette: usage: patchette export [--sigil=quote|--sigil=tab] [--subject=<repo>] [--patches=<dir>] <base-rev> <branch>...
[exit status: 1]

docs/userguide/USAGE.md · 10.4 KiB · bd494f7d30