I use git-svn and I noticed that when I have to fix a merge conflict after performing a git svn rebase
, the meaning of the --ours
and --theirs
options to e.g. git checkout
is reversed. That is, if there's a conflict and I want to keep the version that came from the SVN server and throw away the changes I made locally, I have to use ours
, when I would expect it to be theirs
.
Why is that?
Example:
mkdir test
cd test
svnadmin create svnrepo
svn co file://$PWD/svnrepo svnwc
cd svnwc
echo foo > test.txt
svn add test.txt
svn ci -m 'svn commit 1'
cd ..
git svn clone file://$PWD/svnrepo gitwc
cd svnwc
echo bar > test.txt
svn ci -m 'svn commit 2'
cd ..
cd gitwc
echo baz > test.txt
git commit -a -m 'git commit 1'
git svn rebase
git checkout --ours test.txt
cat test.txt
# shows "bar" but I expect "baz"
git checkout --theirs test.txt
cat test.txt
# shows "baz" but I expect "bar"
That seems consistent with what a rebase does.
git svn rebase
will fetch revisions from the SVN parent of the current HEAD and rebases the current (uncommitted to SVN) work against it.
git rebase
does mention:
Note that a rebase merge works by replaying each commit from the working branch on top of the <upstream>
branch.
Because of this, when a merge conflict happens:
<upstream>
,git rebase replays each commit from the working branch on top of the
<upstream>
branch.
If you reconcile both definitions:
test.txt
file with bar
content)test.txt
file with baz
content) is "their", and each of those local Git commits are being replayed.In other words, SVN or not:
<upstream>
" branch (on top of which anything is replayed, and which is part of the so far "rebased commits") is "ours".Good mnemonic tip by CommaToast:
whatever HEAD's pointing to is "ours"
(and the first thing a git rebase upstream
does is to check out the upstream
branch on top of which you want to rebase: HEAD refers to upstream
-- ours
now.)
This does not contradict:
git checkout --ours codefile.js
selects a file from master
during a git rebase master
master
, the upstream branch you are rebasing onto, is referenced as "ours"The confusion is likely coming from the role of the working branch in a classic git merge
.
When you are merging:
As the git rebase
man page mentions, a merge during a rebase means the side are swapped.
Another way to say the same thing is to consider that:
On a merge:
x--x--x--x--x(*) <- current branch B ('*'=HEAD)
\
\
\--y--y--y <- other branch to merge
, we don't change the current branch 'B', so what we have is still what we were working on (and we merge from another branch)
x--x--x--x--x---------o(*) MERGE, still on branch B
\ ^ /
\ ours /
\ /
--y--y--y--/
^
their
But on a rebase, we switch side (as in git switch
, mentioned in git rebase
man page) because the first thing a rebase does is to check out the upstream branch! (to replay the current commits on top of it)
x--x--x--x--x(*) <- current branch B
\
\
\--y--y--y <- upstream branch
A git rebase upstream
will first change HEAD
of B to the upstream branch HEAD
(hence the switch of 'ours' and 'theirs' compared to the previous "current" working branch.)
x--x--x--x--x <- former "current" branch, new "theirs"
\
\
\--y--y--y(*) <- upstream branch with B reset on it,
new "ours", to replay x's on it
, and then the rebase will replay 'their' commits on the new 'our' B branch:
x--x..x..x..x <- old "theirs" commits, now "ghosts", available through reflogs
\
\
\--y--y--y--x'--x'--x'(*) <- branch B with HEAD updated ("ours")
^
|
upstream branch
The only extra step with git svn rebase
is that a svn "fetch" is performed first on the Git remote branch representing SVN commits.
You have initially:
x--x--x--x--x(*) <- current branch B, "ours" for now.
\
\
\--y--y--y <- SVN tracking branch, "theirs for now"
, you first update the SVN tracking branch with new commits coming from SVN
x--x--x--x--x(*) <- current branch B, still "ours", not for long
\
\
\--y--y--y--y'--y' <- SVN tracking branch updated
, then you switch the current branch to the SVN side (which becomes "ours")
x--x--x--x--x <- for "B", now "their" during the rebase
\
\
\--y--y--y--y'--y'(*) <- SVN tracking branch updated, and branch B:
now "ours" (this is "what we now have")
, before replaying the commits you were working on (but which are now "theirs" during that rebase)
x--x..x..x..x <- old "theirs" commits, now "ghosts", available through reflogs
\
\
\--y--y--y--y'--y'--x'--x'--x'(*) <- branch B with HEAD updated ("ours")
^
|
upstream SVN tracking branch