将分支 A 中仅存在于分支 B 中的文件合并到分支 B

merge files from branch A in branch B that only exist in branch B

我有以下情况:

我们在我们信任的 AS400 上使用了一个软件,对于这个软件,我们在它自己的库 (A) 中修改了一些源代码。其余未修改的源位于不同的库 (B) 中。目前还没有版本控制。

现在我们购买软件的公司会定期更新源代码。我们在不同的库 (C) 中收到这些更新的源代码。

现在我们只是手动检查更新,看看库 C 中是否有任何也在库 A 中,如果有我们手动进入并手动合并它们。

现在我想用 git 自动执行此操作。我知道如何将库转换为分支,但我不知道合并它们的最佳方式是什么。

我需要将分支 C 合并到分支 A,但只有来自分支 C 的源也存在于分支 C。因为其余的将合并到分支 B。

示例:

Branch A: file1, file2, file4

Branch C: file1, file2, file3

(Branch B: file1, file2, file3, file4)

我需要 merge file1file2 来自 branch C 进入 分支 A忽略 file3file4。任何查看 file1 和 file2 是两个分支中都存在的方法也将开始。

我希望有人有解决方案或者至少可以指出正确的方向!我已经寻找了一些解决方案,但遗憾的是没有一种适合我的具体情况。

编辑:从那以后我想出了如何在两个库中获取源代码:

SELECT TABLE_PARTITION FROM syspstat WHERE TABLE_NAME ='QRPGLESRC' and 
TABLE_SCHEMA = 'LIBRARY-A'
and TABLE_PARTITION in (
SELECT TABLE_PARTITION FROM syspstat WHERE TABLE_NAME ='QRPGLESRC' and 
TABLE_SCHEMA = 'LIBRARY-B'); 

TABLE_NAME = 'QRPGLESRC' 可以替换为您想要查看的文件,或者如果您想要比较所有文件则直接省略。

Git 没有做您希望它做的事情。

一旦您理解了 Git 的作用,您就会明白如何实现您的目标。您至少需要一个额外的步骤(为此您可以 使用 Git,但您不会做“Git 事情”,您只会正在使用一些命令)。

首先,你需要避免过多地考虑“分支”。 Git 不是 关于 “分支”; Git 大约 提交 。 Git 中的 branch 这个词被严重过度使用,以至于几乎变得毫无意义,尽管有两三个(或四六个左右)的东西——不幸的是,不同的事物——人类在说“分支”这个词时的意思(另见What exactly do we mean by "branch"?)。不过,最初最好完全避免使用“分支”一词。

所以:Git 大约是 提交 。但是,究竟什么是提交? Git 的回答是声明一些关于 关于 在这里提交的事情:

  • 每个提交都有一个唯一的编号,通常表示为一个大丑hexadecimalhash IDOID(Object ID)。这个数字看起来是随机的(尽管它实际上不是随机的),并且是完全不可预测的。每当您或任何人在 any Git 存储库中进行新提交时,该提交都会获得一个新编号:以前从未使用过的编号。它绝不能被任何 Git 再次用于任何 other 提交,因为该数字现在表示您刚刚进行的提交。1

  • 为了使编号方案有效,任何提交的所有部分都是完全只读的。任何提交的任何部分都不能更改,即使 Git 本身也不能更改。

  • 每个提交存储每个文件的完整快照,及时冻结,作为该文件在您或任何人进行提交时的形式(和存在)。

  • 每个提交还存储一些元数据,或有关此特定提交的信息。元数据包括您的姓名和电子邮件地址等内容。它们包括您提交时的 date-and-time 戳记(这是使未来的哈希 ID 不可预测的部分原因:例如,您必须知道每次未来提交的确切秒数)。而且,对于 Git 自己的操作很重要,每个提交的元数据存储一个 先前提交哈希 ID 的列表

因为哈希 ID 提交,在重要的意义上 - 每个提交都有一个唯一的并且 Git 需要 检索提交的哈希 ID——保留一个 previous 哈希 ID 的列表意味着提交 存储库中的历史。存储库可能只包含提交,在all-commits-in-this-repository的大数据库中,加上提交objects所需的任何支持objects(有一堆)。

