Git 樱桃挑选 vs rebase

我最近开始使用 Git。

在线浏览 垃圾书时,我在“ Git Rebase”部分发现了以下内容:

使用 rebase 命令,您可以获取所有的更改 提交到一个分支上,然后在另一个分支上重播它们。

(引自: http://git-scm.com/book/en/Git-Branching-Rebasing)

我认为这是 git 选择的确切定义(在当前检出的分支上重新应用一个提交或一组提交对象)。

这两者有什么区别?

81346 次浏览

个别提交摘樱桃。

当您进行重定基时,它将历史记录中的 全部提交应用到那里缺少的分支的头部。

自从 git cherry-pick学会了应用多次提交以来,这种区别确实变得有些没有实际意义,但这是一种被称为“趋同演化”的东西; -)

真正的区别在于创造这两种工具的初衷:

  • git rebase的任务是将开发人员在其私有存储库中进行的一系列更改(针对某个上游分支的版本 X 创建)转发到同一个分支的版本 Y (Y > X)。这一系列提交的有效 改变了基础,因此“重定基础”。

    (它还允许开发人员将一系列提交移植到任何任意提交上,但这种方法的用途不太明显。)

  • git cherry-pick是用来将一个有趣的提交从一个开发线带到另一个开发线。一个典型的例子是将在不稳定的开发分支上进行的安全修复支持到一个稳定(维护)分支,在这个分支中,merge没有任何意义,因为它会带来大量不必要的更改。

    自从第一次出现以来,git cherry-pick已经能够一次提交多个提交,一个接一个。

因此,这两个命令之间最显著的区别可能在于它们是如何处理它们所处理的分支的: git cherry-pick通常带来一个提交 从别的地方并将其应用于当前分支的顶部,记录一个 新的提交,而 git rebase以这样或那样的方式接受当前分支和 重写一系列 它自己的提示提交。是的,这是对 git rebase能做什么的一个非常简单的描述,但是它是有意的,试图让大家理解这个概念。

更新 以进一步解释正在讨论的使用 git rebase的示例。

考虑到这种情况,ALT = “重新定基前的回购状态”> < br/> 那本书声明:

但是,还有另一种方法: 您可以使用 C3中引入的更改补丁,并在 C4之上重新应用它。在 Git 中,这称为 rebasing。使用 rebase 命令,您可以将在一个分支上提交的所有更改应用到另一个分支上。

在这个示例中,您将运行以下命令:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

这里的“陷阱”是,在这个例子中,“实验”分支(用于重定基的主题)最初是从“主”分支分叉出来的,因此它 股份提交 C0到 C2,其中 & mdash; 实际上,“实验”是“主”到,并且包括,C2加上 C3在上面提交。(这是可能出现的最简单的情况; 当然,“实验”可以在其原始基础上包含几十个提交。)

现在,git rebase被告知将“实验”重新定位在“主人”的 目前顶端,git rebase是这样做的:

  1. 运行 git merge-base查看“实验”和“主”共享的最后一个提交是什么(换句话说,转移的意义是什么)。这是 C2。
  2. 保存自转移点以来的所有提交; 在我们的玩具示例中,它只是 C3。
  3. 倒回 HEAD (在操作开始运行之前指向“ test”的提交) ,指向“ master”的提交,我们正在重新基于它。
  4. 尝试按顺序应用每个保存的提交(就像使用 git apply一样)。在我们的玩具示例中,它只是一个提交,C3。假设它的应用程序将产生一个提交 C3’。
  5. 如果一切顺利,“实验”引用将更新为指向应用最后一次保存的提交所导致的提交(在我们的例子中是 C3’)。

现在回到你的问题。正如您所看到的,这里 严格来说 git rebase确实将一系列提交从“实验”移植到“主人”的顶端,因此您可以正确地判断在这个过程中确实存在“另一个分支”。但要点是,从“实验”提交的提示最终变成了“实验”提交的新提示,它只是改变了它的基础:
state after merging

同样,从技术上讲,您可以看出这里的 git rebase包含了来自“ master”的某些提交,这是绝对正确的。

使用樱桃选择,原始的提交/分支会一直存在,并且会创建新的提交。使用 rebase,整个分支会随着分支指向重播提交而移动。

假设你是这样开始的:

      A---B---C topic
/
D---E---F---G master

调整基数:

$ git rebase master topic

你会得到:

              A'--B'--C' topic
/
D---E---F---G master

摘樱桃:

$ git checkout master -b topic_new
$ git cherry-pick A^..C

你会得到:

      A---B---C topic
/
D---E---F---G master
\
A'--B'--C' topic_new

