在单独的 git 分支之间仅同步某些文件扩展名并排除其他文件扩展名的最佳方法是什么?

What is the optimal way to only sync certain file extensions and exclude other file extensions between separate git branches?

给定 3 个分支,分别是 master、b1 和 b2。 master 分支只关心 *.txt 文件。它需要忽略其他一切。 分支 b1 只需要 master 包含的 *.h、*.c、*.cpp 文件,而忽略其他所有内容。 分支 b2 也只需要包含 master 包含的那些 *.jpg、*.png、*.html、*.css 等。忽略其他所有内容。

简而言之,master分支包含所有分支共有的信息。示例用例:分支 b1 用于生成输出文件以供分支 b2 使用,但两者都包含一些与 master 共享的信息。

那么,在 master、b1 和 b2 之间只同步那些公共文件,并让每个分支只在其分支中包含某些文件扩展名并忽略它不需要的所有其他内容的最佳方法是什么?

我还研究了具有单独 git 存储库或子模块或子树的替代方案,但目录结构或嵌套模式几乎没有造成困难。有没有更好的办法解决这个问题?

让我从这个开始,因为它可能更有用:

Is there a better way to solve this problem?

从理论上讲,您完全可以不用 master 分支。有三个分支,none 其中 final-assembled-results。在 Git 之外进行组装。如果需要,创建一个“孤立分支”(或使用标签)来记录 assembled 结果,或者将 assembled 结果保存在一个完全不同的存储库中。但是这些都会造成你说的那些小困难

出了什么问题

Git 根本无法按照您想要的方式工作:您不能(无论如何有用)“关心”某些文件并让其他“uncared-about”文件与“关心”切换基于分支。那是因为“分支”,在你使用这个词的意义上,不存在

现在,这是一个强有力的声明,需要证明。显然,分支确实存在。问题出在branch这个词的意思上。它有太多的含义,人们只是在它们之间转换,而没有意识到他们正在这样做,这给他们带来了麻烦。 (另请参见 What exactly do we mean by "branch"?)因此,让我们通过使用 Git 真正使用的内容来避免该术语:提交哈希 ID。

当你 运行:

git checkout br2

你告诉 Git 做两件事:

  • 保存名称br2以备将来使用;
  • 将名称 br2 转换为 提交哈希 ID ,并提取(包括“关心”)所有文件的 快照 来自该提交。

第二个步骤是现在真正重要的步骤:第一步只需要稍后,当您运行git commit制作new 提交,或其他一些需要名称的 Git 命令(例如 git branchgit statusgit rebase)。

除了一个例外——您在尚未 运行 git checkout 的新克隆中看到这一点——Git 现在总是有一些提交被签出 。你的 git checkout 告诉 Git: 扫除我们现在拥有的那个,并给我一些其他的提交作为 checked-out 提交 .

假设现在,您已签出 br1,即现在提交 b100。稍后,name br1 可能意味着一些 other 提交,但现在它意味着那个。你 运行 git checkout br2,它告诉 Git 从提交 b100 切换到提交 b200 因为那是 name br2 表示 现在 .1

好的,没什么大不了的,对吧?我们正在从提交 b100 移动到提交 b200。提交 b100 包含 *.h 文件,并且 完全忽略 *.jpg 文件。所以 Git 在我们有 b100 的时候“关心” *.h 文件。这些文件是 tracked,这意味着它们在(单个)索引中。不过,我们正在从 b100 转移到 b200,其中有 *.jpg 个文件, 省略 *.h 个文件。 Git 必须将 *.jpg 文件 复制到 其索引中,并 删除 *.h 文件 它的索引,这意味着它也必须从你的work-tree中删除*.h文件。

到目前为止,一切进展顺利:您得到了您想要的。但是现在你想得到 master 和 assemble 这些片段。名称 master 表示其他提交,目前可能 a123

无论您如何到达 master,从 br1(现在 b100)或 br2b200)现在,您没有 所有 *.h *.jpg 文件。你只能得到一套或另一套。这里的根本问题是“关心”的发生是因为文件在 Git 的 index 中。在 .gitignore 文件中列出文件,这是防止它们进入 进入 Git 索引的方法,只有在它们不存在时才有用—当你切换到 文件的提交时,Git 将它们放入 Git 的索引中,无论 .gitignore 文件中有什么。当您切换到 忽略 文件的提交时,Git 从 Git 的索引中删除它们,无论.gitignore 文件中的内容。

索引的内容反映了您检出的提交。每个提交都有该提交中每个文件的完整快照。该快照最终出现在 Git 的索引中。除非您更改它们——使用 git addgit rm,或者通过另一种 git checkout 替换它们,例如——这些文件将进入 接下来 提交。

最后,当您使用git merge合并工作时,Git:

  • 找到合并基础提交;
  • 将两个分支提示提交与此合并基础进行比较;和
  • 使用它来确定要放入新提交的内容。

与任何提交一样,新提交具有 所有 f 的快照les: 在 git merge 时 Git 的索引中的所有文件都进行了合并提交,这些文件是上述合并过程的结果。合并提交与任何其他提交相同:它们具有快照和元数据。唯一让它们与众不同的地方——让它们合并提交——是它们的元数据中列出了两个(或更多)父提交哈希 ID。

这些互锁行为阻碍了:要么 master 实际上 确实 拥有所有文件,在这种情况下,其他分支名称找到的其他提交也需要拥有所有文件,或者 master 没有 any 文件,在这种情况下,其他分支可以像这样独占,但你不能将它们合并回来into master,因为 Git 将找到的共同提交将作为合并基础,将导致它们 add 文件 进入 master 的新提交——现在 master 拥有所有文件!如果您在返回分支时删除它们,这次合并将删除这些文件。

最终,Git 就是 提交 提交 决定一切!提交 snapshots-plus-metadata。 branch name 所做的只是找到一个特定的提交:某个链上的最后一个。可以从 多个分支名称 到达提交,并且许多或大多数提交同时在多个分支上。因此 name 与提交中的文件无关:当多个名称找到该提交时,它确实 can't


1提交哈希 ID 映射的分支名称 更改,这就是分支在 Git 中的增长方式。 Git 是为了添加新的提交而构建的,所以名称更改的正常方式是它现在意味着一个更新的提交,通过提交图,回到旧的提交——以及更多的提交。另见 Think Like (a) Git