不过,让人们记住哈希 ID 并不是一个好主意。人类在哈希 ID 方面可怕。人类想要——需要——名字。幸运的是,计算机擅长存储充满 name-to-hash-ID 映射的文件。因此 Git 提供了第二个独立的数据库,其中 Git 存储了这些名称。这些是分支名称、标记名称、remote-tracking 名称和许多其他类型的名称,每个名称都存储一个哈希 ID。

对于分支名称,一个存储的哈希 ID 定义为 最新 提交“在”或“在”该分支. Git 将其称为 提示提交 。由于每个提交都存储一个以前提交的列表——大多数提交只存储该列表中的一个条目——这为 Git 提供了一种向后 .

工作的方法

1简单的理论(特别是 pigeonhole principle) will tell you instantly that this scheme is doomed to fail someday, no matter how big the hash IDs are. The birthday paradox or birthday problem 告诉我们世界末日实际上比你想象的更近。但出于实际目的,它可能已经足够远了未来我们都会死去,而不关心最终的 Git 世界末日。不过,Git 正在从 SHA-1 转移到更大的 SHA-256,这将它推迟了数十亿年, 大概。


分支名称如何找到提交

让我们绘制一个非常简单的存储库,其中只有三个提交和一个分支名称。我们所做的 第一次 提交有一些丑陋的哈希 ID,但我们将其称为“提交 A”。它的 previous 提交哈希 ID 列表必然是空的,因为在我们进行 A 没有其他提交 。所以它单独存在:

A

然后,使用提交 A 作为起点,我们将创建一个新的提交(快照+元数据)B。 Git 将安排提交 B 将提交 A 的哈希 ID 记住为 B 的 (ingle) parent。我们说B指向A,这样画进去:

A <-B

使用提交 B,我们进行新的提交 C。 Git 将在 C 的元数据中存储提交 B 的哈希 ID,以便 C 向后指向 B:

A <-B <-C

一直以来,每次我们进行提交时,Git 都会将该提交的哈希 ID 填充到 当前分支名称 main 中。所以最初我们有:

A   <-- main

然后,一旦我们生成B,我们就有:

A--B   <-- main

(我懒得在提交之间绘制 backwards-pointing 箭头:稍后您就会明白为什么)。请注意,名称 main 现在包含 B 的哈希 ID,因此 main 指向 B.

然后我们提交 C 并得到:

A--B--C   <-- main

这个时候,我们来做一个新的分支名称。这个新名称可能是 develop。正如我们之前看到的,每个分支名称只包含一个哈希 ID,并且只有三个提交,因此新名称 develop 必须 指向 恰好一个 这三个提交。我们可以选择这三个中的任何一个,但通常(并且最容易)我们选择我们现在实际使用的提交:commit C。我们得到:

A--B--C   <-- develop, main

我们现在需要一种方法来记住我们正在使用哪个 name 来查找提交 C。目前,这并不重要,但一旦我们做出新的提交,它就会很重要。所以我们将 Git 附加特殊名称 HEAD,像这样全部大写,恰好一个分支名称:

A--B--C   <-- develop, main (HEAD)

这表明我们使用的分支名称main。该分支名称指向提交 C,因此我们使用提交 C.

我们现在 运行 git switch developgit checkout develop (两者做同样的事情)并得到:

A--B--C   <-- develop (HEAD), main

我们仍在使用提交 C,但现在我们通过 name develop.

当我们进行新的提交时,Git 将制作快照和元数据,使元数据指向现有的提交 C,因此我们的新提交——我们将其称为 D——看起来像这样:

A--B--C
       \
        D

(注意:我没有很好的箭头字体来绘制 D 指向 C 以便它在所有 Whosebug 视图中都能很好地显示)。与往常一样,Git 更新我们的 当前分支名称 以指向新的提交。该名称是附加了特殊名称 HEAD 的名称,即 develop,所以现在我们有:

A--B--C   <-- main
       \
        D   <-- develop (HEAD)

现在,如果我们 git switch maingit checkout main,我们就会看到 工作树 是如何工作的。

