Hexo

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Git——vcstool使用笔记


整体说明

  • vcstool 是一个命令行工具,支持 Git、Mercurial、Subversion 和 Bazaar 等多种版本控制系统,能让用户能基于单一的工作区定义文件(.repos 或 .rosinstall)来批量管理多个代码仓库

  • 注:vcs 是 Version Control System 的简称

  • vcstool 的安装建议使用 conda(亲测好用)

    1
    conda install -c conda-forge vcstool
  • 安装后,使用 vcs 命令访问工具,而不是 vcstool

  • vcstool 是无状态的

    • 它不维护自己的状态文件,完全依赖于:
      • 文件系统中真实存在的仓库目录
      • 一个 .repos 配置文件(YAML 格式)

.repos 配置文件格式(YAML)

  • 所有导入导出操作都基于此格式,标准写法是(一般不用自己手动编辑):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    repositories:
    # 相对路径(克隆后存放的文件夹名)
    path/to/repo1:
    type: git
    url: https://github.com/user/repo1.git
    version: master # 分支名 / tag名 / commit哈希(可选,缺省则拉取默认分支)

    path/to/repo2:
    type: tar # 特殊类型:下载压缩包并解压
    url: https://example.com/archive.tar.gz
    # version: 可选

vcs 子命令全解

vcs export 导出仓库信息

  • vcs export 扫描当前或指定路径下的所有仓库,输出 .repos 格式的 YAML 信息(含路径、类型、URL、版本)
    1
    2
    3
    4
    5
    6
    7
    8
    # 按照分支名导出
    vcs export > my.repos

    # 按照 精确 Commit Hash 导出(若要存储精确使用版本时最推荐)
    vcs export --exact > my.repos

    # 优先 Tag 名 ,若无则用 Commit Hash,语义化版本发布,易读且精确,但这里可能比较慢
    vcs export --exact-with-tags > my.repos

vcs import 导入/克隆仓库

  • vcs import 根据定义文件(通过标准输入或 --input)批量克隆仓库到指定目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 读取 `my.repos`,将仓库克隆到 当前目录
    vcs import < my.repos

    # 读取文件,将所有仓库克隆到 `src` 子文件夹内
    vcs import src < my.repos

    # 指定 `--input` 读取文件,目标路径为当前目录(`.`)
    vcs import --input my.repos .

    # 直接传入 URL ,vcstool 会下载该文件并按此导入
    vcs import http://example.com/repos.yaml .
  • 注:tar 和 zip 类型的条目也会被自动下载并解压到对应路径

vcs pull/vcs push 拉取更新 或推送代码

  • vcs pull/vcs push 使用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # `vcs pull` 会进入每一个仓库,执行拉取最新代码(如 `git pull`)
    # 更新当前目录下所有仓库
    vcs pull

    # 更新 src 目录下所有仓库
    vcs pull src

    # `vcs push` 批量推送所有仓库的本地更改到远程
    # 批量推送
    vcs push

其他常用命令:查看状态与信息

  • 其他常用命令汇总:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 批量查看各仓库的本地修改状态(相当于挨个执行 `git status`)
    vcs status

    # 批量查看各仓库的代码差异(相当于 `git diff`)
    vcs diff

    # 批量查看各仓库的提交历史
    vcs log

    # 只显示各仓库**自上一个 Tag 以来的提交**(非常实用,用于生成版本发布日志)
    vcs log --limit-untagged

    # 批量查看各仓库的当前分支信息
    vcs branch

    # 批量查看各仓库配置的远程仓库地址(如 `git remote -v`)
    vcs remotes

    # 检查 YAML 格式文件(`.repos` 或 `.rosinstall`)的语法和格式是否正确
    # 若文件无错误,终端无输出(静默退出);若有错误,会报错提示
    vcs validate < my.repos

