代碼管理工具git學習筆記04--repo的入門和使用

repo是什麼

Repo是Android爲了方便管理多個git庫而開發的Python腳本。repo的出現,並非爲了取代git,而是爲了讓Android開發者更爲有效的利用git。

Android官方推薦下載repo的方法是通過Linux curl命令,下載完成後,爲repo腳本添加可執行權限:

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

 repo的工作原理

repo需要關注當前git庫的數量、名稱、路徑等,有了這些基本信息,才能對這些git庫進行操作。通過集中維護所有git庫的清單,repo可以方便的從清單中獲取git庫的信息。這份清單會隨着版本演進升級而產生變化,同時也有一些本地的修改定製需求,所以,repo是通過一個git庫來管理項目的清單文件的,這個git庫名字叫manifest。

在客戶端使用repo初始化一個項目時,就會從遠程把manifest和repo這兩個git庫拷貝到本地,但這對於Android開發人員來說,又是近乎無形的(一般通過文件管理器,是無法看到這兩個git庫的)。repo將自動化的管理信息都隱藏根目錄的.repo子目錄中。

repo項目清單庫(.repo/manifests)

AOSP項目清單git庫的default.xml

<?xml version="1.0" encoding="UTF-8"?> 
<manifest>
    <remote name="aosp" fetch=".." review="https://android-review.googlesource.com/" /> 
    <default revision="master" remote="aosp" sync-j="4" /> 
    <project path="build" name="platform/build" groups="pdk,tradefed" >
        <copyfile src="core/root.mk" dest="Makefile" /> 
    </project> 
    <project path="abi/cpp" name="platform/abi/cpp" groups="pdk" /> 
    <project path="art" name="platform/art" groups="pdk" /> ... 
    <project path="tools/studio/translation" name="platform/tools/studio/translation" groups="notdefault,tools" /> 
    <project path="tools/swt" name="platform/tools/swt" groups="notdefault,tools" /> 
</manifest> 

 <remote>:描述了遠程倉庫的基本信息。name描述的是一個遠程倉庫的名稱,通常我們看到的命名是origin;fetch用作項目名稱的前緣,在構造項目倉庫遠程地址時使用到;review描述的是用作code review的server地址

<default>:default標籤的定義的屬性,將作爲<project>標籤的默認屬性,在<project>標籤中,也可以重寫這些屬性。屬性revision表示當前的版本,也就是我們俗稱的分支;屬性remote描述的是默認使用的遠程倉庫名稱,即<remote>標籤中name的屬性值;屬性sync-j表示在同步遠程代碼時,併發的任務數量,配置高的機器可以將這個值調大

<project>:每一個repo管理的git庫,就是對應到一個<project>標籤,path描述的是項目相對於遠程倉庫URL的路徑,同時將作爲對應的git庫在本地代碼的路徑; name用於定義項目名稱,命名方式採用的是整個項目URL的相對地址。 譬如,AOSP項目的URL爲https://android.googlesource.com/,命名爲platform/build的git庫,訪問的URL就是https://android.googlesource.com/platform/build

如果需要新增或替換一些git庫,可以通過修改default.xml來實現,repo會根據配置信息,自動化管理。但直接對default.xml的定製,可能會導致下一次更新項目清單時,與遠程default.xml發生衝突。 因此,repo提供了一個種更爲靈活的定製方式local_manifests:所有的定製是遵循default.xml規範的,文件名可以自定義,譬如localmanifest.xml, anotherlocalmanifest.xml等, 將定製的XML放在新建的.repo/localmanifests子目錄即可。repo會遍歷.repo/local_manifests目錄下的所有*.xml文件,最終與default.xml合併成一個總的項目清單文件manifest.xml。

#local_manifests的修改示例如下:
ls .repo/localmanifests localmanifest.xml anotherlocalmanifest.xml

cat .repo/localmanifests/localmanifest.xml

<?xml version="1.0" encoding="UTF-8"?> 
<manifest> 
    <project path="manifest" name="tools/manifest" />
    <project path="platform-manifest" name="platform/manifest" /> 
</manifest>

repo腳本庫(.repo/repo)

repo對git命令進行了封裝,提供了一套repo的命令集(包括init, sync等),所有repo管理的自動化實現也都包含在這個git庫中。 在第一次初始化的時候,repo會從遠程把這個git庫下載到本地。

倉庫目錄和工作目錄

