是否可以从父项目中修补Git中的子模块?

时间:2017-11-29 21:46:25

标签: git git-submodules

我有一个包含子模块main的项目foo。对于此特定项目,我想对仅适用于此特定项目foo的{​​{1}}进行少量更改。

main

一个常见的解决方案是转到我的子模块,在名为main/ + .git + main.c + lib/ | + bar.c + foo/ # My `foo` submodule + .git + config.h # The file I want to patch from `main` + ... 的新分支上进行提交Applied patch for main,然后推送它。不幸的是,这是一种非常糟糕的方法,因为我对main-project进行的更改只对foo有意义。此外,当我将main更新为最新版本时,我将不得不挑选补丁,这会在foo的历史中引入很多噪音。

另一个解决方案是在foo上有一个真正的补丁文件,该文件在构建之前应用于main。不幸的是,由于这会修改子模块内容,并且我将在foo上更改未提交,因此它也不是一个好的解决方案。

理想的解决方案是使用Git跟踪我的补丁,但是在顶层(例如直接在foo上,而不是在main上)。从理论上讲,可以在指向子模块位置的Git foo上添加blob

tree

有了这个想法,将在blob <sha> main.c tree <sha> lib/ commit <sha> foo blob <sha> foo/config.h 上跟踪属于config.h的修补文件foo

怎么可能这样做?

4 个答案:

答案 0 :(得分:6)

在供应商历史记录上携带特定于项目的补丁的最简单方法是克隆供应商存储库,将更改作为项目分支进行携带,并将该克隆广告作为.gitmodules上游发布。

这使您对供应商上游项目所做的更改完全正常,git clone --recurse-submodules yourproject可以正常工作,您对子模块的更改可以被推回到您对项目子模块的上游(子模块存储库的origin远程),一切正常。

唯一的补充信息是,将项目的子模块版本更新为最新的供应商代码,某些人必须从(上游)供应商仓库中获取并合并

...但是那也很普通:从供应商仓库中获取并合并的方法就是这样做。 git remote add vendor u://r/l; git fetch vendor; git merge vendor/master。或者,如果您希望合并基准,请执行此操作。完成此操作后,照常将结果推送到子模块的origin,即项目的版本。

答案 1 :(得分:4)

  

理想的解决方案是使用Git跟踪我的补丁,但在顶层   (例如,直接位于main而不是foo上)。从理论上讲,   在Git树上添加一个指向子模块位置的blob:

即使这样做可行,我个人还是觉得很困惑。除非这是 本机实现,并包括易于使用的面向用户的命令 了解和管理,我会远离它。

替代1:补丁

  

是否有更优雅/精简的方式在git中打补丁   子模块,而不是可接受的答案?

如果您想完全避免与子模块混淆,我建议 复制/检出其他地方的工作树并仅使用 在构建期间。这样,子模块始终是“干净的”(也许 从main的角度来看,“不可变”),您只需要担心它 在构建目录中。

简化的构建过程示例:

cd main
mkdir -p build
cp -R foo/ build/
cp myconfig.patch build/
cd build
patch <myconfig.patch
make

请注意,这仅生成foo,并且main的生成过程不需要 除了必须指向build/而不是foo/之外,还需要进行更改。

如果您不打算自己修改foo,而是将其保留为“原始”, 您也可以将其变成一个裸仓库并使用 GIT_WORK_TREE="$PWD/build" git checkout HEAD而非cp,因此它是 仅在构建期间签出。这类似于 makepkg(8)(至少以我对AUR的经验)按顺序进行 以避免修改原始来源($source数组与$srcdir)。它 还从构建本身中拆分了源检索(prepare()build())。 另请参见PKGBUILD(5)Creating packages。 在您的情况下,还涉及开发和IDE,因此可能会比较棘手 如果要一次检查原始文件和构建文件。

专家

  • 源与构建文件分开
  • main不影响foo
  • 不依赖git /使其仅是构建自动化问题
  • 只需要补丁文件

缺点

  • 需要使补丁文件保持更新(与更改基准相比)
  • 需要更改构建过程

如果您的补丁很小和/或非常针对main,我会选择这个。

P.S。:可以更进一步并跟踪foo的版本 直接在构建过程中使用,而不是使用子模块:

foo移至一个目录,然后在构建过程中:

cd build
GIT_DIR='../../foo/.git' git checkout "$myrev"
patch <myconfig.patch
make

替代2:单独的分支

  

此外,当我将foo更新到最新版本时,我将不得不选择   补丁也给foo的历史带来了很大的干扰。

您不必真正挑选它,只需将更改保留在其中即可。 而不是您的分支,并偶尔合并 master

我个人会避免这种情况,除非您的更改更为重要 比由于保持同步而产生的噪音(即:合并和冲突)。 我发现合并提交非常不透明,尤其是在涉及冲突时, 因为无关或意外的变化更难检测。

您也可以

重新设置您对master的提交。

专家

  • 不需要单独的存储库
  • 将工作树保留在同一位置(无需弄乱您的IDE)

缺点

  • foo的存储库进行不相关的提交(合并时)
  • foo的存储库进行不相关的提交对象污染(重新设置基准时)
  • 您对config.h所做更改的演变的模糊历史记录(重新定基时)

替代3:软叉

  

此外,当我将foo更新到最新版本时,我将不得不选择   补丁也给foo的历史带来了很大的干扰。

     

不幸的是,这是一个非常糟糕的方法,因为我正在对foo进行更改   才是主要的

如果您想更改foo以适合main,但又不想与上游的foo发生冲突, 为什么不创建foo的软叉?如果你不在乎 foo-fork的历史记录,您只需在main-project上进行更改 分支,并通过重新设置使其与foo的{​​{1}}保持同步:

创建叉子:

master

保持同步:

cd foo
git remote add foo-fork 'https://foo-fork.com'
git branch main-project master
git push -u foo-fork main-project

专家

  • 易于与上游同步(例如:与git checkout main-project git pull --rebase foo/master # (resolve the conflicts, if any) git push foo-fork 同步)
  • 将工作树保留在同一位置(无需弄乱您的IDE)

缺点

  • 您对pull --rebase所做更改的演变的模糊历史记录(由于 重新定标)

使用补丁而不是重新定基础的附加好处是您可以保留 补丁程序的历史记录。但是,如果您想让事情保持真正简单的同步, 我想这就是方法。

替代4:硬叉

如果您发现config.h的更改太多/太频繁和/或您也需要修补 很多事情,您最好的选择可能是创建完整的叉子和采摘樱桃 而是他们的更改。

答案 2 :(得分:2)

您可能会发现,使用git子树而不是子模块更有意义。子树将另一个项目复制到本地存储库中的某个位置,而不是将其作为当前存储库中的单独存储库进行管理。

您可以将更改作为主master分支的一部分本地应用,然后定期更新,并从上游合并更改。

答案 3 :(得分:1)

我仍然会选择第二个选项(在main上有一个真正的补丁文件),但我的构建过程适应:

  • 复制子模块中的config.h
  • 应用补丁
  • 构建
  • config.h恢复为原始内容。

这样,我保持子模块状态不变。

OP在评论中添加:

  

但是你的解决方案不能在IDE中运行,Intellisense会感到困惑 -

正确:为此,我会在结账时自动应用补丁,并在检查时通过smudge/clean content filter driver将其删除。 这样,补丁在整个会话期间保持不变,但在任何git status / diff / checkin上都会消失。

虽然这并不理想,但似乎没有一种原生的Git方法可以解决这个问题。