简要了解您的工作树

出于 space 的原因,我不会在这里详细介绍(并且将完全跳过索引 / staging-area,这对使用 Git 至关重要),但是请记住,我们提到过每次提交的所有部分都是完全 read-only。不仅如此,而且每次提交的 in 文件都以特殊的 Git-only 格式存储,只有 Git 可以阅读。文件的内容在提交内和提交之间 de-duplicated,并且被压缩,有时是高度压缩,使得大多数程序难以 使用 这些文件。

当然,我们首先使用Git来存储文件,如果我们无法取回我们的文件out[= Git 的 518=],这会使 Git 无用。所以当我们检出一个提交时——用git switchswitch是新动词,因为Git 2.23)或git checkout(旧的)—Git 将从该提交中 提取 ,所有 frozen-for-all-time 文件。

Git 需要一个地方来放置这些文件,那个地方就是你的 working tree。 Git 将首先从工作树中删除由于某些 other 提交而存在的文件。然后它将 un-archive 来自所选提交的文件。这会为您提供 普通 文件——而不是一些特殊的 Git-only 文件——所有 程序都可以读取,也可以 write/overwrite。 这些文件不在 Git 中,尽管它们很可能刚刚从 Git 中 出来 。请务必记住,您的工作树文件不是 Git 的文件:它们是 您的。但是 Git 在您告诉它这样做时删除并替换它们。

(Git的索引在这一点上变得非常重要,因为它决定了哪些文件被跟踪并且控制着整个remove-and-replace的细节Git 对您的文件所做的事情。但我们再次跳过它。)

正在合并

随着我们使用 Git,一次又一次地提交,我们在 提交图 中积累了越来越多的提交。例如,我们可能有一个 main-line “分支”(这个词很难避免),由一系列以散列 ID H 结尾的提交组成,如下所示:

...--G--H   <-- main

但可能会有更多的“分支”继续来自 H,例如:

          I--J   <-- br1 (HEAD)
         /
...--G--H
         \
          K--L   <-- br2

重要的是要意识到通过 H 的提交在 所有 分支上。此处的提交 I-J 被绘制为 br1K-L br2,但通过 H 的提交在两个分支上(并且在 main 上,如果名称 main 存在并指向 H)。

请注意,无论是否有 name 指向它们,提交都是独立存在的。将 main 之类的名称指向 H 仅仅为我们提供了一种 快速查找提交 的方法,而无需知道其哈希 ID。有一个像 main 指向 H 这样的名字也给了我们一种找到 更早的 提交的方法,因为 Git 可以跟随 backwards-pointing 箭头从 HG,然后从 GF 等等。 Git 不能 向前HIJ,即使两者IJ 向后指向 H,因为 H 不知道 IJ 的哈希 ID。这些提交是在 创建提交 H 之后创建的,并且提交 H 在创建后一直被冻结。

所以,最后,我们需要 两个名字 br1br2 只是为了 找到 提交“之后”的四个提交 H。正如您将看到的那样,这将在片刻之间发生变化。不过,我们目前 不需要 名称 main,因为 br1br2 足以找到 H。如果我们想要 直接 方法专门通过名称查找提交 H,我们只 需要 名称 main main.

无论如何,我们的图表显示我们现在“在”分支 br1 上,因此使用提交 J。这些是我们将在工作树中看到的文件:提交 J.2 中的文件这对 Git 不是那么重要; Git 重要的是我们当前的 分支名称 br1 并且我们当前的 commit 是 commit J.

我们现在运行:

git merge br2

Git 使用名称 br2 来定位该分支的 tip commit(注意歧义词;tip commit 并不含糊,意味着提交 L)。所以 Git 现在有两个提交:JL.

合并程序现在通过历史向后读取,逐个提交,以找到最佳共享提交。这是在两个分支上的一些提交(又是模棱两可的词:注意含义如何不断从“分支名称”转变为“提示提交”再到“提交集”),并且这比两个分支上的任何其他提交都“更好”。3 合并命令将此 best-shared-commit 称为 合并基础 。在我们的示例中,根据我们绘制图形的方式,合并基础是显而易见的:它是提交 H,两个分支上的最后一次提交。

