86

I have the habit of making a huge number of small commits, and I'm fine with it. But I would like to, from time to time, take a bunch of those linear commits and collapse them together as just one commit with the ability to write a new commit message.

I've looked into the documentation but seemed a little to cryptic to me. Does anybody knows how to do that?

j0k
  • 22,600
  • 28
  • 79
  • 90
rrb_bbr
  • 2,966
  • 4
  • 24
  • 26

4 Answers4

81

Assuming you don't care about retaining any of your existing commit messages, there's a nifty (and fast) git recipe you can use. First, make sure your branch is checked out:

git checkout <branch-to-squash>

For safety, lets tag the current commit.

git tag my-branch-backup

Next, move the branch HEAD back to your last good commit (without modifying the workspace or index). EDIT: The last good commit is the most recent commit on your branch that you want to retain.

git reset --soft <last-good-commit>

Using git status, you'll notice that all changes on your feature branch are now staged. All that's left to do is ...

git commit

This method is great for consolidating long, convoluted git histories and gnarly merges. Plus, there's no merge/rebase conflicts to resolve!

Now, if you need to retain any of your existing commit messages or do anything fancier than the above allows, you'll want to use git rebase --interactive.

Solution derived from: http://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

Reference: http://git-scm.com/docs/git-reset

Reference: http://git-scm.com/docs/git-rebase

Ben Amos
  • 1,750
  • 15
  • 18
  • 7
    This is a great answer - had 310 commits to squash, seemingly every single commit introduced rebase errors. Using your approach was quick, painless, and correct. – tbm Jun 21 '15 at 16:42
  • 7
    @MarceloMason Good question. Please interpret "last good commit" as "the most recent commit on your branch that you want to retain." For example, if you want to squash your topic branch's history all the way down to where it diverges from master branch, the last good commit would be the `merge-base` of master and your topic branch. If you have ten commits on your branch and only want to consolidate the top five, the last good commit would be `HEAD~5`. – Ben Amos Nov 16 '15 at 19:44
  • Useful technique, but I suggest the following: Ensure the repository is backed up off of your hardware (e.g., at Github) and create an entirely new branch from the "branch-to-squash" to work on. I've found fewer issues when I do so. – Brendan Jan 09 '16 at 23:41
  • 3
    Another lovely thing with this approach is that you can easily reset parts of the change set if you don't want them. It's also easy to perform the squash on a new branch by doing `git checkout -b my_squashed_branch` before hand. – Benjohn Sep 13 '16 at 14:40
  • 5
    After your are done collapsing the commits, changes can be pushed to remote with `git push -f`. – Hassan Feb 16 '19 at 22:59
  • This solution requires force push and that is disabled for my repository :( – Sumaiya A Nov 05 '21 at 14:00
62

Suppose you want to rewrite the history of the tree going back until (but not including) commit a739b0d.

export EDITOR=vim # or your favorite editor
git rebase a739b0d --interactive

Be sure to read up on interactive rebasing first.

Community
  • 1
  • 1
yfeldblum
  • 65,165
  • 12
  • 129
  • 169
  • 2
    But let's say that the commits that I want "rebase" are way in the past, prior of a bunch of merges and branch... Is this still valid? – rrb_bbr Jul 30 '11 at 15:38
  • 1
    Yes. You can first re-order the commits when you rebase so that the commits you want to squash together are in a sequence following all the merges. Then you can squash those commits together into one commit. – yfeldblum Jul 30 '11 at 15:41
  • 2
    Your link didn't go anywhere specific for me. http://git-scm.com/book/en/Git-Branching-Rebasing is the rebasing page; http://git-scm.com/book/en/Git-Tools-Rewriting-History has more of an overview. – Michael Scheper Jul 16 '14 at 00:32
  • Does this rebase also end up saving disk space? – CMCDragonkai Apr 26 '15 at 14:10
  • @CMCDragonkai no, the before-and-after commits are stored in your reflog. – Dan Jul 18 '15 at 13:58
18

Use the command git rebase -i <commit> where <commit> is the SHA for the last stable commit.

This will take you to your editor where you can replace the label pick that is next to each commit since the <commit> you included as an argument to your interactive rebase command. On the command you want to start collapsing at, replace pick with reword, and for each commit thereafter which you wish to collapse into it, replace pick with fixup. Save, and you'll then be allowed to provide a new commit message.

Daniel Vérité
  • 58,074
  • 15
  • 129
  • 156
kevmo
  • 677
  • 1
  • 8
  • 20
7

You can squash any number of commits into a single one using

git rebase --interactive <commit>
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • 3
    It's important to consider the case when you already or some of your collaborators have pushed into your repository in Github. See http://stackoverflow.com/questions/5667884/how-to-squash-commits-in-git-after-they-have-been-pushed – e3matheus Jul 30 '11 at 15:48
  • That's a valid point, but since `-i` is the same as `--interactive`, the only difference is the `push`. – Matt Ball Jul 30 '11 at 16:08
  • Mmm yes. But I commented that only to clarify, that if you already pushed to master before the rebase, this push has to be done via --force, right?. And that if another of your collaborators did a push, you shouldn't do a rebase at all. – e3matheus Jul 30 '11 at 16:53
  • thats why you do your rebase on a separate branch and you merge your rebase into master – Dan Jul 18 '15 at 13:59