从分支B中的分支A中合并文件,仅存在于分支B中

发布于 2025-01-28 02:53:53 字数 1120 浏览 2 评论 0 原文

我有以下情况:

我们在受信任的AS400上使用软件,对于此软件,我们已经在其自己的库中修改了一些来源(a)。其余未修改的来源在不同的库中(b)。目前尚无版本控制。

现在,我们从定期购买该软件的公司可以更新到来源。我们在不同的库(C)中收到这些更新的资源。

现在,我们只是手工浏览更新,查看库中图中是否有任何更新,以及是否有任何我们手工进入并手工合并的。

现在,我想用git自动化这一点。我知道如何将库转换为分支机构,但我不知道合并它们的最佳方法是什么。

我需要将分支C合并到分支A中,但是只有分支C中也存在的分支C的来源。由于其余部分将合并到分支B中。

示例:

Branch A: file1, file2, file4

Branch C: file1, file2, file3

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

我需要< strong>合并 file1 file2 来自分支C 进入分支a 忽略 file3 file4 。任何方法可以看到file1和file2是两个分支中存在的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'可以用您想查看的文件代替,或者如果您想比较全部,则可以抛弃。

I have the following situation:

We use a software on our trusted AS400, for this software we have modified some sources ourselves in it's own library (A). The rest of the unmodified sources are in a different library (B). There is no version control present yet.

Now the company whom we bought the software from regularly makes updates to the sources. We recieve those updated sources in a different library (C).

Right now we just go through the updates by hand to see if there are any in library C that are also in library A, and if there are any we go in by hand and merge them by hand.

Now I wanted to automate this with git. I know how to convert the libraries into branches, but I don't know what the best way of merging them is.

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.

Example:

Branch A: file1, file2, file4

Branch C: file1, file2, file3

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

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.

I hope somebody has a solution or can atleast point me in the right direction! I've looked for some solutions, but nothing fits my specific case sadly.

EDIT: I have since figured out how to get the sources present in both libraries:

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' can be replaced by either the file you wanna look into or just leave out if you wanna compare all.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

醉梦枕江山 2025-02-04 02:53:53

git不会做您想拥有的事情。

一旦了解了Git 做什么,您就会看到如何实现目标。您至少需要一个额外的步骤(您可以使用 git,但是您不会做“ git的事情”,只需使用一些命令)。

首先,您需要避免过多考虑“分支”。 git不是关于“分支”的; git大约是 consits 。 git中的单词 branch 被严重使用,以至于几乎变得有意义,尽管有两三个(或四个或六个左右)的东西 - 不幸的是,不同的事物 - 人类的意思是当他们说“分支”一词时(另请参见我们到底是什么意思”。分支”?)。不过,最初,最好完全避免“分支”一词。

因此:git大约是 consits 。但是,究竟是什么呢? git的答案是在此处声明一些关于提交的事情:

  • 每个提交都有一个唯一的数字,通常表示为大丑陋 hexadecimal hash ID oid (对象ID)。这个数字是随机的(尽管实际上不是随机的),并且完全不可预测。每当您(或任何人)在任何 git存储库中进行新的提交,该提交将获得一个新数字:一个从未使用过的数字。任何其他 commits commot commot commot now现在都意味着您刚刚制作的commit。

  • 制定编号方案工作,任何提交的所有部分都是完全阅读的。一旦制造,即使是git本身都不会改变任何一部分。

  • 每个提交都会存储每个文件的完整快照,并在该文件(和存在)的表格(和存在)中冻结,该文件在您或任何人当时都有提交。

  • 每个提交还存储一些元数据,或有关此特定提交的信息。元数据包括您的姓名和电子邮件地址。它们包括您完成该提交时的日期和邮票(这是使未来哈希ID无法预测的部分:例如,您必须知道您将在未来进行的确切次数,例如)。而且,重要的是,对于Git自己的操作,每个提案的元数据存储了以前的提交哈希ID 。。

    的列表。

因为从重要意义上讲,哈希iD 的提交,每个提交都有一个独特的提交,并且git 需要 hash iD来检索该提交>上一个 hash ids表示consits 是存储库中的历史记录。存储库可以不仅包含投入,这是在此类重新命令中的大型数据库中,再加上提交对象所需的任何支持对象(其中有很多)。

但是,让人类记住哈希ID是一个坏主意。人类在哈希ID上是可怕的。人类想要 - 需要 - 名称。幸运的是,计算机擅长存储装满名称对锤映射的文件。因此,Git提供了第二个单独的数据库,其中Git存储这些名称。这些是分支名称,标签名称,远程跟踪名称以及许多其他类型的名称,每个名称都完全存储一个哈希ID。

对于分支名称,一个存储的哈希ID被定义为最新 commit中的 ins in”或“ on”该分支。 git称此称为提示提交。由于每个提交都存储了以前的提交的列表,而且大多数提交的列表中只有一个条目,这使GIT可以一种向后工作的方式


