为什么使用git-svn时“ours”和“theirs”的含义会颠倒

99
我使用git-svn,并注意到在执行git svn rebase后需要修复合并冲突时,例如git checkout--ours--theirs选项的含义被颠倒了。也就是说,如果存在冲突,我想要保留来自SVN服务器的版本并丢弃本地所做的更改,则必须使用ours,而我原本预期应该使用theirs

这是为什么呢?

示例:

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"

1
刚刚更新了我的答案,加入了许多图表以更好地说明“我们”和“他们”的双方。 - VonC
1
请参见 https://github.com/git/git/commit/f30301657b68561392d910f6196380dd3976549e - VonC
1个回答

249

这似乎与rebase的操作一致。

  • git svn rebase将从当前HEAD的SVN父级获取修订版本,并将当前(未提交到SVN)的工作与其进行rebase。

  • git rebase提到:
    请注意,rebase合并通过在<upstream>分支上重放工作分支中的每个提交来完成。
    因此,当发生合并冲突时:

    • 我们报告的一方是迄今为止已经重新基于<upstream>开始的系列,
    • 而他们是工作分支
      换句话说,双方被交换了

git rebase会在<upstream>分支上重放工作分支中的每个提交。

如果您调和两个定义:

从SVN提交的修改是本地Git提交重新播放的基础。它们是“到目前为止已经变基的系列”中的一部分,并被称为“我们的”(在您的情况下,是带有“bar”内容的test.txt文件)。
工作分支(包含对SVN未知的Git提交,在您的情况下是带有“baz”内容的test.txt文件)是“他们的”,并且正在播放每个本地Git提交。
换句话说,不论是SVN还是其他:
- “<upstream>”分支(在其之上重播任何内容,且是到目前为止“变基提交”的一部分)是“我们的”。 - 正在被重新播放的内容(即工作分支)是“他们的”。

关于编程的内容,翻译如下:

mnemonic tip">一个很好的助记技巧,由CommaToast提供:

无论 HEAD 指向什么,都是“我们”的

(而 git rebase upstream 的第一件事是检出你要在其之上进行 rebase 的 upstream 分支:HEAD 指的是 upstream -- 现在是 ours。)

这并不矛盾:

  • 这个答案,其中git checkout --ours codefile.jsgit rebase master期间从master选择一个文件
  • 这个答案,其中“ours”指代你正在进行变基的上游分支master

混淆可能来自于经典的git merge中工作分支的作用。
当您合并时:
  • "工作分支"是包含"到目前为止合并内容"的分支,并被视为"我们",
  • 而另一个提交代表正在"合并顶部的内容",而不是重播,被视为"他们"。
正如git rebase手册所述,在rebase过程中进行的合并意味着两侧将被交换。
另一种表达方式是考虑以下内容:
  • 在检出的分支上,我们拥有的是“我们自己的”,
  • 我们曾经拥有的(正在合并或回放)是“他们的”。

在合并时

x--x--x--x--x(*) <- current branch B ('*'=HEAD)
    \
     \
      \--y--y--y <- other branch to merge

我们不改变当前的分支'B',所以我们仍然在处理我们正在工作的内容(并且从另一个分支合并)

x--x--x--x--x---------o(*)  MERGE, still on branch B
    \       ^        /
     \     ours     /
      \            /
       --y--y--y--/  
               ^
              their

但是在变基操作中,我们会切换方向(如 git switch 中所提到的,在 git rebase man page 中也有提及),因为变基操作首先要检出上游分支!(以便将当前提交重放在其之上)

x--x--x--x--x(*) <- current branch B
    \
     \
      \--y--y--y <- upstream branch

git rebase upstream会先将B的HEAD更改为上游分支的HEAD(因此与之前的“当前”工作分支相比,'ours'和'theirs'的交换)。

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

然后rebase将在新的“我们”的B分支上重放“他们”的提交:

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

使用git svn rebase的唯一额外步骤是首先在表示SVN提交的Git远程分支上执行svn“fetch”。
最初状态如下:

x--x--x--x--x(*) <- current branch B, "ours" for now.
    \                                   
     \
      \--y--y--y <- SVN tracking branch, "theirs for now"

首先,你需要通过更新 SVN 跟踪分支来获取来自 SVN 的新提交。

x--x--x--x--x(*) <- current branch B, still "ours", not for long
    \                                   
     \
      \--y--y--y--y'--y' <- SVN tracking branch updated

然后您将当前分支切换到 SVN 侧(变成“我们”的分支)

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")

在重置期间,回放你正在处理的提交(但现在它们是“他们”的)

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

10
哇,太棒了,谢谢!我一定完全错过了git rebase手册中的那个备注... - Marc Liyanage
9
我的天啊!Torvalds 吃了什么药?这也太复杂了吧!Git 是一个非常危险的工具。如果你尝试使用外部知识或者你的直觉,很容易就会破坏所有的工作。软件开发已经走向了深渊! - ATL_DEV
1
@user148298 这个函数没有任何问题。除非你是 Git 专家,否则你不必了解所有这些东西。如果你确实需要高级功能,那么你首先必须学习它。 - Earth Engine
1
@starriet,我已经编辑了答案,提到这与您提到的两个答案并不冲突,其中 git rebase master 意味着 "ours" 现在是 master。要进行变基操作,首先需要将 HEAD 引用到您正在变基的上游分支 master,这意味着 master 变成了 "ours"。 - VonC
1
@starriet 是的,就像 git switch(我在2019年在这里介绍过的那样)。但是请理解,在我2010年的回答中使用“switch”术语时,2019年的 git switch 命令还不存在! ;) 然而,总体思路仍然相同。HEAD 在 rebase 开始时移动,"ours" 也随之移动。 - VonC
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接