Google Git-Repo 多仓库项目管理

8,846 阅读8分钟

原文:Google Git-Repo 多仓库项目管理

前言

项目模块化/组件化之后各模块也作为独立的 Git 仓库从主项目里剥离了出去,各模块各自管理自己的版本。正常 Android 项目,各剥离出去的子模块仓库则通过 Maven 仓库 来管理,然后和引入第三方库一样依赖到主项目里。这种状态下的项目迭代带来的问题会是:需要频繁发布子模块的 版本并需要把修改的版本提交 Merge Request 到主模块里,尤其像我们一周一版的快速迭代的情况下,问题尤为凸显。此外还有个问题就是 Debug/断点 会变得不太方便,因为可能子模块的源码并未发到 Maven 上。

在上面所说的状态下工作了一段时间后,需要找到个方案来解决这个问题。遂尝试在多仓库管理的方向上尝试。支持 Git 多仓库的管理工具也有好几个:git-repogit-submodulegitslavegit-subtree,看了一番,除了 git-repo 外的几个要么 子模块需要手动指定本地仓库路径、要么 主仓库与子仓库会产生污染 且操作不够简便化。所以最终就采用了 Google 的 Git-repo(Repo)

Git-repo

Repo 是对 Git 构成补充的多(可以巨多的那种)代码库管理工具,简单说就是使用 Python 在 Git 基础上开发的一系列脚本命令。当前整个 Android 项目(AOSP)就是通过 repo 来管理,最新版本的仓库大约 七百多个,可见 Repo 在多仓库的代码管理和版本管理上是可以支撑现有我们的项目的。接下来描述分析下迁移使用 Repo 的过程和一些解释。

环境准备

准备好梯子,把 repo 装到系统里:

mkdir ~/bin
PATH=~/bin:$PATH  # 自己加到 ~/.zshrc or ~/.bashrc or /etc/profile 里 

curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

# 最后 source 下环境变量

然后就可以操作项目了,如初始化 AOSP 项目:

repo init -u https://android.googlesource.com/platform/manifest -b android-4.0.1_r1

Manifest

Repo 管理的核心就在于 Manifest,每个采用 repo 管理的复杂多仓库项目都需要一个对应的 manifest 仓库,如 AOSP 的 manifest ,此仓库用来存储所有子仓库的配置信息,repo 也是读取此仓库的配置文件来进行管理操作。里面的配置就是 xml 定义的结构,一般有两个主要的配置:子仓库用到的仓库地址(remote)、子仓库详细配置信息(project)。举例如下:

<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <remote  name="remote1"
           alias="origin"
           fetch=".."
           review="https://android-review.googlesource.com/" />

  <remote  name="remote2"
           alias="origin"
           fetch="git@github.com:group2/"
           review="https://android-review2.googlesource.com/" />         

  <default revision="master"
           remote="remote1"
           sync-j="4" />

  <project path="build/make" name="platform/build" groups="pdk" >
    <copyfile src="core/root.mk" dest="Makefile" />
    <linkfile src="CleanSpec.mk" dest="build/CleanSpec.mk" />
  </project>
  <project path="build/blueprint" name="platform/build/blueprint" groups="pdk,tradefed" revision="other_branch" remote="remote1"/>

<!-- ... -->

</manifest>

解释下常用的各节点:

remote

远程仓库地址配置,可以多个。

  • name: 名字,也用于子仓库的 git remote 名称(.git/config 里的 remote)
  • alias: 别名,可省略,建议设为 origin, 设置了那么子仓库的 git remote 即为此名,方便不同的 name 下可以最终设置生成相同的 remote 名称
  • fetch: 仓库地址前缀,即 project 的仓库地址为: remote.fetch + project.name
  • pushurl: 一般可省略,省略了则直接用 fetch
  • review: Gerrit code review 的地址,如果没有用 Gerrit 则不需要配置(也就不能用 repo upload 命令了)
  • revision: 使用此 remote 的默认分支

这里的 fetch 遇到个坑git@github.com:group2/ 这样 git scheme 开头的地址会有问题(ssh/https 的正常),最终子仓库在本地的 remote 地址一直是 manifest 的地址拼上 fetch 再加 project 的 name (manifest.git_path + remote.fetch + project.name)。最终发现是 git-repo 里的代码 manifest_xml.py 有问题,需要 fix 下,简单的把 78 行注释掉,然后推到自己的仓库,指定下系统环境变量:

REPO_URL=your_fix_git-repo_git_url
# repo 脚本会使用此变量的地址

project

子项目仓库配置,可以多个。

  • path: repo sync 同步时,相对于根目录的子仓库文件夹路径
  • name: 子仓库的 git 仓库名称
  • group: 分组
  • revision: 使用的分支名
  • clone-depth: 仓库同步 Git 的 depth
copyfile

project 的子节点属性.

  • src: project 下的相对路径
  • dest: 整个仓库根路径下的相对路径
linkfile

project 的子节点属性,类似 copyfile,只是把复制文件变为创建链接文件。

