两种 git 变基方法之间的区别

Difference between two git rebasing methods

我想从 master 执行更新到我的本地分支,该分支在时间轴上较早地从 master 分支出来(从 M2 更改)。 Master 表示有 M 变化,我的本地分支有 L 变化

新分支是从 master 的 M2:

创建的
 M1->M2-->M3->M4
      \
       L1->L2

我想我的本地分支机构的结果应该如下:

M1->M2->M3->M4->L1->L2

这意味着重新创建我的本地分支以首先进行所有主更改,然后才在其之上更改我的本地分支,如中所述:
https://www.atlassian.com/git/tutorials/merging-vs-rebasing (如果我错了请纠正我)

我的问题是以下方法之一是否没有创建上述所需的流程,如果是,为什么?

git checkout master
git pull --rebase 
git checkout branch_to_update
git rebase master` (method mentioned in attlasian tautorial)

VS

git checkout branch_to_update
git pull --rebase origin master

假设there is only one remote repository,这两个会产生相同的效果。

在第一种情况下,您正在更新 master 的本地副本,然后进行变基。

在第二种情况下,您是直接从远程 存储库变基

当您可能不想费心更新要变基的分支的本地副本时,请使用第二个选项。

例如,我们有一个主要的 develop 分支,我们可以从中创建主题分支,例如 feature/0001。工作时,我会检查 feature/0001 并时不时简单地 git pull -r origin develop。在这种情况下,拥有 develop 的本地最新副本是无关紧要的。

我的功能分支合并后,我检出并拉取 develop,然后从更新的副本创建一个新的 feature/0002 分支。


此外,请注意它实际上会创建 this 作为结果:

M1 -> M2 -> M3 -> M4 -> L1' -> L2'

L1' 是什么意思?粗略地说,它将创建一个具有相同内容的新提交——具有新的 SHA 标识符。所以这不是同一个提交 本身 .

git checkout branch_to_update
git rebase master

git checkout branch_to_update
git pull --rebase origin master

这些是相同的结果,但方式不同 pull --rebase 参数简要使用 rebase

与其他两个答案一样,效果大致相同。 但还有更多。要了解是什么以及为什么,我们应该将 git pull 分解成它的成分。

所有挑剔的细节(警告:长)

除了一些小的例外(例如 运行将它放在一个完全空的存储库中),git pull 意味着:

  1. 运行 git fetch 带有各种选项和参数;然后
  2. 运行 第二个 Git 命令,在步骤 1 运行 之前选择,也有各种选项和参数。

第二个命令通常是git merge,但你可以告诉Git使用git rebase。传递给这两个命令的选项和参数取决于传递给 git pull 的选项和其他配置设置,以及第 1 步中获取的一个或多个结果。

不过,作为一种通用规则,传递给 git pull 的参数会传递给 git fetch,因此这意味着您的第二个命令序列——将 origin master 传递给 git pull—也将 origin master 传递给 git fetch。如果你 运行 git pull 没有 这些参数,就像在你的第一个命令序列中一样,Git 提取 remote(通常是 origin)和 上游 b运行ch 名称(通常与当前 b运行ch 名称相同),具体从这两个命令的结果来看:1

git config --get branch.$branch.remote
git config --get branch.$branch.merge

(其中 $branch 是当前 b运行ch)。如果当前 b运行ch 是 master,这将使用 branch.master.remote 作为远程。这就是我们假设只有一个遥控器的意思。 merge 名称可能是 master,但如果不是,那是我们必须做出的另一个假设,然后我们才能声称它们做同样的事情。


1如果你的 Git 够老了,git pull 是一个 shell 脚本,字面意思是 运行s各种其他 Git 命令。如果是更新的,git pull 已经转换为 C 语言程序,并且直接内置了这些。


Rebase 复制提交,然后切换到新副本

如果我们深入研究所有细节,git rebase 的作用会变得复杂,但在较高层次上,它的工作是 复制 提交。要查看它将复制哪些提交,您应该绘制提交图,或使用 git log --graph 让 Git 为您绘制。 (有的GUI总是画出来,有的web界面*咳*GitHub*咳* 永远不要让你看到它!)通过图形绘制,很容易——好吧,有时很容易——分辨出哪些提交被复制了:

...--A--B--C--D   <-- master
         \
          E--F--G   <-- br

将你的 b运行ch br 变基到你的主副本三个提交,这里 EG,将副本放在提交 D 之后。这和你画的差不多。

假设我们添加 origin/ 远程跟踪名称,并显示您自己的 master 当前指向提交 Borigin/master 当前指向提交 D,像这样:

          C--D   <-- origin/master
         /
...--A--B   <-- master
         \
          E--F--G   <-- br

现在我们可以看到,我们必须将 br 变基到 origin/master 上,以便在提交 D 后保留副本。变基到 master 会将副本放在 B 之后,这是原件所在的位置,所以根本不需要复制。 (rebase 是否实际复制,或者只是重新使用原件,是挑剔的细节之一:例如,它取决于 -f 选项。)

复制完成后,git rebase 简单地重新指向 b运行ch 名称以指向最终复制(或重新使用)的提交,我们可以称之为 G' 这里要注意,它是 G 的副本。原始提交被有效地放弃,尽管 HEAD 和原始 b运行ch 的 reflog 条目,以及名称 ORIG_HEAD,暂时保留它们:

               E'-F'-G'  <-- br
              /
          C--D   <-- origin/master
         /
...--A--B   <-- master
         \
          E--F--G   [abandoned, but see ORIG_HEAD and reflogs]

默认情况下,reflog 条目会将原件保留至少 30 天。最终 ORIG_HEAD 由于其他操作而移到别处,reflog 条目过期,原始提交被垃圾收集。

现在我们可以查看您的原始命令序列

让我们假设,为了论证,我们有上面的图表(和你的一样,但在 b运行ch br 上多了一个提交,我们已经 运行 git fetch 以获得 origin/master 更新)。然后 Atlassian 命令序列以这两个命令开始:

git checkout master
git pull --rebase 

这会将我们的 HEAD 附加到我们的 master,检查提交 B;然后,假设上游是 origin/master,运行 git fetch origin master 来更新我们的 origin/master,在这种情况下,origin/master 指向 D。如果我们没有 运行 git fetch 但这将 获得 提交 CD 并将我们的 origin/master 指向 D.

最后,这将运行 git rebase <hash-of-commit-D>。 rebase 操作使用哈希 ID,因为它使用 git fetch.git/FETCH_HEAD 中留下的痕迹,并且根据确切的 Git 版本和我们将在此处忽略的更多细节,还使用 ​​git merge-base --fork-point 找到一个提交哈希,以便从上游 rebases 中恢复。 (此过程有时会出错,具体取决于您自己的工作流程,我不确定我是否喜欢默认行为。)

一旦这一切都完成了,我们得到最后两个命令:

git checkout br
git rebase master

第一个将 HEAD 附加到名称 br,检查提交 G。然后,rebase 将 E-F-G 提交序列复制到 master 现在指向的提交之后。因此,忽略所有 reflog 条目,我们得到图表:

                E'-F'-G'  <-- br (HEAD)
               /
...--A--B--C--D   <-- master, origin/master
         \
          E--F--G   [abandoned]

将此与较短的命令序列进行比较:

git checkout br
git pull --rebase origin master

结帐将 HEAD 附加到 brpull 运行s git fetch origin master,确保我们有提交 C-D(如果我们还没有获取它们)和更新 origin/master(如果我们的 Git 至少是 1.8.4),然后 运行s git rebase <hash-of-D> 复制 E-F-G 链,给出:

               E'-F'-G'  <-- br
              /
          C--D   <-- origin/master
         /
...--A--B   <-- master
         \
          E--F--G   [abandoned]

所以关键的区别是你自己的名字,master,永远不会更新指向提交 D

我推荐什么

重要的是要注意(并且知道)如果你 运行 git fetch 你自己——这是我的首选方法——这会告诉你 Git 在遥控器的 URL 上调用另一个 Git,并为您的 Git、all[=227= 设置另一个 Git 列表] 的(来源,我们假设)b运行ches。然后,您的 Git 将获得 所有 他们拥有而您没有的提交,并将它们放入您的 Git 的存储库中,并更新 所有 的远程跟踪名称,例如 origin/masterorigin/develop 等等。

换句话说,您的远程跟踪名称,即您 Git 记住 他们的 b运行 的方式,将 全部 得到更新。这通常是一件好事。只有当他们有很多 b运行ches 和很多大提交并且你的网络连接很慢时才会糟糕;在这种情况下,您可能需要等待很长时间才能下载所有内容。

git pull 运行 为 git fetch 时,它 运行 带有一个限制选项。例如,如果您的 git pull 运行s:

git fetch origin master

告诉你的 Git 在 URL 呼叫 Git origin 并要求他们 运行 转 只有他们的master提交新的。如果他们更新了他们的 developproductionfeature/tall 等等,你不会得到任何这些 - 你只会得到他们的 master 上的新提交.您的 Git 更新您的 origin/master 以记住新的提交,2 但保持其余的远程跟踪名称不变。

在你的第二个命令序列中,你 运行 一个明确的 git pull origin master (还有 --rebase),所以这限制了你的 Git 更新你的 origin/master.在你的 first 命令序列中,你 运行 git pull 没有参数——但是 git pull 插入 originmaster,假设这些是您的 master b运行ch 的配置设置,因此这也将您的 Git 限制为仅更新您的 origin/master.

我提到所有这些是因为我建议根本不要使用 git pull。 运行 git fetch 你自己——你可以让它默认从 origin 获取所有内容——然后 运行 你想要的任何 git rebase 命令!在 fetch 之后,您拥有所有提交和所有适当的 origin/* 名称;然后你可以 运行:

git checkout <whatever-name>
git rebase origin/<whatever-other-name>

复制任何提交 and/or 调整您想要更新的任何 b运行ch 名称。一次提取让您可以执行任意数量的合并、重置、快进伪合并或变基操作。在决定对 运行!

执行其他 Git 命令之前,您还可以 查看 获取的内容

2这假设您的 Git 版本至少为 1.8.4。如果不是,这种git fetchorigin/master更新都失败。您必须 运行 git fetchgit fetch origin 才能更新您的远程跟踪名称!