在软件开发中,可能会遇到多个 Git 仓库需要合并的情况,例如:

  • 合并多个独立项目,形成一个更大的项目。
  • 将外部项目作为子项目,在不影响原有仓库的情况下进行管理。
  • 拆分单个仓库为多个仓库,提高项目的模块化程度。

Git 提供了多种方法来管理和合并多个仓库,如手动导入、git subtreegit submodule 等。本章将详细介绍这些方法,并分析其适用场景。


17.1 旧解决方案:部分检出(Partial Checkout)

早期,开发者通常使用部分检出(Partial Checkout)或手动复制代码的方式来合并项目。这种方法的主要问题包括:

  1. 代码历史丢失:如果直接复制代码,原始项目的提交历史不会被保留。
  2. 更新同步困难:如果子项目有更新,需要手动复制更新内容,不够灵活。
  3. 无法进行版本管理:无法使用 Git 进行子项目的版本控制。

尽管部分检出在某些简单场景下仍然适用,但 Git 提供了更好的替代方案,如 git subtreegit submodule


17.2 显而易见的解决方案:将代码导入子项目

Git 提供了一种更直观的方法——直接将外部代码作为子项目引入

17.2.1 手动复制导入子项目

如果子项目仅作为代码的一部分,并且无需保留其独立性,可以直接手动复制:

  1. cp -r ../external-repo my-project/external
  2. git add my-project/external
  3. git commit -m "添加 external 代码"

但这种方法无法保留原始仓库的提交历史,适用于不需要版本管理的子项目。

17.2.2 通过 git pull --s subtree 导入子项目

git subtree 是 Git 提供的一种用于管理子项目的工具,它允许在主仓库中包含另一个 Git 仓库,同时保留其历史记录。

导入子项目(subtree)

  1. git subtree add --prefix=subproject https://github.com/example/external-repo.git main --squash
  • --prefix=subproject:指定子项目存放的路径。
  • --squash:合并子项目的提交历史,减少提交记录的数量。

拉取子项目的更新

  1. git subtree pull --prefix=subproject https://github.com/example/external-repo.git main --squash

推送子项目的修改

  1. git subtree push --prefix=subproject https://github.com/example/external-repo.git main

17.2.3 将更改提交到上游

如果子项目有改动,并且希望提交到上游仓库,可以执行以下操作:

  1. git subtree push --prefix=subproject upstream main

这将 subproject 目录的内容推送到 upstream 仓库的 main 分支。


17.3 自动化解决方案:使用自定义脚本检测子项目

在大型项目中,可以使用 Git 钩子(Hooks)或 CI/CD 自动检测和同步子项目。例如,使用 pre-commit 钩子来检查子项目是否有未提交的更改:

  1. vim .git/hooks/pre-commit
  1. #!/bin/bash
  2. if [ -n "$(git status --porcelain=subproject)" ]; then
  3. echo "子项目 subproject 有未提交的更改!请先提交。"
  4. exit 1
  5. fi

然后赋予可执行权限:

  1. chmod +x .git/hooks/pre-commit

17.4 原生解决方案:gitlinkgit submodule

Git 还提供了 git submodulegitlink 来管理子项目,适用于子项目需要独立维护的情况。

17.4.1 gitlink

gitlink 是 Git 存储子模块引用的一种方式,它仅存储子项目的提交哈希,而不包含完整的子项目代码。它的主要优点是可以让主仓库保持轻量级。

17.4.2 git submodule 命令

git submodule 允许在主仓库中引用另一个 Git 仓库,而不会直接存储其代码。

添加子模块

  1. git submodule add https://github.com/example/external-repo.git subproject
  2. git commit -m "添加子模块 subproject"

初始化和更新子模块(克隆仓库后需要执行):

  1. git submodule update --init --recursive

拉取子模块的更新

  1. cd subproject
  2. git pull origin main
  3. cd ..
  4. git commit -am "更新子模块 subproject"

删除子模块

  1. git submodule deinit -f subproject
  2. rm -rf .git/modules/subproject
  3. git rm -f subproject

结论

本章介绍了 Git 处理多个项目的不同方法,包括:

  • 手动复制代码(适用于简单场景,但无法保留历史)。
  • git subtree(适用于需要合并子项目代码,但希望保持子项目历史)。
  • git submodule(适用于子项目需要独立管理的情况)。

如果子项目需要独立维护,建议使用 git submodule;如果希望子项目的代码直接集成到主仓库,建议使用 git subtree