I recently created a branch, branchA
off of master
to develop a feature. While developing this feature, additional commits were pushed to master
. I wanted to bring these changes into the branch, so while in branchA
, I ran
git merge master
I then committed a change (B) on top of that. However, I later realized that I had merged the master into the branch poorly, so I reverted the commit using
git revert [hash of merge of master in branchA] -m 1
I then re-applied the changes from B, committing it. Finally, I re-merged master
into branchA
.
I was satisfied with this merge, so I wanted to bring the changes from branchA
into master
. To my surprise, the changes were already in master
. When I checked out the master and ran
git merge branchA
I saw
Already up to date.
What happened here?
I come from an SVN background so I would have expected to need to merge branchA
back into master
, but it appears that seems to have happened automatically? Is this behavior related to fast-forwarding? And what if I didn't want this behavior to occur (ie to pull the changes in from the master into the branch without having the changes from the branch spill back into the master).
Thanks in advance!
Start with a master
and branchA
, both with a few commits each, and branchA
is checked out (shown with *):
m1 - m2 - m3 <--- master
\
a1 - a2 <--- branchA*
[ Edit: Updated the merge result, per comments, to show that master
doesn't move forward to the merge commit automatically]
Then merge master into branchA, shown as merged commit a2m3:
m1 - m2 - m3 <-- master
\ \
a1 - a2 - a2m3 <-- branchA*
Revert of a merge just applies an "undo" of the changes, it doesn't undo the actual merge - shown as a2m3':
m1 - m2 - m3 <-- master
\ \
a1 - a2 - a2m3 - a2m3' <-- branchA*
And since you did this revert with branchA
still checked out, then branchA
reference will be pointing to the new commit, and master
ref is still pointing to the m3 commit.
You then added another commit:
m1 - m2 - m3 <-- master
\ \
a1 - a2 - a2m3 - a2m3' - a3 <-- branchA*
Finally, when you checked out master
and merged in branchA
, as you guessed, it was just a fast forward, which forwarded master
to point to the same commit as branchA
. This was possible because the merge link from m3 to a2m3 was still present (the revert didn't remove it), so master
is considered to be a parent commit of a3 (there is an unbroken chain to a3) and can just fast-forward to it.
m1 - m2 - m3
\ \
a1 - a2 - a2m3 - a2m3' - a3 <-- branchA / master*
This is the point where you tried to merge branchA
into master
, but got the response that it was "Already up to date".
Now, prior to the second merge attempt, if you had checked out master
and committed at least once before merging in branchA
, or someone else had committed to master
on the remote and you pulled that down, then your branches would have diverged again (think of a new m4 commit to the right of m3). If that had happened, the merge from branchA
into master
would have been a full merge, rather than just a fast-forward.
The breakthrough for me on how to think about branches, is that a branch is really just a reference to a commit - and technically, all of the parent commits connected to it. When you merge two branches, you are just creating a commit that has two parents, rather than one, and at that point there are not two branches, there is one - even the split series of commits that used to be separate master
and branchA
, are no longer separate - they have truly been merged and both branches refer to all of them now.
Based on what I think you wanted to do, you could have done this:
Warning: there are "Bad Things That Can Happen (tm)" if you change history after pushing up to a remote that Other People (tm) have access to... But at least in this case it would only be rewriting the branchA
history, not master
Rather than revert the merge of master
into branchA
, just reset branchA
to the prior commit a2
:
git reset --hard <hash of a2>
Resetting like this removes all references to a2m3 so it effectively is removed from your repo. It will still be in your repo for a while, but there will be no way to get to it unless you saved the hash somewhere. Git tracks commits like this for a while, then garbage-collects and deletes them if they are not reused for long enough.
At this point you would have:
m1 - m2 - m3 <-- master \\ a1 - a2 <-- branchA
Just like before the merge.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.