remote git rebase 如此邪恶的详细原因

Detailed reason why remote git rebase is so evil

所以我来自集中式 VCS 背景,并试图在 Git(新公司,年轻的代码库)中确定我们的工作流程。我找不到一个简单而详细的答案的一个问题是远程分支上的 rebase 究竟做了什么。我理解它改写了历史,一般来说应该仅限于本地分支。

我目前正在尝试审查的工作流程涉及一个远程协作分支,每个开发人员 "owning" 一个用于共享代码的目的。 (在可预见的未来有 2 名开发人员,最多 3 名开发人员,每个项目和功能请求的功能分支似乎过多,开销超过获得的收益。)

然后我遇到了 this answer 并尝试了它,它完成了我想要的 - 开发人员经常提交并推送到他自己的合作分支,当他知道什么被批准发布到他的暂存区时可以在合并到开发之前远程变基(压缩和重组)。

输入原题——如果远程分支是为了协作,别人迟早要拉。如果没有 'guest developer' 提交到该协作分支是一个 process/training 问题,那么分支所有者实际发生了什么事 rebases 该远程分支?

对已发布(远程)分支进行变基(或重写历史)的主要问题是很难重新整合基于它们的工作。因此,如果仅获取这些遥控器以供审查,并且没有提交,即使是合并,也不会在这些遥控器之上进行,您通常不会有很多问题。否则合并和解决冲突可能很快就会成为主要的烦恼。

这并不是真正的邪恶,这是实现和期望的问题。

我们从一堆事实开始:

  • 每个 Git 散列代表一些独特的 object。对于我们这里的目的,我们只需要考虑 commit objects。每个散列都是对 object 的内容应用加密散列函数(对于 Git,具体来说,它是 SHA-1)的结果。对于提交,内容包括源代码树的 ID;作者和提交者的姓名和电子邮件地址以及 time/date-stamp;提交信息;最重要的是,parent 提交的 ID。

  • 即使只是更改内容中的一个位也会产生一个新的 very-different 哈希 ID。散列函数的加密属性,用于验证和验证每个提交(或其他 object),也意味着没有办法有一些 不同 object 具有相同的哈希 ID。 Git 也依靠它在存储库之间传输 objects。

  • Rebase 通过将提交复制到新提交来工作(必然)。即使没有其他变化——通常,与新副本关联的源代码与原始源代码不同——rebase 的整个 point 是 re-parent 一些提交链.例如,我们可以开始于:

    ...--o--*--o--o--o   <-- develop
             \
              o--o       <-- feature
    

    其中分支 feature 在提交 * 时与分支 develop 分开,但现在我们希望 featuredevelop 的尖端提交下降,所以我们重新设定它。结果是:

    ...--o--*--o--o--o        <-- develop
             \        \
              \        @--@   <-- feature
               \
                o--o          abandoned [used to be feature, now left-overs]
    

    其中两个 @ 是原始两个提交的副本。

  • 分支名称,就像develop一样,只是指向(单个)提交的指针。我们倾向于认为 "a branch" 的东西,比如两个提交 @--@,是通过从每个提交向后工作到它的 parent(s).

  • 分支总是会增加新的提交。发现 developmaster 添加了一些新的提交是完全正常的,因此名称现在指向一个提交 - 或者许多提交中的最后一个 - 指向名称 用于指向。

  • 每当你让 你的 Git 同步(到任何程度)你的 存储库与一些other Git 和它的 other 存储库,你的 Git 和他们的 Git 交换了 ID——特别是哈希 ID。确切的 ID 取决于传输的方向,以及您要求 Git 使用的任何分支 names

  • 一个remote-tracking分支实际上是你的Git存储的一个实体,与您的 存储库关联。您的 remote-tracking 分支 origin/master 实际上是您的 Git 要记住的地方 "what the Git at origin said his master was, the last time we talked."

所以,现在我们拿这七项,看看 git fetch 是如何工作的。例如,您可能 运行 git fetch origin。此时,您的 Git 调用 origin 上的 Git 并询问它的分支。他们说 master = 1234567branch = 89abcde 之类的东西(虽然散列值都是 40 个字符长,而不是这些 7 个字符)。

