git submodule的使用

1. submodule简介

submodule是一种git特性,用以将一部分公共代码从主项目中抽离出来成为一个独立的git工程,并以submodule的形式被主项目使用,submodule和主项目(作为区分,本文叫主模块)充分解耦,这样做的好处是作为submodule的公共代码可以被多个主项目工程使用,减少了开发量和版本控制负担。同时,submodule作为独立的git工程,也使得权限管理、代码修改更加灵活。
举个例子,多个互相通信的项目,可以将通信协议部分独立出来作为submodule,所有项目维护同一套通信协议。类似于一个maven中的依赖,但不同的是maven依赖基本上使用者不会去修改依赖的库,但submodule通常需要被使用者修改。


2. submodule使用

我们举例说明submodule的使用方式,有A和B两个开发者协作开发一套系统,其中需要使用一个submodule,我们以时间轴的顺序看看会发生什么情形,以及对应的处理方式。

day1:开发者A为主模块添加submodule

# 开发者A
cd MainProject # 主模块
git submodule add  $submodule_url $submodule_dir  #添加子模块
git add . 
git commit -m "add submodule" # 提交修改
git push origin master # 推送到主项目远程

上面代码中, submodule_url是子模块远程仓库地址,submodule_dir是要创建的文件夹名,用来放submodule的代码 。执行后,submodule_dir被创建,但是空的,因为还没有拉取子模块的代码。.gitmodules文件记录子模块的信息。打开.gitmodules文件,可以看到:

[submodule "my_submod"]
        path = $submodule_dir
        url = $submodule_url
        branch = master

对于submodule_dir,git在提交时不是当作一个普通的子目录提交,只是记录子模块commit信息,本地子模块目录里的文件不会被push到远程仓库,clone主项目时也只会获取submodule_dir空目录而已。


day2:开发者B克隆主模块到本地,同时初始化子模块

# 开发者B
git clone $main_project_url 
cd MainProject # 进入主项目,发现submodule_dir是一个空目录 
git submodule init # 初始化submodule配置,从.gitmodule读取相关配置
git submodule update # 拉取submodule代码到submodule_dir目录 #git submodule update --remote --merge 下面会讲到
# 上面2行代码可以用下面这1行代替:
git submodule update --init # 如果子模块还包含子模块,可以加--recursive

注意,git submodule update拉取的代码是父项目中记录的那个submodule版本,但不一定是submoudle远程仓库里最新的版本。要充分理解这个概念。
可以注意下git submodule update执行后屏幕上打印的信息,最后一句是下面这样:

Submodule path '$submodule_dir': checked out '88498af472ae91690860aeb9a3b2bf7e52c7fc83'

当我们运行 git submodule update 从子模块仓库中抓取修改时, Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作“detached HEAD”的状态,或者叫游离态。如果我们进入子模块目录执行git branch,可以看到当前HEAD不在任何分支:

* (detached from 88498af)
  master

如果进入游离态,那么意味着没有分支去跟踪我们的更改,即使子模块代码被我们本地修改了,下次update的时候可能就丢失了。如果出现这个情况,我们需要checkout回我们的工作分支:

git checkout master

关于游离态的问题,后面具体情形遇到时再细说。


day3:开发者B拉取远程更新(包括主模块和子模块)

# 开发者B
cd MainProject
git pull # 主模块远程更新合并到本地。
git submodule update --remote --merge #子模块远程更新合并到本地
# 或者
cd $submodule_dir; git fetch; git merge origin/master # 拉取子模块更新

注意:
1,在主模块目录pull会更新主模块,但是对于子模块,只会获取子模块的更新记录,但不会去更新子模块文件。
2,子模块submodule update方式更新,需要加–remote和–merge,其中–remote表示将子模块远程仓库的更新合并到本地(否则还是主模块里记录的最后commit的那个submodule版本,而不一定是子模块远程最新版本),–merge表示将更新合并到本地子模块(也可以–rebase)。如果有多个子模块,全部都会更新。
3,子模块也是一个独立的git工程,因此可以直接进入子模块目录,按普通git工程的方式更新。如果多个子模块,需要一个一个更新。如果子模块不是很多,个人推荐这种方式,避免出现detached问题。


