git rebase,跟踪'local'和'remote'的变化。

206
在执行`git rebase`时,我经常很难弄清楚在解决冲突时的“本地”和“远程”发生了什么。有时候我会觉得它们在每次提交之间交换了位置。
这可能(肯定)是因为我还没有完全理解这个范式。
在进行rebase时,谁是“本地”,谁是“远程”?
(我使用P4Merge来解决冲突。)

1
阅读这篇文章有可能对你有所帮助吗?整个教程都非常有用。 - ivans
这个链接是否有帮助?(不是针对'git svn'部分,而是针对'git rebase'部分) - VonC
@VonC,没错,就是这样。如果你想把你回答中相关的部分复制到这里,我会打勾(这次我真的会,我保证!) - Benjol
好的...我来试试;) 发布相关摘录。 - VonC
我曾经遇到过完全相同的问题和感受(当进行变基时,“本地”和“远程”是谁?)。即使我使用P4Merge :-D - IsmailS
我希望有人能够在Git项目上打开一个拉取请求,将分支命名为它实际被称呼的名称! - BenKoshy
4个回答

290

简而言之;

总结一下(根据Benubird评论),当:

git checkout A
git rebase   B    # rebase A on top of B
  • localB(在之上变基),
  • remoteA

还有:

git checkout A
git merge    B    # merge B into A
  • localA(合并),
  • remoteB

重新定义基础会切换 ours(重新定义开始之前的当前分支)和 theirs(您想要重新定义的顶部分支)。


kutschkem 指出,在 GUI 合并工具的上下文中:

  • local 引用部分变基提交:“ours”(上游分支)
  • remote 引用传入的更改:“theirs” - 变基之前的当前分支。

请参见本答案最后部分的插图。


关于变基时的反转

混淆可能与在变基过程中ourstheirs的反转有关
(相关摘录)

git rebase手册页:

请注意,变基合并通过将工作分支中的每个提交记录重播到<upstream>分支顶部来运行。

由于这个原因,当出现合并冲突时:

  • 被报告为'ours'的一侧是迄今已经变基的系列,从<upstream>开始,
  • 而'theirs'是工作分支。 换句话说,双方被交换了。

反转说明

在合并中

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

在变基操作中:

但是在变基操作中,我们会切换方向,因为变基的第一步是检出上游分支!(以重放当前提交并将其放在其之上)

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

然后重新定位将在新的“我们”的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

注意:"upstream"概念是数据的参考集(所有repo或者像这里一样,一个分支,它可以是一个本地分支),从中读取数据或者向其中添加/创建新数据。

'local'和'remote'与'mine'和'theirs'的区别

Pandawood评论中补充道:

对我来说,问题仍然存在,即“local”是谁,“remote”是谁(因为在git中重新定位时不使用“ours”和“theirs”这些术语,只是提到它们会使答案更加混乱)。

GUI git mergetool

kutschkem补充道,而且说得很对:

解决冲突时,git会显示类似于:

local: modified file and remote: modified file. 

我很确定这个问题的目的是在此时定义本地和远程。在我的经验中,似乎是这样的:
- 本地指部分变基提交:"ours"(上游分支)。 - 远程指传入的更改:"theirs" - 变基前的当前分支。
git mergetool确实提到了“本地”和“远程”。
Merging:
f.txt

Normal merge conflict for 'f.txt':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (kdiff3):

例如,KDiff3会像这样显示合并解决方案

kdiff3

并且meld也会显示它:

Meld diff

对于VimDiff,同样适用,它显示的是

使用git mergetool -t gvimdiff将Vimdiff作为合并工具调用。Git的最新版本使用以下窗口布局调用Vimdiff:

+--------------------------------+
| LOCAL  |     BASE     | REMOTE |
+--------------------------------+
|             MERGED             |
+--------------------------------+
LOCAL
当前分支上包含文件内容的临时文件。
BASE
合并的共同基础的临时文件。
REMOTE
待合并文件内容的临时文件。
MERGED
包含冲突标记的文件。
Git已经尽可能地执行了自动冲突解决,该文件的状态是LOCALREMOTE的组合,并用冲突标记围绕着Git无法自行解决的部分。
mergetool应将解决结果写入此文件。

13
对我来说,问题仍然存在,即“本地”是指谁,“远程”是指谁(因为在 Git 中重新设定基准时不使用“我们的”和“他们的”这些术语,提到它们只会让答案更加混淆)。问题是“谁是本地,谁是远程”——因此,答案肯定需要提及“本地”和“远程”这两个词。 - PandaWood
4
总之,当你使用 git checkout A; git rebase B 命令时,本地分支为 B,远程分支为 A。这就是我需要知道的全部内容。 - Benubird
@Benubird 当然。我已将你的摘要添加在这个答案的顶部 ;) - VonC
1
Git的可用性真是一团糟。这毫无意义:当你执行git checkout A; git rebase B时,本地是B,远程是A。如果我checkout A,那么我当前正在查看文件在A上的存在方式,这怎么可能是_remote_?(我不是说Benubird错了;我是说Git的用户体验很愚蠢) - Rafa
3
@VonC 确定,我的(抱怨)观点是不应该需要阅读文档、查看图示并浏览 StackOverflow。如果命令可以给出清晰明确的反馈就好了。例如,不要用 local/remote/theirs/ours/mine/yours 这样的术语,而是直接显示“{分支 A}”和“{分支 B}”或类似的内容。 - Rafa
显示剩余11条评论

