是否可以从父项目中修补 Git 中的子模块?
Is it possible to patch a submodule in Git from the parent project?
我有一个包含子模块 foo
的项目 main
。对于这个特定的项目,我想对 foo
做一个小改动,它只适用于这个特定的项目 main
。
main/
+ .git
+ main.c
+ lib/
| + bar.c
+ foo/ # My `foo` submodule
+ .git
+ config.h # The file I want to patch from `main`
+ ...
一个常见的解决方案是进入我的子模块,在一个名为 main-project
的新分支上提交 Applied patch for main
,然后推送它。不幸的是,这是一种非常糟糕的方法,因为我对 foo
所做的更改仅对 main
重要。此外,当我将 foo
更新到最新版本时,我将不得不挑选补丁,这在 foo
的历史中引入了很多噪音。
另一个解决方案是在 main
上有一个真正的 补丁 文件,它在构建之前应用于 foo
。不幸的是,因为这会修改子模块的内容,而且我将在 foo
上进行未提交的更改,所以这也不是一个好的解决方案。
理想的解决方案是使用 Git 跟踪我的补丁,但在顶层(例如直接在 main
上,而不是在 foo
上)。从理论上讲,可以在指向子模块位置的 Git tree
上添加一个 blob
:
blob <sha> main.c
tree <sha> lib/
commit <sha> foo
blob <sha> foo/config.h
有了这个想法,属于 foo
的补丁文件 config.h
将在 main
上被跟踪。
这怎么可能?
我仍然会选择第二个选项(在 main 上有一个真正的补丁文件),但将我的构建过程调整为:
- 复制子模块中的
config.h
- 应用补丁
- 建造
- 将
config.h
恢复为原始内容。
那样的话,我保持子模块状态不变。
OP 在评论中添加:
But your solution is not working in a IDE, Intellisense will be confused –
正确:为此,我会在结帐时自动应用补丁,并在检查时将其删除,通过 smudge/clean content filter driver。
这样,补丁在所有会话期间保持不变,但会在任何 git status/diff/checkin.
上消失
虽然这并不理想,而且似乎没有原生的 Git 方法来处理这个问题。
您可能会发现使用 git 子树比使用子模块更有意义。子树将另一个项目复制到本地存储库中的某个位置,而不是将其作为当前存储库中的单独存储库进行管理。
您可以在本地应用您的更改作为主要 master 分支的一部分,然后定期更新,合并来自上游的更改。
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:
虽然可以,但我个人觉得很绕。除非这是
本机实现并包含面向用户的命令,使其易于
理解和管理,我会远离它。
备选方案 1:补丁
Is there are more elegant/streamlined way of doing a patch in a git
submodule, other than the accepted answer?
如果你想完全避免弄乱子模块,我建议
将 over/checking 复制到其他地方的工作树并仅使用它
在构建过程中。这样,子模块总是 "clean" (也许
"immutable",从 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
itself/would 而是保留它 "pristine",
你也可以把它变成一个裸仓库并使用
GIT_WORK_TREE="$PWD/build" git checkout HEAD
而不是 cp
,所以它是
仅在构建期间签出。这类似于
makepkg(8) 按顺序执行(至少根据我对 AUR 的经验)
以避免修改原始来源($source
数组与 $srcdir
)。它
还将源检索与构建本身分开(prepare()
vs build()
)。
另见 PKGBUILD(5) and Creating packages。
在您的情况下,还涉及开发和 IDE,因此可能会更棘手
如果您想同时检查原始文件和构建文件。
优点:
- 源与构建文件分开
main
不影响foo
- 不依赖于git/makes它只是一个构建自动化问题
- 只需要补丁文件
缺点:
- 需要更新补丁文件(相对于变基更改)
- 需要更改构建过程
如果您的补丁很小 and/or 非常针对 main
。
,我会选择这个
P.S.: 可以更进一步跟踪 foo
的版本
如果你想直接在构建过程中而不是使用子模块:
向上移动foo
一个目录,然后在构建过程中:
cd build
GIT_DIR='../../foo/.git' git checkout "$myrev"
patch <myconfig.patch
make
备选方案 2:单独分支
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.
你真的不必挑选它,你可以将更改保留在
你的分支而不是 merge master
每隔一段时间。
就个人而言,我会避免这种情况,除非您的更改更为重要
而不是保持同步所引起的噪音(即:合并和冲突)。
我发现合并提交非常不透明,尤其是在涉及冲突时,
因为 unrelated/accidental 变化更难检测。
变基 你的提交到 master
也是一个选项。
优点:
- 不需要单独的存储库
- 将工作树保持在同一个地方(不需要弄乱你的IDE)
缺点:
- 用不相关的提交污染
foo
的存储库(合并时)
- 用不相关的提交对象污染
foo
的存储库(变基时)
- 你对
config.h
的更改演变的模糊历史(当 rebase 时)
备选方案 3:软分叉
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.
Unfortunately, this is a very bad approach because I am making changes to foo
that only matters to main
如果你想改变 foo
以适应 main
,但又不想与 foo
上游混淆,
为什么不创建 foo
的软分叉?如果你不太在乎
foo-fork
的历史记录,您只需在 main-project
上提交您的更改
分支并通过变基使其与 foo
的 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
优点:
- 易于与上游同步(例如:与
pull --rebase
)
- 将工作树保持在同一个地方(不需要弄乱你的IDE)
缺点:
- 您对
config.h
的更改演变的模糊历史(因为
变基)
使用补丁而不是变基的额外好处是你可以保留
补丁的历史。但是如果你想让事情在同步方面非常简单,
我想就是这样。
备选方案 4:硬分叉
如果您发现 foo
也经常变化 much/too and/or 您也需要打补丁
很多事情,你最好的选择可能是创建一个完整的分支和樱桃采摘
他们的改变。
在供应商历史记录中携带项目特定补丁的最简单方法是克隆供应商存储库,将更改作为项目分支携带,并将该克隆作为 .gitmodules
上游发布。
这使得您对供应商上游项目的更改非常普通,git clone --recurse-submodules yourproject
工作正常,您的子模块更改可以推回到您的项目子模块上游(子模块回购 origin
远程),一切正常。
唯一额外的提示是,要将您项目的子模块版本更新为最新的供应商代码,必须有人从(更上游的)供应商存储库中获取和合并
...但这也很普通:从供应商存储库中获取和合并的方法是,这样做。 git remote add vendor u://r/l; git fetch vendor; git merge vendor/master
。或者,如果您更喜欢变基而不是合并,请执行此操作。完成后,像往常一样将结果推送到子模块的 origin
、项目的版本。
我有一个包含子模块 foo
的项目 main
。对于这个特定的项目,我想对 foo
做一个小改动,它只适用于这个特定的项目 main
。
main/
+ .git
+ main.c
+ lib/
| + bar.c
+ foo/ # My `foo` submodule
+ .git
+ config.h # The file I want to patch from `main`
+ ...
一个常见的解决方案是进入我的子模块,在一个名为 main-project
的新分支上提交 Applied patch for main
,然后推送它。不幸的是,这是一种非常糟糕的方法,因为我对 foo
所做的更改仅对 main
重要。此外,当我将 foo
更新到最新版本时,我将不得不挑选补丁,这在 foo
的历史中引入了很多噪音。
另一个解决方案是在 main
上有一个真正的 补丁 文件,它在构建之前应用于 foo
。不幸的是,因为这会修改子模块的内容,而且我将在 foo
上进行未提交的更改,所以这也不是一个好的解决方案。
理想的解决方案是使用 Git 跟踪我的补丁,但在顶层(例如直接在 main
上,而不是在 foo
上)。从理论上讲,可以在指向子模块位置的 Git tree
上添加一个 blob
:
blob <sha> main.c
tree <sha> lib/
commit <sha> foo
blob <sha> foo/config.h
有了这个想法,属于 foo
的补丁文件 config.h
将在 main
上被跟踪。
这怎么可能?
我仍然会选择第二个选项(在 main 上有一个真正的补丁文件),但将我的构建过程调整为:
- 复制子模块中的
config.h
- 应用补丁
- 建造
- 将
config.h
恢复为原始内容。
那样的话,我保持子模块状态不变。
OP 在评论中添加:
But your solution is not working in a IDE, Intellisense will be confused –
正确:为此,我会在结帐时自动应用补丁,并在检查时将其删除,通过 smudge/clean content filter driver。
这样,补丁在所有会话期间保持不变,但会在任何 git status/diff/checkin.
虽然这并不理想,而且似乎没有原生的 Git 方法来处理这个问题。
您可能会发现使用 git 子树比使用子模块更有意义。子树将另一个项目复制到本地存储库中的某个位置,而不是将其作为当前存储库中的单独存储库进行管理。
您可以在本地应用您的更改作为主要 master 分支的一部分,然后定期更新,合并来自上游的更改。
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:
虽然可以,但我个人觉得很绕。除非这是 本机实现并包含面向用户的命令,使其易于 理解和管理,我会远离它。
备选方案 1:补丁
Is there are more elegant/streamlined way of doing a patch in a git submodule, other than the accepted answer?
如果你想完全避免弄乱子模块,我建议
将 over/checking 复制到其他地方的工作树并仅使用它
在构建过程中。这样,子模块总是 "clean" (也许
"immutable",从 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
itself/would 而是保留它 "pristine",
你也可以把它变成一个裸仓库并使用
GIT_WORK_TREE="$PWD/build" git checkout HEAD
而不是 cp
,所以它是
仅在构建期间签出。这类似于
makepkg(8) 按顺序执行(至少根据我对 AUR 的经验)
以避免修改原始来源($source
数组与 $srcdir
)。它
还将源检索与构建本身分开(prepare()
vs build()
)。
另见 PKGBUILD(5) and Creating packages。
在您的情况下,还涉及开发和 IDE,因此可能会更棘手
如果您想同时检查原始文件和构建文件。
优点:
- 源与构建文件分开
main
不影响foo
- 不依赖于git/makes它只是一个构建自动化问题
- 只需要补丁文件
缺点:
- 需要更新补丁文件(相对于变基更改)
- 需要更改构建过程
如果您的补丁很小 and/or 非常针对 main
。
P.S.: 可以更进一步跟踪 foo
的版本
如果你想直接在构建过程中而不是使用子模块:
向上移动foo
一个目录,然后在构建过程中:
cd build
GIT_DIR='../../foo/.git' git checkout "$myrev"
patch <myconfig.patch
make
备选方案 2:单独分支
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.
你真的不必挑选它,你可以将更改保留在
你的分支而不是 merge master
每隔一段时间。
就个人而言,我会避免这种情况,除非您的更改更为重要 而不是保持同步所引起的噪音(即:合并和冲突)。 我发现合并提交非常不透明,尤其是在涉及冲突时, 因为 unrelated/accidental 变化更难检测。
变基 你的提交到 master
也是一个选项。
优点:
- 不需要单独的存储库
- 将工作树保持在同一个地方(不需要弄乱你的IDE)
缺点:
- 用不相关的提交污染
foo
的存储库(合并时) - 用不相关的提交对象污染
foo
的存储库(变基时) - 你对
config.h
的更改演变的模糊历史(当 rebase 时)
备选方案 3:软分叉
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.
Unfortunately, this is a very bad approach because I am making changes to foo that only matters to main
如果你想改变 foo
以适应 main
,但又不想与 foo
上游混淆,
为什么不创建 foo
的软分叉?如果你不太在乎
foo-fork
的历史记录,您只需在 main-project
上提交您的更改
分支并通过变基使其与 foo
的 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
优点:
- 易于与上游同步(例如:与
pull --rebase
) - 将工作树保持在同一个地方(不需要弄乱你的IDE)
缺点:
- 您对
config.h
的更改演变的模糊历史(因为 变基)
使用补丁而不是变基的额外好处是你可以保留 补丁的历史。但是如果你想让事情在同步方面非常简单, 我想就是这样。
备选方案 4:硬分叉
如果您发现 foo
也经常变化 much/too and/or 您也需要打补丁
很多事情,你最好的选择可能是创建一个完整的分支和樱桃采摘
他们的改变。
在供应商历史记录中携带项目特定补丁的最简单方法是克隆供应商存储库,将更改作为项目分支携带,并将该克隆作为 .gitmodules
上游发布。
这使得您对供应商上游项目的更改非常普通,git clone --recurse-submodules yourproject
工作正常,您的子模块更改可以推回到您的项目子模块上游(子模块回购 origin
远程),一切正常。
唯一额外的提示是,要将您项目的子模块版本更新为最新的供应商代码,必须有人从(更上游的)供应商存储库中获取和合并
...但这也很普通:从供应商存储库中获取和合并的方法是,这样做。 git remote add vendor u://r/l; git fetch vendor; git merge vendor/master
。或者,如果您更喜欢变基而不是合并,请执行此操作。完成后,像往常一样将结果推送到子模块的 origin
、项目的版本。