Git rebase 提交不可重复

Git rebase commits not repeatable

当我有 2 个分支指向同一个提交,然后将它们重新定位到同一个新的基本提交上时,为什么重新定位的分支会发生分歧?

我预计他们会以相同的方式重播,并最终指向相同的新提交。

touch a; touch b; touch c
git add a
git commit -m 'a'
git add b
git commit -m 'b'
git checkout -b branch-01 HEAD^
git add c
git commit -m 'c'
git checkout -b branch-02
git rebase master branch-01
git rebase master branch-02
git log --all --graph --decorate --pretty=oneline --abbrev-commit

为了解释发生了什么,试试这个实验:

$ git checkout -b exp1 master
<modify some file; git add; all the usual stuff here>
$ git commit -m commit-on-exp1

此时你有一个名为 exp1 的实验分支,其中有一个提交不在 master:

...--A--B   <-- master
         \
          C1   <-- exp1

现在我们将创建一个指向提交 Bexp2 分支,并将提交 C1 复制到分支 exp2 上的新提交 C2 :

$ git checkout -b exp2 master
$ git cherry-pick exp1

结果是:

          C2   <-- exp2
         /
...--A--B   <-- master
         \
          C1   <-- exp1

现在让我们重复 exp3,创建它以使其指向提交 B,然后再次复制 exp1

$ git checkout -b exp3 master
$ git cherry-pick exp1

您希望 exp3 指向提交 C2 吗?如果是这样,为什么?为什么 exp2 指向 C2 而不是像 exp1 那样指向 C1

这里的问题是提交 C1C2(现在 exp3 上的 C3)是 而不是 bit-for-bit一模一样。的确,他们有相同的 快照、相同的 作者、相同的 日志消息,甚至相同的 parent(所有三个都有 B 作为他们的 parent)。但是这三个都有不同的提交者date-and-time-stamps,所以它们是不同的提交。 (使用 git show --pretty=fuller 显示 date-and-time-stamps。Cherry-pick,因此也变基,复制原始作者信息,包括 date-and-time,但因为它是新提交,所以使用当前 date-and-time 提交者时间戳。)


当您使用 git rebase 时,一般来说,您有 Git 次复制提交,就像 cherry-pick 一样。在复制结束时,Git 然后移动 分支名称 使其指向最后复制的提交:

...--A--B   <-- mainline
      \
       C--D--E   <-- sidebranch

变为:

          C'-D'-E'  <-- sidebranch
         /
...--A--B   <-- mainline
      \
       C--D--E

此处 C'C 的副本,已更改为使用 B 作为其 parent(并且可能与 C 具有不同的源快照), D'D 的副本,E'E 的副本。只有一个名字指向E;该名称现已移动,因此 no 名称指向 E.

但是,如果您有 两个 个名称最初指向 E,那么这两个名称中的 一个 仍然指向 E:

          C'-D'-E'  <-- sidebranch
         /
...--A--B   <-- mainline
      \
       C--D--E   <-- other-side-branch

如果您要求 Git 再次复制 C-D-E,它会这样做——但新副本不是 C'-D'-E',因为它们有新的 date-and-time 邮票。所以你最终得到了你所看到的。

因此,如果你想在复制一些提交链的同时移动两个或更多名字,你可以使用git rebase移动第一个名字,但是你必须做一些其他的事情——比如 运行 git branch -f——来移动剩余的名称,以便它们指向在一次变基期间创建的提交副本。

(我一直想要一个更高级的 git rebase 版本,它可以自动执行此操作,但这显然是一个难题。)

分支为何分歧

在用于计算 git 提交的哈希值的元数据中,不仅有 AuthorAuthorDate;还有一个 Committer 和一个 CommitterDate。这可以通过运行例如

看到
git show --pretty=fuller branch-01 branch-02

每个rebase(或cherry-pick)命令根据当前时间更新新提交中的提交者日期。由于问题中的两个 rebase 是在不同时间执行的,因此它们的 CommitterDate 不同,因此它们的元数据不同,因此它们的提交哈希不同。

如何一起移动Branches/Tags

那个

if you want to move two or more names while copying some chain of commits, you'll be OK using git rebase to move the first name, but you will have to do something else—such as run git branch -f—to move the remaining names, so that they point to the commit copies made during the one rebase.

关于作者与提交者

来自 Difference between author and committer in Git?:

The author is the person who originally wrote the code. The committer, on the other hand, is assumed to be the person who committed the code on behalf of the original author. This is important in Git because Git allows you to rewrite history, or apply patches on behalf of another person. The FREE online Pro Git book explains it like this:

You may be wondering what the difference is between author and committer. The author is the person who originally wrote the patch, whereas the committer is the person who last applied the patch. So, if you send in a patch to a project and one of the core members applies the patch, both of you get credit — you as the author and the core member as the committer.