Git 子分支删除父分支的更新

Git child branch removes updates of parent branch

好的,我觉得 git 有一个奇怪的问题( 使用 BitBucket)。

我有以下(例子)

现在在child-branch我做了很多返工,但在同一时期我也修复了一些bug,修复了来自另一个子分支的pull requests在development-branch。现在,当我从 child-branchdevelopment-branch 发出拉取请求时,它想要删除对development-branch。但是当我想在 child-branch 中合并 development-branch 时,对 development-branch没有合并到child-branch.

我是不是在流程中做错了什么?有没有办法在不手动检查每个文件的差异的情况下解决这个问题?

TL;DR

分支名称与您想象的不同。

鉴于你想做什么,你可能想要 rebase-oriented work-flow,如 。请记住,rebase 意味着 将一些提交复制到 new-and-improved 提交,然后移动分支名称以使用 new-and-improved 提交,放弃现在的 old-and-lousy 提交。这意味着 每个使用重新设置分支的人 必须准备好放弃旧的提交。

Branches,或更准确地说,分支 names,没有 parent/child 关系。分支名称只是标签,附加到(或指向)特定的 commits.

提交,但是,确实有parent/child关系。特别是,用于进行 new 提交的命令序列如下所示:

git checkout somebranch
[edit various files]
git add file1.ext file2.ext
git commit

git checkout 步骤意味着 select 命名分支,通过这样做,select 提交分支名称 selects。此 复制该提交的内容 — 在 Git 中,它是您所有文件的完整快照,存储在特殊的 read-only、Git-only,压缩格式——进入 index aka staging areawork-tree工作树.1

编辑步骤当然适用于 work-tree 中的那些文件。

git add 步骤将更新后的 work-tree 文件——您已进行更改的地方——复制回 索引 / staging-area,最后一步 git commit 从索引中的副本创建一个新快照,其中包含文件的 frozen-for-all-time 副本。您没有git add覆盖的任何文件仍然与您之前git checkout的文件相同,因此新快照包含所有没有改变,任何更新的文件都改变了,视情况而定。

但是,新提交是 previously-labeled 提交的 child。之前的提交没有改变——没有任何东西,甚至 Git 本身,一旦提交就可以改变它——但它现在有一个 child 提交。刚刚完成的 child 提交记录了其 parent 提交的原始哈希 ID。所以链接只有一种方式,向后,从 child 到 parent。由于 child 是一个新的提交,它获得了一个新的、唯一的哈希 ID。

git commit 最后的关键步骤是 将新的提交哈希 ID 写入分支名称 。也就是说,标签不再指向之前的 parent 提交,现在指向 child 提交。

这意味着我们从一系列提交开始,每个提交都有一个唯一(又大又丑)的哈希 ID,我将在这里用一个大写字母替换它:

... <-F <-G <-H   <-- develop

名称 develop 当前 select 提交 H.

为此,您添加一个新名称 feature 也 select 提交 H:

...--F--G--H   <-- develop, feature

我们现在需要一种方法来记住您正在使用的分支名称。为此,我们将特殊名称 HEAD 附加到一个分支。如果您 git checkout develop,我们将姓名 HEAD 附加到 develop:

...--F--G--H   <-- develop (HEAD), feature

如果你git checkout feature,我们得到:

...--F--G--H   <-- develop, feature (HEAD)

无论哪种方式,您都使用提交H,但您是通过附加到/指向提交[=30=的两个名称之一来完成的]. (同时提交 H 指向提交 G,因为它的 parent,并且 G 指向 F,依此类推。)

现在您修改 work-tree 中的一些文件,git add 将它们复制回索引 / staging-area,git commit 进行新提交 I。如果您使用 feature:

,会发生以下情况
...--F--G--H   <-- develop
            \
             I   <-- feature (HEAD)

名称 HEAD 仍附加到名称 feature,但 feature 现在指向新提交 I。新提交 I 指向提交 H——feature 刚才指向的位置——因此 feature 包含通过并包括 Idevelop 包含通过并包括 H 的提交。请注意大多数提交仍然在两个分支上。

如果您将提交添加到 develop,它们只会添加到 develop:

...--F--G--H--K   <-- develop (HEAD)
            \
             I--J   <-- feature

名称 developfeature 之间没有 parent/child 关系。它们只是 标签 ,标识特定的提交。