1 简单理论(尤其是 pigeonhole princtiple )会告诉你)无论哈希ID多大,该方案都注定要失败。 生日悖论或生日问题告诉我们,末日实际上比您想像的实际上更接近。但是出于实际目的,将来可能已经足够遥远了,我们都会已经死了,并且不关心最终的git世界末日。尽管如此,GIT仍在从SHA-1转移到更大的SHA-256,这可能使数十亿年了。


分支名称如何找到提交,

让我们绘制一个非常简单的存储库,其中只有三个提交和一个分支名称。我们有史以来做出的非常丑陋的哈希ID 非常提交,但我们将其称为“ commit a ”。它的上一个提交哈希ID的列表一定是空的,因为在我们制作 a 时,没有其他提交。因此,它单独坐着:

A

然后,使用COMPL a 作为起点,我们将制作一个新的提交(快照+元数据) b 。 git将安排提交 b 记住提交 a 的哈希ID作为 b 's(单)父母。我们说 b 指向 a ,然后将其绘制出来:

A <-B

使用Commit B ,我们做了一个新提交 c 。 git将存储在 c 的元数据中 b 的哈希ID,因此 c 向后指向 b

A <-B <-C

一直以来,每当我们提交提交时,git都会将提交的哈希ID塞入当前分支名称 main 中。因此,最初我们有:

A   <-- main

然后,一旦我们制作 b ,我们就会有:(

A--B   <-- main

我在提交之间绘制向后指向箭头的懒惰:您会发现为什么)。请注意,名称 main 现在保留 b 的哈希ID,因此 main 指向 b

然后,我们进行提交 c 并获取:

A--B--C   <-- main

此时,让我们做一个新的分支名称。这个新名称可能是开发。正如我们之前看到的那样,每个分支名称完全拥有一个哈希ID,只有三个提交,因此新名称开发 必须 正好一个。我们可以选择这三个中的任何一个,但通常(最容易)我们选择了我们现在实际使用的提交:commit c 。我们得到:

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

我们现在需要一种方法来记住我们正在使用哪个 name 来查找COMMIT c 。目前,这并不重要,但是一旦我们提出一个新的提交,就会很重要。因此,我们将让git附加特殊名称 head ,用这样的所有大写编写,恰好是一个分支名称:

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

这表明我们正在使用的是分支名称我们正在使用的是 >主。该分支名称指向提交 c ,因此我们使用提交 c

现在,我们运行 git Switch开发 git Checkout开发(都做同样的事情)并获得:

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

我们仍在使用Commit c ,但是现在,我们正在通过 name 开发这样做。

当我们提出新的提交时,Git将制作快照和元数据,以使元数据重新指向现有提交 c ,因此我们的新提交 - 我们将调用 d - 看起来像这样:(

A--B--C
       \
        D

注意:我没有一个很好的箭头字体来绘制 d 指向 c ,以便在所有stackoverflow视图中都很好地显示出来)。与往常一样,git更新我们的当前分支名称以指向新提交。该名称是附加特殊名称 head 的名称,即,开发,现在我们有:

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

现在,如果我们 git git switch main git Checkout main ,我们可以查看工作树的工作原理。

出于空间原因,简要介绍您的工作树

,我不会在这里详细介绍(并且将完全跳过索引 /登台区域,这对于使用Git至关重要),但请记住,我们提到的是,我们提到的所有部分每个提交都是完全只读的。这不仅是正确的,而且每个提交中的文件 以一种特殊的,仅git的格式存储,只有 git 才能读取。文件的内容是 de de-de undupipated 在跨提交中,并且被压缩,有时是高度压缩的方式,使大多数程序都难以使用这些文件使用这些文件。

当然,我们首先将git用于 store 文件,如果我们无法将文件返回 of git ,那将使git毫无用处。因此,当我们查看 commit时 - git switch switch> switch 是新动词,因为git 2.23)或 git checkout < /code>(旧一个) - git will 提取,从该提交中,所有冷冻的所有时间文件。

git需要一个放置这些文件的地方,那个地方是您的工作树。 Git将首先从工作树中删除由于某些其他 commit而存在的文件。然后,它将从所选提交的文件中取消构建文件。这给了您普通的文件 - 不仅是一些特殊的git 所有程序都可以读取和写入/覆盖。 这些文件不在git 中,尽管它们很可能只是 。重要的是要记住,您的工作树文件不是git的文件:它们是 yours 。但是git 在告诉它这样做时删除并更换它们。

(在这一点上,Git的索引变得非常重要,因为它确定了哪些文件是 tracked ,并控制了Git与您的文件所做的整个删除和重新倒换事物的详细信息。但是,同样,我们是跳过它。)

当我们与git合作

,在提交后进行提交时,我们积累了越来越多的提交在A commit Graph 中。例如,我们可能有一个主线“分支”(难以避免此词),由一系列的提交组成,以哈希ID h 结尾,例如:

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

但是可能有一些。 进行的更多“分支”

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

h 。 consits ij 在此绘制 br1 kl 唯一em> on br2 ,但是通过 h 在两个分支上进行提交(以及 main ,如果名称 main 存在并指向 h )。