倉庫目錄保存的是歷史信息和修改記錄,工作目錄保存的是當前版本的信息。一般來說,一個項目的Git倉庫目錄(默認爲.git目錄)是位於工作目錄下面的,但是Git支持將一個項目的Git倉庫目錄和工作目錄分開來存放。 對於repo管理而言,既有分開存放,也有位於工作目錄存放的:

manifests: 倉庫目錄有兩份拷貝,一份位於工作目錄(.repo/manifests)的.git目錄下,另一份獨立存放於.repo/manifests.git

repo:倉庫目錄位於工作目錄(.repo/repo)的.git目錄下

project:所有被管理git庫的倉庫目錄都是分開存放的,位於.repo/projects目錄下。同時,也會保留工作目錄的.git,但裏面所有的文件都是到.repo的鏈接。這樣,即做到了分開存放,也兼容了在工作目錄下的所有git命令。

既然.repo目錄下保存了項目的所有信息,所有要拷貝一個項目時,只是需要拷貝這個目錄就可以了。repo支持從本地已有的.repo中恢復原有的項目。

使用介紹

repo命令的使用格式如下:

#可選的的有:help、init、sync、upload、diff、download、forall、prune、start、status,每一個命令都有實際的使用場景
repo <COMMAND> <OPTIONS>

init

repo init -u <URL> [<OPTIONS>]
#-u:指定manifests這個遠程git庫的URL,manifests庫是整個項目的清單。默認情況,這個git庫只包含了default.xml一個文件,其內容可以參見Android的樣本
#-m, –manifest-name:指定所需要的manifests庫中的清單文件。默認情況下,會使用maniftests/default.xml
#-b, –manifest-branch:指定manifest.xml文件中的一個版本,,也就是俗稱的“分支”

#運行該命令後,會在當前目錄下新建一個.repo子目錄:
.repo 
    ├── manifests # 一個git庫,包含default.xml文件,用於描述repo所管理的git庫的信息 
    ├── manifests.git # manifest這個git庫的實體,manifest/.git目錄下的所有文件都會鏈接到該目錄 
    ├── manifest.xml # manifests/default.xml的一個軟鏈接 
    └── repo # 一個git庫,包含repo運行的所有腳本

#執行repo命令時,可以通過--trace參數,來看實際發生了什麼。
repo --trace init -u $URL -b $BRANCH -m $MANIFEST

