Tariq Abughofa
Posted on October 18, 2019
Conflicts in git can be a pain to deal with especially that you have to go into each code block which generated a conflict and check which version you want to keep and sometimes it can be hard to be sure. However, in some scenarios, the choice is easy. You want to keep whatever already existed on the server, or the opposite, you want to override it all with what you have. In this case, instead of going through the conflicts manually, git has extremely helpful flags which are --ours
and --theirs
.
Let's assume that the conflict looks like this:
<<<<<<< HEAD
this is some content
=======
this is a totally different content
>>>>>>> new_branch
When you have a merge conflict and you know exactly which version you want to keep, entirely or on a file basis, you can use these flags like this:
git checkout --ours .
Or:
git checkout --our /path/to/your/conflict/file.rb
Then you commit the chosen changes:
git add /path/to/your/conflict/file.rb
git commit
But when you use which? Is it "ours" or "theirs"? well, this can be a bit confusing... In the conflict above for example, it's hard to tell. You might say "HEAD is whatever already on the branch we're merging into and the other part is what's on the new branch" but it's actually more complicated than that.
If you looked into the help for the git checkout command, you'll see the following:
-2, --ours checkout our version for unmerged files
-3, --theirs checkout their version for unmerged files
That doesn't sound very helpful. Let's go to the actual documentation. For version 2.23.0 it says:
--ours
--theirs
When checking out paths from the index, check out stage #2 (ours) or #3 (theirs) for unmerged paths.
Note that during git rebase and git pull --rebase, ours and theirs may appear swapped; --ours gives the version from the branch the changes are rebased onto, while --theirs gives the version from the branch that holds your work that is being rebased.
This is because rebase is used in a workflow that treats the history at the remote as the shared canonical one, and treats the work done on the branch you are rebasing as the third-party work to be integrated, and you are temporarily assuming the role of the keeper of the canonical history during the rebase. As the keeper of the canonical history, you need to view the history from the remote as ours (i.e. "our shared canonical history"), while what you did on your side branch as theirs (i.e. "one contributor’s work on top of it").
Let's explain this further. We need to make the distinction between the three git operations which integrate changes and thus can generate conflicts: merges, rebases, and pulls.
Merges
A merge scenario can be like this:
git checkout master
git merge new_branch
This one feels more intuitive as your guess is probably right:
To keep the changes that are on master
:
git checkout --ours .
To keep the changes that are on new_branch
:
git checkout --theirs .
Rebases
A conflict appears within a rebase you do something like this:
git checkout new_branch
git rebase master
This one feels a bit counter-intuitive as ours is not the branch we're on:
To keep the changes that are on master
:
git checkout --ours .
To keep the changes that are on new_branch
:
git checkout --theirs .
So exactly the same as before.
Pulls
A pull command fetches data from a remote source and incorporate the changes into the local branch. The incorporation part can be done either with a merge operation (which is the default mode), or with a rebase operation.
The fetch + merge scenario looks as follows:
git checkout master
git pull origin new_branch
To keep the changes that are on master
:
git checkout --ours .
To keep the changes that are on new_branch
:
git checkout --theirs .
So exactly the same as a merge operation.
The fetch + rebase scenario happens in the following way:
git checkout new_branch
git pull origin master --rebase
To keep the changes that are on master
:
git checkout --ours .
To keep the changes that are on new_branch
:
git checkout --theirs .
Exactly the same as a rebase operation.
However, you don't have to memorize it like this: merge makes sense and rebase doesn't 😅. It is actually much simpler than that and it can be summerized in one sentence:
"ours
represents the history and theirs
is the new applied commits".
In a merge, git takes the current branch and apply the additional commits to it's HEAD. The current branch is the history ours
and the additional commits are new theirs
. In a rebase, git rewrites the history of the current branch. Making it compatible with the other branch. Then, applies any additional commits from the current branch. The other branch becomes the history thus ours
and the current branch might have new additions theirs
.
Now let's get back to our conflict example if the operations was a merge, the final code would be:
this is some content
while with a rebase it would be:
this is a totally different content
Posted on October 18, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024