关于 git 的更多信息,这本书有它的大部分 ( http://git-scm.com/book )

一个简短的回答:

  • Git 的“选择权”更“低级”
  • 因此,它可以模拟 git 重新定位

上面给出的答案是好的,我只是想给出一个例子,试图证明它们之间的相互关系。

不建议用这一系列操作替换“ git rebase”,它只是“概念验证”,我希望这有助于理解事物是如何工作的。

鉴于以下玩具储存库:

$ git log --graph --decorate --all --oneline
* 558be99 (test_branch_1) Test commit #7
* 21883bb Test commit #6
| * 7254931 (HEAD -> master) Test commit #5
| * 79fd6cb Test commit #4
| * 48c9b78 Test commit #3
| * da8a50f Test commit #2
|/
* f2fa606 Test commit #1

比如,我们在 master 中有一些非常重要的更改(提交 # 2到 # 5) ,我们希望将其包含到 test _ Branch _ 1中。通常我们只需切换到一个分支,然后执行“ git rebase master”。但是,由于我们假装自己只配备了“ Git 初选”功能,我们确实这样做了:

$ git checkout 7254931                # Switch to master (7254931 <-- master <-- HEAD)
$ git cherry-pick 21883bb^..558be99   # Apply a range of commits (first commit is included, hence "^")

在所有这些操作之后,我们的提交图将如下所示:

* dd0d3b4 (HEAD) Test commit #7
* 8ccc132 Test commit #6
* 7254931 (master) Test commit #5
* 79fd6cb Test commit #4
* 48c9b78 Test commit #3
* da8a50f Test commit #2
| * 558be99 (test_branch_1) Test commit #7
| * 21883bb Test commit #6
|/
* f2fa606 Test commit #1

正如我们所看到的,提交 # 6和 # 7是针对7254931(master 的提示提交)应用的。HEAD 被移动并指向一个提交,这实际上是一个基于重新设置的分支的提示。现在我们需要做的就是删除一个旧的分支指针并创建一个新的:

$ git branch -D test_branch_1
$ git checkout -b test_branch_1 dd0d3b4

Test _ Branch _ 1 现在植根于最新的主控位置!

它们都是用于在一个分支之上重写另一个分支的提交的命令: 区别在于哪个分支——“你的”(当前签出的 HEAD)或“他们的”(作为参数传递给命令的分支)——是这次重写的 基地

git rebase 接受 开始承诺重播 < em > 你的 提交后来的 < em > 他们的 (开始提交)。

git cherry-pick 接受 一组提交重放他们在你之后所做的承诺(你的 HEAD)。

换句话说,这两个命令的核心行为(忽略它们不同的性能特征、调用约定和增强选项)是 对称: 检出分支 bar并运行 git rebase foobar分支设置为与检出分支 foo并运行 git cherry-pick ..barfoo设置为相同的历史记录(从 foo改变,然后从 bar改变)。

在命名方面,这两个命令之间的区别可以被记住,因为每个命令都描述了它对 目前分支所做的操作: rebase使另一个头成为 新基地,用于您的更改,而 cherry-pick从另一个分支中挑选更改并将其放入 在你的 HEAD上面(就像圣代上的樱桃一样)。

两者做的事情非常相似; 主要的概念差异(简单来说)是:

  • Rebase 将从 现行分行提交到 另一家分行

  • 另一家分行提交到 现行分行的樱桃拾取 副本。

使用类似于 @ Kenny Ho 的回答的图表:

鉴于这种初始状态:

A---B---C---D master
\
E---F---G topic

... 假设您希望从 topic分支获得提交,并在当前 master分支上重播,那么您有两个选择:

  1. 使用 rebase: 首先通过执行 git checkout topic转到 topic,然后通过运行 git rebase master移动分支,产生:

    A---B---C---D master
    \
    E'---F'---G' topic
    

    结果: 你现在的分支 topic被移到了 master上。
    更新了 topic分支,而保留了 master分支

  2. 使用 cherry-pick : 首先通过执行 git checkout master转到 master,然后通过运行 git cherry-pick topic~3..topic(或者相当于 git cherry-pick B..G)复制分支,产生:

    A---B---C---D---E'---F'---G' master
    \
    E---F---G topic
    

    结果: 从 topicmaster的转运是 复制master的转运。
    更新了 master分支,而保留了 topic分支


当然,在这里您必须使用 范围记数法 foo..bar来实现 显式地告诉初选选择一个提交序列。如果您只是传递了分支名称(如 git cherry-pick topic) ,那么它只会在分支的末端获取提交,结果是:

A---B---C---D---G' master
\
E---F---G topic

它们非常不同:

  • Rebase 是一个“神奇”的操作,修改分支以更新它们。
  • 相比之下,樱桃采摘是一种细粒度的操作,复制提交。

事实上,樱桃镐甚至可以用来替换基座。

过时的分行问题:

过时的分支是日常场景。

  1. 你从一个“主人”的分支,
  2. 做了一些改变,
  3. 同时,最初的“主”分支也发生了变化。

problem case

如上所述,有几种方法可以描述这种初始状态:

  • 尽管当几个分支有相同的提交时,我们确实倾向于显示一种“分支”图。实际上,每个分支都有自己的提交行,其中一些提交行具有相同的哈希代码。
  • 重要的是要认识到,在 git 中,分支之间并不存在“主人”或“子女”关系。所有这些都是通过命名约定精心安排的。

除此之外,通常大多数分支都有两个版本: 本地版本和远程版本。因此,如果我们想要尽可能详细,我们将显示如下:

A--B--D      (remote) origin/master
     

A--B--C--E   (remote) origin/feature/foo
A--B--C--E   (local) feature/foo

现在的挑战是更新我们的2个特性分支的历史记录,添加提交 D时缺少的更改。实际上有两种方法可以实现这一点。

重新调整基础溶液

Rebase 确实是一个神奇的命令,可以为您更新功能分支。它通过将缺少的提交(即主服务器的提交 D)添加到一个“落后于”两次提交的分支来实现这一点。它通过添加新提交的 INFRONT 提交来实现这一点。

rebase

在一个 rebase 之后,我们所需要做的就是推送结果,用更新后的版本更新远程分支。

A--B--D         (remote) origin/master
     

A--B--D--C--E   (remote) origin/feature/foo
A--B--D--C--E   (local) feature/foo

或者就像我们经常看到的那样。

look it moved

这给人的印象是,我们的功能分支“移动”了,而实际上,它只是变长了一点。

采樱桃的办法

事实上,你可以通过结合“硬重置”和多个“选择权”来实现同样的目的。让我们从同一个问题开始,并通过使用初选执行“手动 rebase”。

cherry pick rebase

它的结果与 rebase 相同

A--B--D         master
\
C--E    feature/foo