mkdir .repo; 
cd .repo 
git clone --bare $URL manifests.git 
git clone https://android.googlesource.com/tools/repo 
mkdir -p manifests/.git; 
cd manifests/.git 
for i in ../../manifests.git/*; do 
    ln -s $ı .; 
done 
cd .. 
git checkout $BRANCH -- . 
cd .. 
ln -s manifests/$MANIFEST manifest.xml `

我們還介紹幾個不常用的參數,在國內下載Android源碼時,會用到:
–repo-url:指定遠程repo庫的URL,默認情況是https://android.googlesource.com/tools/repo,但國內訪問Google受限,會導致這個庫無法下載,從而導致repo init失敗,所以可以通過該參數指定一個訪問不受限的repo地址

–repo-branch:同manifest這個git庫一樣,repo這個git庫也是有版本差異的,可以通過該參數來指定下載repo這個遠程git庫的特定分支

–no-repo-verify:在下載repo庫時,會對repo的源碼進行檢查。通過–repo-url指定第三方repo庫時,可能會導致檢查不通過,所以可以配套使用該參數,強制不進行檢查

sync

repo sync [PROJECT_LIST]
#當本地的git庫是第一次觸發同步操作時,那麼,該命令等價於git clone,會將遠程git庫直接拷貝到本地
#當本地已經觸發過同步操作時,那麼,該命令等價於git remote update && git rebase origin/<BRANCH>,<BRANCH>就是當前與本地分支所關聯的遠程分支 代碼合併可能會產生衝突,當衝突出現時,只需要解決完衝突,然後執行git rebase --continue即可。

#在一些場景下,我們會用到sync命令的一些參數:

#-j:開啓多線程同步操作,這會加快sync命令的執行速度。默認情況下,使用4個線程併發進行sync
#-c, –current-branch:只同步指定的遠程分支。默認情況下,sync會同步所有的遠程分支,當遠程分支比較多的時候,下載的代碼量就大。使用該參數,可以縮減下載時間,節省本地磁盤空間
#-d, –detach:脫離當前的本地分支,切換到manifest.xml中設定的分支。在實際操作中,這個參數很有用,當我們第一次sync完代碼後,往往會切換到dev分支進行開發。如果不帶該參數使用sync, 則會觸發本地的dev分支與manifest設定的遠程分支進行合併,這會很可能會導致sync失敗
#-f, –force-broken:當有git庫sync失敗了,不中斷整個同步操作,繼續同步其他的git庫
#–no-clone-bundle:在向服務器發起請求時,爲了做到儘快的響應速度,會用到內容分發網絡(CDN, Content Delivery Network)。同步操作也會通過CDN與就近的服務器建立連接, 使用HTTP/HTTPS的$URL/clone.bundle來初始化本地的git庫,clone.bundle實際上是遠程git庫的鏡像,通過HTTP直接下載,這會更好的利用網絡帶寬,加快下載速度。 當服務器不能正常響應下載$URL/clone.bundle,但git又能正常工作時,可以通過該參數,配置不下載$URL/clone.bundle,而是直接通過git下載遠程git庫

upload

repo upload [PROJECT_LIST]
#upload命令首先會找出本地分支從上一次同步操作以來發生的改動,然後會將這些改動生成Patch文件,上傳至Gerrit服務器。 如果沒有指定PROJECT_LIST,那麼upload會找出所有git庫的改動;如果某個git庫有多個分支,upload會提供一個交互界面,提示選擇其中若干個分支進行上傳操作。
#upload並不會直接將改動合併後遠程的git庫,而是需要先得到Reviewer批准。Reviewer查看改動內容、決定是否批准合入代碼的操作,都是通過Gerrit完成。 Gerrit服務器的地址是在manifests中指定的:打開.repo/manifest.xml,<remote>這個XML TAG中的review屬性值就是Review服務器的URL:
<remote name="aosp" fetch=".." review="https://android-review.googlesource.com/" />
#Gerrit的實現機制不是本文討論的內容,但有幾個與Gerrit相關的概念,是需要代碼提交人員瞭解的:
#Reviewer:代碼審閱人員可以是多個,是需要人爲指定的。Gerrit提供網頁的操作,可以填選Reviewer。當有多個git庫的改動提交時,爲了避免在網頁上頻繁的填選Reviewer這種重複勞動, upload提供了–re, –reviewer參數,在命令行一次性指定Reviewer
#Commit-ID:git爲了標識每個提交,引入了Commit-ID,是一個SHA-1值,針對當次提交內容的一個Checksum,可以用於驗證提交內容的完整性
#Change-ID:Gerrit針對每一個Review任務,引入了一個Change-ID,每一個提交上傳到Gerrit,都會對應到一個Change-ID, 爲了區分於Commit-ID,Gerrit設定Change-ID都是以大寫字母 “I” 打頭的。 Change-ID與Commit-ID並非一一對應的,每一個Commit-ID都會關聯到一個Change-ID,但Change-ID可以關聯到多個Commit-ID
#Patch-Set:當前需要Review的改動內容。一個Change-ID關聯多個Commit-ID,就是通過Patch-Set來表現的,當通過git commit --amend命令修正上一次的提交併上傳時, Commit-ID已經發生了變化,但仍可以保持Change-ID不變,這樣,在Gerrit原來的Review任務下,就會出現新的Patch-Set。修正多少次,就會出現多少個Patch-Set, 可以理解,只有最後一次修正纔是我們想要的結果,所以,在所有的Patch-Set中,只有最新的一個是真正有用的,能夠合併的。

download

repo download <TARGET> <CHANGE>
#upload是把改動內容提交到Gerrit,download是從Gerrit下載改動。與upload一樣,download命令也是配合Gerrit使用的。
#<TARGET>:指定要下載的PROJECT,譬如platform/frameworks/base, platform/packages/apps/Mms
#<CHANGE>:指定要下載的改動內容。這個值不是Commit-ID,也不是Change-ID,而是一個Review任務URL的最後幾位數字。 譬如,AOSP的一個Review任務https://android-review.googlesource.com/#/c/23823/,其中23823就是<CHANGE>。

forall

repo forall [PROJECT_LIST] -c <COMMAND>
#對指定的git庫執行-c參數制定的命令序列。在管理多個git庫時,這是一條非常實用的命令。PROJECT_LIST是以空格區分的,譬如:
repo forall frameworks/base packages/apps/Mms -c "git status"
#該命令的還有一些其他參數:
#-r, –regex: 通過指定一個正則表達式,只有匹配的PROJECT,纔會執行指定的命令
#-p:輸出結果中,打印PROJECT的名稱

prune

repo prune [<PROJECT_LIST>] 
#刪除指定PROJECT中,已經合併的分支。當在開發分支上代碼已經合併到主幹分支後,使用該命令就可以刪除這個開發分支。
#隨着時間的演進,開發分支會越來越多,在多人開發同一個git庫,多開發分支的情況會愈發明顯,假設當前git庫有如下分支:
* master 
dev_feature1_201501 # 已經合併到master 
dev_feature2_201502 # 已經合併到master 
dev_feature3_201503 # 正在開發中,還有改動記錄沒有合併到master
#那麼,針對該git庫使用prune命令,會刪除devfeature1201501和devfeature2201502。
#定義刪除無用的分支,能夠提交團隊的開發和管理效率。prune就是刪除無用分支的”殺手鐗“。

start

repo start <BRANCH_NAME> [<PROJECT_LIST>]
#在指定的PROJECT的上,切換到<BRANCHNAME>指定的分支。
#可以使用–all參數對所有的PROJECT都執行分支切換操作。 
#該命令實際上是對git checkout命令的封裝,<BRANCHNAME>是自定義的,它將追蹤manifest中指定的分支名。
#當第一次sync完代碼後,可以通過start命令將git庫切換到開發分支,避免在匿名分支上工作導致丟失改動內容的情況。

status

repo status [<PROJECT_LIST>]
#status用於查看多個git庫的狀態。實際上,是對git status命令的封裝。

 

使用實踐

Android推薦的開發流程是:

  1. repo init初始化工程,指定待下載的分支
  2. repo sync下載代碼
  3. repo start將本地git庫切換到開發分支(TOPIC BRANCH)
  4. 在本地進行修改,驗證後,提交到本地
  5. repo upload上傳到服務器,等待review

對項目清單文件進行定製

可以通過以下命令導出當前的清單文件,最終snapshot.xml就是融合後的版本:

repo manifest -o snapshot.xml -r
cp snapshot.xml .repo/manifests/ 
repo init -m snapshot.xml 
# -m 參數表示自定義manifest 
repo sync -d 
# -d 參數表示從當前分支脫離,切換到manifest中定義的分支 

解決無法下載Android源碼

由於google訪問受限的緣故,會導致init時,無法下載manifests和repo。這時候,可以使用init-u–repo-url參數,自定義這兩個庫的地址,輔以–no-repo-verify來繞過代碼檢查。

repo init --repo-url [PATH/TO/REPO] -u [PATH/TO/MANIFEST] -b [BRANCH] --no-repo-verify $ repo sync

更快更省的下載遠程代碼

如果實際開發過程中,需要用到另外一個分支,而又不想被其他分支幹擾,可以在已有的工程根目錄下,使用如下命令:

repo manifest -o snapshot.xml -r $ repo init -u [PATH/TO/MANIFEST] -b [ANOTHER_BRANCH] $ repo sync -c -d

如果本地已經有一份Android源碼,假設路徑爲~/android-exsit,想要下載另一份新的Android源碼,通過–reference參數,在數分鐘以內,就能將代碼下載完畢:

mkdir ~/android-new && cd ~/android-new $ repo init --reference=~/android-exsit -u [PATH/TO/MANIFEST] -b [BRANCH] $ repo sync -c 

避免在匿名分支上工作

#在sync完代碼後,所有git庫默認都是在一個匿名分支上(no branch),很容易會由於誤操作導致丟失代碼修改。可以使用如下命令將所有的git庫切換到開發分支:
repo start BRANCH --all

使用upload提交代碼

開發人員可能同時在多個git庫,甚至多個分支上,同時進行修改,針對每個git庫單獨提交代碼是繁瑣的。可以使用如下命令,一併提交所有的修改:

repo upload

如果需要省去Gerrit上填寫reviewer的操作,可以使用–reviewer參數指定Reviewer的郵箱地址:

repo upload --reviewer="[email protected]"

定期刪除已經合併的開發分支

開發分支會越來越多,而一些已經合併到主幹的開發分支是沒有存在價值的,可以通過prune命令定期刪除無用的開發分支:

repo prune [PROJECT_LIST]

同時操作多個git庫

對於部分開發人員而言,同時操作多個git庫是常態,如果針對每個git庫的操作命令都是相同的,那麼可以使用如下命令一次性完成所有操作:

repo forall -c "git branch | grep tmp | xargs git branch -D; git branch"

參數-c指定的命令序列可以很複雜,多條命令只需要用“;”間隔。

參考文獻:

https://duanqz.github.io/2015-06-25-Intro-to-Repo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章