这就是 Git 中分支和标签名称的工作方式:它们标识提交。分支名和标签名的区别是:

  • 分支名称 随时间移动。事实上,它们会自动移动!标记名称永远不能移动。2

  • 分支名称本地于一个特定的存储库。其他存储库可以看到它们,但是当您从 获得提交 时,您的 Git 将复制他们的 branch 名称,例如 master,到你的remote-tracking名字,比如origin/master。标签名称很多ore global:如果他们——无论他们是谁——有一个 v1.2,并且你从他们那里得到你的提交,你的 Git 可能会创建你的 v1.2 来匹配。3

  • 当然,分支名称是分支名称,标签名称是标签名称: 有一堆较小的 knock-on 效果,但以上两点是重要的区别。


1work-tree 包含实际文件,采用常规普通格式,您和计算机上的所有其他 non-Git 程序都可以使用。 Git 未使用这些实际文件。它使用压缩的 read-only、Git-only 提交文件,并使用索引/临时区域进行 new 提交,如上所示。

2可以强制移动标签,也可以删除标签然后重新创建标签。但是,其他软件可能希望标签 not 移动。例如,Go 模块系统可以缓存标签的哈希 ID,如果您稍后移动标签,不会 更新。移动标签通常是一个坏主意:如果可以的话,请避免它。

3这一切都在你的掌控之中,这里的默认规则有些复杂,但如果你不做任何特殊的事情,它们就等于你得到了他们的标签。


使用变基

假设你有这个:

...--F--G--H--K   <-- develop
            \
             I--J   <-- feature (HEAD)

但你想要这个:

                ?--?   <-- feature (HEAD)
               /
...--F--G--H--K   <-- develop

不能更改现有提交。没有什么可以。现有提交 IJ 卡在原处。但是你 可以 复制 现有的提交到 new-and-improved 版本。特别是,您可以将提交 I 复制到一个新的改进的提交 - 我们将其称为 I' 以指示它是 I 的副本,尽管在 Git 中它只是得到一些又大又丑的 random-looking 哈希 ID,它与 I 的哈希 ID 没有任何关系。 II' 之间的区别是:

  • I的parent是H,但是I'的parent会是K.
  • I的快照和I'的快照可能不匹配,但是通过比较H得到的差异 -vs-I 将是 same 作为 difference 你通过比较 K-vs-I'.

I复制到I'后,我们希望Git将J复制到J'。结果如下所示:

                I'-J'
               /
...--F--G--H--K   <-- develop
            \
             I--J

请注意,我已经删除了标签 feature 并完全省略了名称 HEAD,此时,我们已准备好移动 feature 并制作 HEAD附新,移feature。执行这个 "build new commits" 的过程实际上使用了 Git 所谓的 分离的 HEAD 所以现在,实际情况是:

                I'-J'  <-- HEAD
               /
...--F--G--H--K   <-- develop
            \
             I--J   <-- feature

请注意,特殊名称 HEAD 没有 附加到任何分支名称。这就是为什么这是一个 分离的 HEAD。同时名称 feature 仍然 select 现有提交 J

git rebase 命令的最后一幕,即完成变基,是将名称 feature 从其现有提交 (J) 中剥离,并使其指向与HEAD 指向 (J'):

                I'-J'  <-- feature, HEAD
               /
...--F--G--H--K   <-- develop
            \
             I--J   [abandoned]

移动标签后,将 re-attaches HEAD 变基到(移动的)标签,使一切看起来都像我们想要的那样。而且,由于 Git finds 提交的方式是从 labels 开始并向后工作,我们不能 see 提交 IJ 了。我们看到的是:

                I'-J'  <-- feature (HEAD)
               /
...--F--G--H--K   <-- develop

如果我们不记得提交 IJ 的哈希 ID,我们会认为 Git 以某种方式移动了旧提交。它没有——旧的提交仍然存在,每个拥有存储库克隆和旧提交的人,仍然有旧的提交。不过我们只看到我们的

正如 Nikos C. 指出的那样,如果您有一个使用现有提交 I-J 的活动拉取请求,您可能需要使用 git push --force 来更新拉取请求。你必须得到任何 Git 存储库持有他们的提交副本 IJ 放弃他们的提交支持新的和改进的 I'-J' 链。说服其他 Git 从旧提交切换到新提交的精确方法取决于 其他 Git 设置的控件。 Git集线器允许简单的 git push --force 来完成任务。