Chapter 23Lesson 02~190 minutes

Patch-Based and Email Workflows: format-patch, am, apply, and Maintainer Flows: Guided Hands-On Workflow and Core Operations

Create a two-commit topic, export raw and mailbox artifacts, compare git apply with git am, parse one message with mailinfo, abort a failed am safely, and produce a v2 reroll with range-diff review metadata.

Hands-on patchesgit ammailinforange-diff

Learning objectives

  • Export and preflight a raw diff without creating commits.
  • Generate a threaded mailbox series with cover letter and signoff trailer.
  • Use mailinfo to observe the split between message metadata and patch content.
  • Apply mailbox patches with git am and compare authorship/commit boundaries with raw apply.
  • Create and review a v2 series using reroll count and range-diff.

1. Create one source repository and two clean receiving clones

Everything stays inside git-patch-workflow-lab. No SMTP server, hosted account, or valuable repository is involved.

Git Bash / Bash / zsh

mkdir git-patch-workflow-lab
cd git-patch-workflow-lab
git init -b trunk source
cd source
git config user.name "Contributor Example"
git config user.email "contributor@example.invalid"

printf "# Patch Lab\n\nStable baseline.\n" > README.md
printf "mode=baseline\n" > app.conf
git add README.md app.conf
git commit -m "base: create patch workflow lab"
BASE=$(git rev-parse HEAD)

git switch -c topic-v1
mkdir scripts
cat > scripts/show-mode.sh <<'EOF'
#!/bin/sh
mode=$(sed -n 's/^mode=//p' app.conf)
printf 'mode=%s\n' "$mode"
EOF
git add scripts/show-mode.sh
git commit -m "tooling: add mode reporter"

printf "\nUse scripts/show-mode.sh to inspect the configured mode.\n" >> README.md
git add README.md
git commit -m "docs: explain mode reporter"
V1=$(git rev-parse HEAD)
git branch series-v1 "$V1"

cd ..
git clone --branch trunk source raw-consumer
git clone --branch trunk source mailbox-consumer

PowerShell file-creation equivalent

New-Item -ItemType Directory git-patch-workflow-lab | Out-Null
Set-Location git-patch-workflow-lab
git init -b trunk source
Set-Location source
git config user.name "Contributor Example"
git config user.email "contributor@example.invalid"
"# Patch Lab`n`nStable baseline." | Set-Content README.md
"mode=baseline" | Set-Content app.conf
git add README.md app.conf
git commit -m "base: create patch workflow lab"
# Create topic-v1 with the same two logical commits using Set-Content/Add-Content.

2. Prove the series and receivers share the same base

cd source
git status --short --branch
git log --graph --decorate --oneline --all
git merge-base trunk topic-v1
git rev-parse trunk

git diff --check trunk..topic-v1
git diff --stat trunk..topic-v1

The merge base should equal trunk. diff --check should report no whitespace errors. This preflight is the evidence that both export forms describe a clean two-commit topic on a known base.

3. Export one raw diff for the whole topic

git diff trunk..topic-v1 > ../topic-v1.diff
git apply --stat ../topic-v1.diff

The raw diff contains the net file changes from trunk to the topic tip. It does not retain the fact that the contributor intentionally split those changes into two commits.

4. Apply the raw diff and observe that no commit appears

cd ../raw-consumer
git config user.name "Raw Importer"
git config user.email "raw-importer@example.invalid"
BEFORE=$(git rev-parse HEAD)

git status --short --branch
git apply --check ../topic-v1.diff
git apply ../topic-v1.diff

git status --short
test "$(git rev-parse HEAD)" = "$BEFORE"
git diff --stat

Expected: files are modified/added in the working tree, but HEAD is exactly the same commit as before. That is the defining property of this path.

git add -A
git commit -m "import: apply contributor topic as one raw patch"
git log -1 --format=fuller

The receiver has created a new local commit with its own message/committer and has collapsed the contributor's two-commit review structure into one commit.

5. Export the same topic as an explicit v1 mailbox series

cd ../source
mkdir -p ../series-v1

git format-patch --reroll-count=1 --cover-letter --thread=shallow \
  --signoff -o ../series-v1 trunk..series-v1

ls ../series-v1

Expected files: one v1-0000-cover-letter.patch plus two numbered commit patches. --signoff adds a signoff trailer to the exported messages; it does not rewrite the commits in source.

6. Inspect mailbox headers and the review/patch boundary

