• home > tools > versionControl > git >

    Git 工具 - 子模块: submodule与subtree的使用

    Author:zhoulujun Date:

    git日常使用中,基本都是一个项目一个Git仓库的形式,面对比较复杂的项目,我们有可能会将代码根据功能拆解成不同的子模块。主项目对子模块有依赖关系,却又并不关心子模块的内部开发流程细节。个人推荐选用Git子模块的

    git日常使用中,基本都是一个项目一个Git仓库的形式,那么当我们的代码中碰到了业务级别的需要复用的代码,我们一般怎么做呢?

    比如:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。

    所以需要提取一个公共的类库提供给多个项目使用,但是这个library怎么和git在一起方便管理呢?

    现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。

    我们大致的考虑一下,一般有两种方案:

    1. 抽象成NPM包进行复用;

    2. 使用Git的子仓库对代码进行复用;

    但是:两个程序中有部分页面或功能是重叠的,在前端领域,比如AntDesign、element-UI,react 、vue、angular版本样式是一样的,只是组件不同。

    开发过程中重叠部分如果开发两套代码会浪费不少的人力。

    个人推荐选用Git子模块的方式进行开发,父级仓库依赖两个公共的子模块,子模块本身和父级仓库一同进行开发,可避免了版本问题和重复开发的问题

    面对比较复杂的项目,我们有可能会将代码根据功能拆解成不同的子模块。主项目对子模块有依赖关系,却又并不关心子模块的内部开发流程细节

    大致结构可能是这样

    project

    |--moduleA

    |--submoduleC

    |--submoduleD

    |--moduleB

    project和ABCD各个模块中,CD在不同的git仓库中,这时,就需要使用git的的模块功能

    Git 工具 - 子模块

    Git 通过子模块来解决这个问题。 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。

    通俗上的理解, 一个Git仓库下面放了多个其他的Git仓库,其他的Git仓库就是我们父级仓库的子仓库。

    git Submodule 是一个很好的多项目使用共同类库的工具,他允许类库项目做为repository,子项目做为一个单独的git项目存在父项目中,子项目可以有自己的独立的commit,push,pull。而父项目以Submodule的形式包含子项目,父项目可以指定子项目header,父项目中会的提交信息包含Submodule的信息,再clone父项目的时候可以把Submodule初始化。

    可以端详官网:https://git-scm.com/book/zh/v2/Git-工具-子模块

    多个父级仓库都依赖同一个子仓库,但是子仓库自身不单独进行修改,而是跟随父级项目进行更新发布,其他依赖子仓库的项目只负责拉取更新即可

    Git两种子仓库使用方案

    1. git submodule

    2. git subtree

    git submodule(子模块)

    Git子模块允许我们将一个或者多个Git仓库作为另一个Git仓库的子目录,它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立

    在Git 中你可以用子模块submodule来管理这些项目,submodule允许你将一个Git 仓库当作另外一个Git 仓库的子目录。这允许你克隆另外一个仓库到你的项目中并且保持你的提交相对独立。

    开始使用子模块
    git clone https://github.com/zhoulujun/zhoulujun.cn-phpcms.git zhoulujun
    cd  zhoulujun
    git submodule add   tools 
    git submodule add  https://github.com/zhoulujun/zhoulujun.cn-tools-vue.git tools-vue

    添加子模块后运行git status, 可以看到目录有增加1个文件.gitmodules, 这个文件用来保存子模块的信息。

    $ git status
    On branch master
    
    Initial commit
    
    Changes to be committed:
      (use "git rm --cached <file>..." to unstage)
    
        new file:   .gitmodules
        new file:   assets


    使用git init --bare在本地创建两个裸仓库,分别表示主仓库和依赖的子仓库,我们将主仓库命名为main,依赖的子仓库命名为lib, git subtree使用同样的初始化方法,下文不再赘述。

    git submodule常用命令

    • 查看子模块:git submodule

    • 更新子模块:

      • 更新项目内子模块到最新版本:git submodule update

      • 更新子模块为远程项目的最新版本:git submodule update --remote

    • 克隆包含子模块的项目:

      • 克隆父项目:git clone https://github.com/demo.git assets

        • 初始化子模块:git submodule init

        • 更新子模块:git submodule update

      • 递归克隆整个项目submodulegit clone https://github.com/demo.git assets --recursive 

      • 递归更新整个项目submodulegit submodule foreach git pull

    • 删除子模块:git rm --cached subModulesA    rm -rf subModulesA

    --recursive表示递归地克隆git_parent依赖的所有子版本库

    git subtree(子树合并)

    上面介绍的git submodule是Git自带的原生功能,我们接下来将要介绍的git subtree则是由第三方开发者贡献的contrib script,Git本身并不提供git subtree命令,contrib中包含一些实验性的第三方工具,由各自的作者进行维护。

    同时这也让我们认识到git subtree不是Git原生支持的命令,而是第三方开发者通过Git的底层命令写出的一个高层次脚本,所以它是可以由基础的Git命令来实现的。

    git子仓库submodule与subtree区别

    subtree与submodule的作用是一样的,但是subtree出现得比submodule晚,它的出现是为了弥补submodule存在的问题:

    1. submodule不能在父版本库中修改子版本库的代码,只能在子版本库中修改,是单向的;

    2. submodule没有直接删除子版本库的功能;

    subtree则可以实现双向数据修改。官方推荐使用subtree替代submodule

    这里就先不提了

    使用Git subtree命令

    • 创建本地目录

      • 语法:`git remote add  <子仓库名> <子仓库地址>`

      • 实例:`git remote add component [email protected]`

    • 添加远程仓库(本地存在文件目录)

      • 语法:`git remote add -f <子仓库名> <子仓库地址>`

      • 实例:`git remote add -f component [email protected]`

    • 使用 ( pull & push )

      • pull:`git subtree pull --prefix=component component master --squash`

      • push:`git subtree push --prefix=component component master --squash`

    注意:**必须在 `component` 的父级目录执行**,使用起来还不是很方便。

    submodule可以一起clone出来,只需添加--recursive递归参数就可以了,而subtree并不行,只能手动添加

    个人使用submodule习惯了,所以天然觉得subtree复杂难用===难道就像用惯了intelliJ,就没有心思去用VScode了,哈哈!

    有人对 submodule 和 subtree 的区别做的一个总结还是挺形象的: submodule is link; subtree is copy

    更多的推荐阅读: Git应用详解第十讲:Git子库:submodule与subtree https://blog.csdn.net/qq_43425487/article/details/105632114


    Git的子仓库原理分析

    如果不是很了解底层原理,很可能会导致使用子仓库出现云里雾里的现象,搞不清楚是父级仓库先提交,还是子仓库先提交

    git submodule原理分析

    我们知道Git底层大致依赖了四种对象,构成了Git对于文件内容追踪的基础:

    • blob: 二进制大文件,可以通俗理解为对文件的修改

    • tree: 记录了blob对象和其他tree对象的修改,通俗理解为目录

    • commit: 提交对象,记录了本次提交的tree对象和父类的commit对象以及我们的提交信息

    • tag: 我们对当前提交记录版本的对象

    更加详细的内容请参考《深入理解Git

    我们此处需要依赖一个print_all_object的工具函数,它会帮助我们将git仓库下的这四种对象按照反向提交历史的排序展现出来,可以将它放在环境变量下方便全局使用:

    #!/bin/bash
    
    print_all_object() {
      for object in `git rev-list --objects --all | cut -d ' ' -f 1`; do
        echo 'SHA1: ' $object
        git cat-file -p $object
        echo '-------------------------------'
      done
    }
    
    print_all_object

    我们在main仓库下执行print_all_object:

    # 此时处于我们刚对子模块提交的那个时间点
    # 对部分长的hash进行了截取处理,不影响阅读观感
    print_all_object
    
    SHA1:  a1cfd26e
    tree c77ba9c2
    parent ab118b8
    
    feat: 增加子仓库依赖
    -------------------------------
    SHA1:  ab118b8
    tree f5771cd
    
    feat: 父级仓库创建index.js
    -------------------------------
    SHA1:  c77ba9c2
    100644 blob d8c9fb4    .gitmodules
    100644 blob ddd81ae    index.js
    160000 commit 40f8536  lib
    -------------------------------
    SHA1:  d8c9fb4
    [submodule "lib"]
            path = lib
            url = /path/to/repos/lib.git
    -------------------------------
    SHA1:  ddd81ae
    console.log('main');-------------------------------
    SHA1:  f5771cd
    100644 blob ddd81ae    index.js
    -------------------------------

    index.js文件是blob对象,对应的file mode是100644,但是对于lib子仓库的确是一个commit对象, file mode为160000,这是Git中一种特殊的模式,表明我们是将一次提交的commit记录在Git当中,而非将它记录成一个子目录或者文件。

    而这正式git submodule的核心原理,Git在处理submodule引用的时候,并不会去扫描子仓库下的文件的变化,而是取子仓库当前的HEAD指向的commit的hash值,当我们对子仓库进行了更改后,Git获取到子模块的commit值发生变化,从而记录了这个Git指针的变化。

    在暂存区所以我们才发现了new commits这种提示语,Git并不关心子模块的文件如何变化,我只需要在当前提交中记录子模块的commit的hash值即可,之后我们从父级仓库拉取子仓库的时候,Git拉取了本次提交记录中的子模块的hash值对应的提交,就还原了我们的整个仓库的代码。

    git submodule注意点

    虽然使用git submodule为我们的开发带来了很多便利,但是随之而来也会导致一些比较容易犯的错误,整理出来,防止大家采坑:

    1. 当子模块有提交的时候,没有push到远程仓库, 父级引用子模块的commit更新,并提交到远程仓库, 当别人拉取代码的时候就会报出子模块的commit不存在 fatal: reference isn’t a tree。

    2. 如果你仅仅引用了别人的子模块的游离分支,然后在主仓库修改了子仓库的代码,之后使用git submodule update拉取了最新代码,那么你在子仓库游离分支做出的修改会被覆盖掉

    3. 我们假设你一开始在主仓库并没有采用子模块的开发方式,而是在另外的开发分支使用了子仓库,那么当你从开发分支切回到没有采用子模块的分支的时候,子模块的目录并不会被Git自动删除,而是需要你手动的删除了。



    参考文章:

    Git submodule 子模块的管理和使用 https://www.jianshu.com/p/9000cd49822c

    使用Git Submodule管理子模块 https://segmentfault.com/a/1190000003076028

    Git子仓库深入浅出 https://juejin.im/post/6844904034722119694

    子模块 http://gitbook.liuhui998.com/6_2.html

    使用git subtree & submodule管理多个子项目 https://www.jianshu.com/p/84e34ac318e4

     Git应用详解第十讲:Git子库:submodule与subtree https://blog.csdn.net/qq_43425487/article/details/105632114

    git submoudle vs git subtree https://efe.baidu.com/blog/git-submodule-vs-git-subtree/





    转载本站文章《Git 工具 - 子模块: submodule与subtree的使用》,
    请注明出处:https://www.zhoulujun.cn/html/tools/VCS/git/8566.html