• git:如何在一个项目中增加对其他项目的跟踪呢?
  • 发布于 1个月前
  • 49 热度
    0 评论
  • 张蜚
  • 22 粉丝 14 篇博客
  •   
在项目开发过程中,我们可能会有跨项目合作,或者项目组内部多个subgroup之间的协作,以及社区各个开源项目之间的引用,这时候repo需要有一种机制能够引用,跟踪对应的项目;submodule就是git提供的一种项目引用和跟踪的机制;基于此对引用的上游项目也可以进行很容易的进行自定义的修改,合并和推送;

如何在一个项目中增加对其他项目的跟踪呢?

0x1. 新增submodule
如下,通过git submodule add命令在git-test的项目中跟踪另一个项目draw_io;最后的路径表示submodule项目存放的路径,不填和git clone一样,会在当前目录创建repo同名的目录来存放;
git submodule add git@github.com:walkerdu/draw_io.git submodules/draw_io
执行完上述submodule add命令后,可以查看本地仓库的变化如下:
$git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   .gitmodules
        new file:   submodules/draw_io
我们可以看到当前仓库的已经多了两个文件.gitmodules和submoudles/draw_io,且这「两个文件已经被自动加入了stage区域」;

其中:.gitmodules文件是git仓库用来管理所有跟踪的submodule的基本信息,主要是跟踪仓库地址和本地存放路径的一个映射(当然可以配置其他信息,后面会说),注意:**.gitmodules文件是归本仓库进行版本控制的**;.gitmodules内容如下:
$cat .gitmodules 
[submodule "submodules/draw_io"]
        path = submodules/draw_io           // 存放路径
        url = git@github.com:walkerdu/draw_io.git   // submodule的url地址
其中另外一个变化:submodules/draw_io在本地是add submodule后,clone下来的跟踪的项目draw_io的仓库;「但为什么上面git status提示的是他是一个new file呢」?下面研究一下原因:
我们先把submodule的变更进行提交:
$git commit -m 'init submodule' .
[master 4894d7d] init submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 submodules/draw_io
然后查看提交的内容如下:
/* 堆代码 duidaima.com */
$git show 4894d7d
commit 4894d7dbde107741a0956bc018ad1e6ec1ca80b3 (HEAD -> master)
Author: walkerdu <anonym_alias@163.com>
Date:   Thu May 12 15:31:43 2022 +0800

    init submodule

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..df23d83
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "submodules/draw_io"]
+       path = submodules/draw_io
+       url = git@github.com:walkerdu/draw_io.git
diff --git a/submodules/draw_io b/submodules/draw_io
new file mode 160000
index 0000000..9b5dcda
--- /dev/null
+++ b/submodules/draw_io
@@ -0,0 +1 @@
+Subproject commit 9b5dcdac92b0d0e07264ae22267814fd052d4560
我们可以看到submodules/draw_io提交的内容是此跟踪项目的最新的commitid;所以我们得知,「对于submodule管理的模块,会将.gitmodules映射的本地存储路径进行忽略,将其最新的commitid作为文件内容在主仓库中进行跟踪管理」;

0x2. 更新submodule
submodule的更新一般有两个层面的更新,分别是:
拉取submodule子项目最新的提交,如果子项目对应的分支上有更新,那么会拉取下来,并且修改主项目跟踪的改子项目的commitid;这种情况一般是:「明确需要跟踪子项目的特定提交,使用其新特性」;
拉取项目中跟踪的submodule的commitid对应的数据到本地;这种情况一般都是「项目的开发者拉取submodule是否有跟踪的变化,更新一下对应的数据」;
0x21. 更新submodule最新的提交
默认情况下,在主仓库的根目录下,执行git pull命令并不会主动更新submodule最新的commit;
可以通过git submodule update --remote拉取跟踪的最新的远端分支到本地,如下:
$git submodule update --remote
...
From github.com:walkerdu/draw_io
   9b5dcda..26b9e31  master     -> origin/master
Submodule path 'submodules/draw_io': checked out '26b9e31e0367c16894910f3ebce4e2d334a812da'

$git status
...
Changes not staged for commit:
    ...
        modified:   submodules/draw_io (new commits)
更新submodule后,主仓库里面可以发现跟踪的submodules/draw_io已经发生了变化;此时可以将最新的跟踪仓库的commitid的变化进行提交;这样项目就刷新了最新跟踪的submodule信息;我们此时看一下跟踪项目本地目录的状态:
$git status
HEAD detached at 117e833
nothing to commit, working tree clean
「为什么跟踪的submodule本地项目变成了detached状态呢」?这里看一下git submodule update命令的man,简单说一下;
命令git submodule update更新子项目的策略有三种:
checkout方式:子项目checkout到detached的分支,然后在detached分支上更新远端的提交;「默认选项」;
rebase方式:将子项目本地分支上的提交在远端最新的提交上进行rebase;
merge方式:将子项目远端分支的提交本merge到子项目本地分支上;
submodule更新策略的选择有两种方式:
update命令执行时设置:git submodule update --remote --rebase;
修改.gitmodules文件,默认采用某种策略更新git config -f .gitmodules submodule.submodules/draw_io.update rebase,然后将变动的.gitmodules文件提交到主项目中;
0x22. 更新跟踪的commit对应的submodule最新数据
上面介绍的是更新跟踪的子项目的分支上最新的提交,「一般在开源项目中都不会去执行--remote更新」,只有需要引用子项目的特定特性的时候才会去主动更新;一般涉及到submodule的更新都是进行git submodule update更新仓库中跟踪的commit对应的子项目的数据;那么「有没有什么方式在仓库跟踪的子项目的commitid发生变化时,不用每次都git submodule update来拉取子项目最新数据呢」;