sed -n '1,45p' ../series-v1/v1-0001-tooling-add-mode-reporter.patch
grep -E '^(From:|Date:|Subject:|Message-ID:|In-Reply-To:|References:)' \
  ../series-v1/*.patch

PowerShell inspection alternative

Get-Content ../series-v1/v1-0001-tooling-add-mode-reporter.patch -TotalCount 45
Select-String -Path ../series-v1/*.patch -Pattern '^(From:|Date:|Subject:|Message-ID:|In-Reply-To:|References:)'

The mail headers organize authorship and threading. The commit message follows, then a three-dash separator and the patch/diffstat material.

7. Parse one message with mailinfo

git mailinfo ../mailinfo-message.txt ../mailinfo-patch.diff \
  < ../series-v1/v1-0001-tooling-add-mode-reporter.patch

cat ../mailinfo-message.txt
git apply --stat ../mailinfo-patch.diff

Expected stdout: author name, email, and subject. The two output files separate commit-message body from patch content—the same conceptual split git am consumes.

8. Apply the mailbox series and preserve commit boundaries/authorship

cd ../mailbox-consumer
git config user.name "Maintainer Example"
git config user.email "maintainer@example.invalid"
BEFORE=$(git rev-parse HEAD)

git am ../series-v1/v1-0001-tooling-add-mode-reporter.patch \
       ../series-v1/v1-0002-docs-explain-mode-reporter.patch

git log -2 --format=fuller
git status --short --branch
test "$(git rev-list --count "$BEFORE"..HEAD)" -eq 2

Expected: two commits are created. Their author comes from the mailbox metadata; the current maintainer is the committer. The exported signoff appears in the recreated commit messages. OIDs need not match the source commits.

9. Compare the two import paths

cd ../raw-consumer
git log --oneline -2

cd ../mailbox-consumer
git log --oneline -3
git log -2 --format='%h author=%an <%ae> committer=%cn <%ce>%n%B%n---'
Property Raw git apply then local commit git am
Working-tree changes Yes Yes
Creates commits automatically No Yes
Preserves original series boundaries No, unless receiver recreates them manually Yes, one commit per mailbox patch
Uses mailbox author/message No Yes

10. Engineer a failed git am and abort it safely

Create a third receiver from the base, then make an incompatible local edit before applying a one-commit patch:

cd ..
git clone --branch trunk source abort-consumer
cd abort-consumer
git config user.name "Abort Tester"
git config user.email "abort-tester@example.invalid"
printf "# Patch Lab\n\nReceiver changed this baseline independently.\n" > README.md
git add README.md
git commit -m "receiver: diverge documentation"
PRE_AM=$(git rev-parse HEAD)

git am --3way ../series-v1/v1-0002-docs-explain-mode-reporter.patch || true
git status --short --branch
git am --show-current-patch=diff
git rev-parse ORIG_HEAD

git am --abort
test "$(git rev-parse HEAD)" = "$PRE_AM"
git status --short --branch

The failed am session is stateful. --show-current-patch identifies the stopped patch, and --abort restores the original branch/pre-am file state. That is safer than ad-hoc reset commands.

11. Build v2 as a separate review iteration

cd ../source
git switch -c topic-v2 trunk
mkdir -p scripts
cat > scripts/show-mode.sh <<'EOF'
#!/bin/sh
set -eu
mode=$(sed -n 's/^mode=//p' app.conf)
printf 'mode=%s\n' "$mode"
EOF
git add scripts/show-mode.sh
git commit -m "tooling: add robust mode reporter"

printf "\nRun scripts/show-mode.sh to inspect mode; the script exits on errors.\n" >> README.md
git add README.md
git commit -m "docs: explain robust mode reporter"
V2=$(git rev-parse HEAD)

v2 is deliberately created from the same base rather than rewriting the already-exported v1 branch. This keeps both review iterations easy to compare in the lab.

12. Compare v1 and v2 before exporting v2

git range-diff trunk..series-v1 trunk..topic-v2

Expect the two v1 patches to correspond to two v2 patches, with the script and documentation differences highlighted. Use this output for human review, not machine parsing.

13. Export v2 with reroll and embedded range-diff review material

mkdir -p ../series-v2
git format-patch --reroll-count=2 --cover-letter --thread=shallow \
  --range-diff=series-v1 --signoff -o ../series-v2 trunk..topic-v2

sed -n '1,120p' ../series-v2/v2-0000-cover-letter.patch

The cover letter should identify v2 and include range-diff material comparing it with the v1 tip because both series share the same base.

14. Challenge — choose the artifact from the preservation requirement

  1. You need only file changes and will write a new local commit: raw diff plus which command?
  2. You must preserve two commit messages/authors and their order: which export/import pair?
  3. You want to prove a patch will apply before changing files: which option?
  4. You need to see the patch where an am session stopped: which command?
  5. You need to review how v2 differs from v1 as a series: which command?

15. Verification checklist

  • Source v1 has exactly two commits beyond trunk.
  • Raw apply leaves HEAD unchanged until a local commit is created.
  • Mailbox import creates two commits and preserves mailbox author/message metadata.
  • Generated v1 contains cover letter plus two numbered patches with threading headers.
  • mailinfo extracts message and patch material separately.
  • Failed am shows the current patch and abort returns to the pre-am tip.
  • v2 shares the same base as v1 and range-diff shows review deltas.
  • No network, SMTP account, hosted review object, or real credential was required.

16. Cleanup

All repositories and patch files are disposable. Confirm the parent path before recursive deletion.
cd ../..
pwd
rm -rf git-patch-workflow-lab

PowerShell equivalent: Set-Location ../..; Remove-Item -Recurse -Force git-patch-workflow-lab.

17. Knowledge check

Question 1. Why did HEAD stay unchanged after git apply?

Question 2. Which identity becomes the committer for commits made by git am?

Question 3. What does ORIG_HEAD record when an am session begins?

Question 4. Why did v1 and v2 not need matching commit IDs for range-diff?

Question 5. Did adding --signoff to format-patch cryptographically sign the source commits?

18. Summary

You exported the same topic as a raw diff and a mailbox series, proved the different state transitions, parsed mailbox metadata, handled an am failure by aborting, created an independent v2, and used range-diff plus reroll metadata to make review changes explicit.

Next

Turn the workflow into project policy

Lesson 3 covers format/send-email configuration, signoff/trailer/mailmap conventions, whitespace/EOL policy, integration branches, SMTP-free review, and portability decisions.

Authoritative references

 git-format-patch
 git-apply
 git-am
 git-mailinfo
 git-range-diff

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.