Repo 實用指令

泰勞撰寫本篇文章的主要目的是『記錄』,就如學生時期會在課堂上做筆記一樣(有這麼認真?),將自己在做開發或研究的過程中,時常使用且泰勞覺得實用的指令記錄下來,一方面讓自己再複習,一方面提供給大家參考,也許能夠幫助到有需要的人。

註:此文章要感謝YungwenMaksonGeorge與Ouyang這些長輩們的耐心指導。

前言:
看到文章標題和標籤,應該不難發現泰勞主要活躍於Android領域,尤其是Android的原始碼(Source Code),偶爾寫寫簡單的應用程式。因此,泰勞會時常使用Repo與Git來操作存放原始碼的倉庫(Repository),整合不同的分支(Branch),這也是本文真正要記錄的主題喔!

參考資料:
Repo command reference
Git reference 

Git是一個被設計用來處理分散式大型專案的版本控制系統,而Android也採用了這套軟體來管理不同的版本。Repo則是由Google開發出來的工具,其用Python封裝了一些Git指令,目的為幫助開發者能夠更容易的在Git架構之下管理Android原始碼。

在安裝完Git與Repo之後,先從初始化並下載原始碼的動作開始,也就是"Init"與"Sync"!

Init

$ repo init -u <URL> <Options>

-u:指定一個網址,告訴Repo你想從哪裡取得manifest倉庫
-m:選擇倉庫裡的其中一個manifest
-b:指定一個版本,在這裡也可以說是指定一個分支

泰勞特別解釋-m這個參數的用意,在開發Android的過程中,原始碼幾乎每天都會有變動,為了方便日後的測試與除錯,會習慣將每日原始碼的『狀態』做一個『快照Snapshot』,在Repo的架構下,就是用manifest來儲存當日原始碼的狀態,指令如下所示。

$ repo manifest -r -o $DATE.xml

日後若突然發現有一個Bug,想要測試這個Bug從何日何時出現,即可透過-m這個參數加上指定的manifest來做追蹤。

$ repo init -u $URL -b $BRANCH -m $DATE.xml

另外,Android的原始碼非常的龐大,若是網路速度不夠的話,會花費很長的時間在下載,如果只是偶爾下載一次,是沒有什麼關係,但是每天都需要下載一份全新的原始碼的話,建議可以在本地端建立一份"Mirror",用一些『空間』換取寶貴的『時間』。建立的方式很簡單,僅需創建一個空的目錄,進入目錄之後,執行下列指令即可。初次建立時,一樣需要花費不少時間喔!不過可以使日後的Sync工作加速許多。

$ repo init -u $URL -b $BRANCH --mirror    //初始化Mirror
$ repo sync    //下載Mirror

靜待Mirror建立完成,大概會長的像下圖顯示的那樣子。日後在Init的時候,加入--reference參數並指定絕對路徑即可。 

$ repo init -u $URL -b $BRANCH --reference=$MIRROR_PATH


Sync

初始化之後,緊接著就是下載一份原始碼囉!在Repo裡搭配的指令是"Sync"。類似於Git裡的Clone,差別在於Android是由很多個"Git Project"所組成,若是一個一個Clone實在是太麻煩了,而Repo的Sync可以幫你一次完成這些動作。

$ repo sync -j4

上述指令會將完整的原始碼Sync下來至本地端,若只想Sync部份原始碼,在後方加上指定的Project名稱即可,如下範例所示。

$ repo sync platform/frameworks/base

-d(detach):將特定Project調回manifest所指定的版本
-c(current-branch):僅從遠端Fetch當前指定的分支
-q(quiet):be more quiet?我真的不知道此參數的作用...

接下來要分享的是泰勞在整合不同分支的時候,時常會使用的指令,且會著重在"Git Log"與"Repo Forall"這兩個主題,其他Git常用的指令像是"show""diff""merge""cherry-pick"等等,並非本篇文章的重點。

Git Log & Repo Forall