vcs custom 自定义命令

  • vcs custom 对所有仓库执行你自定义的原生 VCS 命令
  • 强烈建议 通过 --git、--svn 等限定仓库类型,避免执行到不支持的仓库上
    1
    2
    3
    4
    5
    6
    7
    8
    # 给所有 git 仓库添加文件
    vcs custom --git --args add .

    # 给所有 git 仓库提交,并附带信息
    vcs custom --git --args commit -m "Batch update from vcs"

    # 给所有 git 仓库抓取远程更新(不合并)
    vcs custom --git --args fetch --all

附录:通用参数(大部分子命令都支持)

  • 在命令后添加以下参数可以改变行为:
    参数 说明 示例
    path 位置参数 ,指定搜索仓库的根路径;若省略,默认为当前目录 . vcs status src
    --workers N 或 -w N 并行处理的线程数;默认 12 ,可根据机器性能调整 vcs import src -w 8 < my.repos
    --nested 或 -n 递归搜索子文件夹中的嵌套仓库(如 Git 子模块内的仓库) vcs pull --nested
    --skip-empty 或 -s 隐藏那些没有输出内容的仓库(让结果更清爽) vcs status -s
    --debug 显示详细的调试信息,用于排查错误 vcs pull --debug
    --repos 列出当前命令实际操作了哪些仓库(不执行具体操作) vcs pull --repos

附录:实操工作流

  • 以搭建新环境为例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 准备空间
    mkdir my_workspace && cd my_workspace

    # 导入所有依赖(假设团队给了 `team.repos`)
    # 从远程拉取所有项目源码,所有源码会被克隆到 `src` 目录下
    vcs import src -w 8 < team.repos

    # [导入时可选下面方法]
    # 从远程拉取时跳过本地已经存在的项目(常用命令),转而直接切换到指定的版本
    # 当版本在本地不存在时,它会自动与远程仓库交互来获取
    vcs import src --skip-existing < my.repos

    # 日常更新代码
    vcs pull src

    # 修改代码后,批量检查状态
    vcs status src

    # 项目发布前,锁定精确版本
    # 把这个文件提交到仓库,方便后续彻底复现
    vcs export --exact > release_v1.0.repos

实用技巧与避坑指南

  • tar / zip 的妙用 :
    • 如果依赖项是预编译的库压缩包,可以在 .repos 中将其 type 设为 tar 或 zip,vcs import 会自动下载解压,无需手动操作
  • 关于 path 参数(解答你的疑问) :
    • 所有命令末尾的路径(如 src)指的就是搜索根目录
    • vcs pull src 意思是“在 src 文件夹里找所有仓库并更新”
    • 如果省略,就是当前目录
  • 配合 Shell 自动补全(已支持 bash/zsh/tcsh/fish):
    • 安装完 vcstool 后,在 shell 配置文件中 source 对应的补全脚本,输入 vcs 后按 Tab 键即可提示子命令和参数,极大提升效率
  • 混用多种 VCS 无压力 :
    • 如果 src 下既有 Git 仓库又有 SVN 仓库,直接运行 vcs status 会同时检测并显示两者的状态,vcstool 会自动识别类型

Git——Submodule管理


整体说明

  • submodule 允许 git 灵活地将其他项目嵌入到当前项目,同时自由地切换 sumodule 分支和内容管理,且保持各自的版本独立
  • 本质上: submodule 还是作为一个独立的项目存在的,主项目管理子模块靠 .gitmodules 文件
  • 子模块默认跟踪的是固定 commit ID ,而非分支:
    • 若子仓库更新,主仓库需手动更新子模块的 commit ID 并提交;
  • .gitmodules 是子项目的核心文件
    • 这个文件必须纳入版本控制,否则他人克隆仓库时无法识别子模块;
  • 避免嵌套过深的子模块,否则会增加版本管理和协作的复杂度

添加子模块

  • 基础语法

    1
    2
    3
    git submodule add <子仓库URL> <本地存放路径(可选)>
    # 上述命令会自动添加 submodule 相关的必要文件,立刻直接 commit 即可添加 submodule 完成,注意这里如果先执行 checkout 等可能导致 submodule 信息无法对齐,建议立即 commit
    git commit "Add: submodule xxx"
    • <子仓库URL>:子模块的 Git 仓库地址(HTTPS/SSH 均可)
    • <本地存放路径>:子模块在当前仓库中的存放目录(省略则默认用仓库名)
  • 若需让子模块跟踪分支,可添加 -b <分支名> 参数:

    1
    git submodule add -b <branch> <子仓库URL> <路径>
  • submodule 的分支可以切换绑定(或新增绑定)

    1
    2
    3
    4
    git config -f .gitmodules submodule.<本地存放路径>.branch <branch>

    # 比如将 dev 分支绑定到名为 libs/sub-repo 的 submodule 下:
    git config -f .gitmodules submodule.libs/sub-repo.branch dev

示例

  • 示例:比如将 https://github.com/example/sub-repo.git 添加到当前仓库的 libs/sub-repo 目录:

    1
    2
    3
    4
    # 进入主仓库根目录
    cd /path/to/your/main-repo
    # 添加子模块
    git submodule add https://github.com/example/sub-repo.git libs/sub-repo
  • 执行上述命令后,Git 会自动完成以下操作:

    • 1)在指定路径(如 libs/sub-repo)拉取子仓库的代码;

      • 就像是普通的 git 项目一样,还包括 .git 目录
    • 2)在主仓库根目录生成 .gitmodules 文件(记录子模块配置),内容示例:

      1
      2
      3
      [submodule "libs/sub-repo"]
      path = libs/sub-repo
      url = https://github.com/example/sub-repo.git
    • 3)主仓库的暂存区会新增 .gitmodules 和 libs/sub-repo 两个条目(子模块条目是一个“链接”,记录子仓库的 commit ID)

添加后续提交子模块到主仓库

  • 添加子模块后,需要将 .gitmodules 和子模块条目提交到主仓库:
    1
    2
    3
    4
    # 提交变更
    git commit -m "添加子模块 sub-repo 到 libs 目录"
    # 推送到远程
    git push

克隆包含子模块的仓库

  • 直接 git clone 只会拉取子模块目录,但不会拉取子模块的代码,需执行:
    1
    2
    3
    4
    5
    6
    7
    8
    # 方式1:克隆时直接拉取子模块
    git clone --recurse-submodules <主仓库URL>

    # 方式2:先克隆主仓库,再初始化+更新子模块
    git clone <主仓库URL>
    cd main-repo
    git submodule init # 初始化子模块配置(读取.gitmodules)
    git submodule update # 拉取子模块的代码

git submodule update vs git submodule update --remote

  • git submodule update 命令还有个特殊参数 --remote,两者有很多容易犯错的区别
  • git submodule update
    • 用于 对齐主项目固定的子模块版本
    • 仅将子模块切换到主项目记录的哈希值(即 .gitmodules/.gitmodules 中固定的子模块版本);
    • 不会主动从子模块的远程仓库拉取新代码;
    • 若子模块本地无该哈希值的代码,会从远程克隆,但仅克隆该版本
    • 同步主项目指定的子模块版本(比如团队协作时,确保所有人用相同版本的子模块)
    • 执行 git submodule update 后:
      • 子模块处于「分离头指针(detached HEAD)」状态,且版本与主项目记录完全一致;
  • git submodule update --remote
    • 用于 更新子模块到远程最新版本
    • 先从子模块的远程仓库拉取最新代码(更新子模块的远程追踪分支);
    • 再将子模块切换到该远程分支的最新哈希值;
    • 会修改主项目中记录的子模块版本(需手动提交主项目的修改)
    • 主动更新子模块到远程最新版本(比如子模块有新功能/修复,需要同步到主项目)
    • 执行 git submodule update --remote 后:
      • 子模块仍处于「分离头指针」状态(仅指向远程最新哈希),主项目的 git 状态会显示子模块版本已修改
      • 需执行 git add <子模块路径> && git commit 才能将新的子模块版本记录到主项目
    • 慎用这个命令