您的 Git 可能已经有这些提交 object。如果是这样,我们就快完成了!如果没有,它会要求他们的 Git 发送那些提交 objects,以及您的 Git 需要理解它们的任何其他 objects。额外的 objects 是那些提交的任何文件,以及那些提交使用的你还没有的任何 parent 提交,加上 parents' parents,依此类推,直到我们到达您 do 的某个提交 object(s)。这将为您提供所有新历史记录所需的所有提交和文件。1

一旦您的 Git 安全地存储了所有 object,您的 Git 就会用新的 ID 更新您的 remote-tracking 分支。他们的 Git 刚刚告诉你他们的 master1234567,所以现在你的 origin/master 设置为 1234567。他们的 branch 也是如此:它变成了你的 origin/branch 而你的 Git 保存了 89abcde 散列。

如果您现在 git checkout branch,您的 Git 使用 origin/branch 创建一个新的本地标签,指向 89abcde。让我们画这个:

...--o--*--o--1   <-- master, origin/master
         \
          o--8    <-- branch, origin/branch

(我在这里将 1234567 缩短为 1,将 89abcde 缩短为 8,以使它们更合适。)

为了让事情变得真正有趣,让我们在 branch 上也进行 自己的 新提交。假设它的编号为 aaaaaaa...:

...--o--*--o--1    <-- master, origin/master
         \
          o--8     <-- origin/branch
              \
               A   <-- branch

(我将 aaaaaaa... 缩短为 A)。

那么有趣的问题是,如果他们—— 从中获取的 Git——变基了一些东西会发生什么。例如,假设他们将 branch 变基到 master。这会复制一些提交。现在你 运行 git fetch 和你的 Git 看到他们说 branch = fedcba9。您的 Git 检查您是否拥有此 object;如果没有,你得到它(和它的文件)和它的parent(以及那个提交的文件)等等,直到我们到达一些共同点——事实上,这将是提交 1234567.

现在有这个:

...--o--*--o--1        <-- master, origin/master
         \     \
          \     o--F   <-- origin/branch
           \
            o--8--A    <-- branch

这里我已经为提交 fedcba9 写了 F,现在 origin/branch points-to.

如果你后来遇到这个而没有意识到上游的人重新设置了他们的branch(你的origin/branch),你可能会看到这个并认为 必须在 o--8--A 链中写入 所有三个 提交,因为它们在你的 branch 而不是 origin/branch 上。但是 原因 他们不在 origin/branch 是上游放弃了他们以支持新副本。很难说这些新副本实际上是副本,而且您也应该放弃这些提交。


1如果树枝以"normal"、"expected"的方式生长,对你的Git和他们的[=226=来说真的很容易] 找出你的 Git 需要他们提交的内容:你的 origin/master 告诉你你上次在哪里看到他们的 master,现在他们的 master 指向更长的链.您需要的提交恰好是 他们的 master 上的提交,这些提交在 您的 origin/master.[=76 的提示之后=]

如果分支以 less-typical 方式乱序排列,则有点困难。在最一般的情况下,他们只需要通过哈希 ID 枚举 all 他们的 object,直到你的 Git 告诉他们他们已经达到了你已经拥有的.具体细节因浅层克隆而变得更加复杂。


也不是不可能

不是不可能说的,自从Git2.0左右的版本以来,现在有built-in工具可以让Git看图给你。 (具体来说,由 git rebase --fork-point 调用的 git merge-base --fork-point 使用 origin/branch 的 reflog 来确定 o--8 链曾经在 origin/branch 上点。这仅适用于保留那些 reflog 条目的 time-period,但这默认为至少 30 天,给你一个月的时间来赶上。在 你的 中是 30 天] time-line:从 运行 git fetch开始30天,不管上游多久之前做了rebase。)

这真正归结为,如果您和您的上游事先同意某些特定的分支集会重新设置基址,您可以安排做 您需要的任何事情 存储库,每次他们这样做。但是,在更典型的开发过程中,你不会期望它们变基,如果它们不变基——如果它们从未 "abandon" 你获取的已发布提交——那么你不需要恢复任何东西 来自.