【问题标题】:How does git update local interim (missing on remote) commits on remote branchgit如何更新远程分支上的本地临时(远程丢失)提交
【发布时间】:2018-07-21 16:28:49
【问题描述】:

说,我有一个分支featureA (A->B->C->F->G),自第三次提交C 以来从master (A->B->C->D->E) 分支出来。

当master 合并到featureA 时,featureA 现在看起来像featureA (A->B->C->D->E->F->G->T),其中->D->E 属于master,T 代表合并的提交。

git status 告诉我Your branch is ahead of 'origin/featureA' by 3 commits.

并且将featureA 推送到远程的 featureA,远程上的featureA 看起来像(A->B->C->D->E->F->G->T)。

我想知道 git 如何在远程合并临时提交 ->D->E(最初在远程功能 A 上缺少)。

git 是否尝试将本地 featureA 复制到远程 featureA 或内部它是如何工作的。我不确定我是否正确地表达了我的疑问。

我希望有人能确定我的疑问,即使它的措辞可能不正确。

谢谢 dk

【问题讨论】:

  • "当 master 合并到 featureA 时,featureA 现在看起来像 featureA (A->B->C->D->E->F->G->T)" 不。那是变基,而不是合并
  • @tkausl 嗨,我从未使用过rebase 命令。

标签: git merge git-merge git-remote


【解决方案1】:

(旁注:you want the word "question" where you are using "doubt".)

...如何 [does] git merge interim commits [when push]?

答案是它没有。 git push 操作只是传输提交,然后发送“请设置”请求。所有真正的工作都发生在本地,在您自己的存储库中。

如果你只是想要答案,你可以在这里停下来,但如果你想知道为什么这就是答案,请继续阅读。你被你画出每个树枝上的东西的方式误导了。

让我们从清楚地绘制提交链开始

让我们更仔细地看一下。当您将提交绘制为链时,您走在正确的轨道上,但首先,让我们稍微调整一下绘图,因为 Git 在内部是向后工作的。

假设,我有一个分支 featureA (A->B->C->F->G),自第三次提交 C 以来,它已从 master (A->B->C->D->E) 分支。

我喜欢将这个(无论如何在 StackExchange 上,图形有点困难)绘制为:

A--B--C--D--E   <-- master
       \
        F--G   <-- featureA

这表示使用单个大写字母的七个提交,而不是 Git 的实际哈希 ID,因此我们将在 26 个提交后用完字母;但它会在这里供我们使用。请注意,在我们自己的存储库中,名称master 只是指定“使用哈希E> 提交”。 commitE本身记录了commitD的实际hash ID,即E指向D;而commitD记录C的hash ID,以此类推。那么featureA这个名字就只记录了G的hash ID。因此,如果我们想画箭头,我们应该将每个箭头附加到持有它的实体上,并使其指向后方(在此图中向左):

A <-B <-C <-D <-E <--master

而不是指向前方。这是理解这一切的第一个关键:Git 逆向工作。

另一个重要的项目,虽然我们不会在这里直接使用它,但 所有 Git 对象在存储后都是只读的。 这解释了为什么箭头必须向后:对象的哈希 ID 是根据对象的内容计算的。例如,当我们提交F 时,我们还不知道提交G 的内容是什么。我们只知道F 的内容是什么。这是因为F 的内容包括 when F 的时间戳,以及其中的快照、您的姓名和电子邮件地址(作为作者和提交者),以及您的日志消息。此外,提交E 的哈希ID 本身就是提交F 的一部分,因此F 的哈希ID 取决于E 的哈希ID。

所有这些——哈希 ID 依赖于所有以前的哈希 ID,并且包括时间戳——是保证哈希 ID 唯一性的一部分。 (其余的取决于散列函数。)但这也是为什么散列必须向后指向的原因:子提交知道其父母是谁,因为在创建子时父母确实存在;但是当父母被创建时,父母并不知道他们的孩子会是谁。

合并操作,减去大部分重要细节

现在让我们看看git merge 在一般情况下的作用,而不是如何 它是如何做到的(这很重要)。一般来说,合并的目的是结合两个不同的“工作线”。通常,但并非总是如此,这些工作将由不同的人或团体完成。然而,在 Git 中,此时此合并中涉及的所有 提交 都必须在您自己的存储库中,无论谁实际进行提交。因此,在这一点上,我们必须有一个很像我们上面绘制的图表,但是让我们在这里重复一遍,在运行命令之后:

