赞
踩
Git 子仓(Submodule)是 Git 提供的一种功能,用于在一个 Git 仓库(称为主仓库或 superproject)中嵌入另一个 Git 仓库(称为子仓或 submodule)。这种功能在管理大型项目或依赖关系较多的项目时非常有用。
使用 git submodule add
命令可以将一个已存在的 Git 仓库添加为子仓。
$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
默认情况下,子仓会将子项目放到一个与仓库同名的目录中,本例中是 “DbConnector”。 如果你想要放到其他地方,那么可以在命令结尾添加一个不同的路径。
可以注意到新的 .gitmodules
文件。 该配置文件保存了项目 URL 与已经拉取的本地目录之间的映射:
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
如果有多个子仓,该文件中就会有多条记录。 要重点注意的是, 该文件也像 .gitignore
文件一样受到(通过)版本控制。 它会和该项目的其他部分一同被拉取推送 。 这就是克隆该项目的人知道去哪获得子仓的原因。
当克隆一个含有子仓的项目时,默认会包含该子仓目录,但其中还没有任何文件:
$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject/DbConnector/
$ ls
$
其中有 DbConnector
目录,不过并不会直接拉取,目录为空。
为了解决直接克隆无法初始化的问题,必须运行两个命令:
git submodule init
: 用来初始化本地配置文件git submodule update
: 从该项目中抓取所有数据并检出父项目中列出的合适的提交。$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
其实也有更简单的方法,可以将 git submodule init
和 git submodule update
合并运行,可以使用命令:
git submodule update --init
:初始化并拉取当前提交。git submodule update --init --recursive
: 初始化、拉取并检出任何嵌套的子仓 。除了上述方法,还有一种更简单的方式:
git clone --recurse-submodules
:自动初始化并同时更新仓库中的每一个子仓, 包括可能存在的嵌套子仓 。$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
这是子仓更新最简单的方法,可以进入到子仓目录中运行 git fetch
与 git merge
,合并上游分支来更新本地代码。
$ git fetch
From https://github.com/chaconinc/DbConnector
c3f01dc..d0354fc master -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
scripts/connect.sh | 1 +
src/db.c | 1 +
2 files changed, 2 insertions(+)
作为协作者,当有自己的 MainProject
主仓的本地克隆, 只是执行 git pull
获取你新提交的更改还不够:
$ git pull From https://github.com/chaconinc/MainProject fb9093c..0a24cfc master -> origin/master Fetching submodule DbConnector From https://github.com/chaconinc/DbConnector c3f01dc..c87d55d stable -> origin/stable Updating fb9093c..0a24cfc Fast-forward .gitmodules | 2 +- DbConnector | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) $ git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: DbConnector (new commits) Submodules changed but not updated: * DbConnector c87d55d...c3f01dc (4): < catch non-null terminated lines < more robust error handling < more efficient db routine < better connection routine no changes added to commit (use "git add" and/or "git commit -a")
默认情况下,git pull
命令会递归地抓取子仓的更改,如上面第一个命令的输出所示。
然而,它不会 更新 子仓 。这点可通过 git status
命令看到,它会显示子仓“已修改”,且“有新的提交”。
此外,左边的尖括号(<)指出了新的提交, 表示这些提交已在 MainProject 中记录,但尚未在本地的 DbConnector
中检出 。
由于git pull
不能直接更新子仓,为了完成子仓的更新,还需要在 git pull
后运行 git submodule update
。
运行 git submodule update --remote 子仓名
,Git 将会进入对应子仓然后抓取并更新。
$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'
如果希望一次性直接更新所有子仓,可以使用 git submodule update --init --recursive
直接递归拉取所有子仓:
$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
如果想自动化前述过程,直接和主仓的拉取命令结合。那么 可以在主仓的 git pull
命令添加 --recurse-submodules
选项 (从 Git 2.14 开始)。 这会让 Git 在拉取后运行 git submodule update
,将子仓置为正确的状态。
$ git pull --recurse-submodules
如果想让 Git 总是以 --recurse-submodules
拉取,可以通过 git config submodule.recurse true
设置 submodule.recurse
选项, 告诉 Git(>=2.14)总是使用 --recurse-submodules
。
$ git config submodule.recurse true
$ git pull
如上所述,这也会让 Git 为每个拥有 --recurse-submodules
选项的命令(除了 git clone
) 总是递归地在子仓中执行。
这样就可以直接在运行 git pull
拉取主仓的时候,同步更新子仓。
上述 git submodule update --remote
命令默认会假定你想要更新并检出子仓仓库的 master
分支, 不过你也可以设置为想要的其他分支。
例如,你想要 DbConnector 子仓跟踪仓库的 “stable” 分支,那么既可以在 .gitmodules
文件中设置 (这样其他人也可以跟踪它),也可以只在本地的 .git/config
文件中设置。 让我们在 .gitmodules
文件中设置它:
$ git config -f .gitmodules submodule.DbConnector.branch stable
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
27cf5d3..c87d55d stable -> origin/stable
Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687'
如果不用 -f .gitmodules
选项,那么它只会为你做修改。但是在仓库中保留跟踪信息更有意义一些,因为其他人也可以得到同样的效果。
当我们运行 git submodule update
从子仓仓库中抓取修改时, Git 将会获得这些改动并更新子目录中的文件,但是 会将子仓库留在一个称作“游离的 HEAD”的状态 。这意味着没有本地工作分支(例如 “master” )跟踪改动。如果没有工作分支跟踪更改,也就意味着 即便你将更改提交到了子仓,这些更改也很可能会在下次运行 git submodule update
时丢失 。
为了将子仓设置得更容易进入并修改,首先需要做两件事来更新代码:
git submodule update --remote
拉取最新的代码。$ cd DbConnector/ $ git checkout stable Switched to branch 'stable' $ cd .. $ git submodule update --remote remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 4 (delta 2), reused 4 (delta 2) Unpacking objects: 100% (4/4), done. From https://github.com/chaconinc/DbConnector c87d55d..92c7337 stable -> origin/stable Updating c87d55d..92c7337 Fast-forward src/main.c | 1 + 1 file changed, 1 insertion(+) Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea'
如果我们进入 DbConnector 目录,可以发现新的改动已经合并入本地 stable
分支。
接着, 可以运行 git submodule update --remote --merge
或者 git submodule update --remote --rebase
,将远端代码合并或者变基到本地提交的代码上。
$ cd DbConnector/
# 修改代码并提交
$ vim src/db.c
$ git commit -am 'unicode support'
[stable f906e16] unicode support
1 file changed, 1 insertion(+)
$ cd ..
# 更新子仓
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
如果我们现在更新子仓,就会看到当我们在本地做了更改时上游也有一个改动,我们需要将它并入本地。
如果 忘记 --rebase
或 --merge
,Git 会将子仓更新为服务器上的状态, 并且会将项目重置为一个游离的 HEAD 状态 。
即便这真的发生了也不要紧,你只需回到目录中 再次检出你的分支(即还包含着你的工作的分支),然后手动地合并或变基 origin/stable
(或任何一个你想要的远程分支) 就行了。
如果没有提交子仓的改动,那么更新子仓也不会成功,此时 Git 会只抓取更改而并不会覆盖子仓目录中未保存的工作:
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
5d60ef9..c75e92a stable -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
如果改动与远端改动冲突,当运行更新时 Git 也会让你知道:
$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
如果我们在主仓中提交并推送但并不推送子仓上的改动,其他尝试检出我们修改的人会遇到麻烦, 因为他们无法得到依赖的子仓改动。那些改动只存在于我们本地的拷贝中。
为了确保这不会发生,可以让 Git 在推送到主仓前检查所有子仓是否已推送。 git push
命令接受可以设置为 “check” 或 “on-demand” 的 --recurse-submodules
参数 。
如果任何提交的子仓改动没有推送,那么 check 选项会直接使 push
操作失败。
$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
DbConnector
Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote.
如你所见,它也给我们了一些有用的建议,指导接下来该如何做。 最简单的选项是进入每一个子仓中然后手动推送到远程仓库,确保它们能被外部访问到,之后再次尝试这次推送。
如果你想要对所有推送都执行检查,那么可以通过设置 git config push.recurseSubmodules check
让它成为默认行为 。
另一个选项是使用 on-demand 值, 会自动检查并推送任何引用了新提交的子仓 。这可以确保主仓库和其子仓之间的引用关系是一致且完整的。
$ git push --recurse-submodules=on-demand Pushing submodule 'DbConnector' Counting objects: 9, done. Delta compression using up to 8 threads. Compressing objects: 100% (8/8), done. Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done. Total 9 (delta 3), reused 0 (delta 0) To https://github.com/chaconinc/DbConnector c75e92a..82d2ad3 stable -> stable Counting objects: 2, done. Delta compression using up to 8 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done. Total 2 (delta 1), reused 0 (delta 0) To https://github.com/chaconinc/MainProject 3d6d338..9a377d1 master -> master
如你所见,Git 进入到 DbConnector 模块中然后在推送主项目前推送了它。 如果那个子仓因为某些原因推送失败,主项目也会推送失败。
你也可以通过 设置 git config push.recurseSubmodules on-demand
让它成为默认行为 。
当 .gitmodules
文件中记录的子仓的 URL 发生了改变。此时,若父级项目引用的子仓提交不在仓库中本地配置的子仓远端上,那么执行 git pull --recurse-submodules
或 git submodule update
就会失败。
为了补救,需要借助 git submodule sync
命令:
# 将新的 URL 复制到本地配置中
$ git submodule sync --recursive
# 从新 URL 更新子仓
$ git submodule update --init --recursive
使用 git submodule foreach
子仓命令,它能在每一个子仓中运行任意命令。
例如,可以创建一个新分支,并将所有子仓都切换过去。
$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'
你可能想为其中一些命令设置别名,因为它们可能会非常长而你又不能设置选项作为它们的默认选项。 我们在 Git 别名 介绍了设置 Git 别名, 但是如果你计划在 Git 中大量使用子仓的话,这里有一些例子。
$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'
这样当你想要更新子仓时可以简单地运行 git supdate
,或 git spush
检查子仓依赖后推送。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。