前言
这个问题复杂在既有Git又有子模块,本身Git的门槛就稍微高一点,再加上子模块的运用,一旦出现这种远端地址发生修改的情况会让人有些懵,不知道怎么处理,通常会乱改一通,有时候好使有时候不好使,并不清楚其中的缘由,退一步讲,如果是一个单一的Git库,如果远端地址发生了变化,使用 git remote set-url origin git@new_xxx
命令就可以就行修改,但是这个命令在子模块情况下行不通,为了说明子模块影响哪些配置文件,接下来会简单罗列下git子模块相关命令。
git submodule 相关命令
向一个项目中添加子模块:
1 | git submodule add git@github.com:zzz/xxx.git xxx |
执行之后会 clone 该子模块对应的远程项目文件到本地父项目目录下的同名文件夹中(./xxx/),父项目下也会多一个叫 .gitmodules
的文件,其内容大致如下:
1 | [submodule "xxx"] |
同时父项目下的 .git 目录中也会新增 modules/xxx/
目录,里面的内容对应子模块仓库中原有的 .git 目录中的文件,此时虽然子模块目录下的 .git 依然存在,但是已经从一个文件夹变成了文件,内容如下:
1 | gitdir: ../.git/modules/xxx |
即指向了父项目的 .git/modules/xxx
目录,如果运行 git config --list
查看项目的配置选项,会发现多了类似下面两行的内容:
1 | submodule.xxx.url=git@github.com:zzz/xxx.git |
如果修改 submodule.xxx.url
的值,则会覆盖 .gitmodules
文件中对应的 url 值;
这其实是主(父)工程 .git/config
文件中的内容,而在主工程的 .git/modules/xxx/config
文件中会有下面这些内容:
1 | [remote "origin"] |
总结来说子模块的远端地址出现在三个文件中 .gitmodules
、.git/config
和 .git/modules/xxx/config
记住这些地方,我们后续会用到,这就是让人迷惑的地方。
接下来按照以下命令查看当前项目下的子模块:
1 | git submodule status |
结果格式为:
1 | b01bf0c72235aba2e92e5c5f5173dd4cae9b374c xxx (heads/master) |
如果将父项目推送到远程仓库(如gitlab),在网页浏览该项目时子模块所在的目录会多一个类似 @b01bf0c 的后缀,即上面查看子模块命令输出内容的 hash 值的前面部分,点击这个目录会跳转到这个子模块对应的仓库地址。
如果执行以下删除了子模块的命令:
1 | git submodule deinit |
再次查看时输出会是这样的:
1 | -b01bf0c72235aba2e92e5c5f5173dd4cae9b374c xxx |
在取消初始化子模块后,子模块目录会变为空目录,避免该子模块的文件继续占用空间。
解决方案
聊了这么多,总要给出一个解决方案:
修改主工程下 .gitmodules
文件内的远端地址 url = git@newdomain.com:zzz/xxx.git
,然后执行 git submodule sync
命令即可完成
这样操作之后 .git/config
和 .git/modules/xxx/config
文件中的远端的地址会一起被修改掉。
多说一点:
在执行 git submodule init
或 git submodule sync
命令时,Git 会将 .gitmodules
中的配置同步到 .git/config
中,以确保 .git/config
的信息是最新的。
而在执行 git submodule update
进行更新时,Git 主要使用 .git/config
中的子模块配置来操作子模块。
当进入子模块目录操作git时,Git使用的是 .git/modules/xxx/config
中的远端地址来进行更新和推送
怎么保存子模块的版本呢
了解了子模块的机制,我们知道主工程只是保存了一个子模块工程的版本号,但是前面看了这么多配置文件,里面只有子模块的本地路径和远端地址,没有看到版本号啊?接下来我们找一下:
首先查看最新的提交commit,得到 5c997695ed383ea52879a17f0ef6944bf99d374f
1 | $ git log -1 |
利用 cat-file -p
命令查看指定 commit id
的仓库状态
1 | $ git cat-file -p 5c997695ed383 |
从结果中可以看出提交人的信息,父提交id b287b7783e1d44a6149de132142b88bf92b95bb3
,以及一棵树tree 8499d7cb5c4811918bfc1341bb869d8bb38c40ef
,怎么理解这棵树呢?其实可以类比文件系统中的文件夹,每一个commit id实际上对应着项目文件夹的版本,其中包含子文件夹版本和各个文件的版本,那么tree 8499d7cb5c4811918bfc1341bb869d8bb38c40ef
可以认为是项目文件夹内所有内容的Hash
接下来再利用 cat-file -p
命令来看看这棵tree 8499d7cb5c4811918bfc1341bb869d8bb38c40ef
1 | $ git cat-file -p 8499d7cb5c4811918bfc1341bb869d8bb38c40ef |
这里边有3类内容,分别是 blob
、tree
、commit
,blob就是具体的文件,tree就是代表一个文件夹,而 xxx
的类型是一个commit,从上文我们知道 xxx
是包含了另一个项目的文件夹,如果是一个单纯的文件夹这里的类型应该是tree,但是因为是子模块,所以类型是记录另一个项目commit id,这样我们就找到了主工程引用子模块的版本存储的位置了。
总结
- 添加子模块的命令
git submodule add git@github.com:zzz/xxx.git xxx
- 初始子模块
git submodule init
,查看子模块git submodule status
- 注销子模块
git submodule deinit xxx
,注销后文件夹清空,但是配置文件需要手动删除 - 子模块远端地址发生变化,手动修改
.gitmodules
文件中地址,然后执行git submodule sync
再提交修改
放下助人情节,尊重他人命运,可怜之人必有可恨之处,子非鱼,安知鱼之经历与内心~
2024-11-5 19:49:42