git checkout featureA

你现在有了这个:

        F--G   <-- featureA (HEAD)
       /
A--B--C--D--E   <-- master

这与我们之前绘制的图表相同,尽管我将featureA 放在了顶部。图几乎不考虑顺序,而且链接——在我们的例子中是弧,因为这是一个有向图——是有弹性的。如果需要,我们可以移动每个图形顶点,以使绘图工作得更好。一个重要的区别是在分支名称后添加(HEAD)。这就是 Git 如何知道我们在哪个分支上。您在 Git 中的 HEAD 通常附加到某个分支; git checkout <em>branch</em> 为您的工作准备索引和工作树,并将HEAD 附加到给定的branch。

现在你运行git merge master。 (顺便说一句,避免将 from 主 合并到 功能分支通常更明智,但要达到目标,您最终需要了解所有关于 git rebase 的知识。变基是一旦我们了解所有细节,就会变得相当复杂。这是因为 rebase 通过 copying 提交工作,就像通过 git cherry-pick 一样,cherry-pick 依赖于 Git 中的合并机制;所以每个单独的副本是一种小型合并!)

合并由两部分组成:合并动作,或者我喜欢称之为作为动词的合并;然后进行merge commit,它使用单词merge作为形容词(修饰提交),或作为名词:a merge表示合并提交,但使用单词 merge 来表示特定类型的提交。

首先,我们执行动词合并,因此我们执行合并操作。这包括找到合并基础,在本例中是提交C,然后以某种方式将自C 上feature 以来完成的所有事情与自C 上master 上完成的所有事情结合起来。在内部,Git 有效地运行了两个 git diff 命令,比较 C 和 G 以找出 我们 在 HEAD 中对 --ours 做了什么,并比较 C 和 E了解他们在--theirs master 上做了什么。然后,它将这些更改合并为一项重大更改,以应用于 C 以获得我们希望提交的结果。

git merge 的最后一步是进行 a 合并,即作为名词的合并。合并或合并提交只是具有至少两个(通常正好是两个)父提交的提交。两个父项是 current (HEAD) 的提交,加上你在运行 git merge 时命名的提交。在这种情况下,将G 提交为HEAD,再加上提交E,因为master 指向E。所以我们得到了一个新的提交,这使得我们当前的分支名称 featureA 也提前了。您将此提交标记为 T,所以我们将其作为 T 在这里:

        F--G--T   <-- featureA (HEAD)
       /     /
A--B--C--D--E   <-- master

这是你犯的第一个错误:

当master 合并到featureA 时,featureA 现在看起来像featureA (A-&gt;B-&gt;C-&gt;D-&gt;E-&gt;F-&gt;G-&gt;T),其中-&gt;D-&gt;E 属于 master,T 表示合并后的提交。

不再可能正确地绘制图形,同时将其绘制为单线,因为提交T,即合并,具有两个父母。两个父级之一(实际上是第一个)是G,另一个父级是E。 (Git 对 first parent 的记录最终很重要,或者更确切地说,如果您愿意,它可能很重要。我们更复杂的二维图形绘制并不能很好地表示 first-vs-second,但是这就是为什么我将featureA 放在顶行而不是底行。)

git push 操作

当您运行 git push 时,您的 Git 会连接到其他 Git。两个 Git 都有自己的一组分支;两者都有自己的提交存储库(和其他 Git 对象)。

我们真的不需要知道他们有什么提交,除了说明目的。 (我可以从你的 Git 打印的 ahead of 消息中找出其中的一些,但我必须做一些猜测。)让我们假设它们看起来像这样:

A--B--C   <-- master

我们从我们的端运行git push origin featureA:featureA,所以我们的Git,现在有:

        F--G--T   <-- featureA (HEAD)
       /     /
A--B--C--D--E   <-- master