请注意,无论是否指向它们,这些委托就存在于其自己的权利。具有 main 的名称,指向 h 仅给我们一个快速的方法来找到该提交而不知道其哈希ID。具有 main 指向 h 的名称,也使我们有一种找到早期提交的方法,因为git可以从 H to g ,然后 g to f 等等。 git go forwards h to i 或 j ,即使两者都<代码> i 和 j 向后向 h 向后点,因为 h 不知道任何一个 i i < /代码>或 J 。创建了这些提交在创建 commit h 之后,并在创建后,commit h 一直冻结。

因此,最后,我们需要两个名称 br1 br2 此时,只需 find 这四个提交“在”提交 h 。正如您所看到的那样,这种情况会在片刻之内发生变化。不过,我们不需要 name Main 目前,因为 br1 br2 足够 H 。我们只需要 name main 如果我们想拥有 direct 的方法来查找COMMIT H 特别是按名称

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

我们现在运行:

git merge br2

git使用名称 br2 来找到该分支的提示commit (注意模棱两可的单词; tip commit 都不是模棱两可的,表示提交 l )。因此,git现在拥有两个提交: j l

现在,合并程序通过历史记录,提交委员会向后读取,以找到最佳共享commit 。这是一个在两个分支上的提交(再次模棱两可的单词:请注意,含义如何不断从“分支名称”转移到“提示”到“ consits”),这就是” 3 Merge命令称此最佳共享信号为 Merge base 。在我们的示例中,合并基础是根据我们绘制图形的方式很明显的:它是提交 h ,这是两个分支上的最后一个提交。

git merge 在这一点上运行的方式是运行 git diff 命令。我们尚未谈论 git diff ,所以让我们非常简短地说,在这种情况下,它将合并基础commit中的每个文件 两个分支尖端之一之一。对于某些文件,该文件都存在于两个提交中,并且在两个提交中都是相同的。对于某些文件,它存在于两个提交中,但不同。对于某些文件,该文件可能根本不存在于基本或提示提交中。 (如果两个提交中的文件都不存在,那么没有什么可比较的。)

请注意,git 直接跳过所有中间提交。它不将合并基础的 H i 进行比较。它仅将 H J 进行比较。因此,无论 h j 中的任何文件,这些文件都可以比较。有些匹配,有些则不匹配,对于那些不匹配的人,差异可以找到这些文件中有什么不同。

对于 h 中的文件,但不是 j 中的,git调用该文件已删除。对于不在 h 中但 do 中的文件中, j 中,git调用文件添加(新创建) 。我们将忽略重命名文件的棘手情况。 4

同时,作为一个单独的步骤,git将 h 中的内容与 l ,完全跳过 k 。同样,两者都会存在一些文件,而有些文件将在两者中都匹配,而有些文件则不会。某些文件可能会添加或删除。

Merge 操作包括将这两个单独的差异(这些比较将快照变成变化)和结合更改的比较。要组合“左侧, h -vs- j ,没有触摸文件 f ,而右侧确实触摸了文件 f “ git只需保留文件的右侧版本。为了结合“左侧更改文件,右侧没有”,Git只需保留左侧版本即可。如果双方都更改了文件,则GIT必须将单个更改组合起来,并将两个(组合)更改应用于文件的基本版本版本。

对于我所说的高级操作 - 例如“左侧删除的file3”或“右侧创建的all-new file4” - git将其提交建议,如果对方没有触摸文件,或者没有该文件,git保留删除或全新文件。但是,如果一个方面删除文件,另一个方更改了同名文件,则GIT在此处声明A 高级冲突。

当双方都以一个共同的合并基本文件开头并对它做出不同的更改,并且两个更改以不兼容的方式重叠时,GIT在该文件上声明了A 低级别的冲突。

发生冲突,如果发生,则会导致合并操作停在中间。您,程序员必须清理混乱。对于低级别的冲突,git将写给工作树,它自己最能努力将两组更改放入文件中,并将用“冲突标记”标记矛盾的区域。您的工作是使用任何您愿意这样做的方式提出正确的组合文件。

对于高级冲突,git会告诉您它的所作所为(例如,“我给您的版本 f ”,例如),并且您的工作再次是提出正确的文件名称(以及该文件是否应该完全存在,如果是这样,则该文件应该是什么)。然后,您使用 git add git rm git mv 以及任何或所有其他或所有其他或所有其他git-index-rectect-toctect-toctect-thececting operations 索引存储正确的合并结果,无论您决定是什么。

如果存在否冲突,或者在修复所有冲突并运行 git合并之后 - continue ,git现在将成为一个新提交。新提交是像其他任何提交一样做出的:新提交 m 将作为父级现有commit j ,因为 br1 指向<代码> J 在Git制作新提交之前。请记住,我们从此开始:

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

关于新提交 m 的特殊之处在于它是A Merge Commit ,因此它具有A second parent 。它不仅链接到现有的consion j ,还可以链接到现有的commit l 。这就是我们在 git Merge 命令行上命名的提交,当我们说 git Merge br2 。因此, m 指向 j l ,然后git stucks M 的新哈希ID,无论是什么,无论是当前分支名称

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

