How do I squash my last N commits together into a single commit in Git?
Brief Answer
To squash your last N commits into a single commit in Git, use the interactive rebase command with git rebase -i HEAD~N
where N is the number of commits you want to squash. This opens an editor where you can change “pick” to “squash” (or “s”) for all but the first commit, then save and close the editor to merge them into one.
Contents
- How to Squash Commits: Step-by-Step Instructions
- Understanding the Interactive Rebase Editor
- Alternative Methods to Squash Commits
- Best Practices and Common Pitfalls
- Handling Merge Conflicts
- Undoing a Squash Operation
How to Squash Commits: Step-by-Step Instructions
To squash the last N commits into a single commit, follow these steps:
-
Start an interactive rebase session for the last N commits:
bashgit rebase -i HEAD~N
Replace N with the number of commits you want to squash. For example, to squash the last 3 commits:
bashgit rebase -i HEAD~3
-
This will open your default text editor with a list of commits, like this:
pick a1b2c3d Commit message 1 pick d4e5f6g Commit message 2 pick h7i8j9k Commit message 3
-
Change the action for all commits except the first one you want to keep. Use
squash
(ors
) for these commits:pick a1b2c3d Commit message 1 s d4e5f6g Commit message 2 s h7i8j9k Commit message 3
-
Save and close the editor. Git will now attempt to combine all commits.
-
Another editor will open with a combined commit message. Edit this message as needed, then save and close.
-
The commits are now squashed into one. You can verify with:
bashgit log --oneline -5
-
If you’re satisfied with the result, force push to update the remote branch (if it’s already pushed):
bashgit push --force-with-lease
Important Note: Force pushing will rewrite history, which can affect teammates if they’ve based work on these commits. Only force push when you’re working on a feature branch that hasn’t been shared with others or when you’re sure it’s appropriate.
Understanding the Interactive Rebase Editor
When you run git rebase -i
, Git opens an editor with a list of commits and their associated actions. Each commit line starts with a command that determines what to do with that commit:
- pick (or p): Use the commit as-is (default)
- squash (or s): Combine this commit with the previous one
- fixup (or f): Combine this commit with the previous one, but discard its commit message
- reword (or r): Use the commit, but edit its commit message
- edit (or e): Use the commit, but stop for amending
- exec (or x): Run the following command using the shell
- drop (or d): Remove the commit
For squashing commits:
- Leave the first commit as
pick
- Change all subsequent commits to
squash
ors
The commits are listed in reverse chronological order, meaning the most recent commit appears last in the list.
# Example of a properly configured interactive rebase for squashing
pick 1a2b3c4 First commit to keep
squash 5d6e7f8 Second commit - to be squashed
squash 9h0i1j2 Third commit - to be squashed
squash 3k4l5m6 Fourth commit - to be squashed
Alternative Methods to Squash Commits
While interactive rebase is the most common method, there are alternative approaches to squashing commits:
Using git commit --amend
This method is useful for squashing the most recent commit with the staged changes:
-
Stage all changes you want to include in the squashed commit:
bashgit add .
-
Amend the most recent commit with the staged changes:
bashgit commit --amend
-
Edit the commit message and save.
This only works for combining the most recent commit with newly staged changes, not for multiple historical commits.
Using git merge --squash
This method creates a new commit that squashes all changes from another branch or commit range:
-
Create and switch to a new branch (optional):
bashgit checkout -b squashed-branch
-
Merge with the
--squash
option:bashgit merge --squash origin/main
-
Create a new commit with all the changes:
bashgit commit -m "Squashed commit message"
This is useful when you want to bring in changes from another branch but don’t want to preserve their individual commit history.
Using git reset
This method is more destructive and should be used with caution:
-
Reset to N commits back, keeping changes staged:
bashgit reset --soft HEAD~N
-
Commit all the staged changes:
bashgit commit -m "New squashed commit message"
This approach doesn’t require an interactive editor but gives you less control over the final commit message.
Best Practices and Common Pitfalls
Best Practices
-
Squash on feature branches, not main/master: Squashing rewrites history, which can cause problems for teammates who’ve based work on your commits.
-
Write clear commit messages: When squashing multiple commits, take time to write a comprehensive commit message that explains the changes made across all the original commits.
-
Review before squashing: Use
git log --oneline -N
to review the commits you’re about to squash. -
Use
git rebase -i --autosquash
: Git can automatically mark commits that can be squashed if they fix up the previous commit. -
Backup your work: Consider using
git stash
before a rebase operation, especially if you’re new to interactive rebasing.
Common Pitfalls
-
Squashing commits that have been pushed: This requires force pushing, which can disrupt other team members’ workflows.
-
Forgetting to stage changes: Ensure all changes you want to include are staged before amending a commit.
-
Accidentally dropping commits: Double-check your interactive rebase configuration before saving and closing the editor.
-
Poor commit messages after squashing: Take time to write a meaningful commit message that captures the essence of all the squashed changes.
-
Ignoring merge conflicts: If conflicts occur during rebase, resolve them before continuing with
git rebase --continue
.
Handling Merge Conflicts
Sometimes, squashing commits can lead to merge conflicts. Here’s how to handle them:
-
Start the interactive rebase process as usual:
bashgit rebase -i HEAD~N
-
When a conflict is encountered, Git will stop and show you which files have conflicts.
-
Open the conflicted files and resolve the conflicts manually. Git marks conflicts with:
<<<<<<< HEAD Your changes ======= Their changes >>>>>>> branch-name
-
Stage the resolved files:
bashgit add <resolved-file>
-
Continue the rebase:
bashgit rebase --continue
-
If you encounter another conflict, repeat the process.
-
If you want to abort the rebase due to conflicts:
bashgit rebase --abort
Pro Tip: If you’re squashing many commits and expect conflicts, it might be easier to resolve the conflicts in the original commits first, then squash them.
Undoing a Squash Operation
If you’ve squashed commits and want to undo the operation, you have several options:
Using git reflog
Git’s reflog keeps a record of all changes to your branch heads:
-
Find the commit you want to return to:
bashgit reflog
-
Reset to that commit:
bashgit reset --hard <commit-hash>
Using git rebase --abort
If you’re in the middle of a rebase and haven’t completed it:
git rebase --abort
Using git revert
After you’ve pushed the squashed commit, you can revert it:
git revert <squashed-commit-hash>
Recovering lost commits
If you’ve accidentally dropped commits during interactive rebase, you can find them in the reflog and cherry-pick them back:
git reflog git cherry-pick <dropped-commit-hash>
Conclusion
Squashing commits in Git is a powerful way to clean up your commit history and create more meaningful, atomic commits. The interactive rebase method gives you precise control over which commits to combine and how to structure the final commit message.
Key takeaways:
- Use
git rebase -i HEAD~N
to start an interactive rebase for the last N commits - Change “pick” to “squash” for all commits except the first one
- Be cautious when force pushing squashed commits to remote repositories
- Always review your changes before finalizing a squash operation
- Know how to handle merge conflicts and undo squash operations when needed
By following these techniques, you can maintain a clean, understandable Git history that makes it easier for you and your team to understand the evolution of your codebase.