51

最重要的是

git rebase

  • LOCAL = 你正在rebase到的基础(即目标分支)
  • REMOTE = 要移动到顶部的提交

git merge

  • LOCAL = 要合并到的原始分支
  • REMOTE = 其他分支,其提交将与原始分支合并

换句话说,LOCAL总是原始分支,而REMOTE总是那些提交之前不存在的人,因为它们正在被合并或在其之上进行rebase。

验证一下!

当然。不要相信我的话!这里有一个简单的实验,可以让你自己看到结果。

首先,确保你已经正确地配置了git mergetool。(如果没有,你可能根本不会读到这个问题。)然后找一个目录来工作。

设置你的代码库:

md LocalRemoteTest
cd LocalRemoteTest

创建一个初始提交(包含空文件):
git init
notepad file.txt  (use the text editor of your choice)
  (save the file as an empty file)
git add -A
git commit -m "Initial commit."

在不是 master 分支的分支上创建一个提交:
git checkout -b notmaster
notepad file.txt
  (add the text: notmaster)
  (save and exit)
git commit -a -m "Add notmaster text."

在主分支上创建一个提交:
git checkout master
notepad file.txt
  (add the text: master)
  (save and exit)
git commit -a -m "Add master text."

gitk --all

此时您的存储库应如下所示:

Repository with a base commit and two one-commit branches

现在进行rebase测试:

git checkout notmaster
git rebase master
  (you'll get a conflict message)
git mergetool
  LOCAL: master
  REMOTE: notmaster

现在是合并测试。关闭您的合并工具而不保存任何更改,然后取消变基:

git rebase --abort

那么:

git checkout master
git merge notmaster
git mergetool
  LOCAL: master
  REMOTE: notmaster
git reset --hard  (cancels the merge)

你的结果应该与顶部显示的相同。


1
+1。这澄清了我在自己的答案中遇到的“本地”/“远程”方面的困惑(其实更多是关于“我们”的倒置与“他们”的倒置)。 - VonC

2
我也曾经很困惑,常常做出错误的决定,不得不重新开始。
免责声明:我不是git专家,如果有任何错误,请纠正我!
我认为我已经意识到我的困惑是因为我对rebase的理解与许多人所画的不同。以下是两个常用于描述rebase的图示:
--1--2--3--4--5 \ 6--7--8
和接下来的
--1--2--3--4--5--6--7--8
当然这是一种画法,但我的感觉是rebase发生的情况如下:
--1--2--3--4--5 \ 6--7--8
当然这完全相同。但从“我们/他们”的角度来看,它是不同的。在第二种情况下,感觉好像“我们”仍然在“分支”上(“6-7-8”),我们想从“主分支”中获取更改。所以在这个世界里,“ours”仍然是“branch”。这就是困扰我的地方。
但在第一个“世界观”中,我想这是Git的观点,我们移动到主分支(我们要将其rebase到的提交)并从那里依次挑选分支上的每个提交并应用它们。所以“ours”变成了“master”,最初是5。成功应用了6之后,“ours”就是6,但实际上是“在”主分支上的6':
--1--2--3--4--5--6' \ 6--7--8
然后我们继续处理7。
所以在合并中,你“在”8上并将两者组合成一个新的提交,但在rebase中,你移动到5并尝试将分支中的差异作为新的提交应用于那里。
因此,rebase的最终结果的“真实”图片应该是:
--1--2--3--4--5--6'-7'-8' \ 6--7--8
在rebase之后,你在8'上。你的分支也是(我想!)。这可以在我的脑海中可视化为:
--1--2--3--4--5 \ \ 6--7--8 6'-7'-8'

1
简单的规则是“ours”或“local”是HEAD所在的地方。在变基期间,HEAD在6'、7'、8'。 - VonC
因为在这种情况下,数字6只会得到一个新的父节点,我们不需要进行新的提交?数字7也会得到一个新的父节点:6'。因此,数字7变成了7',因为它的父节点发生了改变。数字8同理。 - VonC
@VonC 你的意思是,在快进的情况下,我们仍然会得到6'、7'和8'吗?还是说,正如我最后一张图片试图展示的那样,如果我们无法快进整个序列,就会生成新的提交? - thoni56
如果你的回答涉及到rebase,那么就没有所谓的“快进”:因为它们的父级正在改变,所以rebase总是会触发新的提交。 - VonC
是的,你说得对。我会删除那个评论。我在考虑使用rebase拉取,这不是一样的吗?或者这可能是一个太长的讨论/教程,在评论中进行不太合适;-) - thoni56
一个带有rebase的pull(可以默认设置:https://dev59.com/J10a5IYBdhLWcg3wipNA#30209750)是一个fetch + git rebase origin/master(假设这里是“master”)。如果在origin/master中有任何新提交,那么rebase就不会有任何快进,因为每个本地提交都会在origin/master之上重放(新父级)。如果没有新提交...那么rebase就是一个无操作(它什么也不做,甚至不是快进)。 - VonC

1

我没有完全理解你的问题,但我认为下面的图表可以解决你的问题。(变基:远程仓库 ---> 工作区)

http://assets.osteele.com/images/2008/git-transport.png

来源:我的 Git 工作流程


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