我们现在在 br1 上有一个合并提交作为新提示 br1 的提交。提交 m 允许Git向后工作至 J l ,因此现在可以安全地删除名称 br2 ,如果我们没有理由拥有name br2 直接查找COMPL L 直接:

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

Branch BR2 现在已经消失了,但是 Consits 仍然可以找到它们。由于git就是关于 consits 的,所以分支名称只是为了使我们有可能找到 consits-在这里都很好。

请注意,快照 m 中就像其他任何快照一样。它说,如果您查看 commit m ,git应该安装到工作树(和git索引)的文件是快照中的文件。 M 。关于 m ,唯一特别的事情是它有两个父母。


2 由于我们的工作树是我们计算机上普通文件系统上的普通文件夹,因此我们可以创建Git从未听说过的文件,和/或Turn 没有从当前的提交中出来。这些文件也将在我们的工作树中,但是Git不会使用或触摸它们。 git调用这些未跟踪的文件。确切地说,这些文件不在GIT的索引中(再次有讨厌的索引)。

3 从技术上讲,Git在这里所做的工作正在运行在提交图上。这可以产生多个提交节点,而Git在这种情况下的作用变得复杂,但是对于我们在这里绘制的图表,在大多数情况下,在实践中,我们大多只是得到一个提交。

4 git不会 store 重命名操作,因此它必须从提交内容中查找。这等于一种猜测。 GIT的执行过程是可调节的,因此您可以使用差异来进行差异,以告诉它以不同的方式进行猜测。您也可以将其传递给 git Merge ,因此合并可以根据您在运行 git Merge 时指定的参数找到或无法找到,将其命名。 Git的重命名检测并不完美,有时需要这些提示(有时它完全失败了);这是Git的合并代码始终可以使用一些改进的领域。


回到你的欲望

我需要合并file1 file2 来自分支C 进入分支A。忽略File3 和<强> file4 。看到File1和file2是两个分支中存在的file1和file2的任何方法。

如您所见,这并不能真正正确地转化为git-ese。文件不在分支中。他们在 consits 中。

但是,我们可以使用GIT的设施来检查某些分支名称的提示提交(例如 branch-c branch-a )和查看这些提交中存在的文件。然后,我们可以在某些分支名称上进行一个或多个 new 提交 - 也许不是 branch-a branch-c ,由于这些新提交将成为我们制作的任何分支机构的新提示 - 在我们已删除的文件中,我们不希望Git查看。

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

幸运的是,我们知道 git Merge 工作的方式就是将 base 文件与 tip commit文件进行比较。如果我们确保我们 delete file3 file4 来自所讨论的任何提示提交的提交,则git要么根本看不到它们 - 他们以前不在基地,现在不在尖端中 - 或者将它们视为已删除:它们曾经是并且在基地中,但并不是两个提示。因此,git要么无事可做,要么将“ delete file3”与“ delete file3”或“ delete file3”或“ delete file3”结合在一起,而“无所事事”。

