当前位置:   article > 正文

Git 子仓(Git Submodule)学习_git 子仓库

git 子仓库

Git 子仓学习

Git 子仓(Submodule)是 Git 提供的一种功能,用于在一个 Git 仓库(称为主仓库或 superproject)中嵌入另一个 Git 仓库(称为子仓或 submodule)。这种功能在管理大型项目或依赖关系较多的项目时非常有用。

Git 子仓的特点和用途

  1. 模块化管理:可以将项目中的某些部分独立成一个子仓库进行独立管理,这样可以保持代码的模块化和独立性。
  2. 版本一致性:每个子仓都有独立的版本控制,可以锁定在某个特定的提交或版本,从而保证主项目中使用的子仓版本的稳定性。
  3. 复用代码:子仓可以在多个项目中复用,避免代码重复和版本不一致的问题。

添加已有的子仓

使用 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.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

默认情况下,子仓会将子项目放到一个与仓库同名的目录中,本例中是 “DbConnector”。 如果你想要放到其他地方,那么可以在命令结尾添加一个不同的路径。

可以注意到新的 .gitmodules 文件。 该配置文件保存了项目 URL 与已经拉取的本地目录之间的映射:

[submodule "DbConnector"]
	path = DbConnector
	url = https://github.com/chaconinc/DbConnector
  • 1
  • 2
  • 3

如果有多个子仓,该文件中就会有多条记录。 要重点注意的是, 该文件也像 .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
$ 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

其中有 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

其实也有更简单的方法,可以将 git submodule initgit 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

子仓更新

在子仓直接更新

这是子仓更新最简单的方法,可以进入到子仓目录中运行 git fetchgit 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(+)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

从主仓拉取子仓更新

作为协作者,当有自己的 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")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

默认情况下,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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
手动拉取所有子仓

如果希望一次性直接更新所有子仓,可以使用 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
自动拉取所有子仓

如果想自动化前述过程,直接和主仓的拉取命令结合。那么 可以在主仓的 git pull 命令添加 --recurse-submodules 选项 (从 Git 2.14 开始)。 这会让 Git 在拉取后运行 git submodule update,将子仓置为正确的状态。

$ git pull --recurse-submodules
  • 1

如果想让 Git 总是以 --recurse-submodules 拉取,可以通过 git config submodule.recurse true 设置 submodule.recurse 选项, 告诉 Git(>=2.14)总是使用 --recurse-submodules

$ git config submodule.recurse true
$ git pull
  • 1
  • 2

如上所述,这也会让 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如果不用 -f .gitmodules 选项,那么它只会为你做修改。但是在仓库中保留跟踪信息更有意义一些,因为其他人也可以得到同样的效果。

在子仓修改代码

更新子仓

当我们运行 git submodule update 从子仓仓库中抓取修改时, Git 将会获得这些改动并更新子目录中的文件,但是 会将子仓库留在一个称作“游离的 HEAD”的状态 。这意味着没有本地工作分支(例如 “master” )跟踪改动。如果没有工作分支跟踪更改,也就意味着 即便你将更改提交到了子仓,这些更改也很可能会在下次运行 git submodule update 时丢失

为了将子仓设置得更容易进入并修改,首先需要做两件事来更新代码:

  1. 进入每个子仓并检出其相应的工作分支。
  2. 运行 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如果我们进入 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果我们现在更新子仓,就会看到当我们在本地做了更改时上游也有一个改动,我们需要将它并入本地。

如果 忘记 --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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果改动与远端改动冲突,当运行更新时 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

发布子仓改动

如果我们在主仓中提交并推送但并不推送子仓上的改动,其他尝试检出我们修改的人会遇到麻烦, 因为他们无法得到依赖的子仓改动。那些改动只存在于我们本地的拷贝中。

为了确保这不会发生,可以让 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.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如你所见,它也给我们了一些有用的建议,指导接下来该如何做。 最简单的选项是进入每一个子仓中然后手动推送到远程仓库,确保它们能被外部访问到,之后再次尝试这次推送。

如果你想要对所有推送都执行检查,那么可以通过设置 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如你所见,Git 进入到 DbConnector 模块中然后在推送主项目前推送了它。 如果那个子仓因为某些原因推送失败,主项目也会推送失败。

你也可以通过 设置 git config push.recurseSubmodules on-demand 让它成为默认行为

更新子仓远端仓库

.gitmodules 文件中记录的子仓的 URL 发生了改变。此时,若父级项目引用的子仓提交不在仓库中本地配置的子仓远端上,那么执行 git pull --recurse-submodulesgit submodule update 就会失败。

为了补救,需要借助 git submodule sync 命令:

# 将新的 URL 复制到本地配置中
$ git submodule sync --recursive
# 从新 URL 更新子仓
$ git submodule update --init --recursive
  • 1
  • 2
  • 3
  • 4

子仓技巧

子仓遍历

使用 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'
  • 1
  • 2
  • 3
  • 4
  • 5

子仓别名

你可能想为其中一些命令设置别名,因为它们可能会非常长而你又不能设置选项作为它们的默认选项。 我们在 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'
  • 1
  • 2
  • 3

这样当你想要更新子仓时可以简单地运行 git supdate,或 git spush 检查子仓依赖后推送。

参考文献

  1. Git - 子仓


部分图片来源网络,如有侵权请联系我删除。
如有疑问或错误,欢迎和我私信交流指正。
版权所有,未经授权,请勿转载!
Copyright © 2024.07 by Mr.Idleman. All rights reserved.


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/1013229
推荐阅读
相关标签
  

闽ICP备14008679号