git submodule update vs git submodule update --remote 示例

  • 假设主项目 main-proj 包含子模块 sub-proj:

  • 同步主项目指定的子模块版本:

    1
    git submodule update
  • 更新子模块到远程最新版本,并提交主项目的版本修改:

    1
    2
    3
    4
    git submodule update --remote sub-proj  # 拉取sub-proj远程最新代码,切到最新哈希
    # 提交主项目的修改
    git add sub-proj
    git commit -m "update sub-proj to latest remote version"

附录:初始化带有 submodule 的仓库详细理解

  • 正常拉取外层项目

    1
    git pull origin master:master
    • 此时除了 ./.gitmodules 文件包含关于子模块的信息外,其他的文件都不包含,包括 ./.git/ 中
  • 初始化子模块

    1
    git submodule init
    • 将 .gitmodules 中的所有子模块注册到外层项目中
    • 注册方式:添加子模块信息(文件夹路径和子模块项目地址)到 .git/config 文件中并指明子模块对应的 active = true
    • 注:如果子模块之前存在于 .git/config 中 且 active = false,这个初始化操作会修改为 active = true
  • 初始化指定子模块(其他子模块可以不初始化,也不会影响,未初始化的子模块会是一个空文件夹)

    1
    git submodule init <path_to_sub_module_name>
    • 仅初始化 <path_to_sub_module_name> 这个模块
      • 测试发现:注意初始化时 <path_to_sub_module_name> 是 submodule 的文件夹路径
      • 可以是相对路径或绝对路径,执行这个命令时需要在 submodule 的外面
  • 更新子模块

    1
    2
    3
    4
    # 更新所有子模块('.git/config' 和 '.gitmodules' 中的所有子模块)
    git submodule update
    # 更新单个路径下对应的模块
    git submodule update <path_to_sub_module_name>
    • 具体含义:根据主仓库中记录的 子模块 commit ID ,从子模块的远程仓库拉取对应版本的代码,并存放到主仓库指定的子模块路径中
    • 这行代码执行下面的操作:
      • 如果还没有下载,则所有子模块的链接地址项目下载到 .git/modules/ 中
      • 将对应的 commit ID checkout 到 submodule 文件夹(工作目录)中
      • 常常用来在切换分支后同步子模块数据
    • <path_to_sub_module_name> 模块参数的使用方法同上
  • 特别注意:

    • git submodule init 后,.git/config 和 .gitmodules 应该是一致的
    • .git/config 和 .gitmodules 中都有,且在 .git/config 中 active = true 的 submodule 才能被 update 操作下载和 checkout
  • 特别说明:解耦初始化 deinit

    1
    2
    3
    4
    5
    # 将 <path_to_sub_module_name> 初始化,后续执行 git submodule update 等命令时自动更新 <path_to_sub_module_name> 这个 submodule 
    git submodule init <path_to_sub_module_name>

    # 将 <path_to_sub_module_name> 解耦初始化,后续执行 git submodule update 等命令时不会再自动更新 <path_to_sub_module_name> 这个 submodule
    git submodule deinit <path_to_sub_module_name>

更新子模块到自己的最新 commit

  • 更新逻辑:
    1
    2
    3
    4
    5
    6
    7
    # 进入子模块目录
    cd libs/sub-repo
    git pull origin master # 拉取子仓库最新代码
    cd ../.. # 回到主仓库
    git add libs/sub-repo # 提交子模块的新 commit ID
    git commit -m "update: 子模块 sub-repo 同步到最新版本"
    git push

删除子模块

  • 若需移除子模块,步骤稍多(Git 无直接 git submodule delete 命令):
    1
    2
    3
    4
    5
    6
    7
    8
    # 1. 解除子模块关联
    git submodule deinit -f libs/sub-repo
    # 2. 删除 .git 中的子模块缓存
    rm -rf .git/modules/libs/sub-repo
    # 3. 删除工作区的子模块目录,这一步后会看到 .gitmodules 中的相关 submodule 也被删除了
    git rm -f libs/sub-repo
    # 4. 提交删除操作
    git commit -m "remove: 移除子模块 sub-repo"

