Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git Rebase Conflict: Who is HEAD?

Tags:

git

meld

I have this project where the remote repo has the main development branch, and I have a fork containing the experimental branch. I'm required to rebase changes from the development branch to my experimental branch before I push to my fork. So it goes like:

git checkout experimentalbranch
git fetch remoterepo
git rebase remoterepo/developmentbranch

By this time, I hit conflicts. However, I'm not familiar with any of these changes (I'm rebasing weeks worth of changes, because they didn't merge my changes immediately). Also, it's my first time doing rebase. I'm more accustomed to merge.

In meld, it usually is like <<LOCAL||REMOTE>> for merge, which sounds very intuitive. But in rebase, it's <<HEAD||COMMIT MESSAGE>>. Who is HEAD? Is it the HEAD of the development branch? Is it the latest code in the development branch or somewhere else?

like image 988
Joseph Avatar asked Apr 21 '14 23:04

Joseph


People also ask

Which is head in rebase?

But during the rebase, HEAD is either their branch-tip (commit L ), or one of the new commits copied and appended past their branch-tip; and --ours means the branch being grown at the end of L while --theirs means the commit being copied-from ( G or H above).

What is head merge conflict?

A merge conflict usually occurs when your current branch and the branch you want to merge into the current branch have diverged. That is, you have commits in your current branch which are not in the other branch, and vice versa. Typically, there is one branch point, which is the latest common commit.


2 Answers

TL;DR (added May 2018)

The whole thing is fundamentally at least a little bit confusing because Git lets its internal workings show right through to you.

Note that the cases we are concerned with here occur when you run:

git checkout somebranch; git rebase origin/their-branch

or similar. The rebase has halted temporarily to force you to resolve a merge conflict, after which you're supposed to git add the resolved conflict and run git rebase --continue. (If you use some merge tool with git mergetool, or a fancy GUI interface, that interface may do some or all of this for you some other way, but underneath, it's git adding the resolved files and running git rebase --continue.)

At the very beginning, the HEAD commit is their branch, so that if you use git checkout --ours or git checkout --theirs, --ours means theirs—the final commit of origin/their-branch—while --theirs means yours, the first commit you're rebasing. This is the normal everyday sort of Git confusion (see What is the precise meaning of "ours" and "theirs" in git?) and is not what led to the original question.

Later, however, the HEAD commit is actually a kind of mixture. It's the result of copying some number of your commits atop their latest commit. You're now getting a conflict between your own partly-built new series of commits, and your own original commits. The source of this conflict is usually something "they" did (something that changed along the way in origin/their-branch). You still have to resolve this conflict. When you do, you may see the very same conflict recur in later commits.

Again, HEAD or local or --ours is a commit that rebase has built by combining your changes and their changes, and the other commit (remote or >>>>>>> or --theirs) is your own commit, which rebase is trying to copy atop HEAD.

Longer

When merging (including rebasing, which is a special case of repeated "merging" internally), there are two "heads" (two specific branch-tips) involved. Let's call these your-branch and origin/their-branch:

              G - H --------      <-- HEAD=your-branch
            /               \
... - E - F                   M   <-- desired merge commit [requires manual merge]
            \               /
              I - J - K - L       <-- origin/their-branch

This point is commonly (and unsurprisingly) confusing, although when labeled like this it's clear enough.

Making it worse, though, git uses --ours and --theirs to refer to the two head commits during a merge, with "ours" being the one you were on (commit H) when you ran git merge, and "theirs" being, well, theirs (commit L). But when you're doing a rebase, the two heads are reversed, so that "ours" is the head you're rebasing on-to—i.e., their updated code—and "theirs" is the commit you're currently rebasing, i.e., your own code.

This is because rebase actually uses a series of cherry-pick operations. You start with much the same picture:

              G - H           <-- HEAD=your-branch
            /
... - E - F
            \
              I - J - K - L   <-- origin/their-branch

What git needs to do here is to copy the effect of commits G and H, i.e., git cherry-pick commit G, then do it again with commit H. But to do that, git has to switch to commit L first, internally (using "detached HEAD" mode):

              G - H           <-- your-branch
            /
... - E - F
            \
              I - J - K - L   <-- HEAD, origin/their-branch

Now it can start the rebase operation by comparing the trees for commits F and G (to see what you changed), then comparing F vs L (to see if some of your work is already in L) and taking any changes not already in L and add it. This is a "merge" operation, internally.

              G - H           <-- your-branch
            /
... - E - F                   G'   <-- HEAD
            \               /
              I - J - K - L   <-- origin/their-branch

