Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Squash the first two commits in Git? [duplicate]

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?


Moderately related:

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?

like image 795
kch Avatar asked Feb 28 '09 20:02

kch


People also ask

Is it possible to have 2 starting commits in git?

Normally, rebase drops merges entirely, but you have to have a starting commit on which to rebase. Here there are two potential starting points and both are root commits; rebase is not prepared to handle this.

How do you squeeze two commits?

Make sure the topmost, first commit says “pick” and change the rest below from “pick” to “squash”. This will squash each commit into the previous commit, which will continue until every commit is squashed into the first commit. Save and close the editor. It will give you the opportunity to change the commit message.


2 Answers

Update July 2012 (git 1.7.12+)

You now can rebase all commits up to root, and select the second commit Y to be squashed with the first X.

git rebase -i --root master  pick sha1 X squash sha1 Y pick sha1 Z 
git rebase [-i] --root $tip 

This command can now be used to rewrite all the history leading from "$tip" down to the root commit.

See commit df5df20c1308f936ea542c86df1e9c6974168472 on GitHub from Chris Webb (arachsys).

As noted in the comments, a git push --force-with-lease (safer than --force, as Mikko Mantalainen remind us) would be needed after any rebase operation, if you need to publish that rework in a remote repository.


Original answer (February 2009)

I believe you will find different recipes for that in the SO question "How do I combine the first two commits of a git repository?"

Charles Bailey provided there the most detailed answer, reminding us that a commit is a full tree (not just diffs from a previous states).
And here the old commit (the "initial commit") and the new commit (result of the squashing) will have no common ancestor.
That mean you can not "commit --amend" the initial commit into new one, and then rebase onto the new initial commit the history of the previous initial commit (lots of conflicts)

(That last sentence is no longer true with git rebase -i --root <aBranch>)

Rather (with A the original "initial commit", and B a subsequent commit needed to be squashed into the initial one):

  1. Go back to the last commit that we want to form the initial commit (detach HEAD):

     git checkout <sha1_for_B> 
  2. Reset the branch pointer to the initial commit, but leaving the index and working tree intact:

     git reset --soft <sha1_for_A> 
  3. Amend the initial tree using the tree from 'B':

     git commit --amend 
  4. Temporarily tag this new initial commit (or you could remember the new commit sha1 manually):

     git tag tmp 
  5. Go back to the original branch (assume master for this example):

     git checkout master 
  6. Replay all the commits after B onto the new initial commit:

     git rebase --onto tmp <sha1_for_B> 
  7. Remove the temporary tag:

     git tag -d tmp 

That way, the "rebase --onto" does not introduce conflicts during the merge, since it rebases history made after the last commit (B) to be squashed into the initial one (which was A) to tmp (representing the squashed new initial commit): trivial fast-forward merges only.

That works for "A-B", but also "A-...-...-...-B" (any number of commits can be squashed into the initial one this way)

like image 200
VonC Avatar answered Sep 19 '22 00:09

VonC


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 
like image 31
fonsinchen Avatar answered Sep 19 '22 00:09

fonsinchen