I have two branches: master
and test-branch
(which is branched off of master
). My work has looked something like the following:
git checkout master
git checkout -b test-branch
git checkout master
git pull
-> out other people have made changes mastergit checkout test-branch
git rebase -i master
pick
in the interactive console to s
What I'd like to do is to squash all of the commits on test-branch
before rebasing so that I only have to solve merge conflicts once. Is this possible? If so, how?
Before rebasing such branches, you may want to squash your commits together, and then rebase that single commit, so you can handle all conflicts at once. Here's how to do that. Imagine you've been working on the feature branch show_birthday , and you want to squash and rebase it onto main .
You can just use git rebase -i master from your test-branch and then elect to squash all the commits except the most recent.
So the differences are: squash does not touch your source branch ( tmp here) and creates a single commit where you want. rebase allows you to go on on the same source branch (still tmp ) with: a new base.
It is possible, even easy. Git being what it is, there are many different ways to do it.
To literally do what you suggested originally:
... squash all of the commits on test-branch before rebasing
is easiest if you do it before running git merge
while on branch master
. (I know you didn't list git merge
as a command, but you did run git merge
in your step 6:
git pull
-> out other people have made changes master
because git pull
is just git fetch
followed by git merge
.) But it's still pretty easy to do after; we just need to target the correct commit-ID.
Let's make a drawing of the commit graph that you have at step 4:
...<- o <- * <-- master, origin/master
\
A <- B <-- HEAD=test-branch
What this drawing shows is that there are two labels1 pointing to the commit I marked *
, namely master
and origin/master
. Commit *
points back to commit o
which points back to yet more commits: that's the history of branch master
.
The label for the branch you created (and are now on, hence the HEAD=
part) points to commit B
. Commit B
then points to commit A
, which points to commit *
. That's the history of branch test-branch
: you created it when at *
, then added A
, then added B
.
Here are two ways you can easily squash commits A
and B
at this point:
git rebase -i master
This gives you an interactive edit session where you can "pick" the first commit and then "squash" the second, and it will gather together both commit messages and let you edit the result, in the usual way. It then makes a (single) new commit whose tree is that of commit B
.
git reset --soft master; git commit
This does not open an interactive edit session for the rebase: it just keeps the staging-area-and-tree from commit B
(that's the --soft
part of git reset --soft
), moves the label test-branch
back to point to commit *
directly (that's the git reset
part of git reset --soft
), and makes a new commit as usual (git commit
).
The drawback is that you have to compose a new commit message, but you can recover the commit message from commit A
or B
in any number of ways. For instance, you can use the -c
or -C
flag to git commit
(you'd have to identify commit A
or B
, e.g., using @{1}
or @{1}^
or @{yesterday}
or some other reflog specifier). Or, before doing the git reset --soft
, you can use git log
and save the log message(s) in a file, or whatever.
(This second method shines when instead of just two commits to squash, you have 42 or so.)
Both of these methods really do the same thing: they add a new commit (let's call it AB
), leaving A
and B
behind and kind of greyed-out, which I can't really draw correctly:
AB <-- HEAD=test-branch
/
...<- o <- * <-- master, origin/master
\
A <- B [ghost version of test-branch]
The ghost-version of your branch is invisible to most normal usage, and eventually (after 30 days or so by default) is garbage-collected away. (Until then it's still in your repository, and in your reflogs, so that you can find original commits A
and B
in case you need them after all.)
What if you've already done step 6? In that case, you must still identify commit *
. You can do it the way jsexpert suggested while I was writing this up, or you can find it with git merge-base
:
$ mergebase=$(git merge-base HEAD master)
# then pick just ONE of the next two
$ git rebase -i $mergebase
$ git reset --soft $mergebase; git commit
Here's how this works. After git checkout master; git fetch; git merge; git checkout test-branch
(steps 5 and 6, more or less), your commit-graph now looks more like this:
...<- o <- * <- o <-- master, origin/master
\
A <- B <-- HEAD=test-branch
That new o
commit that master
and origin/master
are pointing to—or it may be a whole chain of commits—are "in the way", but the "merge base" of test-branch
(where you are now) and master
is commit *
: the nearest shared commit before the two branches diverge.
We then simply target the rebase
or reset --soft
at that commit. When we're done and have a single new AB
commit, it looks like this:
AB <-- HEAD=test-branch
/
...<- o <- * <- o <-- master, origin/master
\
A <- B [ghost version of test-branch]
Once you have the squashed AB
commit, you can then git rebase
that onto master
in the usual way.
Note that the other answer is doing exactly the same thing, it's just identifying commit *
by counting commits. If you know there are two commits between the tip of test-branch
and the "interesting" commit *
, HEAD~2
identifies the same commit as $(git merge-base HEAD master)
. Using git merge-base
just allows you to avoid counting.
1"References" is the actual general git term. In this case they're branch names, and I am using the word "label" to distinguish them from the branch history formed by this commit graph. The word "branch" in git is used to refer to at least these two different things, and it gets confusing.
Of course it possible, yoiu simply need to type: git rebase -i HEAD~<# of commits to squash>
The -i is for interactive rebase. once you do it you will see the vi with instructions what to so next per commit.
A very details post about it can be found here Rewriting history:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With