git rebase provides a simple way of combining multiple commits into a single one. However using rebase to squash an entire branch down to a single commit is not completely straightforward.
Squashing normal commits
Using the following repository:
$ git log --oneline
c172641 Fix second file
24f5ad2 Another file
97c9d7d Add first file
we can combine the last two commits (c172641
and 24f5ad2
) by rebasing up to the first commit:
$ git rebase -i 97c9d7d
and specify the following commands in the interactive rebase screen:
pick 24f5ad2 Another file
squash c172641 Fix second file
which will rewrite the history into this:
$ git log --oneline
1a9d5e4 Another file
97c9d7d Add first file
Rebasing the initial commit
Trying to include the initial commit in the interactive rebase screen will return this error:
$ git rebase -i 97c9d7d^
fatal: Needed a single revision
Invalid base
and squashing the top commit in the interactive rebase screen:
$ git rebase -i 97c9d7d
squash 24f5ad2 Another file
squash c172641 Fix second file
will return this error:
Cannot 'squash' without a previous commit
So we need to use a different approach to deal with the initial commit.
Amending the initial commit
Here is an alternative to rebase which will work on commits that don't have a parent.
Taking the previously rebased branch:
$ git log --oneline
1a9d5e4 Another file
97c9d7d Add first file
we can rewind the branch to the initial commit:
$ git reset 97c9d7d
$ git log --oneline
97c9d7d Add first file
without losing any of the changes introduced in 1a9d5e4
(shown here as uncommitted changes):
$ git status
# On branch master
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: file1
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# file2
no changes added to commit (use "git add" and/or "git commit -a")
Then we can reopen commit 97c9d7d
and add the changes present in the working directory:
$ git add .
$ git commit -a --amend -m "Initial version"
which will finally give us a fully squashed branch:
$ git log --oneline
fcb85fb Initial version
$ git status
# On branch master
nothing to commit (working directory clean)
this works, but there's a better way. If you use git merge, you can specify the --squash option to tell git to merge everything as one commit.
Of course, that only works if you're merging, not while you're still working on the same branch, like you can with rebase.
You could also use the much simpler 'git merge --squash && git commit'
Yes, the mechanism is different: it uses the "merge" machinery, not the "rebase" machinery. But, if it ever makes a difference, you have bigger headaches, and shouldn't be making a "squash commit" in the first place.