此时git merge的操作方式是运行两个git diff命令。我们还没有讨论 git diff,所以让我们简单地说一下,在这种情况下,它将 将合并基础提交中的每个文件 每个文件进行比较在两个分支提示提交之一中 。对于某些文件,该文件存在于两个提交中并且在两个提交中是相同的。对于某些文件,它存在于两个提交中但不同。对于某些文件,该文件可能根本不存在于 base 或 tip 提交中。 (如果文件在任何一次提交中都不存在,当然没有什么可比较的。)

请注意 Git 直接跳过 所有 中间 提交。它不会将合并基础 HI 进行比较。它只比较 HJ。因此,无论 HJ 中的文件是什么,都会对这些文件进行比较。有些匹配,有些不匹配,对于那些 不匹配 的文件,diff 会找出这些文件中的不同之处。

对于HJ中不的文件,Git将该文件称为 已删除 。对于不在 Hdo 存在于 J 中的文件,Git 调用该文件 added (新创建的)。我们将忽略 重命名 文件的棘手情况。4

同时,作为一个单独的步骤,Git 将 H 中的内容与 L 中的内容进行比较,完全跳过 K。同样,有些文件会同时存在于两者中,有些文件会在两者中匹配,而有些则不会。可能会添加或删除一些文件。

合并 操作包括获取这两个单独的差异——这些将快照转化为更改的比较——以及合并更改。组合“左侧,H-vs-J,没有触及文件 F,右侧确实触及 fle F" Git 只保留文件的 right-side version。要合并 "左侧更改了一个文件和右侧没有",Git 只保留左侧版本。如果双方都更改了文件,Git 必须合并单独的更改并应用 both(合并)对文件的 merge base 版本的更改。

对于我所说的 high-level 操作——例如“左侧删除文件 3”或“右侧创建 all-new 文件 4”——Git 将其纳入建议,如果对方没动文件,或没有文件,Git保留删除或all-new文件。但是,如果一方删除文件,另一方更改same-named文件,Git在这里声明高级别冲突。

当双方从一个共同的合并基础文件开始并对其进行不同的更改,并且这两个更改以不兼容的方式重叠时,Git 声明一个低级别该文件存在冲突。

如果发生冲突,会导致合并操作中途停止。你,程序员,需要收拾残局。对于 low level 冲突,Git 将在将两组更改放入文件时将其自己的 best-effort 写入工作树,并将标记带有“冲突标记”的冲突地区。您的工作是提出 正确的 组合文件,使用您喜欢的任何方式。

对于 high-level 冲突,Git 会告诉你它做了什么(例如“我给你留下了文件 F 的任何版本”)并且,再一次,你的工作是想出正确的文件名(以及该文件是否应该存在,如果存在,它的内容应该是什么)。然后,您使用 git addgit rmgit mv 和任何或所有其他 Git-index-affecting 操作来调整 Git 的 index 来存储 正确的 合并结果,无论你决定是什么。

如果没有冲突,或者在你解决了所有的冲突之后运行git merge --continue,Git现在会创建一个新的犯罪。新提交与任何其他提交一样进行:新提交 M 将具有 parent 现有提交 J,因为 br1 指向 J 之前Git 进行新的提交。记住,我们是从这个开始的:

          I--J   <-- br1 (HEAD)
         /
...--G--H
         \
          K--L   <-- br2

新提交 M 的特别之处在于它是一个 合并提交 ,因此它有一个 第二 parent。它不仅链接回现有提交 J,还链接回现有提交 L。这就是我们在 git merge 命令行中说 git merge br2 时命名的提交。所以M指向两者JL,然后Git东西M 的新哈希 ID,不管是什么,像往常一样进入 当前分支名称

          I--J
         /    \
...--G--H      M   <-- br1 (HEAD)
         \    /
          K--L   <-- br2

我们现在在 br1 上有一个合并提交作为 br1 的新提示提交。提交 M 允许 Git 向后工作到 both J and L,所以现在删除名称br2是安全的,如果我们没有理由让名称br2直接找到提交L

          I--J
         /    \