If the merge does not go well, HEAD is still left at commit L (because commit G' does not yet exist). Thus, yes, HEAD is the head of their development branch—at least, it is right now.

Once the copy of G exists, though, HEAD moves to G' and git attempts to copy the changes from H, in the same manner (diff G vs H, then diff F vs G', and merge the results):

              G - H           <-- your-branch
            /
... - E - F                   G' - H'   <-- HEAD
            \               /
              I - J - K - L   <-- origin/their-branch

Again, if the merge fails and needs help, you're left with HEAD pointing to G' instead of H' as H' does not yet exist.

Once the merges all succeed and commits G' and H' do exist, git removes the label your-branch from commit H, and makes it point to commit H' instead:

              G - H
            /
... - E - F                   G' - H'   <-- HEAD=your-branch
            \               /
              I - J - K - L   <-- origin/their-branch

and you are now rebased and HEAD is once again what you would expect. But during the rebase, HEAD is either their branch-tip (commit L), or one of the new commits copied and appended past their branch-tip; and --ours means the branch being grown at the end of L while --theirs means the commit being copied-from (G or H above).

(This is basically git exposing the raw mechanism of how it does what it does, which happens rather a lot in git.)

like image 161
torek Avatar answered Oct 20 '22 00:10

torek


Definitions

In this section we are going to see the deficiones that we are asked in response:

Who is HEAD?

HEAD: the current commit your repo is on. Most of the time HEAD points to the latest commit in your branch, but that doesn't have to be the case. HEAD really just means "what is my repo currently pointing at".

In the event that the commit HEAD refers to is not the tip of any branch, this is called a "detached head".

Is it the HEAD of the development branch?

At the moment in which a merge or rebase is made the HEAD immediately passes to point to the created or refactorized commit and therefore will be pointing to the development branch.

In git bash we can see theHEAD situation, listing commit:

# Normal commit list
git log
# List of commit in a single line
git log --oneline 
# All commits graphically-linear (Recommended as alias)
git log --all --graph --decorate --oneline

Practice

In this section we will see the _how_ works some actions performed by the user

When the user proceeds to :

# Command to change from the branch to the current one to experimentalbranch
git checkout experimentalbranch
# Command that traverses the typical workflow to synchronize its local repository with the main branch of the central repository (remoterepo)
git fetch remoterepo
# git fetch origin
# git fetch origin branch:branch
# With the command git rebase, you can take all the changes confirmed in one branch (remoterepo), and reapply them over another developmentbranch
git rebase remoterepo/developmentbranch

By this time, I hit conflicts. However, I'm not familiar with any of these changes (I'm rebasing weeks worth of changes, because they didn't merge my changes immediately). Also, it's my first time doing rebase. I'm more accustomed to merge.

The union of branches is done in two ways:

  • git merge

  • git rebase.

Note :

For the examples we will use the following tree :

* a122f6d (HEAD -> remoterepo) Commit END
* 9667bfb Commit MASTER
| * b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
|/
* 0e834f4 (origin/remoterepo) First commit

git merge

The best known form is git merge, which performs a fusion to three bands between the last two snapshots of each branch and the common ancestor to both, creating a new commit with mixed changes.

For example :

git checkout remoterepo
git merge experimentalbranch

It would produce us :

*   003e576 (HEAD -> remoterepo) Merge branch 'experimentalbranch' in remoterepo
|\
| * b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
| * 110b2fb Commit 2
| * e597c60 Commit 1
* | a122f6d Commit END
* | 9667bfb Commit MASTER
|/
* 0e834f4 (origin/remoterepo) First commit

git rebase

git rebase basically what it does is to collect one by one the changes confirmed in one branch, and reapply them on another.

Using rebase can help us avoid conflicts whenever it is applied to commits that are local and have not been uploaded to any remote repository. If you are not careful with the latter and a partner uses affected changes, sure you will have problems since these types of conflicts are usually difficult to repair.

For example :

git checkout remoterepo
git rebase experimentalbranch


* f8a74be (HEAD -> remoterepo) Commit END
* 4293e9d Commit MASTER
* b9bcaf0 (origin/experimentalbranch, experimentalbranch) Commit 3
* 110b2fb Commit 2
* e597c60 Commit 1
* 0e834f4 (origin/remoterepo) First commit

What is origin?

origin : the default name that git gives to your main remote repo. Your box has its own repo, and you most likely push out to some remote repo that you and all your coworkers push to. That remote repo is almost always called origin, but it doesn't have to be.

like image 26
Nicolás Alarcón Rapela Avatar answered Oct 20 '22 00:10

Nicolás Alarcón Rapela