Is it possible to patch a submodule in Git from the parent project?

now*_*wox 17 git diff patch build git-submodules

I have a project main that contains a submodule foo. For this particular project, I would like to make a small change to foo that only applies to this particular project main.

main/
  + .git
  + main.c
  + lib/
  |   + bar.c
  + foo/           # My `foo` submodule
      + .git
      + config.h   # The file I want to patch from `main`
      + ...
Run Code Online (Sandbox Code Playgroud)

A common solution would be to go down to my submodule, make a commit Applied patch for main on a new branch called main-project, then push it. Unfortunately, this is a very bad approach because I am making changes to foo that only matters to main. Also, when I update foo to the latest version, I will have to cherry-pick the patch too which introduces a lot of noise in foo's history.

Another solution is to have a real patch file on main that is applied to foo just before the build. Unfortunately as this modifies the submodule content, and I will have uncommitted changed on foo, so it is not a good solution either.

The ideal solution would be to track my patch using Git, but at the top-level (e.g. directly on main, not on foo). Theoretically, it would be possible to add a blob on the Git tree that points into the submodule location:

blob   <sha> main.c
tree   <sha> lib/
commit <sha> foo
blob   <sha> foo/config.h
Run Code Online (Sandbox Code Playgroud)

With this idea, the patched file config.h belonging to foo will be tracked on main.

How is it possible to do it so?

Von*_*onC 8

我仍然会选择第二个选项(在main上有一个真实的补丁文件),但是将构建过程调整为:

  • config.h在子模块中制作的副本
  • 应用补丁
  • 建立
  • 恢复config.h其原始内容。

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

OP在注释中添加:

但是您的解决方案无法在IDE中使用,Intellisense会感到困惑–

正确:为此,我会在结帐时自动应用补丁程序,并在检查时通过污迹/干净的内容过滤器驱动程序将其删除。
这样,补丁将在所有会话期间都保留在原处,但是在任何git status / diff / checkin上都将消失。

但是,这并不是理想的选择,并且似乎没有本机的Git处理方式。

  • @nowox同意。为此,您需要在签出时应用该补丁,并在签入时将其删除:这就是我在此处描述的内容过滤器驱动程序方法:/sf/answers/2822207251/ (2认同)

kel*_*vin 7

理想的解决方案是使用 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
Run Code Online (Sandbox Code Playgroud)

请注意,这只构建foo,并且main除了必须指向build/而不是之外,不需要更改构建过程foo/

如果您不打算修改foo自身/宁愿保持“原始”状态,您也可以将其转换为裸存储库并使用 GIT_WORK_TREE="$PWD/build" git checkout HEAD代替cp,以便仅在构建期间检出。这类似于makepkg(8)如何 做到这一点(至少根据我对 AUR 的经验)以避免修改原始源($source数组 vs $srcdir)。它还从构建本身(prepare()vs build())中分离了源检索。另请参阅PKGBUILD(5)创建包。在您的情况下,还涉及开发和 IDE,因此如果您想同时检查原始文件和构建文件可能会更棘手。

优点

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

缺点

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

如果您的补丁很小和/或非常特定于 main.

PS:可以更进一步跟踪foo如果您想,在构建过程中直接的版本,而不是使用子模块:

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

cd build
GIT_DIR='../../foo/.git' git checkout "$myrev"
patch <myconfig.patch
make
Run Code Online (Sandbox Code Playgroud)

备选方案 2:单独的分支

此外,当我将 foo 更新到最新版本时,我也必须挑选补丁,这在 foo 的历史中引入了很多噪音。

你真的不必挑选它,你可以只保留你的分支中的更改并合并 master每隔一段时间一次。

就个人而言,我会避免这种情况,除非您的更改比保持同步(即:合并和冲突)引起的噪音大得多。我发现合并提交非常不透明,尤其是在涉及冲突时,因为不相关/意外的更改更难检测。

重新提交您的提交master也是一种选择。

优点

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

缺点

  • 污染 foo具有无关提交的存储库(合并时)
  • 污染 foo使用不相关的提交对象的存储库(重新设置基准时)
  • 你的变化演变的模糊历史config.h(当变基时)

备选方案 3:软分叉

此外,当我将 foo 更新到最新版本时,我也必须挑选补丁,这在 foo 的历史中引入了很多噪音。

不幸的是,这是一种非常糟糕的方法,因为我正在对仅对 main 重要的 foo 进行更改

如果你想改变foo以适应main,但又不想惹怒foo上游,为什么不创建一个软分叉foo呢?如果您不太关心 foo-fork的历史记录,您可以在main-project 分支上提交您的更改并通过 rebase使其与foo's保持同步master

创建分叉:

cd foo
git remote add foo-fork 'https://foo-fork.com'
git branch main-project master
git push -u foo-fork main-project
Run Code Online (Sandbox Code Playgroud)

保持同步:

git checkout main-project
git pull --rebase foo/master
# (resolve the conflicts, if any)
git push foo-fork
Run Code Online (Sandbox Code Playgroud)

优点

  • 易于与上游同步(例如:与 pull --rebase
  • 将工作树保持在同一位置(无需弄乱您的 IDE)

缺点

  • 你的变化演变的模糊历史config.h(因为变基)

使用补丁而不是变基的额外好处是您可以保留补丁的历史记录。但是如果你想让事情在同步方面非常简单,我想这就是方法。

备选方案 4:硬分叉

如果您发现foo更改太多/太频繁和/或您需要修补太多东西,您最好的选择可能是创建一个完整的分支并挑选他们的更改。


jth*_*ill 6

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

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

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

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