文章目录
- 综述
- 要点
- 细节
- 如何新增一个submodule?
- 如何手动.gitmodules修改首次增加一个submodule?`git submodule init`,init子命令依据.gitmodules
- .gitmodules如何命令修改某个成员以及同步?
- 如果submodule需要修改分支怎么办?`git submodule update`,update子命令依据.git/config
- 仅仅修改.gitmodules不执行sync的效果
- 如何新增一个submodule比较方便的方法:`git submodule add`
- 实验一个完全新增本地仓库作为子模块
- 组合命令`git submodule update --init`
- 如何查看所有submodule的信息
- 查看哪些submodule
- 查看每个submodule的分支信息`git submodule foreach 'git status | head -n 1'`
- 其他
- 其他一些命令
- 查看每个submodule的和远端不同修改`git diff HEAD --submodule=log`
- 查看某个submodule对应的gitlink的commitid
- 如何拉取子模块远端最新代码
- 要点再回顾
- 综述
综述
submodule的一些问题记录
要点
# 首次增加
git submodule add xxx localdir = vim .gitmodule + init + sync + update
细节
如何新增一个submodule?
如何手动.gitmodules修改首次增加一个submodule?git submodule init
,init子命令依据.gitmodules
一句话原理是:新增/修改项目根目录下.gitmodules,然后执行git submodule init
。
git submodule init 告知git要关联哪些子模块。它会从 .gitmodules文件中拷贝子模块设置,复制到主项目的配置 .git/config文件。修改后 .git/config会有submodule对应的配置项,内容和.gitmodules相同。
如果只指定一个submodule就是用:git submodule init plugins/demo
.gitmodules格式:一般只用指定path、url、branch
# 每个子模块一个 [submodule "<name>"] 段
[submodule "deps/spdlog"]path = deps/spdlog # 检出到哪个目录(相对仓库根)url = https://github.com/gabime/spdlog.git # 远端仓库地址branch = v1.x # 可选;--remote 时默认跟踪的分支# 下面都是可选扩展字段fetchRecurseSubmodules = false # 递归拉子模块?true/falseupdate = checkout # 默认更新策略:checkout/merge/rebaseignore = dirty # git status 是否忽略子模块改动[submodule "external/json"]path = external/jsonurl = git@github.com:nlohmann/json.gitbranch = develop
字段 | 可选值 | 含义 / 效果说明 |
---|---|---|
path | 任意相对路径字符串(必填) | 子模块在父仓库中的检出目录;示例:path = deps/libfoo |
url | 任何 Git 支持的地址(必填) | 远端仓库地址;示例:url = https://... 或 url = git@... 或相对路径 ../bar.git |
branch | 任意远端分支名(可选) | git submodule update --remote 时默认拉取的分支;未写则默认 master /main |
update | checkout (默认) | 直接跳到父仓库记录的 commit;最常用 |
merge | 把本地改动做 merge,可能产生 merge commit | |
rebase | 把本地提交 rebase 到新 commit,历史线性 | |
none | 禁止自动更新,需手工进入子模块目录操作 | |
fetchRecurseSubmodules | true | 父仓库执行 git fetch /git pull 时递归拉取子模块 |
false (默认) | 只 fetch 父仓库本身 | |
ignore | none | 子模块任何差异都出现在父仓库 git status (最严格) |
untracked | 忽略子模块内未跟踪文件;已修改/已提交差异仍显示 | |
dirty (默认) | 忽略子模块内所有未提交改动,仅显示 HEAD 移动 | |
all | 完全忽略子模块,任何变化都不出现在父仓库 status | |
shallow | true | 浅克隆,只拉最近一次 commit(省磁盘/时间) |
false (默认) | 完整历史,后续可 log/blame |
注意修改后记得提交.gitsubmodule到主仓库
.gitmodules如何命令修改某个成员以及同步?
前面提到了.gitmodules可以通过修改文件,还可以命令修改:git config -f .gitmodules submodules.mytest1.branch master
修改
修改后需要sync
如果submodule需要修改分支怎么办?git submodule update
,update子命令依据.git/config
一句话是:先修改.gitmodules,再sync,在git submodule update会根据.git/config中的配置拉取。核心是git submodule update根据.git/config来的。
首先更新.gitmodules
然后将.gitmodules文件的配置,显示同步到.git/config中,执行git submodule sync
。会提取新的URL
注意截止目前仅仅修改了配置,并未让工作区的子模块内容修改,然后使用git submodule update
更新子项目中的文件。
该命令的动作:
- 访问.git/config中的子模块版本库(注意:如果修改.gitmodules,不会同步到.git/config中, 前文提到了必须git submodule sync后,后面的命令才能用。也即如果修改.gitmodules之后,需要sync之后才能执行update)
- 拉取版本库提交对象的ID
- 将代码checkout到对应目录(config中指定)
为了保险起见,如果本地未修改子模块的代码,可以先删掉或者备份子模块的目录,然后再执行git submodule update
值得注意的:
- 如果主项目有git reset回退历史记录或者git checkout了主干分支,子模块的版本信息等不会处理,需要手动处理。子模块假设回退中修改了.gitmodules,需要git submodule sync, 然后git submodule update
- 如果用户在子模块执行了git拉取或者切换版本,会更新gitlink。
- git submodule update会让子模块脱离分支,而转向某个commitid,该commitid就是gitlink,可以用git ls-tree HEAD path/to/yoursubmodule查看
- git submodule的本质是修改gitlink的机制。所以在子模块执行git相关修改,变化了gitlink的值,同样会生效,但是是否需要执行submodule相关的操作,需要关注.git/config .gitmodules文件是否有更新
仅仅修改.gitmodules不执行sync的效果
注意:如果首次添加不能用sync,需要先init或者submodule add(参考后文)
如何新增一个submodule比较方便的方法:git submodule add
git submodule add ../repo1/repo1.git mytest1
这会自动更新 .gitmodules 和 .git/config 文件。
- 如果遇到错误如何处理:
$ git submodule add ../repo1 mytest1
Cloning into '/Users/yourusername/workspace/gitsubmodule/repo2/mytest1'...
fatal: transport 'file' not allowed
fatal: clone of '/Users/yourusername/workspace/gitsubmodule/repo1' into submodule path '/Users/yourusername/workspace/gitsubmodule/repo2/mytest1' failed
原因:传输协议限制
fatal: transport ‘file’ not allowed 表明 Git 配置可能限制了使用本地文件系统作为仓库来源。
解决办法:
git config --global protocol.file.allow always
修改后执行:
推荐这种方法进行submodule add,并且自动修改.gitmodules和config。简单理解可以是:git submodule add xxx xxx = vim .gitmodules + git submodule sync
cat .gitmodules可以看到对应的新增的配置:(第一次手动修改.gitmodules因为url指定不对并未成功)
以及自动sync的结果:
所以如果新增submodule,建议直接使用命令添加。避免手动添加的错误。添加后可以修改url等,然后再sync。
实验一个完全新增本地仓库作为子模块
可以看到一条add就自动同步所有动作:git submodule add xxx localdir = vim .gitmodule + init + sync + update
组合命令git submodule update --init
一句话:修改.gitmodules文件后,如果是新增一个,git submodule update --init = 先init + 再update
, 修改文件角度看是:init修改了.gitmodules, 并且 sync,然后执行update,update根据.git/config更新工作空间。
还有一个理解是:init是控制链路,影响的是git的配置相关文件,update是数据链路,将控制文件在实际工作空间生效。
还有一个不太准确的等式是,在新增一个submodule场景下:git submodule add xxx dir = 修改.gitmodules + init + update。如果是新增的仅仅执行修改.gitmodules + sync + update是不生效的。换而言之sync的内部实现,可能会先检查.git/config是否已经有submodule,如果没有sync并不会将.gitmodules新增的同步过去。所以init对.git/config而言有 类似create+modify的能力,而sync只有modify的能力,如果“表项”不存在,不能create能力。
如何查看所有submodule的信息
查看哪些submodule
git submodule summary
查看每个submodule的分支信息git submodule foreach 'git status | head -n 1'
git submodule foreach 'git status | head -n 1'
其他
其他一些命令
查看每个submodule的和远端不同修改git diff HEAD --submodule=log
git diff HEAD --submodule=log
查看某个submodule对应的gitlink的commitid
git diff HEAD – mytest1 #指定某个submodule
如果有修改会加上后缀dirty
在主仓库git diff,如果未配置,只能看到某个submodule的commitid被修改。看某个子仓库,初阶玩法建议直接cd到目录下进行查看:cd mytest1 && git diff
如何拉取子模块远端最新代码
git submodule update --remote
下面以一个实验演示:实验会在远端仓库修改文件,然后本地submodule无法直接看到,然后submodule --remote更新,可以同步远端,并且更新gitlink的commitid和远端一致
-
第一步:修改远端仓库并且提交
-
第二步:确认本端不受影响,保持老版本
-
第三步:执行git submodule update --remote更新
-
第四步:查看本地gitlink变化
接下来如果要提交就直接提交掉新的link
要点再回顾
- 新增模块尽量用git submodule add xxx xxx来会自动处理不少事
- 修改.gitmodules之后需要执行sync,然后update
- update拉取远端的代码需要加上–remote,如果在.gitmodules中制定了分支会拉取对应分支,拉取后会修改本仓库的gitlink的commitid。如果本仓库要更新需要提交
综述
submodule的一些问题记录