调用他们的 Git。我们的 Git 现在可以用什么名称询问他们有哪些提交,他们会说“我有 master 识别提交 C”。然后,我们的 Git 会枚举我们特别要求推送的提交,即提交 T。然后我们的 Git 知道他们有提交 C,因此1他们也有提交 B 和 A — 提交哈希 ID 在任何地方都是唯一的,不只是在我们自己的存储库中!对于我们给他们提交T,然后,我们必须给他们完成导致T的图表所需的一切:我们必须给他们G ,因为那是T 的父母之一,但我们还必须给他们E,因为那是T 的另一个父母。我们必须给他们F,因为G 需要F。我们必须给他们D,因为E 需要D。然后我们就完成了,因为F 和D 需要C,但他们已经有了C。

既然我们的 Git 已经为他们的 Git 提供了完成图表所需的所有提交,我们的 Git 会向他们的 Git 发送一个请求,格式如下:请将您的 featureA 设置为指向提交 T。 em> 这个featureA 是git push 命令中featureA:featureA 对的second 部分。如果您省略了 :featureA 第二部分,则它是隐含的。

第一部分,在冒号之前,决定了我们的 Git 发送哪些提交,所以你可以——有时——同样说git push origin HEAD:featureA,例如。但是,隐含部分是使用提交部分计算的,因此如果您改为运行git push origin master,我们将向他们发送提交E,因此也发送D,但不是 F、G 和 T;然后我们会要求他们设置他们的master。请注意,您也可以一次推送多个内容:

git push origin master featureA

将发送所有提交——仅一次;无需发送两次D 和E——然后提出两个礼貌请求:请将您的master 设置为指向E,并请设置您的featureA指向T。

是否允许这些设置取决于其他 Git。他们会告诉您他们是接受还是拒绝您的每个请求。如果他们同意设置他们的featureA,你的Git 现在将在你自己的存储库中,记住他们的featureA 指向提交T。如果您不要求他们设置他们的master,那么您的origin/master 将不会发生任何事情(您对他们的master 的记忆)。2

作为一般规则,当您的 Git 要求他们的 Git 设置他们现有的分支名称之一时,他们的 Git 会检查这样做是否会保留他们已经拥有的所有提交,并且仅 添加新的提交到链的末尾。如果是这样,则该操作是快进,并且是允许的。如果不是,则该操作是非快进,默认情况下会被拒绝。例如,考虑一下如果他们确实有一个名字featureA会发生什么:

A--B--C   <-- master
       \
        F--I--J   <-- featureA

我们向他们发送了我们的D-E 和F-G 和T,他们拿走了我们的:他们的I 和J 将无法再访问;他们的存储库将:

            I--J   [no name]
           /
          F--G--T   <-- featureA
         /     /
        /_-D--E
       //
A--B--C    <-- master

一旦提交没有名称,该提交可以3垃圾收集并从存储库中删除。所以这最终会丢弃提交 I 和 J,这就是他们默认拒绝它的原因。


1这种推论,即提交意味着在提交之前拥有历史中的所有内容,实际上在浅克隆中是不可用的,其中故意省略了一些历史。但是,推送操作仍然以相同的方式工作。在大多数情况下,Git 实际上使用提供/请求协议,而在其他通信流有限的情况下,它在 DAG 的约束下工作。

2原则上,您的 Git 可以在此处更新其所有分支的内存,只要它们列出了所有分支。但是,假设他们要告诉我们他们的master 确定了提交H。如果我们没有提交H,我们必须首先获得提交H,因为Git 选择从不存储指向我们本地没有的提交的名称。我们使用git fetch 从他们的 Git 中获取他们所有分支和提交的列表,然后获取他们的任何我们没有的提交。因此,git push 仅在收到“OK,I did your request setting”响应时更新名称,即仅更新成功推送的名称。

3为了避免在你自己的存储库中过快地丢弃提交,你的Git有特殊的隐藏名称,Git称之为reflogs,默认情况下将所有您的提交保留至少 30 天。但是,接受 git push 命令的服务器默认禁用 reflogs — 因此,确实会丢失提交的强制推送可能会立即丢失提交。出于各种其他曾经很好的原因,服务器 Gits 也倾向于在完成推送接收后立即运行git gc。 (Git 用于传入提交的新“隔离”区域消除了对 post-receive GC 的需要,但是那里有很多旧服务器。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-24
    • 1970-01-01
    • 1970-01-01
    • 2020-09-08
    • 2015-07-24
    • 1970-01-01
    • 2021-03-14
    相关资源
    最近更新 更多