附录:关于 Git submodule 的理解

  • submodule 自己知道自己被当做 submodule

    • 一个项目被作为 submodule 后,他的 ./submodule_name/.git 将不再是一个文件夹,而是一个指明 .git/ 文件夹路径的配置文件

      1
      cat ./submodule_name/.git

      gitdir: ../.git/modules/submodule_name

    • .git/文件夹可以在./.git/modules/submodule_name/.git/中找到

  • submodule 相关信息都在外层项目中显示出来

  • 在 submodule 文件夹./submodule_name/下, submodule 的更新,提交等操作正常按照一般项目进行即可

    • 这里操作时虽然仓库在外层项目的./.git/modules/submodule_name/.git/中,但是在 submodule 的目录下我们可以正常访问 submodule 的仓库
    • 也就是说在 submodule 文件夹下的git操作(add, commit)实际上不修改当前文件夹下的任何文件,修改都在外层项目的./.git/modules/submodule_name/.git/仓库中
  • 外层项目只存储

    • submodule 文件夹
    • 在./.gitmodules中存储 submodule 相关信息(文件夹路径与 submodule 远程地址)
    • 在GitHub中,直接用网页打开项目可以看到 submodule 会被自动解析远程地址和最近提交的ID信息,点击 submodule 对应的文件夹链接即可跳转到 submodule 远程仓库地址中

递归 submodule

  • 递归时记住项目的库都在父项目的库中即可
    • 这句话等价于所有项目的库都在根项目的 .git/ 中

特别说明

  • 非必要不建议使用 submodule

附录:移除 submodule .git 但内容保留到主项目

  • 如果 Git 项目下面有个 submodule 也是包含 Git 的(可能是 git clone 命令下载的)的,往往不能正常的提交和管理项目,这是因为项目变成了 Git submodule 了
  • 现象:如果 submodule 是 git clone 别人的项目,我们将 submodule 提交到整个大项目中时
    • 会提示:modified:xxx(modified content, untracked content)
    • 此时如果直接提交,那么远程仓库里面 submodule 将是空的
  • 若不想再保留 submodule 的 git 仓库,则需要删除 submodule 相关的所有信息

第一步:需要先删除子模块

  • 移除子模块:
    1
    2
    3
    4
    # 1. 解除子模块关联
    git submodule deinit -f libs/sub-repo
    # 2. 删除 .git 中的子模块缓存
    rm -rf .git/modules/libs/sub-repo

第二步:重新添加文件路径(当做普通的文件)

  • 重新添加 submodule 文件夹
    1
    git add xxx

git submodule 是否跟踪分支的区别

  • 在 Git 中使用子模块(Submodule)时,“不跟踪分支(默认行为/锁定特定提交)” 和 “跟踪分支” 的核心区别在于父项目(Superproject)如何决定子模块应该停留在哪个版本 ,以及 更新子模块时的流程

不跟踪分支(默认行为 / 锁定特定 Commit)

  • 这是 Git 子模块最原始也是最常用的工作方式
  • 父项目只关心:“子模块必须是 a1b2c3d 这个提交”,不在乎这个提交属于哪个分支,也不在乎这个提交是不是最新的
  • 当克隆父项目并运行 git submodule update 时,Git 会进入子模块目录,强制将其 checkout 到父项目记录的那个 SHA-1 哈希值
    • 注:此时,子模块处于 Detached HEAD(游离指针)状态
  • 不跟踪分支时,如果要切换到某个分支的最新 commit,需要执行如下操作:
    • 1)进入子模块目录:cd submodule_dir
    • 2)手动拉取或切换:git checkout master && git pull
    • 3)回到父项目目录:cd ..
    • 4)提交变更:git add submodule_dir -> git commit
  • 建议使用这种方式, 团队所有成员拉取代码后,得到的子模块代码完全一致,不会因为子模块远程仓库更新了代码而导致父项目构建失败

