When working with Git, developers often face a common challenge: keeping the main branch's commit history clean and organized. While a detailed commit history is valuable during feature development, the main branch can benefit from a more concise history where each commit represents a complete, logical change. One popular approach to achieve this is by squashing feature branches into single, well-documented commits before merging them into the main branch.
There are various approaches to integrating changes in Git - merges, rebases, and squashing each have their own trade-offs well documented by others (e.g. see Mitchell Hashimoto's excellent analysis). This post doesn't aim to address that debate, but rather focuses on making the rebase & squash workflow more straightforward and less error-prone.
The traditional approach to squashing a branch can become surprisingly painful, especially with long-running feature branches. During rebase, you may need to resolve conflicts for each commit being rebased, often leading to a cascade of conflict resolutions as later commits encounter similar conflicts.
This becomes particularly frustrating because you often find yourself resolving the same conflicts multiple times, even though you know that ultimately all these changes will be squashed into a single commit anyway.
A Solution
Today, I'll share a custom ZShell function I wrote to streamline this process.
This function, which I call git-hard-rebase
, takes a different from
traditional rebase & squash. Instead of trying to replay each commit, it:
- Creates a single patch containing all changes between branches
- Makes a backup of your current branch (safety first!)
- Resets your current feature branch to match the target branch
- Applies all your changes as a single commit
- Resets your branch to that new commit
Here's how to use it:
git-hard-rebase <target-branch> "Commit message"
As an example, assume you want to rebase a feature branch feature-b
on top of
main
. First, check out the feature-b
branch. Then the following command will
create a new single commit on top of main
with all the changes from
feature-b
, and then reset the feature-b
branch to point to that commit:
git-hard-rebase main "Add my awesome feature"
The Implementation
Here's the complete implementation, with detailed explanations of each part:
I hope this helps with your Git workflows!