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: [testmark]:# (fresh/setting-up/session) ``` ~$ 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. The sessions in the rest of this guide each start as though you'd just done that (apart from the last two sections, which start unpaired, from `~`). Taking snapshots ---------------- Work on your branches however you like; then export them with the `patchette export` command, and (presumably, if you want history over your patches) commit the result in the patches repo. Here we've got a repo where we have two branches with a handful of commits each that we're going to export. Export takes the upstream commit your branches are based on, then the branches: [testmark]:# (snapshot/session) ``` ~/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 (2 patches) ~/src/frob$ ls ../frob-patches/docs-polish STACK mention-the-oiling-schedule.patch 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, containing a `STACK` file outlining the branch, what it's based on, and what's in it; and one patch file per commit. ### the files Here's a quick peek into the files produced by the export: First, here's `~/src/frob-patches/docs-polish/STACK`: [testmark]:# (snapshot/STACK) ``` ref refs/heads/docs-polish base 5b0130cfd2153bcdb4a6d7ec356e7d805018afc9 tip 2dfecf1d7ce26028ef2dcc7430b5b9bc9c8aa86a series polish-the-readme mention-the-oiling-schedule ``` It names the branch, the upstream commit it's based on, and its tip; then, after `series`, its patches, in order. `~/src/frob-patches/docs-polish/polish-the-readme.patch` is one commit: its parents (`BASE` is the STACK's base), then the rest of its headers exactly as git stored them; then the commit message, marked by flanking `----` lines and with `>` prefixes; and everything after that is finally the diff itself: [testmark]:# (snapshot/polish-the-readme.patch) ``` parents BASE author Ada Downstream 1700000660 +0000 committer Ada Downstream 1700000660 +0000 ---- > Polish the readme ---- diff --git a/README.md b/README.md index a330cd687e7fdede54bbe0aca5df47c06d2e8b64..c195933372242cdbb3c014665ebcff2bb3fb4449 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,5 @@ Upstream docs. More upstream docs. + +Downstream notes: be kind to the machinery. ``` Both formats are specified in full in [FORMAT.md](../hacking/FORMAT.md). ### change over time 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: [testmark]:# (paired/evolving/session) ``` ~/src/frob$ patchette export origin/main docs-polish export: wrote ~/src/frob-patches/docs-polish (2 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/weekly/daily/' 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 (2 patches) ~/src/frob$ git -C ../frob-patches diff diff --git a/docs-polish/STACK b/docs-polish/STACK index c56f84d..99ac5cd 100644 --- a/docs-polish/STACK +++ b/docs-polish/STACK @@ -1,6 +1,6 @@ ref refs/heads/docs-polish base 5b0130cfd2153bcdb4a6d7ec356e7d805018afc9 -tip 2dfecf1d7ce26028ef2dcc7430b5b9bc9c8aa86a +tip 88f146ae1801f5fd8d443d6eb5904f09b88927c6 series polish-the-readme mention-the-oiling-schedule diff --git a/docs-polish/mention-the-oiling-schedule.patch b/docs-polish/mention-the-oiling-schedule.patch index 8b0dda3..1f6c371 100644 --- a/docs-polish/mention-the-oiling-schedule.patch +++ b/docs-polish/mention-the-oiling-schedule.patch @@ -1,17 +1,17 @@ parents polish-the-readme author Ada Downstream 1700000720 +0000 -committer Ada Downstream 1700000720 +0000 +committer Ada Downstream 1700086400 +0000 ---- > Mention the oiling schedule ---- diff --git a/README.md b/README.md -index c195933372242cdbb3c014665ebcff2bb3fb4449..073a43988cb91f0fe493964670f6d52517082346 100644 +index c195933372242cdbb3c014665ebcff2bb3fb4449..61f9c849c8c9962d481e8ce3874abcc47a640eba 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,4 @@ Upstream docs. More upstream docs. Downstream notes: be kind to the machinery. -+Oil the gears weekly. ++Oil the gears daily. ``` Commit that with a message saying why it changed, and the history of your patches tells its own story. Checking a snapshot ------------------- `patchette verify` runs a reconstruction of each branch from its patches (without actually modifying the git repo!), and checks it comes out exactly as it was recorded. Useful for the paranoid and cautious, effectively. But also can tell you if you've edited the patches manually (see more about this in the next section) and have forgotten to regenerate any of the logged hashes. One can also name specific branches to check just those. Usage looks like this: [testmark]:# (paired/verifying/session) ``` ~/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 (2 patches) ~/src/frob$ patchette verify verify: ok: refs/heads/docs-polish reproduces recorded tip 2dfecf1d7ce26028ef2dcc7430b5b9bc9c8aa86a 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 ``` (See `patchette import`, in the next section, if you want to actually have an effect rather than just check.) Rebuilding branches in a fresh clone ------------------------------------ `patchette import` can rebuild your branches, exactly, in a clone that never had them. Pair the new clone, and import: [testmark]:# (paired/recreating/session) ``` ~/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 (2 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 2dfecf1d7ce26028ef2dcc7430b5b9bc9c8aa86a 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 ``` (This is the same thing as `patchette verify`, but actually... having an effect.) Editing patches by hand ----------------------- Patch files are plain text, and *can be edited directly*, if desired! Branches in the repo can then be rebuilt from the (changed) patches -- this is kinda like an interactive rebase process on steroids. (Do mind, however, that `patchette import` won't replace a branch that already exists unless you say `-f`, for some safety against accidents. And for reasons of git's norms, it also won't apply an import to the branch you have checked out: switch away first.) Using Patchette and editing patches to change history looks like this: [testmark]:# (paired/hand-editing/session) ``` ~/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 ``` Note that `patchette verify` fails right after the manual edits: this makes sense, because if the patches have been changed, of course they no longer produce the exact same resulting history and content in git, so the expected tip hash has to be updated. A `patchette import -f` and `patchette export` round trip will cause the tip hash to be freshly recorded and result in a happy verify 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. [testmark]:# (fresh/flags/session) ``` ~$ 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 (2 patches) ~$ patchette verify --subject=src/frob --patches=src/frob-patches verify: ok: refs/heads/docs-polish reproduces recorded tip 2dfecf1d7ce26028ef2dcc7430b5b9bc9c8aa86a ``` When something's wrong ---------------------- ... then Patchette will still try to give reasonable error messages and guidance. Here's some of those cases on display: [testmark]:# (fresh/refusals/session) ``` ~$ cd src/frob ~/src/frob$ patchette verify patchette: --subject/--patches not given, and '~/src/frob' is not paired; run 'patchette pair ' 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 (2 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 2dfecf1d7ce26028ef2dcc7430b5b9bc9c8aa86a ~/src/frob$ patchette export patchette: usage: patchette export [--sigil=quote|--sigil=tab] [--subject=] [--patches=] ... [exit status: 1] ```