1. 概要
repo是Android為了方便管理多個(gè)git庫(kù)而開(kāi)發(fā)的Python腳本。repo的出現(xiàn),并非為了取代git,而是為了讓Android開(kāi)發(fā)者更為有效的利用git。
Android源碼包含數(shù)百個(gè)git庫(kù),僅僅是下載這么多git庫(kù)就是一項(xiàng)繁重的任務(wù),所以在下載源碼時(shí),Android就引入了repo。 Android官方推薦下載repo的方法是通過(guò)Linux curl命令,下載完后,為repo腳本添加可執(zhí)行權(quán)限:
$ curlhttps://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
$ chmod a+x~/bin/repo
由于國(guó)內(nèi)Google訪問(wèn)受限,所以上述命令不一定能下載成功。其實(shí),我們現(xiàn)在可以從很多第三方渠道找到repo腳本,只需要取下來(lái),確保repo可以正確執(zhí)行即可。
2. 工作原理
repo需要關(guān)注當(dāng)前git庫(kù)的數(shù)量、名稱、路徑等,有了這些基本信息,才能對(duì)這些git庫(kù)進(jìn)行操作。通過(guò)集中維護(hù)所有g(shù)it庫(kù)的清單,repo可以方便的從清單中獲取git庫(kù)的信息。 這份清單會(huì)隨著版本演進(jìn)升級(jí)而產(chǎn)生變化,同時(shí)也有一些本地的修改定制需求,所以,repo是通過(guò)一個(gè)git庫(kù)來(lái)管理項(xiàng)目的清單文件的,這個(gè)git庫(kù)名字叫manifests。
當(dāng)打開(kāi)repo這個(gè)可執(zhí)行的python腳本后,發(fā)現(xiàn)代碼量并不大(不超過(guò)1000行),難道僅這一個(gè)腳本就完成了AOSP數(shù)百個(gè)git庫(kù)的管理嗎?并非如此。 repo是一系列腳本的集合,這些腳本也是通過(guò)git庫(kù)來(lái)維護(hù)的,這個(gè)git庫(kù)名字叫repo。
在客戶端使用repo初始化一個(gè)項(xiàng)目時(shí),就會(huì)從遠(yuǎn)程把manifests和repo這兩個(gè)git庫(kù)拷貝到本地,但這對(duì)于Android開(kāi)發(fā)人員來(lái)說(shuō),又是近乎無(wú)形的(一般通過(guò)文件管理器,是無(wú)法看到這兩個(gè)git庫(kù)的)。 repo將自動(dòng)化的管理信息都隱藏根目錄的.repo子目錄中。
2.1 項(xiàng)目清單庫(kù)(.repo/manifests)
AOSP項(xiàng)目清單git庫(kù)下,只有一個(gè)文件default.xml,是一個(gè)標(biāo)準(zhǔn)的XML,描述了當(dāng)前repo管理的所有信息。
AOSP的default.xml的文件內(nèi)容如下:
<?xmlversion="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" >
<copyfilesrc="core/root.mk" dest="Makefile" />
</project>
<project path="abi/cpp"name="platform/abi/cpp" groups="pdk" />
<project path="art"name="platform/art" groups="pdk" />
...
<projectpath="tools/studio/translation"name="platform/tools/studio/translation"groups="notdefault,tools" />
<project path="tools/swt"name="platform/tools/swt" groups="notdefault,tools" />
</manifest>
:描述了遠(yuǎn)程倉(cāng)庫(kù)的基本信息。name描述的是一個(gè)遠(yuǎn)程倉(cāng)庫(kù)的名稱,通常我們看到的命名是origin;fetch用作項(xiàng)目名稱的前緣,在構(gòu)造項(xiàng)目倉(cāng)庫(kù)遠(yuǎn)程地址時(shí)使用到;review描述的是用作code review的server地址
:default標(biāo)簽的定義的屬性,將作為標(biāo)簽的默認(rèn)屬性,在標(biāo)簽中,也可以重寫(xiě)這些屬性。屬性revision表示當(dāng)前的版本,也就是我們俗稱的分支;屬性remote描述的是默認(rèn)使用的遠(yuǎn)程倉(cāng)庫(kù)名稱,即標(biāo)簽中name的屬性值;屬性sync-j表示在同步遠(yuǎn)程代碼時(shí),并發(fā)的任務(wù)數(shù)量,配置高的機(jī)器可以將這個(gè)值調(diào)大
:每一個(gè)repo管理的git庫(kù),就是對(duì)應(yīng)到一個(gè)標(biāo)簽,path描述的是項(xiàng)目相對(duì)于遠(yuǎn)程倉(cāng)庫(kù)URL的路徑,同時(shí)將作為對(duì)應(yīng)的git庫(kù)在本地代碼的路徑; name用于定義項(xiàng)目名稱,命名方式采用的是整個(gè)項(xiàng)目URL的相對(duì)地址。 譬如,AOSP項(xiàng)目的URL為https://android.googlesource.com/,命名為platform/build的git庫(kù),訪問(wèn)的URL就是https://android.googlesource.com/platform/build
如果需要新增或替換一些git庫(kù),可以通過(guò)修改default.xml來(lái)實(shí)現(xiàn),repo會(huì)根據(jù)配置信息,自動(dòng)化管理。但直接對(duì)default.xml的定制,可能會(huì)導(dǎo)致下一次更新項(xiàng)目清單時(shí),與遠(yuǎn)程default.xml發(fā)生沖突。 因此,repo提供了一個(gè)種更為靈活的定制方式local_manifests:所有的定制是遵循default.xml規(guī)范的,文件名可以自定義,譬如local_manifest.xml, another_local_manifest.xml等, 將定制的XML放在新建的.repo/local_manifests子目錄即可。repo會(huì)遍歷.repo/local_manifests目錄下的所有*.xml文件,最終與default.xml合并成一個(gè)總的項(xiàng)目清單文件manifest.xml。
local_manifests的修改示例如下:
$ ls.repo/local_manifests
local_manifest.xml
another_local_manifest.xml
$ cat.repo/local_manifests/local_manifest.xml
<?xmlversion="1.0" encoding="UTF-8"?>
<manifest>
<project path="manifest"name="tools/manifest" />
<projectpath="platform-manifest" name="platform/manifest" />
</manifest>
2.2 repo腳本庫(kù)(.repo/repo)
repo對(duì)git命令進(jìn)行了封裝,提供了一套repo的命令集(包括init,sync等),所有repo管理的自動(dòng)化實(shí)現(xiàn)也都包含在這個(gè)git庫(kù)中。 在第一次初始化的時(shí)候,repo會(huì)從遠(yuǎn)程把這個(gè)git庫(kù)下載到本地。
2.3 倉(cāng)庫(kù)目錄和工作目錄
倉(cāng)庫(kù)目錄保存的是歷史信息和修改記錄,工作目錄保存的是當(dāng)前版本的信息。一般來(lái)說(shuō),一個(gè)項(xiàng)目的Git倉(cāng)庫(kù)目錄(默認(rèn)為.git目錄)是位于工作目錄下面的,但是Git支持將一個(gè)項(xiàng)目的Git倉(cāng)庫(kù)目錄和工作目錄分開(kāi)來(lái)存放。 對(duì)于repo管理而言,既有分開(kāi)存放,也有位于工作目錄存放的:
manifests: 倉(cāng)庫(kù)目錄有兩份拷貝,一份位于工作目錄(.repo/manifests)的.git目錄下,另一份獨(dú)立存放于.repo/manifests.git
repo:倉(cāng)庫(kù)目錄位于工作目錄(.repo/repo)的.git目錄下
project:所有被管理git庫(kù)的倉(cāng)庫(kù)目錄都是分開(kāi)存放的,位于.repo/projects目錄下。同時(shí),也會(huì)保留工作目錄的.git,但里面所有的文件都是到.repo的鏈接。這樣,即做到了分開(kāi)存放,也兼容了在工作目錄下的所有g(shù)it命令。
既然.repo目錄下保存了項(xiàng)目的所有信息,所有要拷貝一個(gè)項(xiàng)目時(shí),只是需要拷貝這個(gè)目錄就可以了。repo支持從本地已有的.repo中恢復(fù)原有的項(xiàng)目。
2.4 repo 目錄結(jié)構(gòu)分析
.repo:
此為repo目錄,可用于提取相應(yīng)項(xiàng)目工作目錄到外面的repo工作目錄。
.repo/manifests.git:
此為repo配置信息的git庫(kù),不同版本包含不同配置信息。每個(gè)repo項(xiàng)目初始化后也會(huì)有自己的git倉(cāng)庫(kù)的repo也會(huì)建立一個(gè)Git倉(cāng)庫(kù),用來(lái)記錄當(dāng)前Android版本下各個(gè)子項(xiàng)目的Git倉(cāng)庫(kù)分別處于哪一個(gè)分支,這個(gè)倉(cāng)庫(kù)通常叫做:manifest倉(cāng)庫(kù)。
.repo/manifests:
此為repo配置信息的工作目錄(將配置信息的工作目錄和相應(yīng)的實(shí)際git目錄分離管理,并且配置信息中的.git目錄實(shí)際只是指向?qū)嶋Hgit庫(kù)的軟連接)。此目錄中可能包含一個(gè)或多個(gè)xml文件描述的配置。每個(gè)xml文件是獨(dú)立的一套配置,配置內(nèi)容包括當(dāng)前repo工作目錄包含哪些git項(xiàng)目、所有g(shù)it項(xiàng)目所處的默認(rèn)公共分支、以及遠(yuǎn)端地址等。
.repo/manifest.xml:
repo工作目錄中的內(nèi)容同一時(shí)刻只能采用manifests中的一個(gè)xml文件做為其配置,該文件就是其軟連接,通過(guò)init的-m選項(xiàng)指定采用哪個(gè)文件;另外,同一xml文件也可能處于manifests庫(kù)的不同版本或者不同分支,通過(guò)init的-b選項(xiàng)指定使用manifests中的哪個(gè)分支,每次init命令都會(huì)從服務(wù)器更新最新的配置。這里通過(guò)-m指定的manifests中的xml文件中。
.repo/repo:
此為repo腳本集的git庫(kù),用于repo管理所需的各種腳本,repo的所有子命令就是其中的對(duì)應(yīng)腳本實(shí)現(xiàn)。這些腳本也通過(guò)git管理,.repo/repo/.git為對(duì)其應(yīng)的git目錄,用git進(jìn)行版本管理。
repo命令的使用格式如下所示:
$ repo <COMMAND> <OPTIONS>
可選的的有:help、init、sync、upload、diff、download、forall、prune、start、status,每一個(gè)命令都有實(shí)際的使用場(chǎng)景, 下面我們先對(duì)這些命令做一個(gè)簡(jiǎn)要的介紹:
$ repo init -u <URL> [<OPTIONS>]
-u:指定manifests這個(gè)遠(yuǎn)程git庫(kù)的URL,manifests庫(kù)是整個(gè)項(xiàng)目的清單。默認(rèn)情況,這個(gè)git庫(kù)只包含了default.xml一個(gè)文件,其內(nèi)容可以參見(jiàn)Android的樣本
-m, –manifest-name:指定所需要的manifests庫(kù)中的清單文件。默認(rèn)情況下,會(huì)使用maniftests/default.xml
-b, –manifest-branch:指定manifest.xml文件中的一個(gè)版本,,也就是俗稱的“分支”運(yùn)行該命令后,會(huì)在當(dāng)前目錄下新建一個(gè).repo子目錄:
.repo
├── manifests # 一個(gè)git庫(kù),包含default.xml文件,用于描述repo所管理的git庫(kù)的信息
├── manifests.git # manifest這個(gè)git庫(kù)的實(shí)體,manifest/.git目錄下的所有文件都會(huì)鏈接到該目錄
├── manifest.xml # manifests/default.xml的一個(gè)軟鏈接
└── repo # 一個(gè)git庫(kù),包含repo運(yùn)行的所有腳本
這些本地的目錄是如何生成的呢?執(zhí)行repo命令時(shí),可以通過(guò)–trace參數(shù),來(lái)看實(shí)際發(fā)生了什么。
$ 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
首先,在當(dāng)前目錄下創(chuàng)建.repo子目錄,后續(xù)所有的操作都在.repo子目錄下完成;
然后,clone了兩個(gè)git庫(kù),其中一個(gè)是-u參數(shù)指定的manifests,本地git庫(kù)的名稱是manifest.git;另一個(gè)是默認(rèn)的repo,后面我們會(huì)看到這個(gè)URL也可以通過(guò)參數(shù)來(lái)指定;
接著,創(chuàng)建了manifest/.git目錄,里面的所有文件都是到manifests.git這個(gè)目錄的鏈接,這個(gè)是為了方便對(duì)manifests目錄執(zhí)行g(shù)it命令,緊接著,就會(huì)將manifest切換到-b參數(shù)指定的分支;
最后,在.repo目錄下,創(chuàng)建了一個(gè)軟鏈接,鏈接到-m參數(shù)制定的清單文件,默認(rèn)情況是manifests/default.xml。
這樣,就完成了一個(gè)多git庫(kù)的初始化,之后,就可以執(zhí)行其他的repo命令了。
我們還介紹幾個(gè)不常用的參數(shù),在國(guó)內(nèi)下載Android源碼時(shí),會(huì)用到:
–repo-url:指定遠(yuǎn)程repo庫(kù)的URL,默認(rèn)情況是https://android.googlesource.com/tools/repo,但國(guó)內(nèi)訪問(wèn)Google受限,會(huì)導(dǎo)致這個(gè)庫(kù)無(wú)法下載,從而導(dǎo)致repo init失敗,所以可以通過(guò)該參數(shù)指定一個(gè)訪問(wèn)不受限的repo地址
–repo-branch:同manifest這個(gè)git庫(kù)一樣,repo這個(gè)git庫(kù)也是有版本差異的,可以通過(guò)該參數(shù)來(lái)指定下載repo這個(gè)遠(yuǎn)程git庫(kù)的特定分支
–no-repo-verify:在下載repo庫(kù)時(shí),會(huì)對(duì)repo的源碼進(jìn)行檢查。通過(guò)–repo-url指定第三方repo庫(kù)時(shí),可能會(huì)導(dǎo)致檢查不通過(guò),所以可以配套使用該參數(shù),強(qiáng)制不進(jìn)行檢查
$ repo sync [PROJECT_LIST]
下載遠(yuǎn)程代碼,并將本地代碼更新到最新,這個(gè)過(guò)程稱為“同步”。如果不使用任何參數(shù),那么會(huì)對(duì)所有repo管理的進(jìn)行同步操作;也可以PROJECT_LIST參數(shù),指定若干要同步的PROJECT。 根據(jù)本地git庫(kù)代碼不同,同步操作會(huì)有不同的行為:
當(dāng)本地的git庫(kù)是第一次觸發(fā)同步操作時(shí),那么,該命令等價(jià)于git clone,會(huì)將遠(yuǎn)程git庫(kù)直接拷貝到本地
當(dāng)本地已經(jīng)觸發(fā)過(guò)同步操作時(shí),那么,該命令等價(jià)于git remote update && git rebase origin/,就是當(dāng)前與本地分支所關(guān)聯(lián)的遠(yuǎn)程分支 代碼合并可能會(huì)產(chǎn)生沖突,當(dāng)沖突出現(xiàn)時(shí),只需要解決完沖突,然后執(zhí)行g(shù)it rebase --continue即可。
當(dāng)sync命令正確執(zhí)行完畢后,本地代碼就同遠(yuǎn)程代碼保持一致了。在一些場(chǎng)景下,我們會(huì)用到sync命令的一些參數(shù):
-j:開(kāi)啟多線程同步操作,這會(huì)加快sync命令的執(zhí)行速度。默認(rèn)情況下,使用4個(gè)線程并發(fā)進(jìn)行sync
-c, –current-branch:只同步指定的遠(yuǎn)程分支。默認(rèn)情況下,sync會(huì)同步所有的遠(yuǎn)程分支,當(dāng)遠(yuǎn)程分支比較多的時(shí)候,下載的代碼量就大。使用該參數(shù),可以縮減下載時(shí)間,節(jié)省本地磁盤(pán)空間
-d, –detach:脫離當(dāng)前的本地分支,切換到manifest.xml中設(shè)定的分支。在實(shí)際操作中,這個(gè)參數(shù)很有用,當(dāng)我們第一次sync完代碼后,往往會(huì)切換到dev分支進(jìn)行開(kāi)發(fā)。如果不帶該參數(shù)使用sync, 則會(huì)觸發(fā)本地的dev分支與manifest設(shè)定的遠(yuǎn)程分支進(jìn)行合并,這會(huì)很可能會(huì)導(dǎo)致sync失敗
-f, –force-broken:當(dāng)有g(shù)it庫(kù)sync失敗了,不中斷整個(gè)同步操作,繼續(xù)同步其他的git庫(kù)
–no-clone-bundle:在向服務(wù)器發(fā)起請(qǐng)求時(shí),為了做到盡快的響應(yīng)速度,會(huì)用到內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN, Content Delivery Network)。同步操作也會(huì)通過(guò)CDN與就近的服務(wù)器建立連接, 使用HTTP/HTTPS的U R L / c l o n e . b u n d l e 來(lái)初始化本地的 g i t 庫(kù), c l o n e . b u n d l e 實(shí)際上是遠(yuǎn)程 g i t 庫(kù)的鏡像,通過(guò) H T T P 直接下載,這會(huì)更好的利用網(wǎng)絡(luò)帶寬,加快下載速度。當(dāng)服務(wù)器不能正常響應(yīng)下載 URL/clone.bundle來(lái)初始化本地的git庫(kù),clone.bundle實(shí)際上是遠(yuǎn)程git庫(kù)的鏡像,通過(guò)HTTP直接下載,這會(huì)更好的利用網(wǎng)絡(luò)帶寬,加快下載速度。 當(dāng)服務(wù)器不能正常響應(yīng)下載URL/clone.bundle來(lái)初始化本地的git庫(kù),clone.bundle實(shí)際上是遠(yuǎn)程git庫(kù)的鏡像,通過(guò)HTTP直接下載,這會(huì)更好的利用網(wǎng)絡(luò)帶寬,加快下載速度。當(dāng)服務(wù)器不能正常響應(yīng)下載URL/clone.bundle,但git又能正常工作時(shí),可以通過(guò)該參數(shù),配置不下載$URL/clone.bundle,而是直接通過(guò)git下載遠(yuǎn)程git庫(kù)
$ repo upload [PROJECT_LIST]
從字面意思理解,upload就是要上傳,將本地的代碼上傳到遠(yuǎn)程服務(wù)器。upload命令首先會(huì)找出本地分支從上一次同步操作以來(lái)發(fā)生的改動(dòng),然后會(huì)將這些改動(dòng)生成Patch文件,上傳至Gerrit服務(wù)器。 如果沒(méi)有指定PROJECT_LIST,那么upload會(huì)找出所有g(shù)it庫(kù)的改動(dòng);如果某個(gè)git庫(kù)有多個(gè)分支,upload會(huì)提供一個(gè)交互界面,提示選擇其中若干個(gè)分支進(jìn)行上傳操作。
upload并不會(huì)直接將改動(dòng)合并后遠(yuǎn)程的git庫(kù),而是需要先得到Reviewer批準(zhǔn)。Reviewer查看改動(dòng)內(nèi)容、決定是否批準(zhǔn)合入代碼的操作,都是通過(guò)Gerrit完成。 Gerrit服務(wù)器的地址是在manifests中指定的:打開(kāi).repo/manifest.xml,這個(gè)XML TAG中的review屬性值就是Review服務(wù)器的URL:
<remote name="aosp"
fetch=".."
review="https://android-review.googlesource.com/" />
Gerrit的實(shí)現(xiàn)機(jī)制不是本文討論的內(nèi)容,但有幾個(gè)與Gerrit相關(guān)的概念,是需要代碼提交人員了解的:
Reviewer:代碼審閱人員可以是多個(gè),是需要人為指定的。Gerrit提供網(wǎng)頁(yè)的操作,可以填選Reviewer。當(dāng)有多個(gè)git庫(kù)的改動(dòng)提交時(shí),為了避免在網(wǎng)頁(yè)上頻繁的填選Reviewer這種重復(fù)勞動(dòng), upload提供了–re, –reviewer參數(shù),在命令行一次性指定Reviewer
Commit-ID:git為了標(biāo)識(shí)每個(gè)提交,引入了Commit-ID,是一個(gè)SHA-1值,針對(duì)當(dāng)次提交內(nèi)容的一個(gè)Checksum,可以用于驗(yàn)證提交內(nèi)容的完整性
Change-ID:Gerrit針對(duì)每一個(gè)Review任務(wù),引入了一個(gè)Change-ID,每一個(gè)提交上傳到Gerrit,都會(huì)對(duì)應(yīng)到一個(gè)Change-ID, 為了區(qū)分于Commit-ID,Gerrit設(shè)定Change-ID都是以大寫(xiě)字母 “I” 打頭的。 Change-ID與Commit-ID并非一一對(duì)應(yīng)的,每一個(gè)Commit-ID都會(huì)關(guān)聯(lián)到一個(gè)Change-ID,但Change-ID可以關(guān)聯(lián)到多個(gè)Commit-ID
Patch-Set:當(dāng)前需要Review的改動(dòng)內(nèi)容。一個(gè)Change-ID關(guān)聯(lián)多個(gè)Commit-ID,就是通過(guò)Patch-Set來(lái)表現(xiàn)的,當(dāng)通過(guò)git commit --amend命令修正上一次的提交并上傳時(shí), Commit-ID已經(jīng)發(fā)生了變化,但仍可以保持Change-ID不變,這樣,在Gerrit原來(lái)的Review任務(wù)下,就會(huì)出現(xiàn)新的Patch-Set。修正多少次,就會(huì)出現(xiàn)多少個(gè)Patch-Set, 可以理解,只有最后一次修正才是我們想要的結(jié)果,所以,在所有的Patch-Set中,只有最新的一個(gè)是真正有用的,能夠合并的。
$ repo download <TARGET> <CHANGE>
upload是把改動(dòng)內(nèi)容提交到Gerrit,download是從Gerrit下載改動(dòng)。與upload一樣,download命令也是配合Gerrit使用的。
:指定要下載的PROJECT,譬如platform/frameworks/base, platform/packages/apps/Mms
:指定要下載的改動(dòng)內(nèi)容。這個(gè)值不是Commit-ID,也不是Change-ID,而是一個(gè)Review任務(wù)URL的最后幾位數(shù)字。 譬如,AOSP的一個(gè)Review任務(wù)https://android-review.googlesource.com/#/c/23823/,其中23823就是。
$ repo forall [PROJECT_LIST] -c <COMMAND>
對(duì)指定的git庫(kù)執(zhí)行-c參數(shù)制定的命令序列。在管理多個(gè)git庫(kù)時(shí),這是一條非常實(shí)用的命令。PROJECT_LIST是以空格區(qū)分的,譬如:
$ repo forall frameworks/base packages/apps/Mms -c "git status"
表示對(duì)platform/frameworks/base和platform/packages/apps/Mms同時(shí)執(zhí)行g(shù)it status命令。 如果沒(méi)有指定PROJECT_LIST,那么,會(huì)對(duì)repo管理的所有g(shù)it庫(kù)都同時(shí)執(zhí)行命令。
該命令的還有一些其他參數(shù):
-r, –regex: 通過(guò)指定一個(gè)正則表達(dá)式,只有匹配的PROJECT,才會(huì)執(zhí)行指定的命令
-p:輸出結(jié)果中,打印PROJECT的名稱
$ repo prune [<PROJECT_LIST>]
刪除指定PROJECT中,已經(jīng)合并的分支。當(dāng)在開(kāi)發(fā)分支上代碼已經(jīng)合并到主干分支后,使用該命令就可以刪除這個(gè)開(kāi)發(fā)分支。
隨著時(shí)間的演進(jìn),開(kāi)發(fā)分支會(huì)越來(lái)越多,在多人開(kāi)發(fā)同一個(gè)git庫(kù),多開(kāi)發(fā)分支的情況會(huì)愈發(fā)明顯,假設(shè)當(dāng)前git庫(kù)有如下分支:
* master
dev_feature1_201501 # 已經(jīng)合并到master
dev_feature2_201502 # 已經(jīng)合并到master
dev_feature3_201503 # 正在開(kāi)發(fā)中,還有改動(dòng)記錄沒(méi)有合并到master
那么,針對(duì)該git庫(kù)使用prune命令,會(huì)刪除dev_feature1_201501和dev_feature2_201502。
定義刪除無(wú)用的分支,能夠提交團(tuán)隊(duì)的開(kāi)發(fā)和管理效率。prune就是刪除無(wú)用分支的”殺手锏“。
$ repo start <BRANCH_NAME> [<PROJECT_LIST>]
在指定的PROJECT的上,切換到<BRANCH_NAME>指定的分支。可以使用–all參數(shù)對(duì)所有的PROJECT都執(zhí)行分支切換操作。 該命令實(shí)際上是對(duì)git checkout命令的封裝,<BRANCH_NAME>是自定義的,它將追蹤manifest中指定的分支名。
當(dāng)?shù)谝淮蝧ync完代碼后,可以通過(guò)start命令將git庫(kù)切換到開(kāi)發(fā)分支,避免在匿名分支上工作導(dǎo)致丟失改動(dòng)內(nèi)容的情況。
$ repo status [<PROJECT_LIST>]
status用于查看多個(gè)git庫(kù)的狀態(tài)。實(shí)際上,是對(duì)git status命令的封裝。
Android推薦的開(kāi)發(fā)流程是:
repo init初始化工程,指定待下載的分支
repo sync下載代碼
repo start將本地git庫(kù)切換到開(kāi)發(fā)分支(TOPIC BRANCH)
在本地進(jìn)行修改,驗(yàn)證后,提交到本地
repo upload上傳到服務(wù)器,等待review
在實(shí)際使用過(guò)程中,我們會(huì)用到repo的一些什么子命令和參數(shù)呢?哪些參數(shù)有助于提高開(kāi)發(fā)效率呢?下面我們以一些實(shí)際場(chǎng)景為例展開(kāi)說(shuō)明。
通過(guò)local_manifest機(jī)制,能夠避免了直接修改default.xml,不會(huì)造成下次同步遠(yuǎn)程清單文件的沖突。
CyanogenMod(CM)適配了上百款機(jī)型,不同機(jī)型所涉及到的git庫(kù)很可能是有差異的。以CM對(duì)清單文件的定制為例,通過(guò)新增local_manifest.xml,內(nèi)容如下:
<manifest>
<!-- add github as a remote source -->
<remote name="github" fetch="git://github.com" />
<!-- remove aosp standard projects and replace with cyanogenmod versions -->
<remove-project name="platform/bootable/recovery" />
<remove-project name="platform/external/yaffs2" />
<remove-project name="platform/external/zlib" />
<project path="bootable/recovery" name="CyanogenMod/android_bootable_recovery" remote="github" revision="cm-10.1" />
<project path="external/yaffs2" name="CyanogenMod/android_external_yaffs2" remote="github" revision="cm-10.1" />
<project path="external/zlib" name="CyanogenMod/android_external_zlib" remote="github" revision="cm-10.1" />
<!-- add busybox from the cyanogenmod repository -->
<project path="external/busybox" name="CyanogenMod/android_external_busybox" remote="github" revision="cm-10.1" />
</manifest>
local_manifest.xml會(huì)與已有的default.xml融合成一個(gè)項(xiàng)目清單文件manifest.xml,實(shí)現(xiàn)了對(duì)一些git庫(kù)的替換和新增。 可以通過(guò)以下命令導(dǎo)出當(dāng)前的清單文件,最終snapshot.xml就是融合后的版本:
$ repo manifest -o snapshot.xml -r
在編譯之前,保存整個(gè)項(xiàng)目的清單,有助于問(wèn)題的回溯。當(dāng)項(xiàng)目的git庫(kù)發(fā)生變更,需要回退到上一個(gè)版本進(jìn)行驗(yàn)證的時(shí)候,只需要重新基于snapshot.xml初始化上一個(gè)版本即可:
$ cp snapshot.xml .repo/manifests/
$ repo init -m snapshot.xml # -m 參數(shù)表示自定義manifest
$ repo sync -d # -d 參數(shù)表示從當(dāng)前分支脫離,切換到manifest中定義的分支
在repo init的時(shí)候,會(huì)從遠(yuǎn)程下載manifests和repo這兩個(gè)git庫(kù),默認(rèn)情況下,這兩個(gè)git庫(kù)的地址都是寫(xiě)死在repo這個(gè)python腳本里面的。對(duì)于AOSP而言,這兩個(gè)git庫(kù)的地址顯然是google提供的。但由于google訪問(wèn)受限的緣故,會(huì)導(dǎo)致init時(shí),無(wú)法下載manifests和repo。這時(shí)候,可以使用init的-u和–repo-url參數(shù),自定義這兩個(gè)庫(kù)的地址,輔以–no-repo-verify來(lái)繞過(guò)代碼檢查。
$ repo init --repo-url [PATH/TO/REPO] -u [PATH/TO/MANIFEST] -b [BRANCH] --no-repo-verify
$ repo sync
repo默認(rèn)會(huì)同步git庫(kù)的所有遠(yuǎn)程分支的代碼,但實(shí)際開(kāi)發(fā)過(guò)程中,用到的分支是有限的。使用sync的-c參數(shù),可以只下載manifest中設(shè)定的分支,這會(huì)節(jié)省代碼下載時(shí)間以及本地的磁盤(pán)空間:
$ repo sync -c
如果實(shí)際開(kāi)發(fā)過(guò)程中,需要用到另外一個(gè)分支,而又不想被其他分支干擾,可以在已有的工程根目錄下,使用如下命令:
$ repo manifest -o snapshot.xml -r
$ repo init -u [PATH/TO/MANIFEST] -b [ANOTHER_BRANCH]
$ repo sync -c -d
以上命令序列,相當(dāng)更新了manifest,而且僅僅只下載ANOTHER_BRANCH的代碼,這樣本地只保存了兩個(gè)分支的代碼。利用保存的snapshot.xml,還能將所有g(shù)it庫(kù)方便的切換回原來(lái)的分支。
如果本地已經(jīng)有一份Android源碼,假設(shè)路徑為~/android-exsit,想要下載另一份新的Android源碼,通過(guò)–reference參數(shù),在數(shù)分鐘以內(nèi),就能將代碼下載完畢:
$ mkdir ~/android-new && cd ~/android-new
$ repo init --reference=~/android-exsit -u [PATH/TO/MANIFEST] -b [BRANCH]
$ repo sync -c
在sync完代碼后,所有g(shù)it庫(kù)默認(rèn)都是在一個(gè)匿名分支上(no branch),很容易會(huì)由于誤操作導(dǎo)致丟失代碼修改??梢允褂萌缦旅顚⑺械膅it庫(kù)切換到開(kāi)發(fā)分支:
$ repo start BRANCH --all
開(kāi)發(fā)人員可能同時(shí)在多個(gè)git庫(kù),甚至多個(gè)分支上,同時(shí)進(jìn)行修改,針對(duì)每個(gè)git庫(kù)單獨(dú)提交代碼是繁瑣的??梢允褂萌缦旅睿徊⑻峤凰械男薷模?/span>
$ repo upload
不用擔(dān)心會(huì)漏提交或者誤提交,upload會(huì)提供一個(gè)交互界面,開(kāi)發(fā)人員選擇需要提交的git庫(kù)和分支即可。
如果需要省去Gerrit上填寫(xiě)reviewer的操作,可以使用–reviewer參數(shù)指定Reviewer的郵箱地址:
$ repo upload --reviewer="R.E.viewer@google.com"
Git鼓勵(lì)在修復(fù)Bug或者開(kāi)發(fā)新的Feature時(shí),都創(chuàng)建一個(gè)新的分支。創(chuàng)建Git分支的代價(jià)是很小的,而且速度很快,因此,不用擔(dān)心創(chuàng)建Git分支的成本,而是盡可能多地使用分支。
隨著時(shí)間的演進(jìn),開(kāi)發(fā)分支會(huì)越來(lái)越多,而一些已經(jīng)合并到主干的開(kāi)發(fā)分支是沒(méi)有存在價(jià)值的,可以通過(guò)prune命令定期刪除無(wú)用的開(kāi)發(fā)分支:
$ repo prune [PROJECT_LIST]
對(duì)于部分開(kāi)發(fā)人員而言,同時(shí)操作多個(gè)git庫(kù)是常態(tài),如果針對(duì)每個(gè)git庫(kù)的操作命令都是相同的,那么可以使用如下命令一次性完成所有操作:
$ repo forall -c "git branch | grep tmp | xargs git branch -D; git branch"
參數(shù)-c指定的命令序列可以很復(fù)雜,多條命令只需要用“;”間隔。