...--G--H      M   <-- br1 (HEAD)
         \    /
          K--L

分支 br2 现在不见了,但是 提交 仍然存在,我们仍然可以找到它们。由于 Git 完全是关于 提交 — 分支名称就在那里使我们可以 找到 提交 — 所有这里很好

请注意 M 中的 快照 与任何其他快照一样。它说如果你 检出 提交 M,Git 的文件应该安装到你的工作树中(和 Git 的索引) 是 M 中的快照。 M 唯一特别的是它有两个 parent。


2由于我们的工作树 我们计算机上普通文件系统上的一个普通文件夹,我们可以创建文件 Git 没听说过,and/or 没有 从当前提交中出来。这些文件也将在我们的工作树中,但 Git 不会使用或接触它们。 Git 调用这些 未跟踪的 文件。准确地说,这些文件不在 Git 的索引中(又是那个讨厌的索引)。

3从技术上讲,Git 在这里所做的是 运行 在提交图上设置 lowest common ancestor (LCA) algorithm。这可以产生多个提交节点,Git 在这种情况下所做的事情变得复杂,但是对于我们在这里绘制的图表,在实践中的大多数情况下,我们大多只得到一个提交。

4Git 不会 store 重命名操作,所以它必须 find 它们来自提交内容。这相当于一种猜测。 Git 执行此操作的过程是可调整的,因此您可以 运行 diff 使用告诉它以不同方式进行猜测的参数。你可以把这个传递给git merge,因此合并可以根据您在 运行 git merge 时指定的参数找到或找不到重命名。 Git 的重命名检测并不完美,它有时需要这些类型的提示(有时它会完全失败);这是 Git 的合并代码总是可以进行一些改进的地方。


回到你的愿望

I would need to merge file1 and file2 from branch C into branch A. Ignoring file3 and file4. Any way to see that file1 and file2 are the ones that are present in both branches would be start too.

如您所见,这并没有真正正确地转化为 Git-ese。文件不在 分支 中。他们在提交.

但是,我们可以使用 Git 的工具来检查某些分支名称(例如 branch-Cbranch-A) 并查看这些提交中存在哪些文件。然后我们可以在某些分支名称上进行一个或多个 new 提交——也许不是 branch-A branch-C,因为这些新的提交将成为我们在创建它们时所处的任何分支的新提示——我们在其中 删除了 我们不想要的文件 Git来看看。

现在的问题是,当我们 运行 git merge 时,我们会得到一些提交作为 合并基础 ,Git 将执行两个 git diff 命令。我们可以通过进行新的提交来控制分支提示中的文件集,但是 Git 将选择的合并基础取决于 graph——历史,因为由现有提交记录。

幸运的是,我们知道 git merge 的工作方式是将 base 文件与 tip 提交进行比较文件。如果我们确保我们 delete file3file4 来自任何有问题的提示提交,Git要么根本看不到它们——它们之前不在基础中,现在不在提示中——或者将它们视为已删除:它们现在和曾经在基础中,但不在两个提示中。所以 Git 要么无事可做,要么将“删除文件 3”与“删除文件 3”或“删除文件 3”与“什么都不做”结合起来。

(类似地,如果file3存在于base中,并且在两个提示中但在两个提示中不同但我们不希望Git 影响文件,我们可以进行新的 tip 提交,其中文件的 base 副本现在与 tip 副本或多个副本相同。文件现在将出现,而不是不存在,在最终的合并快照中,但它将与基本副本匹配。或者我们可以选择任一尖端副本并将其设为另一个 new-tip-copy。这些不是您描述的情况,但是知道 git merge 是如何工作的,我们就知道如果出现 的情况我们需要做什么。)

由于 merge-base-ness 的属性(如脚注 3 中链接的 LCA 算法),在一个或两个现有提示提交之后进行新的提示提交不会更改所选的合并基础。所以我们可以 运行:

git merge-base --all

在两个现有的提示提交上找到合并基础提交的哈希 ID。5然后您可以在合并基础提交上使用 Git 工具来查看什么那里有文件。


