# Patch-Based Git Workflows A comprehensive reference for kernel-style and suckless-style patch workflows. Covers the full stack from git config to mailing list tooling to stacked branch management. --- ## 1. Kernel Maintainer Git Config These settings are used by active kernel maintainers and make the diff/merge/rebase experience substantially better. ```ini [diff] algorithm = histogram # better than myers for code changes colorMoved = default indentHeuristic = true [merge] conflictstyle = zdiff3 # shows base alongside ours/theirs, not just ours+theirs [rerere] enabled = true autoupdate = true # auto-stage rerere resolutions (you still commit manually) [rebase] autosquash = true # honor fixup!/squash! prefixes automatically updateRefs = true # update all intermediate refs when rebasing a stack autoStash = true [sendemail] smtpserver = smtp.example.com smtpserverport = 587 smtpencryption = tls smtpuser = you@example.com confirm = always # ask before sending chainreplyto = false # thread under cover letter, not last patch suppresscc = self # don't CC yourself on your own patches annotate = false # don't open editor for every patch [format] coverLetter = auto # generate cover letter when >1 patch notes = true # include git notes in format-patch output signatureFile = ~/.git-signature [log] date = format:%Y-%m-%d ``` Critical line: `chainreplyto = false`. Without it, each patch threads under the previous patch instead of under the cover letter, which breaks list threading. --- ## 2. b4 Toolchain `b4` is the current standard for kernel patch submission. It handles threading, versioning, trailers, and sending. ### Installation ```bash pip install b4 # or pipx install b4 ``` ### Prep: Setting Up a Series ```bash # Start a new series on a branch git checkout -b feature/my-driver # After commits are ready, prep the branch b4 prep --new v1 --trailers --cover-from-branch main # Edit cover letter b4 prep --edit-cover # Check what will be sent b4 prep --show-revision ``` ### Send: Submitting to a List ```bash # Dry run first b4 send --dry-run # Send to the list b4 send # Send a re-roll (v2) b4 send --revision 2 --in-reply-to ``` b4 automatically: - embeds a range-diff against the previous revision - threads the re-roll under the v1 cover letter - handles the `[PATCH v2 0/N]` prefix ### am: Applying a Series from the List ```bash # Apply from a message ID (pulls from lore.kernel.org) b4 am # Apply and create a local branch b4 am --create-branch # Apply with 3-way merge fallback b4 am -3 ``` ### shazam: Apply + Sign-off ```bash # Apply a patch series and add your Reviewed-by / Tested-by b4 shazam # Add a specific trailer b4 shazam --add-trailer "Tested-by: You " ``` ### diff: Compare Series Versions ```bash # Compare v1 vs v2 of a series b4 diff # Compare against a local branch b4 diff --local-branch feature/my-driver ``` --- ## 3. lei: Searching Mailing Lists Without Subscribing `lei` is part of `public-inbox` and gives you a local index of any mailing list you care about, without actually subscribing. ```bash # Install (often packaged as public-inbox-lei) # Arch: yay -S public-inbox # Add a list to watch lei add-watch https://lore.kernel.org/linux-mm/ # Search across all indexed lists lei q "drm/i915 cursor regression" --no-remote # Search with field filters lei q 's:RFC patch series' --no-remote # Get a specific message by ID lei q "mid:20230601120000.12345-1-author@kernel.org" # Convert results to mbox for reading in mutt/neomutt lei q "from:torvalds" --format=mboxrd -o ~/mail/torvalds-search.mbox # Watch for new patches matching a query lei watch-add "s:drm/nouveau v2" ``` The key advantage: no inbox noise. You search when you need to, and the index updates in the background. --- ## 4. git format-patch + range-diff ### Basic format-patch ```bash # Format last 3 commits as patches git format-patch -3 # Format a range git format-patch origin/main..HEAD # Output to a directory git format-patch -o /tmp/patches origin/main..HEAD # Add a cover letter git format-patch --cover-letter -o /tmp/patches origin/main..HEAD # Set the subject prefix for a re-roll git format-patch --subject-prefix="PATCH v3" -o /tmp/patches origin/main..HEAD # Automatically detect base commit git format-patch --base=auto origin/main..HEAD ``` `--base=auto` records which upstream commit the series is based on. Reviewers can use this to check out the exact base and apply cleanly. ### Embedding range-diff in the Cover Letter When sending a v2+, include a range-diff so reviewers can see what changed: ```bash # Generate range-diff and pipe it into the cover letter manually git range-diff origin/main..v1-branch origin/main..v2-branch # With b4, this is automatic. Without b4, do it manually: git format-patch --cover-letter --range-diff=v1-branch -o /tmp/patches origin/main..HEAD ``` The `--range-diff` flag makes git insert the range-diff output into the cover letter body, which is the convention on LKML. ### range-diff standalone ```bash # Compare two versions of a 5-patch series git range-diff \ base..old-branch \ base..new-branch # Limit to specific patches git range-diff base..old~2 base..new~2 ``` Output reads like a diff of diffs. Lines prefixed with `>` are new content inside a changed patch. --- ## 5. git send-email Power Usage ### Basic Invocation ```bash # Send all patches in a directory git send-email /tmp/patches/*.patch # Send a formatted range directly git send-email origin/main..HEAD ``` ### Re-rolls: Threading Under the Original Cover Letter ```bash # Reply to the v1 cover letter so the thread stays together git send-email \ --in-reply-to="<20230601120000.12345-0-author@kernel.org>" \ /tmp/patches-v2/*.patch ``` The message ID comes from the original `[PATCH v1 0/N]` cover letter. Get it from lore.kernel.org or from the email headers. ### Identity Profiles ```bash # ~/.gitconfig [sendemail "kernel"] smtpserver = smtp.kernel.org smtpuser = me@kernel.org from = Me [sendemail "personal"] smtpserver = smtp.fastmail.com smtpuser = me@fastmail.com from = Me # Use a specific identity git send-email --identity=kernel /tmp/patches/*.patch ``` ### Safety Options ```bash # Dry run: print what would be sent without sending git send-email --dry-run /tmp/patches/*.patch # Validate patches before sending git send-email --validate /tmp/patches/*.patch # Suppress CC to people already on To: git send-email --suppress-cc=bodycc /tmp/patches/*.patch # Suppress all auto-detected CCs git send-email --suppress-cc=all /tmp/patches/*.patch # Control CC handling explicitly git send-email \ --to=linux-kernel@vger.kernel.org \ --cc=maintainer@example.com \ --suppress-cc=self \ /tmp/patches/*.patch ``` `--validate` runs `git am --dry-run` on the patches first. Catches malformed patches before they hit the list. --- ## 6. Josef Bacik's Review Aliases Josef Bacik (btrfs maintainer) uses a workflow that lets him walk a patch series commit by commit, opening vimdiff at each step. ### Aliases ```bash # In ~/.gitconfig [alias] # Show the series log with short stat series-log = log --oneline --stat origin/main..HEAD # Review each commit interactively series-review = "!f() { \ git log --reverse --format='%H' origin/main..HEAD | \ while read sha; do \ git show $sha | vim -R -; \ echo 'Next? [y/n]'; \ read ans; \ [ \"$ans\" = 'n' ] && break; \ done; \ }; f" # Vimdiff a single commit against its parent show-diff = "!f() { \ git difftool -t vimdiff \"$1\"^ \"$1\"; \ }; f" # Check each patch applies to a clean base check-series = "!f() { \ git format-patch -o /tmp/review-patches origin/main..HEAD && \ git stash && \ git checkout origin/main && \ git am /tmp/review-patches/*.patch; \ }; f" ``` ### Usage Walkthrough ```bash # See what's in the series git series-log # Walk through each patch, reading in vim git series-review # Vimdiff a specific commit git show-diff a3f4c2b # Review from a mailing list message ID via b4 b4 am --create-branch review/feature git checkout review/feature git series-review ``` --- ## 7. Greg KH's Stable Kernel Workflow with Quilt Greg Kroah-Hartman uses quilt to manage the stable kernel patch queue. The workflow applies to any project where you maintain patches on top of a moving upstream. ### Setup ```bash # Install quilt # Arch: sudo pacman -S quilt # Configure cat >> ~/.quiltrc << 'EOF' QUILT_DIFF_ARGS="--no-timestamps --no-index -p ab --color=auto" QUILT_REFRESH_ARGS="--no-timestamps --no-index -p ab" QUILT_SERIES_ARGS="--color=auto" QUILT_PATCH_OPTS="--unified" QUILT_DIFF_OPTS="-p" EDITOR=nvim EOF ``` ### Daily Workflow ```bash # Import patches from upstream stable queue b4 am --output-dir stable-queue/patches/5.15/ # Navigate the queue with quilt quilt series # list all patches quilt next # what's the next unapplied patch quilt push # apply next patch quilt push -a # apply all remaining quilt pop # unapply last patch quilt pop -a # unapply all # When a patch fails to apply quilt push # fails quilt refresh # after resolving conflicts manually quilt header -e # edit the patch header/description # Export for re-submission quilt diff # diff of current patch quilt export # export to directory ``` ### Backporting a Fix to Stable ```bash # Start a new patch quilt new drivers-my-fix.patch # Add files you'll modify to the patch quilt add drivers/gpu/drm/i915/display.c # Edit the file nvim drivers/gpu/drm/i915/display.c # Refresh the patch with your changes quilt refresh # Check the result quilt diff quilt header -e ``` --- ## 8. Junio Hamano's Integration Branch Model Junio (git maintainer) maintains git.git with a formal branch graduation model. The same pattern works for any project with a long-running stable history. ### The Four Branches | Branch | Purpose | |--------|---------| | `maint` | Bug fixes for the last stable release | | `master` | Current stable release | | `next` | Integration branch; topics proposed for master | | `seen` | Everything received; experimental / unstable | ### Graduation Flow ``` topic branch -> seen -> next -> master -> maint (backports) ``` A topic lands in `seen` first. If it looks good after review, Junio merges it into `next`. Once the next release cycle closes, `next` is merged into `master`. Only regression fixes ever go into `maint`. ### Maintaining This Model ```bash # Merge a topic into seen for initial integration git checkout seen git merge --no-ff pu/feature-branch -m "Merge branch 'pu/feature-branch' into seen" # Graduate to next after positive review git checkout next git merge --no-ff pu/feature-branch # At release time, merge next into master git checkout master git merge --no-ff next # Backport a critical fix to maint git checkout maint git cherry-pick git tag v2.41.1 # Mark a topic as "cook more" # (just don't merge it to next yet, leave in seen) ``` ### What-Cooking Document Junio sends a periodic "What's cooking in git.git" email. You can replicate this: ```bash # Show topics not yet in next git log next..seen --oneline --no-merges # Show topics in next not yet in master git log master..next --oneline --no-merges ``` --- ## 9. git worktree Bare Repo Pattern The bare repo pattern lets you work on multiple branches simultaneously without stashing or switching. Each branch gets its own directory. ### Setup ```bash # Clone as bare git clone --bare https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux.git # Or convert an existing clone cd linux && git config core.bare true # Add worktrees for each branch you're working on git -C linux.git worktree add ../linux-main main git -C linux.git worktree add ../linux-drm drm-fixes git -C linux.git worktree add ../linux-review review/incoming-series # List all worktrees git -C linux.git worktree list ``` ### Daily Use ```bash # Work on the drm branch without affecting main cd ../linux-drm vim drivers/gpu/drm/i915/display.c git add -p git commit # Simultaneously test something in the main tree cd ../linux-main git log --oneline -10 # Remove a worktree when done git -C linux.git worktree remove ../linux-review ``` ### Why Bare A regular clone has a checked-out working tree tied to the `.git` directory. A bare clone has no working tree of its own, so every checkout lives in a separate `worktree add` directory. You never have a "main" checkout competing with your worktrees. --- ## 10. git rerere for Repeated Conflicts `rerere` (reuse recorded resolution) records how you resolved a conflict and replays it automatically next time the same conflict appears. Essential when rebasing a long-lived branch repeatedly onto a moving upstream. ### Configuration ```ini [rerere] enabled = true autoupdate = true # stage the resolved file automatically ``` ### Basic Usage ```bash # Enable if not in config git config --global rerere.enabled true # Conflict occurs during rebase git rebase upstream/main # ... conflict in drivers/net/foo.c ... # Resolve it manually nvim drivers/net/foo.c git add drivers/net/foo.c git rebase --continue # rerere records this resolution. Next time the same conflict appears, # it resolves automatically. ``` ### Training from Existing Merges If you have old merge commits where conflicts were already resolved, you can train rerere from them: ```bash # Walk through all merge commits and extract resolutions git log --merges --format="%H" | \ while read sha; do git rerere train "$sha" done ``` ### Inspecting and Forgetting ```bash # List all recorded resolutions git rerere list # Show the rerere directory ls .git/rr-cache/ # Forget a bad resolution (before applying it to commits) git rerere forget path/to/file.c # Forget by conflict ID git rerere forget # forgets all resolutions in the current conflict ``` ### When rerere Saves You Typical scenario: you have a topic branch with a conflict against upstream. You rebase weekly. Without rerere you resolve the same conflict every time. With rerere you resolve it once, and all subsequent rebases apply the resolution automatically. --- ## 11. Stacked Branches with --update-refs `rebase --update-refs` is the native git answer to stacked diffs. It keeps all the intermediate branch pointers correct when you rebase the bottom of a stack. ### Setup ```bash # Enable globally (git >= 2.38) git config --global rebase.updateRefs true ``` ### Building a Stack ```bash # Create a base feature branch git checkout -b feature/base main # ... make commits A, B ... # Build on top of it git checkout -b feature/part2 feature/base # ... make commits C, D ... # Build further git checkout -b feature/part3 feature/part2 # ... make commits E, F ... ``` ### Rebasing the Whole Stack ```bash # Without updateRefs, rebasing feature/base orphans part2 and part3. # With updateRefs, all three branches move together. git checkout feature/base git rebase --update-refs main # feature/base, feature/part2, feature/part3 all point to their # rebased equivalents. No manual branch moving required. ``` ### Explicit Per-Rebase Control ```bash # Turn on for a single rebase if you haven't set it globally git rebase --update-refs main # Turn off for a single rebase even if globally enabled git rebase --no-update-refs main ``` ### Checking the Stack ```bash # See the stack visually git log --oneline --graph feature/part3 # See which refs are on which commits git log --oneline --decorate feature/part3 ``` --- ## 12. git-autofixup + autosquash Workflow `git-autofixup` generates fixup commits targeting the right commit automatically, based on the blame of changed lines. ### Installation ```bash # Install git-autofixup cpan App::GitAutofixup # or pip install git-autofixup # some distributions package it here ``` ### Workflow ```bash # You have a series: commits A (add function), B (use it), C (tests) # You find a bug in A while reviewing B # Fix the bug in the working tree (don't commit yet) nvim drivers/net/foo.c # git-autofixup looks at the blame of changed lines and # creates a fixup! commit targeting the right parent git autofixup # Result: a new commit "fixup! " git log --oneline # C tests # B use function # fixup! A add function # A add function # Now interactively rebase to collapse the fixup git rebase -i main # autosquash=true handles it automatically # or explicitly: git rebase --autosquash main ``` ### Manual autosquash Without `git-autofixup`, you create fixup commits manually: ```bash # Create a fixup targeting commit A git commit --fixup= # Create a squash (opens editor during rebase to merge messages) git commit --squash= # Apply git rebase --autosquash main ``` ### Amending Specific Commits Without Interactive Rebase ```bash # Modify a specific commit without rebase -i git commit --fixup= GIT_SEQUENCE_EDITOR=true git rebase --autosquash -i main ``` `GIT_SEQUENCE_EDITOR=true` skips the editor entirely, letting autosquash apply automatically. --- ## 13. Suckless Patch Management Suckless projects (dwm, st, dmenu, etc.) release minimal C programs meant to be patched per-user. The recommended workflow uses a branch per patch, merges them to a local `build` branch, and uses rerere to survive upstream updates. ### Initial Setup ```bash cd ~/dev/dwm # Upstream tracking branch (never commit local patches here) git remote add upstream https://git.suckless.org/dwm git fetch upstream git checkout -b upstream upstream/master # Your clean base for building git checkout -b build upstream ``` ### Adding a Patch ```bash # Download a patch from suckless.org curl -O https://dwm.suckless.org/patches/systray/dwm-systray-6.4.diff # Apply it on its own branch git checkout -b patch/systray upstream git apply dwm-systray-6.4.diff git add -A git commit -m "patch: systray" # Add another git checkout -b patch/fullgaps upstream curl -O https://dwm.suckless.org/patches/fullgaps/dwm-fullgaps-6.5.diff git apply dwm-fullgaps-6.5.diff git add -A git commit -m "patch: fullgaps" ``` ### Building Your Combined Branch ```bash # Reset build to upstream git checkout build git reset --hard upstream # Merge patches one at a time git merge --no-ff patch/systray # resolve conflicts, git rerere saves them git merge --no-ff patch/fullgaps # resolve conflicts, rerere saves them # build branch is now upstream + all your patches ``` ### Surviving Upstream Updates ```bash # New upstream release git fetch upstream git checkout upstream git reset --hard upstream/master # Rebuild the combined branch git checkout build git reset --hard upstream # Re-merge each patch branch. rerere replays saved resolutions. git merge --no-ff patch/systray # probably auto-resolves via rerere git merge --no-ff patch/fullgaps # same # If a patch no longer applies cleanly, rebase it first git checkout patch/systray git rebase upstream git checkout build git reset --hard upstream git merge --no-ff patch/systray ``` ### Pushing to Your Remote ```bash # Push all branches git push origin upstream build patch/systray patch/fullgaps ``` --- ## 14. patchutils: interdiff, filterdiff, lsdiff, splitdiff, combinediff `patchutils` provides tools for manipulating patch files directly, without applying them. ```bash # Arch: sudo pacman -S patchutils ``` ### interdiff: Diff Between Two Patches Shows what changed between v1 and v2 of a patch, similar to range-diff but operates on raw patch files: ```bash interdiff v1.patch v2.patch interdiff v1.patch v2.patch | colordiff | less -R ``` Useful when you received patches by email and don't have the git history. ### lsdiff: List Files Affected by a Patch ```bash lsdiff big-series.patch # drivers/net/ethernet/intel/igb/igb_main.c # drivers/net/ethernet/intel/igb/igb_ethtool.c # With line numbers showing where each hunk starts lsdiff -s big-series.patch ``` ### filterdiff: Extract or Exclude Hunks ```bash # Extract only patches touching drivers/net/ filterdiff -i 'drivers/net/*' big-series.patch > net-only.patch # Exclude generated files filterdiff -x '*.pb.go' big-series.patch > no-generated.patch # Extract a specific file filterdiff -i 'Makefile' big-series.patch ``` ### splitdiff: Split a Multi-file Patch Into Per-file Patches ```bash splitdiff -a big-series.patch # Creates 001-drivers_net_foo.patch, 002-include_linux_bar.patch, ... splitdiff -D /tmp/split-patches big-series.patch ``` ### combinediff: Combine Two Patches into One ```bash # Create a single patch that is the net effect of applying p1 then p2 combinediff p1.patch p2.patch > combined.patch ``` Useful when you've iterated on a fix and want to send a single clean patch to stable@ instead of a series. --- ## 15. git notes for Review Metadata git notes attach arbitrary text to commits without changing the commit SHA. Useful for recording review status, test results, or stable-queue tags. ### Basic Usage ```bash # Add a note to a commit git notes add -m "Reviewed-by: You " # Append to existing note git notes append -m "Tested-by: CI " # Read notes in log git log --notes --oneline # Read a specific note git notes show # Edit a note git notes edit # Remove a note git notes remove ``` ### Custom Note Namespaces By default notes go in `refs/notes/commits`. You can have multiple note tracks: ```bash # Add a review note in its own namespace git notes --ref=refs/notes/review add -m "Acked-by: Maintainer" # Add stable-queue metadata git notes --ref=refs/notes/stable add -m "stable: 6.1+" # Show notes from a specific ref git log --notes=refs/notes/review ``` ### Sharing Notes Notes are not pushed/fetched by default. Configure it: ```bash # Push notes git push origin refs/notes/commits # Fetch notes from upstream git fetch origin refs/notes/commits:refs/notes/commits # Configure automatic fetch git config --add remote.origin.fetch '+refs/notes/*:refs/notes/*' ``` --- ## 16. git replace for Non-Destructive History `git replace` creates a redirect: references to the original SHA transparently use the replacement instead. The original object is unchanged. ### Use Cases - Grafting a project's history onto a different parent - Replacing a commit with a fixed version without rewriting history - Splicing two repos together ### Basic Replace ```bash # Replace commit A with a corrected commit A' (same content, fixed message) git replace # Now git log, git show, git diff all see A' when they encounter A git log --oneline # List replacements git replace -l # Remove a replacement git replace -d ``` ### Grafting History Scenario: your project's git history only goes back to 2019, but you have an older repo with history to 2015. You want to connect them without a destructive rebase. ```bash # In the newer repo, create a replacement for the root commit # that points to the last commit in the old repo git replace --graft # Now git log shows continuous history back to 2015 # Anyone who fetches gets the replacement if you push it git push origin 'refs/replace/*' ``` ### Sharing Replacements ```bash git push origin 'refs/replace/*' git fetch origin '+refs/replace/*:refs/replace/*' ``` --- ## 17. git-branchless `git-branchless` provides a Mercurial-style smartlog, stack-aware navigation, and safe history rewriting. Particularly useful for anonymous branch workflows. ### Installation ```bash # Arch: yay -S git-branchless # or cargo install git-branchless ``` ### Setup ```bash cd ~/dev/linux git branchless init ``` ### smartlog: Visual Stack Overview ```bash git sl # or git branchless smartlog # Output shows a DAG of your local commits relative to main: # O abc1234 main # | # o def5678 (HEAD) feat: add drm helper # | # o ghi9012 feat: add drm structure ``` ### Navigation ```bash # Move to the parent commit git prev # Move to the child commit (when unambiguous) git next # Move multiple steps git prev 3 git next 2 # When there are multiple children, pick one git next --interactive ``` ### restack: Rebase an Entire Stack ```bash # After main advances, rebase your whole stack onto it git restack # Equivalent to: rebase each commit in topological order, # respecting parent relationships, updating all refs. ``` ### move: Relocate Commits ```bash # Move a commit to be on top of a different base git move --base --onto main # Move a range git move --base --onto ``` ### sync: Pull and Restack ```bash # Fetch upstream and restack all local work on top git sync # Equivalent to: git fetch + git restack ``` ### hide/unhide ```bash # Hide a commit from smartlog without deleting it git hide # Bring it back git unhide # Undo the last operation git undo ``` --- ## Speed Stack Ranking Ranked by leverage: how much workflow pain each tool removes relative to the time to learn and configure it. | Rank | Tool / Setting | Pain Removed | Time to Learn | |------|---------------|-------------|--------------| | 1 | `rerere.enabled + autoupdate` | Eliminates repeated conflict resolution on long-lived branches | 5 minutes | | 2 | `merge.conflictstyle = zdiff3` | Makes conflicts readable; you see base + both sides | 1 minute | | 3 | `rebase.updateRefs = true` | Stacked branches just work after rebase | 1 minute | | 4 | `diff.algorithm = histogram` | Better diffs, fewer false matches in code | 1 minute | | 5 | b4 send / am | Full patch series management with automatic range-diff embedding | 30 minutes | | 6 | git worktree (bare pattern) | Parallel branch work with no stashing | 20 minutes | | 7 | git-branchless smartlog + restack | Visual stack + safe automated rebase | 45 minutes | | 8 | git-autofixup + autosquash | Automatic fixup commit targeting; eliminates manual rebase -i surgery | 20 minutes | | 9 | patchutils (filterdiff / interdiff) | Patch surgery without git history; useful for stable backports | 30 minutes | | 10 | git format-patch --range-diff + --base=auto | Clean re-roll submission with embedded diff; reviewers thank you | 15 minutes | | 11 | lei | Searchable mailing list archive locally without subscribing | 45 minutes | | 12 | quilt (Greg KH model) | Patch queue management for stable/backport trees | 1 hour | | 13 | Junio's seen/next/master/maint model | Formal topic graduation; only needed if you maintain a public tree | 1 hour | | 14 | git notes | Review metadata without commit mutation | 15 minutes | | 15 | git replace | Non-destructive history surgery; niche but irreplaceable when needed | 20 minutes | The first four are pure config with no UX change other than better behavior. Set them globally, forget about them, and reap the benefit forever. Everything else requires a workflow shift, but each one has a concrete break-even point within the first week of use.