Google Git-Repo 多倉庫專案管理

鄭曉鵬-Rocko發表於2019-03-04

原文: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

相關文章