在這之前,泰勞先簡單說明一下自己的工作背景,以下皆會以此背景做為範例。

branchA:最原始的版本。從官方抓下來的原始碼,完全沒有修改過。
branchB:以A為基礎分割出去的分支。此分支用來開發驅動程式。
branchC:以B為基礎分割出去的分支。此分支用來開發Framework層。

情境一
當某個部份的驅動程式開發好了,開發者將修改的部份Add並Commit然後Push至Gerrit Code Review,通過Reviewer的審核之後Submit,此時branchB有了一張新的Commit,接下來需要將此整合至branchC裡面,泰勞的操作順序如下所示。

先列出branchB有的Commit,但branchC沒有的。由於C是以B為基礎分割出去的分支,此時列出來的Commit都會是新的。

$ repo forall -cvp 'git log --pretty="%cd-%an-%s" origin/branchC..origin/branchB'

Forall在管理Android原始碼時是個很好用的指令,其會逐一進入每一個Projects,並執行接在後面的參數和指令。

-c:欲執行的指令
-v:顯示寫入stderr的訊息
-p:輸出訊息前顯示Project名稱

Git Log用於查看Commit的歷史紀錄是相當方便的,其有很多的參數與組合方式,後面會陸續的出現,再依序來介紹。這裡先來看看--pretty這個參數。

--pretty:指定Log顯示的格式
%cd:committer date
%an:author name
%s:subject
oneline:指定Log僅以一行來顯示,也可以直接以--oneline參數代替。

而git log branchC..branchB(兩個小數點)則是一個實用的Log公式,其原文的解釋為"Display all commit that are in B but not in C",用於查看兩個具有父子關係的分支的Log紀錄,實在是非常方便!

列出Commit並確認這些新的修改都需要整合至branchC之後,即可開始進行合併(Merge),指令如下所示。

$ repo forall -cvp 'if [ "$REPO_RREV" = "branchC" ];then git merge --no-ff origin/branchB; fi'

為了保險起見,在進行Merge動作之前,先透過"REPO_RREV"參數來檢查當前Project的版本是否是正確的,其會去檢查被寫在manifest裡面的版本(Revision)。完成Merge之後,檢查一下是否有發生衝突(Conflict),指令如下所示。

$ repo forall -cvp 'git ls-files -u'

若是平時做很多善事,老天幫忙,都沒有發生任何衝突,那就可以Push回存放原始碼的伺服器囉!

情境二
假設現在有兩個分支,當初都是以branchC為基礎分割出去,分別用於開發兩個不同國家的客製化,現在需要『比較』兩個分支的Commit歷史紀錄的『差異』。

此時可以選擇使用另一個實用的Log公式,並搭配一些參數使其能夠更為細膩的篩選出特定的條件,馬上來看看下列範例吧!

$ git log --oneline branchY...branchZ    //三個小數點(Decimal point)

--no-merges:將Merge的Commit(擁有兩個或兩個以上的Parent)過濾掉
--cherry-pick:將相同的Change過濾掉
--left-right:標示Commit所在的位置(左邊右邊或兩邊)
--left-only:僅顯示左邊分支的Commit
--right-only:僅顯示右邊分支的Commit
--after:僅顯示某時間點之後的Commit

泰勞本身最常用的組合如下範例所示。

$ git log --oneline --no-merges --cherry-pick --left-only branchY...branchZ --after="2017-05-01"

上述指令會去比較Y與Z這兩個分支的Commit歷史紀錄,並且將Merge或是相同Change的Commit過濾掉,最後結果會顯示2017年5月1日之後僅出現於branchY這邊的Commit。下圖為實際執行結果。


最後,因為每個人查看Commit歷史紀錄的目的都會不同,泰勞僅分享自己覺得實用的參數組合給大家參考,若想更詳細的了解其他Log公式,建議還是去詳閱官方的文獻喔!

留言

這個網誌中的熱門文章

程式語言常用之符號與詞彙 - 中英文對照

什麼是 Bootloader?