简体   繁体   中英

Master contains changes from branch after merging master into branch

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.


How to think about branches

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.


What else could you have done?

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

  1. 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.

  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM