git rebase 影响远程分支还是本地分支

Does git rebase affect the remote branch or local branch

我正在我的分支 "role" 上工作。远程主机正在频繁更新。因此,在我将更改提交到远程分支之后,我需要将分支重新设置为最新的主分支。所以这是我的步骤:

# from local
git add .
git commit 'Add feature'
git push origin role

# rebase
git pull --rebase origin master

变基是只影响本地分支还是远程分支,还是两者都有?如果只有本地分支变基,我是否应该在变基后再次提交到原点?

总结:是的,但是你的问题是错误的。 :-)

这里有一堆东西要直说。 Git 术语开始时不太好——“远程”、“跟踪”、“分支”和“remote-tracking 分支”的意思完全不同!——而你正在使用另一个一组术语,这让事情变得更加棘手。所以,在我回答你想问的问题之前,让我们定义这些术语。 (另请参阅 gitglossary,但并非所有这些都在其中。)

  • A remote 是一个类似于 origin 的短名称。它主要作为一个URL的占位符,这样你就不用一直输入一些很长的URL,同时也是[=339=的名称的一部分] branch(我们稍后会定义)。

  • A branch 是一个模棱两可的术语:参见 What exactly do we mean by "branch"? 在这种特殊情况下,我们将首先使用它来表示 分支名称 喜欢master。 Git 中的分支名称只是特定提交的名称,我们称之为分支的 tip,但是 local(普通)分支名称如 master 有一个特殊的 属性,我们很快就会得到。

  • A remote-tracking 分支 是您的 Git 分配的名称,并在 您的分支中为您更新 存储库,基于你的 Git 在你的 Git 调用其他 Git 时看到的内容。这些是像 origin/master 这样的名字。这些 remote-tracking 分支名称 没有 普通(本地)分支名称的特殊 属性。

  • Tracking 是一个可怕的动词,Git 用作 shorthand 更现代和冗长的方式来表达同样的事情.可以将本地分支设置为“跟踪”remote-tracking 分支。更现代的说法是,本地分支将 remote-tracking 分支设置为其 上游 。设置 upstream 不会做任何你不能“手动”做的事情,但是 Git 会自动为你做,这很方便。

    通常,我们会将remote-tracking分支origin/master设置为本地分支master的上游。同样,我们将 origin/role 设置为本地分支 role 的上游。 请务必记住,此时本地分支和 remote-tracking 分支都在 您自己的 Git 中。它们实际上都是本地的! remote-tracking 分支被称为“remote-tracking”,因为它们是自动更新

接下来,请记住,当您使用 git fetchgit push(以及 git pull,但请参阅末尾)时,您的 Git 会调用另一个 Git。另一个 Git 有自己独立的存储库。您的 Git 获取另一个 Git 的方式是通过一些 URL,并且 URL 存储在“远程”名称下,例如 origin : 你的 Git 通过 Internet-phone 呼叫来源的 Git 并与另一个 Git 交谈。你的 Git 从他们那里得到东西 (git fetch) 或给他们东西 (git push),你的 Git 和他们的 Git 都 纯粹在本地工作,你的Git在你的存储库上工作,他们的Git在他们的存储库上工作。

一切都是本地的

这就是为什么,在 Git 中,至少在第一个近似值中,一切都是本地的。 实际上没有任何远程的东西,只有本地实体,加上这些特殊的 Internet-phone-调用,其中多个 Git 在本地工作时相互交换数据。幸运的是,由于 fetchpush 是主要的两个接触点,因此很容易保持直截了当:在您获取或推送之前,一切都是本地的。 这些是您的 Git 和另一个 Git 传输数据并进行(本地!)更改的点。

那么,现在我们可以回答您的问题了:

Does rebase affect only the local branch or remote branch or both?

这个很简单:如果你不推动,你只能影响当地的事情。

If only local branch is rebased, should I commit to origin again after the rebase?

