The Git Patch Stack Methodology
TL;DR
Modern Git workflows optimize for features, but best practices optimize for integration, review quality, and history.
The Git Patch Stack Methodology replaces feature-centric pull requests with small, architectural patches stacked directly on top of mainline. Each patch is:
- Small and reviewable
- Continuously rebased and integrated
- Architecturally meaningful
- Historically valuable
Development happens outside-in, while review and integration happen inside-out—one patch at a time. The result is better reviews, fewer conflicts, cleaner history, and faster integration without abandoning pull requests.
A Note on Tooling
This article describes a methodology, not a tool requirement.
That said, some of the workflows described here—tracking patch identity across rebases, managing patch stacks, and opening pull requests per patch—introduce friction if done manually.
To address that, the Git Patch Stack CLI exists.
The CLI is designed to support this methodology directly on top of Git and existing platforms like GitHub and Bitbucket. It automates the mechanical work (branching, cherry-picking, tracking patch identity, updating pull requests) so developers can focus on creating high-quality patches and integrating continuously.
The rest of this article explains why the methodology works. Tooling simply makes it practical at scale.
Git Patch Stack Methodology
To truly understand the Git Patch Stack Methodology, it helps to start with how software changes are typically made.
At a high level, most changes fall into one of two categories:
- Large changes — new features or major system modifications
- Small changes — bug fixes, copy updates, refactors, and minor improvements
On the surface, this works fine if you don’t care much about peer review quality, conflict reduction, architectural clarity, or the long‑term historical value of your code. In that world, all you need is a feature branch, following the workflow popularized by platforms like GitHub and Bitbucket.
At best, a pull request created this way corresponds to a single, coherent change from the diagram above. At worst, it looks like a feature branch full of loosely related commits—snapshots of half‑finished thoughts—with little structure or narrative. The commits don’t clearly explain what changed, why it changed, or how the changes relate to one another. Unfortunately, this pattern is extremely common.
Best Practices
So what should we actually be aiming for?
Small Pull Requests
One widely accepted best practice is to keep pull requests small. The primary reason is to enable high‑quality peer review.
When a pull request is large and overwhelming, reviewers tend to skim it and respond with a quick LGTM. We’ve all done it. The result is a review that adds little value—often no more than what an automated linter could have caught.
Small pull requests, on the other hand, make it realistic for reviewers to:
- Understand the change in context
- Evaluate architectural decisions
- Reason carefully about correctness and edge cases
Continuous Integration
Another best practice is continuous integration—not the tooling (GitHub Actions, CircleCI, etc.), but the behavior.
Continuous integration means integrating changes into the mainline as quickly and as frequently as possible. Doing so:
- Dramatically reduces merge conflicts
- Makes conflicts easier to resolve when they do occur
- Narrows the scope of investigation when a bug is introduced
Small, frequent integrations keep the system stable and make failures easier to reason about.
Architecture & Historical Value
A third set of best practices concerns the long‑term readability and usefulness of your Git history. Ideally, each change should:
- Align with a specific architectural element
- Clearly describe what changed
- Explain why the change was necessary
- Indicate how the change was implemented
When these conditions are met, your Git history becomes a form of documentation. You can understand why the system evolved the way it did, not just what the final state looks like. Each commit becomes a small, logical step toward a larger goal—similar to showing your work in a math problem. This is often described as proof of work.
The Conflict
When you try to apply these best practices while relying primarily on feature branches, tensions quickly emerge.
Feature Branches vs. Continuous Integration
Branches exist to isolate work. Continuous integration exists to avoid isolation. By definition, the two concepts are in conflict.
Teams often try to work around this by rebasing feature branches or repeatedly merging mainline back into them. Rebasing adds cognitive and operational overhead. Merging mainline repeatedly results in tangled Git histories that are difficult to understand or reason about.
Feature Branches vs. Small Pull Requests
Feature branches typically bundle an entire feature into a single pull request. The result is often a massive PR that discourages careful review and encourages superficial approval.
Feature Branches vs. Historical Value
Finally, feature branches encourage developers to think in terms of features, not architectural changes. As a result, commit messages often lack clarity and historical usefulness. A feature is not an architectural unit; it is composed of many smaller architectural steps.
Resolving the Conflict
To better align with these best practices, we need a different mental model—one that emphasizes small, architectural changes and frequent integration.
Our goals are straightforward:
- Small Pull Requests — to enable meaningful peer review
- Continuous Integration — to reduce conflicts and integration pain
- Architectural & Historical Value — to preserve a readable, valuable history
The Patch
Let’s introduce a new unit of work: the patch.
A patch is a single, logical, architecturally meaningful change that provides historical value. This terminology is intentional—it echoes the original patch‑based workflows used in early open‑source development, and still used today by projects like Git and the Linux kernel.
Conceptually, a patch maps directly to a well‑formed Git commit.
Continuous Integration Without Feature Branches
Instead of developing long‑lived feature branches, we develop directly on top of mainline.
Local commits represent patches. As mainline advances, we continuously rebase our local patch stack on top of it. Git makes this easy:
[pull]
rebase = true
With this configuration, git pull automatically rebases your local patches onto the latest mainline, surfacing conflicts early—when they’re small and easy to fix.
The Stack
Rarely do we create just one patch. More often, we create a stack of patches, each building on the one below it.
This stack lives locally on top of origin/main. Later patches can depend on earlier ones, which mirrors how real features are developed. Git’s interactive rebase allows us to:
- Reorder patches
- Edit them
- Split or squash them
- Drop them entirely
Work‑in‑progress commits can be marked with a WIP: prefix and refined over time until they’re ready for review.
Pull Requests as Patch Reviews
Pull requests still matter—they’re how we request review. But instead of representing features, they represent patches.
A simple workflow looks like this:
- Create a branch from
origin/mainnamed after the patch - Cherry‑pick the patch onto that branch
- Push the branch and open a pull request
This introduces some overhead, but it’s manageable—and automatable.
Independent vs. Dependent Patches
Independent patches can be reviewed and merged on their own.
Dependent patches require more care. If Patch B depends on Patch A, then Patch A must be reviewed and merged first. This encourages an inside‑out review order, even if development happens outside‑in.
In some cases, a patch series—a pull request containing multiple dependent patches—may be appropriate. This trades faster integration for convenience and should be used selectively.
Updating Pull Requests
When a reviewer requests changes, we don’t add new commits—we update the patch.
Using interactive rebase, we modify the original commit and force‑push the updated branch. The pull request stays focused on a single logical change, even as it evolves.
Tracking Patch Identity
Because rebasing changes commit hashes, we need a stable identifier. Each patch includes a UUID in its commit message:
ps-id: <uuid>
This allows tooling to track patches across rebases, branches, and pull requests—and to detect when a patch has diverged from its reviewed version.
Development: Outside‑In, Review: Inside‑Out
Patch stacks naturally reflect dependency structure: foundational changes sit at the bottom, higher‑level behavior at the top.
This pairs well with outside‑in development—starting from the system boundary and letting requirements drive lower‑level interfaces. Early patches may be incomplete or marked as WIP.
As inner layers solidify, outer patches are revisited and finalized. Review, however, happens inside‑out: dependencies first, consumers later.
This rhythm—develop outside‑in, review inside‑out—is the heart of the Git Patch Stack Methodology.