跟踪分支

  • 可通过配置 .gitmodules 文件来实现的一种更动态的模式

  • 在 .gitmodules 中明确告诉 Git:“这个子模块应该跟随 main (或 dev) 分支”

    • 也可以通过命令行绑定,如添加 submodule 时,或之后直接修改:

      1
      2
      3
      4
      5
      6
      7
      8
      # # 新建 submodule:
      git submodule add -b submodule_master https://github.com/xxx/lib.git
      # 此时 .gitmodules 中会添加 branch = submodule_master

      # # 已有 submodule 绑定某个 分支:
      git config -f .gitmodules submodule.<本地存放路径>.branch <branch>
      # 比如将 dev 分支绑定到名为 libs/sub-repo 的 submodule 下:
      git config -f .gitmodules submodule.libs/sub-repo.branch dev
    • .gitmodules 文件中会多一行配置:

      1
      2
      3
      4
      [submodule "my-lib"]
      path = my-lib
      url = https://github.com/example/my-lib.git
      branch = main # 多出来的配置
  • 虽然父项目在数据库中依然存储的是 SHA-1 哈希值,但当你使用特定参数更新时(update 时添加 --remote),Git 会忽略本地记录的哈希值,直接去拉取远程分支的最新代码

  • 跟踪分支时,如果要切换到某个分支的最新 commit,只需要执行如下操作:

    • 不需要进入子模块目录,只需在父项目根目录运行:

      1
      git submodule update --remote
      • Git 会自动去子模块的远程仓库抓取 branch 字段指定分支的最新提交,并将子模块更新到该提交
      • 注意:如果不添加 --remote 则只是切换到当前 commit_id 而不会拉取最新分支(这与不绑定分支的 git submodule update 执行含义完全相同)
    • 注:运行完上述命令后,父项目的状态会显示子模块有变化(指向了新的 Hash),仍然需要 在父项目中执行 git add 和 git commit 来固化这个变更

  • 如果开发的项目依赖另一个正在快速迭代的库,且总是希望使用该库的最新版本,这种方式可以简化更新流程,但是要小心使用

一些实操及理解

  • 新增 submodule 时,默认(不跟踪):

    1
    2
    git submodule add https://github.com/xxx/lib.git
    # 此时 .gitmodules 中没有 branch 字段
  • 新增 submodule 时,跟踪分支:

    1
    2
    git submodule add -b main https://github.com/xxx/lib.git
    # 此时 .gitmodules 中会添加 branch = main
  • 更新 submodule 默认(不跟踪):

    • 如果运行 git submodule update,什么都不会发生 ,因为它只会把子模块恢复到父项目当前记录的旧 Hash 值
  • 更新 submodule (若 .gitmodules 中已经跟踪分支):

    • 如果运行 git submodule update,什么都不会发生 ,因为它只会把子模块恢复到父项目当前记录的旧 Hash 值,与 .gitmodules 中是否已经跟踪分支无关
    • 如果运行 git submodule update --remote,Git 会检测到配置了分支,于是去拉取远程最新代码,并更新本地子模块的指针
  • 特别说明:

    • “跟踪分支” 不意味着自动更新:即使配置了跟踪分支,当 git pull 父项目时,子模块不会自动更新到远程最新
      • 必须显式执行 git submodule update --remote
    • “跟踪分支” 后父项目仍然会记录 Hash 值,Git 的底层数据结构决定了父项目永远 只记录子模块的 Commit Hash
      • 所谓“跟踪分支”,只是提供了一个快捷命令(--remote)来帮你自动找到那个最新的 Hash 值并 checkout 过去,省去了手动进入子目录 pull 的过程
    • 如果是引用第三方稳定的开源库 ,或者要求构建环境绝对可复现,使用默认(不跟踪) 模式
    • 如果是迭代很快的开发,且父项目需要时刻集成子模块的最新开发成果,使用跟踪分支 模式
1…279280281…352
San Ye

San Ye

Stay Hungry. Stay Foolish.

704 posts
53 tags
© 2026 San Ye
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4