default

project 没有设置属性时会使用的默认配置,常用的是 remote 和 revision。

其他

其他现在暂时用的较少,详细完整的 manifest 格式说明请看官方文档。 了解了 Repo 下的 Manifest 配置仓库的概念后就可以根据自己的项目来创建了,Just do it!

local_manifest

local_manifest 简单说就是一个比 repo init 时设置的 manifest 有更高优先级的本地配置,一般用在不改动远程 manifest 配置又想设置到本地的专属配置时用得到。版本高一点的 repo 下,在工作根目录新建配置文件即可: .repo/local_manifests/local_manifest.xml

有一点需要注意的是如果需要覆盖主 manifest 文件已有 project 的配置那么需要先 remove-project 才行,不然会报错:

fatal: duplicate path project_name in .repo/manifest.xml

fix :

<remove-project name="project_name" />
<!-- 再重写 project 配置 -->
<project path="xxx" name="xxx" revision="xxxx" remote="xxxx"  />

Repo 命令

repo init -u yout_manifest_git_url 初始化了你的项目 repo 工作区后,repo sync,你就可以进入正常的特性开发状态了。那么我们以开发一个 feature 为例,顺序说明下需要使用到的常用命令,也是我现在尝试使用的 repo workflow 了。

repo init

初始化。

repo init -u your_project_git_url

其他常用可选参数:

  • -b 选取的 manifest 仓库分支,默认 master
  • -m 选取的默认配置文件,默认 default.xml
  • --depth=1 git clone 的深度,一般如在 Jenkins 上打包时可用,加快代码 clone 速度
  • --repo-url=URL 使用自定义的 git-repo 代码,如前面说到的 fix 了 bug 的 git-repo
  • --no-repo-verify 不验证 repo 的源码,如果自定义了 repo url 那么这个一般也加上

repo sync

初始化好一个 repo 工作目录后下一步就是把代码同步下来了:

repo sync

同步完成后就会在当前目录下生成 manifest 里配置的各仓库和其对应目录。其他常用可选参数:

  • -f 不阻断同步,即一个 project 失败会继续下一个 project 同步
  • --force-sync 如果需要,强制覆盖现有的 git 目录指向不同的对象目录。此操作可能会导致数据丢失
  • -d 把项目回退到 manifest 里配置的分支
  • -m 手动指定当前操作使用哪个 manifest 文件
  • -t 使用对应 tag 里的 manifest 文件

同步完后你就可以用你的 IDE 打开项目了。此时,你需要根据当前的子仓库目录配置,在项目里处理下子仓库的依赖问题,比如 Android 项目,在 settting.gradle 里就需要根据此 repo 仓库的项目目录把子仓库 include 进来。

repo start

从 manifest 配置文件中指定的分支里,创建一个新的分支进行开发,如:

repo start your_feature_branch_name --all | [project1 project2]

后续如果发现还改动到了其他模块仓库那么再 start 一个对应 project 的分支

Work in progress

开发过程中需要用到的常用命令:

  • repo status: 跟 git status 类似,会把当前 repo 工作区的状态信息列出来
  • repo diff: 同理 git diff
  • repo forall <PROJECT_LIST> -c : 在(所有)子仓库下执行命令,比如 repo 没有类似 git stash 的命令,利用 forall 就可以实现:repo forall -c git stash
  • repo prune: 删除已经合并分支
  • repo stage: 把文件添加到 index 表(暂存区)中
  • repo manifest: 显示当前使用的 manifest 信息内容

repo upload

提交代码到 Gerrit code review 中,如果没有使用 Gerrit,则可以跟之前那样手动操作单个仓库 push 或者使用 repo forall -c git push xxx

Other

其他更详细的参考可以查看 Repo 命令参考资料

Workflow

上面一节介绍的 repo 命令的顺序过程可以说就是一个常用的 repo 工作流程,AOSP 给的开发流程可以先看下: 开发

Git 和 Repo 快速参考表

那么根据上一节的介绍,给出下当前尝试使用的 repo-workflow 的图示:

Git-repo workflow

具体到每个仓库来说,主仓库位置原有的 git workflow 不用变,各子仓库新增 devbuild 分支,那么对应的合版打包与发版打包对应 Manifest 固定的分支配置就行,如果没有项目删减,那么合版与发版时的子模块仓库版本也是不用动的。避免了频繁的合版时,各模块版本频繁/繁琐的修改合并。

Jenkins

使用 Jenkins 的需要使用 repo 插件,也还算挺方便的,稍微由原来的 git 迁移到 repo 的配置就行。 配合 Jenkins 的自动化打包,加一些自动化的打包配置,如远程参数化构建、自定义的 Gradle 打包插件等,如果需要在 Jenkins 上打 Feature 包的,那么在 Manifest 项目里可以不需要再新建 Feature 分支了。所以综合来看,合版时的工作量减轻了不少~

End

参考:

AOSP 文档

Git多项目管理

git-repo README