有两种方式可以在主仓库git pull的时候自动根据最新跟踪的commitid来刷新跟踪的子仓库数据:
1.通过git pull --recurse-submodules拉取跟踪commit的子项目的数据;
2.通过git config --global submodule.recurse true来配置,该配置在「Git 2.15」引入,意思是「对所有主仓库的git操作都同样对跟踪的子仓库生效」,注意这里对git clone无效,需要在clone后的仓库中的操作;在「Git 2.34(Q4 2021)」版本开始,如果git clone --recurse-submodules拉取的仓库,那么默认submodule.recurse会被设置为true,这是针对社区开发者对submodule使用反馈和统计后做的一个优化;具体可以参考这里;
0x3. 跟踪submodule特定分支
默认submodule在初始化的时候,跟踪的是子项目的默认分支,一般都是master,如果想跟踪特定分支的话,有两种方式:
1.在git submodule add -b branch_name xxx初始化的时候指定特定的分支;
2.在仓库中执行git config -f .gitmodules submodule.submodules/draw_io.branch develop;「更新某个submodule跟踪的分支名;这种在基于版本发布的项目中会更常见」,如下:
$git config -f .gitmodules submodule.submodules/draw_io.branch develop
$git status
On branch develop
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:   .gitmodules
跟踪draw_io的develop分支,执行完修改后.gitmodules发生变化,如下:
 [submodule "submodules/draw_io"]
        path = submodules/draw_io
        url = git@github.com:walkerdu/draw_io.git
+       branch = develop
然后执行git submoudle update --remote就可以刷新主仓库跟踪的commitid到分支的最新提交上面;

当然,如果想跟踪submodule的某个分支的某个提交,可以在跟踪到改分支后,在对应的submodule的本地目录中,将子项目reset到特定的分支,然后提交要跟踪的commitid就可以了;

这里需要注意的是:在主项目的特定分支跟踪submodule的特定分支后,在「Git 2.34(Q4 2021)」[1]版本之前,如果没有设置git config --global submodule.recurse true,那么「在主项目切换不同分支的时候,submodule不会自动切换过去」,这会导致主项目在分支切换的时候跟踪的submodule发生修改,需要手动进行submodule分支的切换;所以根据「不同版本需要进行不同的修复方案」,如下:
「Git 2.15」[2]之前,需要在切换主项目分支的时候,手动切换跟踪的submodule的分支,命令git submodule foreach git checkout branch_name,这个方式所有版本都可以使用;
「Git 2.15」及以后,直到「Git 2.34(Q4 2021)」[3]版本之前,设置git config --global submodule.recurse true,那么在主项目切换不同分支的时候,submodule会自动切换过去;

「Git 2.34(Q4 2021)」版本及以后,在git clone --recurse-submodules拉取的仓库时,默认会设置git config --global submodule.recurse true,不需要再关心submodule的跟踪问题了;


0x4. 拉取含有submodule的repo
前面都是使用submoudle的一些前置操作,那在拉取一个含有submodule repo的时候,我们需要做什么吗?是的,默认git clone并不会拉取所有跟踪的submodule的源码数据,有两种方式可以拉取:
1.在git clone的时候带上参数,git clone --recurse-submodules https://github.com/grpc/grpc,其中--recurse-submodules选项,它会自动初始化并更新仓库中的每一个子模块, 包括可能存在的嵌套子模块。
2.在git clone之后,进入仓库中,执行git submodule update --init --recursive,其中--recursive选项和git clone时候的--recurse-submodules的含义是一样的;
0x5. 推送含有变更的submodule
有时候我们可能修改了submoudle的内容,在推送主项目的时候,也希望推送跟踪的子项目,也是可以操作的,方式和拉取的时候是类似的,如下:
通过git push --recurse-submodules --on-demand;在推送主项目的时候会递归的推送子项目;

通过git config --global push.recurseSubmodules=on-demand来配置;和上述含义一样;不过这里为什么不使用git config --global submodule.recurse true的设置呢?「看来submodule.recurse的配置不仅不对clone生效,在push的时候也不生效…」


0x6. 其他命令
git submodule foreach
git提供了可以在主项目中执行所有git命令来直接操作子项目的方式,命令:git submodule foreach 'git cmd',foreach可以遍历所有submodule,然后执行后面的git命令,如下查看子项目的所有分支:
$git submodule foreach 'git branch -av'
Entering 'submodules/draw_io'
* (HEAD detached at a62a01f) a62a01f change name
  master                     117e833 [behind 4] update README.md
  remotes/origin/HEAD        -> origin/master
  remotes/origin/develop     a62a01f change name
  remotes/origin/master      abe26b7 sync
我们可以alias git-sf='git submodule foreach'来便捷的操作submodule;
用户评论