Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge conflict with myself

I had a merge conflict a while ago and had the suspicion, that it was with myself. Right now I had the same problem again and this time I'm certain, that I'm responsible for the conflict.

Yesterday I made some changes to my branch commited the changes and pushed them. This was the first and so far only commit to the branch (local, as well as remote).

Today I made some more changes (in the same files obviously), added the changes and commited them with commit --amendand tried to push them.

This is were I had a merge conflict: Updates were rejected because the tip of your current branch is behind

I fixed the changes, added, commited and pushed them. All fine now.

But I want to learn from my mistakes. What IS the mistake? (I assume it has something to do with using git commit --amend)

like image 674
Bernd Strehl Avatar asked Dec 08 '16 07:12

Bernd Strehl


2 Answers

amend -- changes history.

Initial state

Origin: A
Local:  A

Then you commited and pushed

Origin: A - B
Local:  A - B

Then you commited with amend

Origin: A - B
Local:  A - C // local commit B replaced by new commit C

Then you tried to push and got message, that you need to pull before. Commit C contains changes from B but it is a different commit. And there for you have conflict after pull.

like image 99
sectus Avatar answered Oct 15 '22 09:10

sectus


First, this:

Updates were rejected because the tip of your current branch is behind

is not a merge conflict. A merge conflict is something more specific. It occurs when a three-way merge is trying to combine two different sets of changes, but the changes overlap. You are running git push, which does not merge anything.

The opposite of git push is not git pull. The git pull command is a convenience command that runs two Git commands for you, in sequence. The first command is git fetch—which is the opposite of git push, or at least, as close as one can get. The second command that git pull runs is normally git merge, which can result in a merge conflict. Or, you can direct Git to run git rebase second instead; and git rebase can also result in a merge conflict.

Some key facts to just memorize

This can get very confusing, and to use Git well, it's important to learn how Git actually does all of these things, because Git tends to let the raw implementation show through. So as a base overview, keep in mind the following:

  • What matters most, most of the time, is commits.
  • Every commit has a single "true name", which is its hash ID. Hash IDs are those big ugly 40-character things like face0ff (but longer). No two different commits ever have the same ID, and the ID is determined entirely by the contents of the commit.
  • The contents of a commit are a little bit detailed but not too long—to see one in its entirety, run git cat-file -p HEAD, which just prints it out—but the main important thing to remember is that the contents include:

    • the parent commit, which is again one of those hashes;
    • the commit message (which --amend seems to let you edit); and
    • the source tree that you commit, i.e., whatever you have git add-ed.


    When you use git commit --amend, this seems to change the commit, but it doesn't. It can't! The commit's ID is determined by its contents. If you change the contents, you get a new and therefore different commit.

  • Because commits have their parent IDs inside them, Git (and you) can draw a chain of commits, starting from the more recent ones and working backwards. Git runs everything backwards: we like to think of branches as going forwards. If we have a time line of commits, where we make commit A, then B, then C, and so on, we'd expect them to go "A then B then C":

    A -> B -> C
    

    but Git does them backwards, so they're actually:

    A <- B <- C
    
  • Therefore, a branch name just points to the tip-most commit. This is why Git keeps talking about "the tip of your branch".

  • And, that means we want to draw our graphs like this:

    A--B--C   <-- branch
    

    The name branch points to the latest commit, C. Git then goes backwards, from C to B to A. Most of the time we don't have to worry about that part, but we do need to remember that the name just points to the tip commit.

This leads to the way branches grow

Branch names point to branch-tips. Suppose you have one of those chains:

A--B--C   <-- branch

Now you make a new commit D. What Git does is make D with its parent being C, then make branch point to D. We can draw that now:

A--B--C--D   <-- branch

What git commit --amend does is now very simple to see. Instead of tacking D onto the end by making D point back to C, it makes D point back to B (i.e., C's parent). Then it shoves C aside by making branch point to D:

     C
    /
A--B--D   <-- branch

Now we can see what git push does as well

When you run git push, your Git calls up some other Git and has a little conversation. Your Git tells their Git about your commits. Their Git tells your Git about their commits. Your Git then sends their Git some of your commits—whichever ones you have, that they don't, that you've asked your Git to send.

Suppose they already have A--B--C and you have A--B--D. By asking your Git to git push branch, you get your Git to send them your D (it would send your A and B too, but they already have those; again, these are all done by those big ugly hash IDs, rather than simple one-letter names, but the idea is the same).

Then comes the last part, which is where the problem is. Your Git asks their Git to set their branch to point to the same commit as your branch. That is, you ask them to set their branch to point to D.

Your D points back to B, not to C. If they were to set their branch to point to the new shared D, they would "lose" their C.

Of course, because of your amend, you want them to "lose" their C, just like you did when your Git shoved it aside. So you can use --force. The force flag changes your Git's polite request—"please, if you will, change your branch to point to D—into a command: "set your branch to point to D right now!" They can still refuse, but generally they will obey.

If that only loses C, that's fine: that's what you wanted. But if their Git is one others also push to, perhaps a third person, with a third Git repository, has written a commit E and pushed it to their Git, so that they actually have A--B--C--E. If you send them your D, which points back to B, and get them to set their branch to point to your D, they will lose the C--E chain, not just the C commit.

This is why (and when) amending is bad

If you amend a commit you have already shared, it's possible that someone else has built on it. If you now "take it away", you may create problems for this someone-else.

On the other hand, if you have never shared the commit, or if the other Git is your own private repository, or if you and all third-parties agree in advance that this sort of "taking away" is normal and expected, you won't have a problem here.

Otherwise—if "taking away" is bad—then when the other Git has a commit, you should make sure that whatever new commits you push merely add to their commits. You can do this by merging, or by rebasing, and that's where git merge or git rebase come in. Not exactly coincidentally, these two commands are the second half of git pull—but let's leave that for another time.

like image 37
torek Avatar answered Oct 15 '22 09:10

torek