With git rebase --interactive <commit>
you can squash any number of commits together into a single one.
That's all great unless you want to squash commits into the initial commit. That seems impossible to do.
Are there any ways to achieve it?
In a related question, I managed to come up with a different approach to the need of squashing against the first commit, which is, well, to make it the second one.
If you're interested: git: how to insert a commit as the first, shifting all the others?
This question is related to
git
rebase
git-rebase
squash
Squashing the first and second commit would result in the first commit being rewritten. If you have more than one branch that is based off the first commit, you'd cut off that branch.
Consider the following example:
a---b---HEAD
\
\
'---d
Squashing a and b into a new commit "ab" would result in two distinct trees which in most cases is not desirable since git-merge and git-rebase will no longer work across the two branches.
ab---HEAD
a---d
If you really want this, it can be done. Have a look at git-filter-branch for a powerful (and dangerous) tool for history rewriting.
I've reworked VonC's script to do everything automatically and not ask me for anything. You give it two commit SHA1s and it will squash everything between them into one commit named "squashed history":
#!/bin/sh
# Go back to the last commit that we want
# to form the initial commit (detach HEAD)
git checkout $2
# reset the branch pointer to the initial commit (= $1),
# but leaving the index and working tree intact.
git reset --soft $1
# amend the initial tree using the tree from $2
git commit --amend -m "squashed history"
# remember the new commit sha1
TARGET=`git rev-list HEAD --max-count=1`
# go back to the original branch (assume master for this example)
git checkout master
# Replay all the commits after $2 onto the new initial commit
git rebase --onto $TARGET $2
For what it's worth, I avoid this problem by always creating a "no-op" first commit, in which the only thing in the repository is an empty .gitignore:
https://github.com/DarwinAwardWinner/git-custom-commands/blob/master/bin/git-myinit
That way, there's never any reason to mess with the first commit.
There is an easier way to do this. Let's assume you're on the master
branch
Create a new orphaned branch which will remove all commit history:
$ git checkout --orphan new_branch
Add your initial commit message:
$ git commit -a
Get rid of the old unmerged master branch:
$ git branch -D master
Rename your current branch new_branch
to master
:
$ git branch -m master
You can use git filter-branch for that. e.g.
git filter-branch --parent-filter \
'if test $GIT_COMMIT != <sha1ofB>; then cat; fi'
This results in AB-C throwing away the commit log of A.
You could use rebase interactive to modify the last two commits before they've been pushed to a remote
git rebase HEAD^^ -i
This will squash second commit into the first one:
A-B-C-... -> AB-C-...
git filter-branch --commit-filter '
if [ "$GIT_COMMIT" = <sha1ofA> ];
then
skip_commit "$@";
else
git commit-tree "$@";
fi
' HEAD
Commit message for AB will be taken from B (although I'd prefer from A).
Has the same effect as Uwe Kleine-König's answer, but works for non-initial A as well.
If you simply want to squash all commits into a single, initial commit, just reset the repository and amend the first commit:
git reset hash-of-first-commit
git add -A
git commit --amend
Git reset will leave the working tree intact, so everything is still there. So just add the files using git add commands, and amend the first commit with these changes. Compared to rebase -i you'll lose the ability to merge the git comments though.
Source: Stackoverflow.com