5如果这吐出两个或更多哈希ID,问题就变得更复杂了。递归合并将首先 将这些提交 git merge 合并,以获得用作合并基础的快照。 “解决”策略将在 apparent 处随机选择 one 并使用它。两者都不是特别好,但是您可以将它们收集起来并 运行 git merge-base 在这些上弄清楚合并将做什么。或者你可以只 运行 git merge --no-commit 让 Git 做它的事情,然后手动计算细节。不过,出于 space 的原因,我们不会在这里讨论这个问题。


您需要的工具

除了 already-mentioned git merge-base 命令(与 --all 一起使用),您还需要 git ls-tree -r。在任何提交上使用它来获取存储在提交的文件名列表。

要查找任何给定提交对所共有的文件——需要确定您希望 Git 省略哪些文件——使用像 comm 这样的程序,或者编写您自己的程序。请注意,git ls-tree -r 的输出已经排序,因此该程序很容易编写。

请记住,git merge 三个 提交一起使用,而不是两个。而且,作为 ,总有 git merge-file,它执行单个文件的合并。它需要相同的三个输入:

  • 两个提示版本,“我们的”和“他们的”,以及
  • 基本版本

要从特定的提交中提取这些文件,请考虑使用 git restore(将文件放入您的工作树),然后随时重命名每个文件,或使用 git show:

git show a123456:path/to/file.ext

从特定提交(冒号左侧)中提取指定的冻结文件(按路径名,冒号右侧)。

您的 AS400 源存储在一个或多个源物理文件的成员中?

如果是这样,我会将涉及的所有源代码复制到 PC 上的一个文件夹中。然后在 PC 上设置第二个文件夹,其中包含供应商的源代码。设置 Github 存储库并使用 Git 使这两个文件夹保持同步。

然后有一个独立的过程,在 AS400 上的源成员和 PC 上包含源代码 repo 的文件夹之间复制源代码。

I would need to merge branch C into branch A, but only the sources from branch C that are also present in branch C. As the rest will be merged into branch B.

好的,您有一个供应商基础系列。我不知道 AS400,所以我将使用 posix 约定来回答,并将 AS400-ese 的翻译留给其他人。

工作一:制作 Git 供应商掉落的历史记录。工作二:添加您对这些掉落的修改/选择的历史记录。工作三:自动将新的供应商掉落与您正在进行的更改合并。

那么让我们从头开始吧。你在所谓的 C 中得到供应商掉落。每个掉落都是一个完整的快照,这很方便,因为这正是 Git 喜欢的。

如果您还有旧快照,您可以非常、非常 轻松地从它们构建 Git 历史记录:

git init history; cd $_
git checkout -b vendor
git --work-tree=/path/to/oldest/snapshot add .; git commit -m C0
git --work-tree=/path/to/next-oldest/snapshot add .; git commit -m C1
git --work-tree=/path/to/next-newer/snapshot add .; git commit -m C2
git --work-tree=/path/to/even-newer/snapshot add .; git commit -m C3

等等。现在你有一个 vendor-branch 系列:

C0---C1---C2---C3      vendor

选择提交消息以使用 Git 的 message-search 语法非常方便地引用单个提交,:/C0 is Git's name for "the newest (reachable) commit with a C0 in its message"..

接下来:您现有的 A 库系列基于这些掉落,您需要一个步骤来在扩展时记录额外的祖先。

git checkout -b Alib :/C0
git --work-tree=/path/to/C0-based-A/snapshot add .; git commit -m A0    
git merge -s ours --no-commit :/C1    # record a merge from C1
git --work-tree=/path/to/C1-based-A/snapshot add .; git commit -m A1    

等等 -s ours --no-commit 合并以设置添加的父级 link,以及内容和第一个父级的 add-and-commit 快照舞蹈。

到目前为止的示例序列将帮助您

   A0---A1   Alib
  /    /
C0---C1      vendor

使用您已有的确切内容。再做一次Blib得到

   A0---A1    Alib
  /    /
C0---C1       vendor
  \    \
   B0---B1    Blib