这个有一个内在的误解。当您进行提交时,您仍然只影响本地事物。要将您本地的东西发送到其他地方,您需要推送它们(或者,如果您的机器可以充当服务器,让您的机器回答 别人的 git fetch phone-call —但我们不要去那里,至少现在不要去!)。

这里有一个大问题,因为 git fetchgit push 不对称。

推送和获取不对称

当您 运行 git fetch 时,您的 Git 会调用他们的 Git 并从他们那里获得新的东西。您的 Git 保存新的东西——提交和那些提交需要的任何其他东西,真的——在您的存储库中,然后更新您的 remote-tracking 分支名称。这些与您自己的本地分支名称完全不同:您的 origin/master 与您的 master 是分开的,您的 origin/role 与您的 role 是分开的。 唯一 你的 origin/* 分支名称做的事情是记住你的 Git 在他们的 Git 上看到的东西,所以这是很安全的随时更改 他们 Git 在他们的 Git 上看到新内容。

当您 运行 git push 时,您的 Git 会调用他们的 Git,向他们发送 您的 内容,然后要求他们更改 masterrole他们没有 ddd/masterddd/role:您让 Git 要求他们更改自己的,并且只有 masterrole. 如果他们在 masterrole 中有一些新内容,而您还没有将其纳入您正在推动的内容中,您将要求他们 放弃那些新事物,取而代之。

现在是时候提出本地分支名称的特殊 属性,看看分支名称和提交是如何工作的,看看 git rebase 实际上是如何工作的。这也是回顾另一个问题的好时机,What exactly do we mean by "branch"?

分支是如何正常生长的,暴力变基对此有何影响

Git 主要是关于提交。提交是 Git 的 raison d'être:它们是您提交的任何内容的永恒不变的快照。没有提交可以 ever 更改,尽管可以将现有提交复制到新的(不同的)提交,方法是提取旧快照,进行更改(无论它是什么)并制作新快照在一些新的位置。但是这里的“位置”是什么意思?这是 提交图 进入画面的地方。

每个提交都有一个唯一的 ID。这是丑陋的 commit face0ff... 种 number-and-letter 组合 Git 在不同时期的表现。 (事实上​​ ,每个 Git 对象都有这些 ID 之一,但现在我们只关心提交对象。)

每次提交都会保存,连同您从上一次提交中 git add 编辑或保留的任何内容的快照、您的姓名、您的电子邮件地址、您的日志消息,以及——这是这里的关键项目— 之前提交的 ID。

这意味着提交形式(向后)链:

A <- B <- C   <-- master

分支 names,如 master,指向 tip 提交。提示提交是链中最新的。最近的指向其 parent(较早的)提交,它指向另一个父级,依此类推。此链仅在我们到达第一个没有父项的提交时结束。

由于新提交指向旧提交,我们需要Git“推进”分支名称,每当我们创建新提交时,指向我们刚刚做出的新承诺。也就是说,如果我们有上面的 A <- B <- C 并且我们创建一个新的 D,我们现在需要 Git 移动 master 指向 D

A <- B <- C <- D   <-- master

这是本地分支名称的特殊属性。他们自动前进,到我们刚刚做出的新提交,当我们在该分支上进行提交时。

这引出了一条关于分支名称的简单规则(但很快就违反了这条规则):它们以一种保留所有旧提交的方式前进。我们向一个分支添加一个 new 提交,新提交指向旧提交,我们只 添加到 分支。 (请注意,我们现在谈论的分支是 提交集合 ,而不是 名称 指向 一个提交!)

Rebase 故意违反了这个简单的规则。让我们看看创建分支时会发生什么。让我们也停止绘制所有内部箭头,只记住它们都指向后方:当我们从较新的提交向后移动到较旧的提交时,我们总是移动 left-ish。

A--B--C--D        <-- master
       \
        E--F--G   <-- role

这里我们总共有七个提交,其中三个仅在分支 role 上,一个提交 D - 仅在分支 master 上,三个 - A-B-C 链——在 两个 分支上。 (这是 Git 的另一个奇特之处:提交通常在 许多 分支上。)

当我们使用 git rebase 时,我们希望移动提交,通常紧跟在其他分支的(新)提示之后。例如,在这种特殊情况下,我们希望将 E-F-G 链移动到 D 之后,而不是 C 之后。 但是不能更改提交,只能复制。

这就是 git rebase 所做的:它 复制 提交。我们将 E-F-G 复制到新的提交 E'-F'-G',它们与 很像 E-F-G,但它们在图中的不同位置:它们在 D:

之后
A--B--C--D           <-- master
       \  \
        \  E'-F'-G'  <-- role
         \
          E--F--G    [previous role, now abandoned]

rebase 命令首先复制提交,然后将分支 name role 从旧的 tip 提交 G 剥离并将其粘贴到新副本上G'.

这会丢失旧的 E-F-G 链,放弃它以支持新的闪亮 E'-F'-G' 链。新提交略有不同——如果没有别的,E'E 的不同之处在于 E'D 作为它的父级,而 C——有不同的 ID。

因此当我们推送时我们必须force-push

如果我们将原始 E-F-G 推送到 origin,我们就已经给了他们 exact-duplicate 个原始 E-F-G 的副本。这些在 C 之后,而不是在 D 之后。现在我们又要去git push origin role了,所以我们会叫我们的Git叫另一个Git交出E'-F'-G',然后让他们设置他们的role 指向提交 G'.

他们会说“不”!

如果他们将 role 设置为指向 G',他们将 失去 提交 E-F-G。现在,他们 Git 找到 G 唯一 方法是查看(他们的)名字 role,如果他们 改变他们的role指向G',他们会失去这个link到G

当然,这就是我们希望他们做的!但默认情况下他们会说“不,如果我这样做,我将失去一些提交”。 (这个默认的原因很简单:他们不知道也不关心 我们 给了他们 G,他们只知道,现在,他们会 lose G.) 所以我们必须将 Git 的礼貌请求“请移动 role” 更改为更有力的命令:“移动 role!"

理想情况下,我们甚至可以说:“我们认为您的 role 名称 G,如果是这样,它应该改为名称 G';但如果我们知道,请告诉我们你的 role 错了,好吗?” Git 称其为 --force-with-lease。并非 Git 的每个版本都支持它,如果您 确定 是唯一更改过的人,则没有必要他们的role;但是需要某种强制,因为你需要让 他们的 Git 放弃旧的(复制的)提交,就像你的 Git 所做的那样。

旁白:git pull

git pull 命令是为了方便起见。每当你 运行 git fetch 时,你都可以像往常一样从远程获取一堆新提交,但这些新提交都在它们的 remote-tracking 分支下结束。一旦你 这些提交,你通常想 做一些事情。您可以用它们做的两件事是 git mergegit rebase。所以 git pullgit fetch 与立即 git mergegit rebase 结合起来。

这样做的主要问题是,在您获取 并检查 任何新提交之前,您不一定确切地知道 什么 你会想和他们一起做。使用 git pull 可以让您提前决定如何处理它们。想象一下:“我要把手伸进这个黑暗的壁橱,拿出一瓶酒,不管是什么,我都要喝掉它。”如果您拿出啤酒,那很好;但是如果你拿出一瓶漂白剂呢?

无论如何,以上所有内容仍然适用于git pull因为只是git fetch后面跟了第二个命令。 fetch 通过存储在远程名称下的 URL 调用另一个 Git。第二步,无论是git merge还是git rebase,然后在本地工作。 git pull 特别令人困惑的是,因为它早于 remote-tracking 分支名称的发明,所以它有自己的特殊语法。这就是为什么,即使你是从原点获取,然后在 origin/master 上变基,你写的是“pull (as rebase) origin master”而不是“pull (and then rebase on) origin/master”。