Git Rebase Conflict: Who is HEAD?
Solution 1:
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 add
ing 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.)
Solution 2:
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.