并扩展您实际拥有快照的距离。


这是你的基准。您现在有一个 Git 历史记录,所有您仍然有记录的东西。


要合并新的供应商投放并传播您的更改,导入真的很简单:

git checkout vendor
git --work-tree=/path/to/new/vendor/snapshot add .; git commit -m CN

但 Git 并未真正针对您正在执行的 on-the-fly 子集进行设置。它是可行的,并且可以高效地执行,但是需要更深入地了解正在发生的事情。

您只想传播对您已经在 A(或其他)分支中跟踪的“供应商”C 分支上文件的更改。 Git 处理提交,好处是 绝对其他一切都是 window 敷料 ,并且 你可以编造任何你喜欢的提交 .

Git 保留一个索引¹,指向工作树中有趣路径的回购内容。 git checkout 加载索引和工作树,git add 添加到 repo 的对象数据库并更新索引条目,git commit 写入任何新树(又名目录内容,“树节点”将是more-accurate 他们的名字,但我们很忙,“树”在上下文中起作用)对于 currently-indexed 内容,这是更新舞蹈。

git checkout Alib
( git ls-tree -r vendor; git ls-tree -r @ ) \
| sort -sk4 | uniq -df3 \
| git update-index --index-info

将使用(指向)vendor-tip 版本的 Alib tip 中已经存在的文件加载索引。您希望将供应商的临时 更改 与您的更改合并到这些文件中,因此您希望祖先显示这些文件的 previously-merged 供应商版本——您已经拥有这些文件。

selected=$(git commit-tree -p @^2 -m - `git write-tree`)
git reset -q
git merge -s ours --no-commit vendor
git cherry-pick -n $selected
git commit

我通过制作一堆 Git 版本的历史记录然后仅将名称包含字母 'w' 的文件作为我的子集进行一些相当随意的更改来进行抽烟测试:

snaptemp=`mktemp -d`
newhistory=`mktemp -d`
git init $newhistory; cd $_

git -C ~/src/git archive v0.99  | tar Cx $snaptemp
git --work-tree=$snaptemp add .; git commit -m C0
git -C ~/src/git archive v1.0.0 | tar Cx $snaptemp
git --work-tree=$snaptemp add .; git commit -m C1
rm -rf $snaptemp; mkdir $snaptemp
git -C ~/src/git archive v2.0.0 | tar Cx $snaptemp
git --work-tree=$snaptemp add .; git commit -m C2
rm -rf $snaptemp; mkdir $snaptemp
git -C ~/src/git archive origin/master | tar Cx $snaptemp
git --work-tree=$snaptemp add .; git commit -m C3


rm -rf $snaptemp; mkdir $snaptemp
git checkout -b Alib :/C0
git archive :/C0 :*w* | tar Cx $snaptemp
cd $snaptemp; find -type f -exec sed -si 5a'Hi, I changed this file' {} +
cd -; git --work-tree=$snaptemp add .; git commit -m A0

git show --diff-filter=d :/A0    # just to check

然后将补丁向前推进一步(基本上是为了让我们得到一个符合上述配方的 merged-from-vendor Alib 提交)

git checkout Alib # already there but hey
# the references here are specific to this step in the smoketest 
( git ls-tree -r :/C1; git ls-tree -r @ ) \
| sort -sk4 | uniq -df3 \
| git update-index --index-info
selected=$(git commit-tree -p @^ -m - `git write-tree`)
git reset -q
git merge -s ours --no-commit :/C1
git cherry-pick -n $selected
git commit    

现在已经设置好进一步的步骤可以按照上面正文中给出的方法进行

git checkout Alib
( git ls-tree -r vendor; git ls-tree -r @ ) \
| sort -sk4 | uniq -df3 \
| git update-index --index-inf
selected=$(git commit-tree -p @^2 -m - `git write-tree`)
git reset -q
git merge -s ours --no-commit vendor
git cherry-pick -n $selected
git commit

¹ 您可以保留任意数量的索引文件,我本可以使用 GIT_INDEX_FILE 环境变量以更少的命令和更少的工作树流失来完成此操作,但这是更深入的一步仙境