day4:开发者B修改子模块并提交到远程

# 开发者B
vim $submodule_dir/$some_file # 修改子模块代码
cd $submodule_dir
git add $submodule_dir/$some_file
git commit -m "alter some_file"
git pull # push前先pull保持远程同步
git push

注意
push的前提是当前submodule不在detached游离状态,如果在游离态,需要checkout回工作分支(比如master分支)。这里就有3种情况:
1,修改子模块和commit之前,子模块已经处于或者已经从游离态checkout回到工作分支,那么就正常add commit push操作即可;
2,修改了子模块但还没有commit,突然想起来当前处于detached状态,这个时候因为有未提交的修改,checkout会报错,那么你可以git stash先把修改缓存起来,然后checkout回工作分支,再用git stash pop把缓存的修改应用到工作分支上来,或者你想直接丢弃掉修改,那就不pop就好。
3,修改了子模块且commit了,突然想起来当前处于detached状态,这个时候可以checkout回工作分支,但是你commit的修改也就丢失了,这个时候如果需要把这个修改应用到工作分支上,可以把detached HEAD合并到当前工作分支。举例,下面model_training是主项目,odl_config是一个submodule,我们在detached状态下新建一个test.txt文件:

[VM_64_46_centos ~/model_training/odl_config]$ git branch # 查看当前所在分支,在detached head状态
* (detached from a0de66c)
  master
[VM_64_46_centos ~/model_training/odl_config]$ touch test.txt # 新建test.txt
[VM_64_46_centos ~/model_training/odl_config]$ ls # 查看文件
README.md  odldp  odlpp  odltp  test.txt
[VM_64_46_centos ~/model_training/odl_config]$ git add test.txt  
[VM_64_46_centos ~/model_training/odl_config]$ git commit -m "commit in a detached state" # 提交修改,注意这时还在游离态
[detached HEAD b6fa872] commit in a detached state 
 1 file changed, 1 insertion(+)
 create mode 100644 test.txt
[VM_64_46_centos ~/model_training/odl_config]$ git checkout master # checkout回工作分支,注意下面的warning
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
  b6fa872 commit in a detached state
If you want to keep them by creating a new branch, this may be a good time
to do so with:
 git branch new_branch_name b6fa872
Switched to branch 'master'
Your branch is behind 'origin/master' by 26 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)
[VM_64_46_centos ~/model_training/odl_config]$ git branch # 已经回到master分支
 * master
[VM_64_46_centos ~/model_training/odl_config]$ ls # 再次查看文件,刚添加的test.txt在master分支不存在
 README.md  odldp  odlpp  odltp

上面我们看到,切换回master分支后,test.txt文件并没有出现在master分支,因为上次修改commit到detached状态上了,commit后到版本号是b6fa872,如果需要把修改同步到当前工作分支,可以执行下面命令:

git merge b6fa872 # 把b6fa872提交merge到当前master分支
git pull # 然后从远程分支拉取最新到更新合并到本地
git push # 然后再push

至此,你完成了修改submodule代码并提交远程,学会了处理detached状态。如果pull时出现conflict,按照git正常处理confilct流程处理就好。


day5: 子模块远程仓库url被修改(不常见的情形)

一种不常见的情形:上游仓库中将子模块的URL做了修改,比如换了域名,远程仓库主模块的.gitmodule文件里子模块的URL会改变,但本地主模块里关于submodule的commit记录还是旧的,这个时候执行submodule update操作时会失败。针对这种情况,需要使用git submodule sync命令:

git submodule sync --recursive # 更新submodule的url(防止出现域名替换等问题,虽然不常见)
posted @ 2022-09-29 10:51  阿木古冷  阅读(4360)  评论(0编辑  收藏  举报