git-rebase 如何识别 "aliased" 提交?
How does git-rebase recognize "aliased" commits?
我正在努力更好地理解 git-rebase 背后的魔力。我今天对以下行为感到非常惊喜,这是我没想到的。
TLDR:我重新定位了一个共享分支,导致所有提交的 sha1 都发生了变化。尽管如此,派生分支能够准确地识别出其原始提交是 "aliased" 到具有不同 sha1 的新提交中。变基根本没有造成任何混乱。
详情
取主分支:M1
将其分支到 branch-X,并添加了一些额外的提交:M1-A1-B1-C1
。
记下 git-log 输出。
从 branch-X 分支到 branch-Y,并添加了一个额外的提交:M1-A1-B1-C1-D1
。记下 git-log 输出。
在 master 分支的顶端添加一个新提交:M1-M2
将 branch-X 变基到更新的 master 上:M1-M2-A2-B2-C2
。请注意,A2-B2-C2 都与 A1-B1-C1 具有相同的消息、内容和作者日期。但是,它们具有完全不同的 sha1 值以及提交日期。根据 this writeup,SHA1 不同的原因是提交的父项已更改。
将 branch-Y 变基到更新后的 branch-X。结果:M1-M2-A2-B2-C2-D2
。
值得注意的是,只有 D1 提交被应用(并成为 D2)。 git-rebase 完全忽略分支 Y 中的 A1-B1-C1 提交。您可以在输出日志中看到这一点。
这很好,但是 git-rebase 怎么知道忽略 A1-B1-C1? git-rebase 如何知道 A2-B2-C2 与 A1-B1-C1 相同,因此可以安全地忽略?我一直假设 git 使用 sha1 标识符跟踪提交,但尽管上述提交具有不同的 sha1,git 仍然以某种方式知道它们链接在一起。它是如何做到的?鉴于上述行为,什么时候 rebase a shared branch 才是真正危险的?
branch-Y 提交在第二次变基时为空
里面真的没有隐藏魔法。 Rebase 搜索共同历史并忽略它(在这种情况下仅提交 M1)。从 rebased 分支 (Y) 分离历史并尝试在新基础 (branch-X) 上选择它。
采摘方法从先前采摘的提交派生补丁。由于 A1、B1 和 C1 为空,因此它只是跳过这些提交。然后只选择 D1,因此创建 D2(使用新的 SHA 作为 header 中的 parent link 更改;如问题中正确所述)。
在内部,git rebase
列出应重新设置基准的提交,然后为这些提交计算 patch-id。与 commit id 不同的是,它只散列 patch 的内容,而不是树和提交对象的内容。 因此,A1 和 A2 虽然具有不同的标识符,但具有相同的补丁 ID。然后,git rebase
跳过 patch-id 已经存在的补丁。
有关详细信息,请在此处搜索 patch-id
:https://git-scm.com/book/en/v2/Git-Branching-Rebasing
上面的相关部分(缺少图表):
If someone on your team force pushes changes that overwrite work that you’ve based work on, your challenge is to figure out what is yours and what they’ve rewritten.
It turns out that in addition to the commit SHA-1 checksum, Git also calculates a checksum that is based just on the patch introduced with the commit. This is called a “patch-id”.
If you pull down work that was rewritten and rebase it on top of the new commits from your partner, Git can often successfully figure out what is uniquely yours and apply them back on top of the new branch.
For instance, in the previous scenario, if instead of doing a merge when we’re at Someone pushes rebased commits, abandoning commits you’ve based your work on we run git rebase teamone/master, Git will:
- Determine what work is unique to our branch (C2, C3, C4, C6, C7)
- Determine which are not merge commits (C2, C3, C4)
- Determine which have not been rewritten into the target branch (just C2 and C3, since C4 is the same patch as C4')
- Apply those commits to the top of teamone/master
This only works if C4 and C4' that your partner made are almost exactly the same patch. Otherwise the rebase won’t be able to tell that it’s a duplicate and will add another C4-like patch (which will probably fail to apply cleanly, since the changes would already be at least somewhat there).
实际上有几种不同的方法 git rebase
用于消除冗余副本。
补丁 ID
第一个也是最安全的方法是通过与 git cherry
uses to identify cherry-picked commits. If you read the linked documentation, though, the only clue as to how this works is at the end, where the manual page links to the git patch-id
documentation.
相同的方法
阅读这第二个手册页会让您很好地了解 "commit equivalence" 是如何建立的:Git 简单地计算 git patch-id
的输出,例如 git show
任何普通(非合并)提交。真的,它运行git diff-tree
而不是面向用户的git show
,但是效果差不多
但是仍然缺少一些东西,并且在 git rebase
或 git cherry
中都没有很好的记录。 git rev-list
, which is a rather daunting manual page. There are two keys: the notion of symmetric difference, using the three-dot syntax described in the gitrevisions documentation 和 git rev-list
的 --left-right
和 --cherry-mark
选项中有更好的记录。
一旦您理解了我们如何采用 DAGlet 例如:
...--o--o--L1--L2--L3 <-- left
\
R1--R2--R3 <-- right
并使用 left...right
到 select 这三个 L
和 R
提交,--left-right
选项本身很有意义:它标记了哪个提交在文本输出中,三个点的左侧是右侧提交。
这里的第二步是发现 git rev-list
可以为每个 "side" 上的每个提交计算补丁 ID。 Git 然后可以将所有左侧补丁 ID 与所有右侧补丁 ID 进行比较。 --cherry-mark
选项及其相关选项使用它们来标记等效或不等效的提交,或省略等效的提交。
这个特殊难题的最后一块是 git rebase
并不像文档中声称的那样使用 <upstream>..HEAD
。相反,它使用 git rev-list --cherry-pick --right-only --no-merges <upstream>...HEAD
的等价物来获取要复制的提交集。 (对于这些选项,我们还必须添加 --topo-order
和 --reverse
。)
叉点
git rebase
用于省略提交的第二种方法是 --fork-point
机制现在内置于 git merge-base
中。这种机制特别难以描述,此外,依赖于 reflog 条目来了解 是 在过去的分支上的提交,但现在已经不在了。有时它也会产生不良结果,并且在这种特殊类型的变基中没有用。
我在这里主要提到它是因为有人在寻找 git rebase
遗漏某些提交的原因时可能遇到了分叉点机制失灵的情况。参见,例如:
- Why is git rebase discarding my commits?
我正在努力更好地理解 git-rebase 背后的魔力。我今天对以下行为感到非常惊喜,这是我没想到的。
TLDR:我重新定位了一个共享分支,导致所有提交的 sha1 都发生了变化。尽管如此,派生分支能够准确地识别出其原始提交是 "aliased" 到具有不同 sha1 的新提交中。变基根本没有造成任何混乱。
详情
取主分支:M1
将其分支到 branch-X,并添加了一些额外的提交:M1-A1-B1-C1
。
记下 git-log 输出。
从 branch-X 分支到 branch-Y,并添加了一个额外的提交:M1-A1-B1-C1-D1
。记下 git-log 输出。
在 master 分支的顶端添加一个新提交:M1-M2
将 branch-X 变基到更新的 master 上:M1-M2-A2-B2-C2
。请注意,A2-B2-C2 都与 A1-B1-C1 具有相同的消息、内容和作者日期。但是,它们具有完全不同的 sha1 值以及提交日期。根据 this writeup,SHA1 不同的原因是提交的父项已更改。
将 branch-Y 变基到更新后的 branch-X。结果:M1-M2-A2-B2-C2-D2
。
值得注意的是,只有 D1 提交被应用(并成为 D2)。 git-rebase 完全忽略分支 Y 中的 A1-B1-C1 提交。您可以在输出日志中看到这一点。
这很好,但是 git-rebase 怎么知道忽略 A1-B1-C1? git-rebase 如何知道 A2-B2-C2 与 A1-B1-C1 相同,因此可以安全地忽略?我一直假设 git 使用 sha1 标识符跟踪提交,但尽管上述提交具有不同的 sha1,git 仍然以某种方式知道它们链接在一起。它是如何做到的?鉴于上述行为,什么时候 rebase a shared branch 才是真正危险的?
branch-Y 提交在第二次变基时为空
里面真的没有隐藏魔法。 Rebase 搜索共同历史并忽略它(在这种情况下仅提交 M1)。从 rebased 分支 (Y) 分离历史并尝试在新基础 (branch-X) 上选择它。
采摘方法从先前采摘的提交派生补丁。由于 A1、B1 和 C1 为空,因此它只是跳过这些提交。然后只选择 D1,因此创建 D2(使用新的 SHA 作为 header 中的 parent link 更改;如问题中正确所述)。
在内部,git rebase
列出应重新设置基准的提交,然后为这些提交计算 patch-id。与 commit id 不同的是,它只散列 patch 的内容,而不是树和提交对象的内容。 因此,A1 和 A2 虽然具有不同的标识符,但具有相同的补丁 ID。然后,git rebase
跳过 patch-id 已经存在的补丁。
有关详细信息,请在此处搜索 patch-id
:https://git-scm.com/book/en/v2/Git-Branching-Rebasing
上面的相关部分(缺少图表):
If someone on your team force pushes changes that overwrite work that you’ve based work on, your challenge is to figure out what is yours and what they’ve rewritten.
It turns out that in addition to the commit SHA-1 checksum, Git also calculates a checksum that is based just on the patch introduced with the commit. This is called a “patch-id”.
If you pull down work that was rewritten and rebase it on top of the new commits from your partner, Git can often successfully figure out what is uniquely yours and apply them back on top of the new branch.
For instance, in the previous scenario, if instead of doing a merge when we’re at Someone pushes rebased commits, abandoning commits you’ve based your work on we run git rebase teamone/master, Git will:
- Determine what work is unique to our branch (C2, C3, C4, C6, C7)
- Determine which are not merge commits (C2, C3, C4)
- Determine which have not been rewritten into the target branch (just C2 and C3, since C4 is the same patch as C4')
- Apply those commits to the top of teamone/master
This only works if C4 and C4' that your partner made are almost exactly the same patch. Otherwise the rebase won’t be able to tell that it’s a duplicate and will add another C4-like patch (which will probably fail to apply cleanly, since the changes would already be at least somewhat there).
实际上有几种不同的方法 git rebase
用于消除冗余副本。
补丁 ID
第一个也是最安全的方法是通过与 git cherry
uses to identify cherry-picked commits. If you read the linked documentation, though, the only clue as to how this works is at the end, where the manual page links to the git patch-id
documentation.
阅读这第二个手册页会让您很好地了解 "commit equivalence" 是如何建立的:Git 简单地计算 git patch-id
的输出,例如 git show
任何普通(非合并)提交。真的,它运行git diff-tree
而不是面向用户的git show
,但是效果差不多
但是仍然缺少一些东西,并且在 git rebase
或 git cherry
中都没有很好的记录。 git rev-list
, which is a rather daunting manual page. There are two keys: the notion of symmetric difference, using the three-dot syntax described in the gitrevisions documentation 和 git rev-list
的 --left-right
和 --cherry-mark
选项中有更好的记录。
一旦您理解了我们如何采用 DAGlet 例如:
...--o--o--L1--L2--L3 <-- left
\
R1--R2--R3 <-- right
并使用 left...right
到 select 这三个 L
和 R
提交,--left-right
选项本身很有意义:它标记了哪个提交在文本输出中,三个点的左侧是右侧提交。
这里的第二步是发现 git rev-list
可以为每个 "side" 上的每个提交计算补丁 ID。 Git 然后可以将所有左侧补丁 ID 与所有右侧补丁 ID 进行比较。 --cherry-mark
选项及其相关选项使用它们来标记等效或不等效的提交,或省略等效的提交。
这个特殊难题的最后一块是 git rebase
并不像文档中声称的那样使用 <upstream>..HEAD
。相反,它使用 git rev-list --cherry-pick --right-only --no-merges <upstream>...HEAD
的等价物来获取要复制的提交集。 (对于这些选项,我们还必须添加 --topo-order
和 --reverse
。)
叉点
git rebase
用于省略提交的第二种方法是 --fork-point
机制现在内置于 git merge-base
中。这种机制特别难以描述,此外,依赖于 reflog 条目来了解 是 在过去的分支上的提交,但现在已经不在了。有时它也会产生不良结果,并且在这种特殊类型的变基中没有用。
我在这里主要提到它是因为有人在寻找 git rebase
遗漏某些提交的原因时可能遇到了分叉点机制失灵的情况。参见,例如:
- Why is git rebase discarding my commits?