# How to Squash Commits in Git
Ever struggled to understand a messy Git history filled with tiny, incremental commits? Squashing commits is your solution for a cleaner, more navigable project timeline. This powerful Git technique allows you to combine multiple related commits into a single, meaningful one, significantly improving clarity when reviewing pull requests or tracking down changes.
TLDR:
- git rebase -i HEAD~N
- pick → s/f
- :wq
- git push --force-with-lease
1. Start an Interactive Rebase: git rebase -i HEAD~N or git rebase -i {commit-hash}
The first step is to initiate an interactive rebase. This is a useful Git command that allows you to modify commits in various ways, including squashing them.
- git rebase -i HEAD~N:
- git rebase: This command is fundamentally about re-applying commits on top of a new base commit.
- -i (or --interactive): This flag tells Git you want to manually edit the list of commits that will be rebased. It opens an editor with a list of the commits you're about to work on.
- HEAD~N:
- HEAD refers to your current commit (the tip of your current branch).
- ~N specifies the number of commits to include in the rebase, counting backwards from HEAD. For example, HEAD~3 will list the last three commits for you to edit. So, if you want to squash the last 3 commits, you'd use N=3.
- Alternative: git rebase -i {commit-hash}:
- Instead of counting commits with HEAD~N, you can specify a particular commit hash. This command will list all commits on your current branch that came after the specified {commit-hash}. This is useful if you want to squash a series of commits that aren't necessarily the most recent ones, or if you know the exact commit before the sequence you want to squash.
Once you run this command, Git will open your default text editor (usually Vim or Nano) with a list of the commits you've selected. Each line will look something like this:
pick f7f3f6d Good commit message pick 310154e Fix typo pick a5f4a0d Upd style
These are listed with the oldest commit in your selection at the top and the newest at the bottom.
2. Choose Which Commits to pick and squash
Now, you'll tell Git how to handle each commit in the list. You do this by changing the word pick at the beginning of each line (except usually the first one you want to keep).
- pick: Keep this command for the commit you want to be the base of your squashed commit. This is typically the earliest commit in the sequence you're modifying (the one at the top of the list in the editor). This commit will remain, and others will be merged into it.
- squash (or s): This means you want to combine this commit with the previous one. The commit message of the squashed commit will be added to the previous commit's message.
- fixup (or f): Similar to squash, but it discards the commit message of the squashed commit, keeping only the message of the previous commit.
Example:
pick f7f3f6d Good commit message f 310154e Fix typo f a5f4a0d Upd style
Other useful interactive rebase commands (though not the focus here) include reword (to change the commit message without altering the content), edit (to amend the commit's content), and drop (to delete the commit entirely).
3. Save & Edit Commit Message
Once you've marked your commits with pick and squash (or other commands), save the file and close your editor.
If your default editor is Vim, you do this by typing :wq and pressing Enter. For Nano just press Ctrl+X. For other editors, use their standard save and quit commands.
After you save the rebase instruction file, Git will process the squashing. It will then open another editor window. This time, it's for you to create the commit message for the new, combined (squashed) commit.
Git will typically pre-fill this new message with a concatenation of all the commit messages from the commits you squashed. You should edit this thoroughly to create a single, clear, and concise message that accurately describes the combined changes.
Push Your Changes: git push --force or git push --force-with-lease
Because rebasing (and thus squashing) rewrites commit history, your local branch will now have a different history than its remote counterpart. A regular git git push will be rejected by the remote repository because the histories have diverged.
To update the remote branch with your new, squashed history, you must use a force push:
- git push --force: This command tells Git to overwrite the remote branch with the state of your local branch.
Warning: git push --force is a destructive command. If other people have pulled the old version of the branch and based their work on it, force pushing will make it very difficult for them to integrate their changes. It can effectively erase history on the remote that others might be relying on. - git push --force-with-lease: This is a safer alternative. It will only force the push if the remote branch is in the state you expect it to be (i.e., no one else has pushed new commits to it since your last pull/fetch). If someone else has pushed changes, the --force-with-lease push will fail, preventing you from accidentally overwriting their work. You'd then need to pull their changes, potentially rebase again, and then try the push.
Best Practices for Force Pushing:
- Never force push to shared branches like main, master, or develop unless you are absolutely certain of what you are doing.
- Always communicate with your team before force pushing, especially on shared branches.
- Use --force-with-lease whenever possible to avoid overwriting others' work.
What if something goes wrong during the rebase?
If you make a mistake during the interactive rebase process or change your mind, you can usually abort it and return your branch to its state before you started the rebase by running: git rebase --abort