(同样,如果File3存在于基础中,并且在这两个提示中都不同,但是我们不希望Git to altim formation file ,我们可以在其中进行新的提示提交 base 现在在 tip 副本或副本中相同。匹配基本副本,否则我们可以选择其他情况,而不是您描述的情况。 出现了。

如果情况 t更改所选合并基础。因此,我们可以运行:

git merge-base --all

在两个现有的提示上,要找到合并基础提交的哈希ID。 5 您可以使用Merge Base Commit上的GIT工具来查看那里存在哪些文件。


5 如果这吐出了两个或多个哈希ID,则问题会变得更加复杂。递归合并将与 git merge 首先合并这些提交,以获取快照以用作合并基础。 “解决”策略将明显随机选择其中的一个,并使用它。这两个都不是特别好,但是您可以在其中收集这些并运行 git Merge-base ,以弄清合并将做什么。或者,您只需运行 git合并-No-Commit ,然后让Git做它的事情,然后手动制定详细信息。不过,出于空间原因,我们不会在这里介绍它。


除了已经提到的 git merge-base 命令

(与一起使用)之外,您还需要 git git ls ls-tree -r < /代码>。在任何提交中使用此提交的列表,以获取中存储的文件名称的列表。

要查找任何给定的提交对的文件(需要确定要省略的文件),请使用 COMM 之类的程序,或者自己写自己的程序。请注意,来自 git ls -tree -r 的输出已被排序,因此该程序很容易编写。

请记住, git Merge 可与三个提交一起使用,而不是两个。并且,如 Matt在评论中指出,总是有 git Merge-file ,它执行单个文件的合并。它需要相同的三个输入:

  • 两个提示版本,“我们的”和“他们的”,以及
  • 从特定提交提取这些文件的基本版本

,请考虑使用 git Restore (将文件输入您的工作树),然后在您进行时重命名每个,或使用 git show

git show a123456:path/to/file.ext

从特定的提交中提取指定的冷冻文件(按路径名,在结肠的右侧)(在结肠)。

Git does not do what you are looking to have it do.

Once you understand what Git does do, however, you'll see how to accomplish your goal. You need at least one extra step (for which you may use Git, but you won't be doing a "Git thing", you'll just be using some commands).

First, you need to avoid too much thought about "branches". Git is not about "branches"; Git is about commits. The word branch in Git is badly overused, to the point of becoming almost meaningless, though there are two or three (or four or six or so) things—unfortunately, different things—that humans mean when they say the word "branch" (see also What exactly do we mean by "branch"?). Initially, though, it's best to just avoid the word "branch" entirely.

So: Git is about commits. But what, exactly, is a commit? Git's answer is to declare a few things about commits here:

  • Every commit has a unique number, normally expressed as a big ugly hexadecimal hash ID or OID (Object ID). This number appears random (though it isn't actually random), and is entirely unpredictable. Whenever you, or anyone, make a new commit in any Git repository, that commit gets a new number: one that has never been used before. It must never be used again, by any Git, for any other commit, because that number now means the commit you just made.1

  • To make the numbering scheme work, all parts of any commit are completely readonly. No part of any commit can ever be changed, not even by Git itself, once it's made.

  • Every commit stores a full snapshot of every file, frozen in time, as of the form (and existence) that that file has at the time you, or whoever, make the commit.

  • Every commit also stores some metadata, or information about this particular commit. The metadata include things like your name and email address. They include the date-and-time stamp for when you made the commit (this is part of what makes future hash IDs unpredictable: you have to know the exact second at which you'll make each future commit, for instance). And, importantly for Git's own operation, each commit's metadata stores a list of previous commit hash IDs.

Because the hash ID is the commit, in an important sense—every commit has a unique one and Git needs the hash ID to retrieve the commit—keeping a list of previous hash IDs means that commits are the history in a repository. A repository could consist of nothing but commits, in a big database of all-commits-in-this-repository, plus any supporting objects required by the commit objects (there are a bunch of those).

Making humans memorize hash IDs, though, turns out to be a bad idea. Humans are terrible at hash IDs. Humans want—neednames. Fortunately, computers are good at storing files full of name-to-hash-ID mappings. So Git provides a second, separate database, in which Git stores these names. These are branch names, tag names, remote-tracking names, and numerous other kinds of names, and each name stores exactly one hash ID.

For a branch name, the one stored hash ID is defined as the latest commit "in" or "on" that branch. Git calls this the tip commit. Since each commit stores a list of previous commits—and most commits store just one entry in this list—that gives Git a way to work backwards.


1Simple theory (in particular the 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 tells us that doomsday is actually closer than you might think. But for practical purposes, it's probably sufficiently far in the future that we'll all be dead and not care about the ultimate Git Doomsday. Still, Git is moving from SHA-1 to the larger SHA-256, which puts it off by more billions of years, probably.


How branch names find commits

Let's draw a very simple repository with just three commits and one branch name in it. The very first commit we ever made has some big ugly hash ID, but we'll just call it "commit A". Its list of previous commit hash IDs is necessarily empty, because there are no other commits at the time we make A. So it sits alone:

A

Then, using commit A as a starting point, we'll make a new commit (snapshot+metadata) B. Git will arrange for commit B to remember commit A's hash ID as B's (single) parent. We say that B points to A, and draw it in like this:

A <-B

Using commit B, we make a new commit C. Git will store in C's metadata the hash ID of commit B, so that C points backwards to B:

A <-B <-C

All along, each time we make a commit, Git will stuff that commit's hash ID into the current branch name main. So initially we have:

A   <-- main

Then, once we make B, we have:

A--B   <-- main

(where I've gotten lazy about drawing the backwards-pointing arrows between commits: you'll see why in a moment). Note that the name main holds the hash ID of B now, so that main points to B.

Then we make commit C and get:

A--B--C   <-- main

At this time, let's make a new branch name. This new name might be develop. As we saw earlier, every branch name holds exactly one hash ID, and there are only three commits, so the new name develop must point to exactly one of these three commits. We can pick any of the three, but typically (and most easily) we pick the commit we're actually using right now: commit C. We get:

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

We now need a way to remember which name we are using to find commit C. At the moment, it doesn't matter, but as soon as we make one new commit, it will matter. So we will have Git attach the special name HEAD, written in all uppercase like this, to exactly one branch name:

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

This indicates that the branch name we are using is main. That branch name points to commit C, so we are using commit C.

We now run git switch develop or git checkout develop (both do the same thing) and get:

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

We're still using commit C, but now we are doing so through the name develop.

When we make a new commit, Git will make the snapshot and metadata such that the metadata point back to existing commit C, so our new commit—which we will call D—looks like this:

A--B--C
       \
        D

(note: I don't have a good arrow font to draw D pointing to C so that it shows up well in all StackOverflow views). As always, Git updates our current branch name to point to the new commit. That name is the name to which the special name HEAD is attached, i.e., develop, so now we have:

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

Now, if we git switch main or git checkout main, we get to see how the working tree works.

A brief look at your working tree

For space reasons, I won't go into much detail here (and will skip entirely over the index / staging-area, which is crucial to using Git), but remember that we mentioned that all parts of every commit are completely read-only. Not only is that true, but the files in each commit are stored in a special, Git-only format that only Git can read. The files' contents are de-duplicated within and across commits, and are compressed, sometimes highly compressed, in ways that make it too hard for most programs to use these files.

Of course, we use Git to store files in the first place, and if we could not get our files back out of Git, that would make Git useless. So when we check out a commit—with git switch (switch is the new verb, since Git 2.23) or git checkout (the old one)—Git will extract, from that commit, all of the frozen-for-all-time files.

Git needs a place to put these files, and that place is your working tree. Git will first remove, from the working tree, files that are there because of some other commit. Then it will un-archive the files from the selected commit. That gives you ordinary files—not some special Git-only thing—that all programs can read and also write/overwrite. These files are not in Git though they may well just have come out of Git. It's important to remember that your working tree files are not Git's files: they're yours. But Git will remove and replace them when you tell it to do that.

(Git's index becomes very important at this point, as it determines which files are tracked and controls the details of this whole remove-and-replace thing that Git does with your files. But again, we're skipping it.)

Merging

As we work with Git, making commit after commit, we accumulate more and more commits in a commit graph. For instance, we might have a main-line "branch" (this word is difficult to avoid) consisting of a series of commits ending at one with hash ID H, like this:

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

But there might be some more "branches" that go on from H, e.g., like this:

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

It's important to realize that commits up through H are on all the branches. Commits I-J here are drawn such that they are only on br1, and K-L are only on br2, but commits up through H are on both branches (and on main if the name main exists and points to H).

Note that the commits exist in their own right, whether or not there is a name pointing to them. Having a name like main pointing to H merely gives us a quick way to find that commit without knowing its hash ID. Having a name like main pointing to H gives us a way to find earlier commits, too, because Git can follow the backwards-pointing arrows from H to G, then G to F and so on. Git cannot go forwards from H to I or J, even though both I and J point backwards to H, because H does not know the hash IDs of either I or J. Those commits were created after commit H was created, and commit H was frozen for all time once it was created.

So, in the end, we need both names br1 and br2 at this point just to find the four commits that are "after" commit H. That will change in a moment, as you'll see. We don't need the name main at the moment, though, because either br1 or br2 suffices to find H. We only need the name main if we want to have a direct way to find commit H specifically by the name main.

In any case, our diagram shows us that we are "on" branch br1 right now, and hence using commit J. Those are the files we'll see in our working tree: the files that are in commit J.2 That's not so important to Git; what matters to Git is that our current branch name is br1 and our current commit is commit J.

We now run:

git merge br2

Git uses the name br2 to locate the tip commit of that branch (note ambiguous word; tip commit is not ambiguous though and means commit L). So Git now has hold of two commits: J and L.

The merge program now reads backwards through history, commit by commit, to find the best shared commit. This is some commit that is on both branches (the ambiguous word again: note how the meaning keeps shifting from "branch name" to "tip commit" to "set of commits"), and that is "better" than any other commit also on both branches.3 The merge command calls this best-shared-commit the merge base. In our example, the merge base is obvious based on how we drew the graph: it's commit H, the last commit that is on both branches.

The way git merge operates at this point is to run two git diff commands. We have not talked about git diff, so let's just very briefly say that in this case, it will compare every file in the merge base commit to every file in one of the two branch tip commits. For some files, the file exists in both commits and is identical in both commits. For some files, it exists in both commits but is different. For some files, the file might not exist at all in either the base or the tip commit. (If the file doesn't exist in either commit, there's nothing to compare, of course.)

Note that Git skips right over all the intermediate commits. It does not compare H, the merge base, to I. It compares H to J only. So whatever files are in both H and J, those files get compared. Some match and some don't, and for those that don't match, the diff finds what's different in those files.

For files that were in H but are not in J, Git calls that file deleted. For files that are not in H but do exist in J, Git calls that file added (newly created). We'll ignore the tricky case of renamed files.4

Meanwhile, as a separate step, Git compares what's in H to what's in L, skipping right over K entirely. Again, some files will exist in both, and some files will match in both, and some won't. Some files may be added or deleted.

The merge operation consists of taking these two separate diffs—these comparisons that turn snapshots into changes—and combining the changes. To combine "left side, H-vs-J, didn't touch file F, and right side did touch file F" Git gets to just keep the right-side version of the file. To combine "left side changed a file and right side didn't", Git gets to just keep the left side version. If both sides changed a file, Git has to combine the individual changes and apply both (combined) changes to the merge base version of the file.

For what I call high-level operations—"left side deleted file3" for instance or "right side created all-new file4"—Git takes that into advice, and if the other side didn't touch the file, or does not have the file, Git keeps the deletion or all-new file. However, if one side deletes a file and the other changes the same-named file, Git declares a high level conflict here.

When both sides start with a common merge base file and make different changes to it, and the two changes overlap in incompatible ways, Git declares a low level conflict on that file.

Conflicts, if they occur, cause the merge operation to stop in the middle. You, the programmer, are required to clean up the mess. For low level conflicts, Git will write, to the working tree, its own best-effort at putting both sets of changes into the file, and will mark up the conflicted areas with "conflict markers". Your job is to come up with the correct combined file, using any way you care to do it.

For high-level conflicts, Git will tell you what it did ("I left you version whatever of file F" for instance) and, once again, your job is to come up with the correct file name (and whether the file should exist at all and if so what its contents should be). You then use git add, git rm, git mv, and any or all other Git-index-affecting operations to adjust Git's index to store the correct merge result, whatever you decide that is.

If there are no conflicts, or after you fix all the conflicts and run git merge --continue, Git will now make one new commit. The new commit is made like any other commit is made: new commit M will have as a parent existing commit J, because br1 points to J before Git makes the new commit. Remember, we started with this:

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

What's special about the new commit M is that it is a merge commit, so it has a second parent. It links back not only to existing commit J, but also to existing commit L. That's the commit we named on the git merge command line when we said git merge br2. So M points to both J and L, and then Git stuffs M's new hash ID, whatever that is, into the current branch name as usual:

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

We now have a merge commit on br1 as the new tip commit of br1. Commit M allows Git to work backwards to both J and L, so it is now safe to delete the name br2, if we have no reason to have a name br2 to find commit L directly:

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

Branch br2 is now gone, but the commits remain, and we can still find them. Since Git is all about the commits—the branch names are just there to make it possible for us to find the commits—all is well here.

Note that the snapshot in M is just like any other snapshot. It says that if and when you check out commit M, the files that Git should install into your working tree (and Git's index) are those in the snapshot in M. The only thing special about M is that it has two parents.


2Since our working tree is an ordinary folder on an ordinary file system on our computer, we can create files that Git has never heard of, and/or that did not come out of the current commit. These files will also be in our working tree, but Git won't be using or touching them. Git calls these untracked files. To be precise, these files are not in Git's index (there's that pesky index again).

3Technically, what Git is doing here is running the lowest common ancestor (LCA) algorithm on the commit graph. This can produce more than one commit node, and what Git does in that case gets complicated, but for the graph we've drawn here, and for most cases in practice, we mostly just get one commit.

4Git does not store rename operations, so it has to find them from the commit contents. This amounts to a form of guesswork. Git's process of doing this is adjustable, so you can run diff with arguments that tell it to do its guessing differently. You can pass this to git merge as well, so merges can find, or fail to find, renames based on parameters you specify when you run git merge. Git's rename detection is not perfect and it sometimes needs these kinds of hints (and sometimes it just fails altogether); this is one area where Git's merge code can always use a bit of improvement.


Back to your desires

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.

As you can see, this doesn't really translate properly into Git-ese. Files are not in branches. They are in commits.

We could, however, use Git's facilities to inspect the tip commit of some branch names (such as branch-C and branch-A) and see what files exist in those commits. We could then make one or more new commits, on some branch names—perhaps not branch-A or branch-C, since these new commits are going to be the new tip(s) of whatever branch(es) we are on as we make them—in which we have removed files we don't want Git to see.

The problem now is that when we run git merge, we'll get some commit as the merge base, from which Git will do two git diff commands. We can control the set of files in the branch tip(s) by making new commits, but the merge base that Git will choose depends on the graph—the history, as recorded by the existing commits.

Fortunately, we know that the way git merge works is to compare just the base files to just the tip commit files. If we make sure that we delete file3 and file4 from any tip commit(s) in question that have them that shouldn't, Git will either not see them at all—they're not in the base before, and now not in the tip(s)—or see them as deleted: they are and were in the base but aren't in both tips. So Git will either have nothing to do, or will combine "delete file3" with "delete file3" or "delete file3" with "do nothing".

(Similarly, if file3 exists in the base, and is in both tips but different in both tips but we don't want Git to affect the file, we can make new tip commit(s) in which the base copy of the file is now the same in the tip copy or copies. The file will now be present, not absent, in the final merge snapshot, but it will match the base copy. Or we can pick either tip copy and make that the other new-tip-copy. These aren't cases you described, but knowing how git merge works, we know what we need to do if the case does come up.)

Because of the properties of merge-base-ness (LCA algorithms as linked in footnote 3), making a new tip commit after one or both existing tip commits won't change the chosen merge base. So we can run:

git merge-base --all

on the two existing tip commits to find the merge base commit's hash ID.5 You can then use the Git tools on the merge base commit to see what files exist there.


5If this spits out two or more hash IDs, the problem gets more complicated. Recursive merge will merge these commits with git merge first, to get a snapshot to use as a merge base. The "resolve" strategy will pick one of these at apparent random and use that. Neither is particularly nice, but you can collect these up and run git merge-base on these to figure out what the merge will do. Or you can just run git merge --no-commit and let Git do its thing, and then work out the details manually. We won't cover this here though, again for space reasons.


The tools you need

Besides the already-mentioned git merge-base command (use it with --all), you will need git ls-tree -r. Use this on any commit to get a list of the names of files stored in that commit.

To find the files that are common to any given pair of commit—needed to identify which files you want to have Git omit—use a program like comm, or write your own. Note that the output from git ls-tree -r is already sorted, so this program is easy enough to write.

Remember that git merge works with three commits, not two. And, as matt noted in a comment, there's always git merge-file, which performs a merge of a single file. It needs the same three inputs:

  • the two tip versions, "ours" and "theirs", and
  • a base version

To extract these files from a specific commit, consider using git restore (to get the file into your working tree) and then renaming each as you go, or use git show:

git show a123456:path/to/file.ext

extracts the specified frozen file (by path name, to the right of the colon) from the specific commit (to the left of the colon).

独自唱情﹋歌 2025-02-04 02:53:53

我需要将分支C合并到分支A中,但只有分支C中也存在的分支C的来源。由于其余的将合并到分支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

:现在,您已经有一个供应商 - 分支系列:

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

选择提交消息可以使用GIT的消息搜索语法来引用单个提交,非常方便, :/c0 是git的名称,是“最新(可访问)的最新(可访问)提交的消息> c0 在其消息中”。

下一步:您现有的 库系列基于这些滴滴,您需要一个步骤来记录延长时的额外祖先。

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    

依此类推 - 我们的 - 我们的 - 毫无疑问合并以设置添加的父链接,以及为内容和第一个父母的添加和命令快照舞蹈。

到目前为止,示例序列将为您提供

   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并不是真正为您正在执行的子集设置的设置。这是可行的,并且可以有效地可行,但是需要更深入地了解正在发生的事情。

您只想在A(或其他)分支中已经跟踪的“供应商” C分支上的文件上的更改。 git的作用是提交的,好事是绝对是其他一切都是窗口敷料您可以烹饪任何喜欢的提交。

git保留了索引¹,指示您工作树中有趣的路径的回购内容。 git Checkout 加载索引和工作树, git add 添加到仓库的对象db并更新索引条目, git commit 编写任何新的新内容树(又名目录内容,“树节点”对它们来说是一个更精确的名称,但我们很忙,“ Tree”在上下文中起作用),这是当前索引的内容,这是Update Dance。

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

将在供应商 -TIP版本中加载您的索引,仅在 alib 提示中的文件。您想将供应商的临时更改合并到这些文件中,因此您希望祖先显示这些文件的先前合并供应商版本 - 您已经拥有。

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

。 现在设置的进一步步骤可以遵循上述

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 环境变量更少的命令和更少的工作树搅动,但这是仙境的一步。

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.

Okay, you've got a single vendor base series. I don't know AS400 so I'm going to answer using posix conventions and leave translation to AS400-ese for someone else.

Job one: make a Git history of the vendor drops. Job two: add the history of your modifications of / selections from those drops. Job three: automate incorporating a new vendor drop with the changes you're carrying.

So let's start from scratch. You get vendor drops in what you're calling C. Each drop is a full snapshot, which is convenient since that's exactly what Git likes.

If you still have the old snapshots, you can build a Git history from them very, very easily:

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

and so on. Now you've got a vendor-branch series:

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

with the commit messages chosen to make referring to the individual commits using Git's message-search syntax very convenient, :/C0 is Git's name for "the newest (reachable) commit with a C0 in its message"..

Next up: your existing A library series based off those drops, you need a step to record the additional ancestry when extending.

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    

and so on with -s ours --no-commit merges to set up the added parent link, and the add-and-commit snapshot dance for the content and the first parent.

The example sequence so far will get you

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

with the exact contents you already have. Do it again for Blib to get

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

and extend for however far you actually have snapshots.


And there's your baseline. You now have a Git history for everything you still have records for.


For incorporating a new vendor drop and propagating your changes, the import really is this easy:

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

but Git's not really set up for the on-the-fly subsetting you're doing. It's doable, and doable efficiently, but it's going to need a little deeper dive into what's going on.

You want to propagate only the changes to files on the "vendor" C branch you're already tracking in your A (or whatever) branch. Git works off commits, and the nice thing is absolutely everything else is window dressing, and you can cook up any commit you like.

Git keeps an index¹, pointers to the repo content for interesting paths in your work tree. git checkout loads the index and the work tree, git add adds to the repo's object db and updates the index entries, git commit writes any new trees (aka directory contents, "tree node" would be a more-accurate name for them but we're busy, "tree" works in context) for the currently-indexed content, this is the update dance.

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

will load your index with (pointers to) the vendor-tip versions of only the files already in the Alib tip. You want to merge the vendor's interim changes to those files with your changes, so you want ancestry showing the previously-merged vendor versions of those files -- which you already have.

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

I smoketested this by making a history of a bunch of Git releases and then treating only the files whose names contain the letter 'w' as my subset with some rather arbitrary changes:

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

and then to carry the patches forward one step (basically to get us a merged-from-vendor Alib commit that fits the recipe above)

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    

Now that's set up further steps can follow the recipe given in the body above

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

¹ you can keep as many index files as you want, I could have done this with fewer commands and less work tree churn using the GIT_INDEX_FILE environment variable but that's a step deeper into the land of faerie

最丧也最甜 2025-02-04 02:53:53

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

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

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

your AS400 source is stored in members of one or more source physical files?

If so, I would have all the source code involved copied to a folder on a PC. Then setup a 2nd folder on the PC that contains the source code of the vendor. Setup a Github repo and use Git to keep those two folders in sync.

Then have a standalone process which copies source code between the source members on the AS400 and the folder that contains the source code repo on the PC.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文