Android 為企業提供一個新的市場,無論大企業,小企業都是處於同一個起跑線上。研究 Android 尤其是 Android 系統核心或者是驅動的開發,首先需要做的就是本地克隆建立一套 Android 版本庫管理機制。
Android 使用 Git 作為代碼管理工具,開發了 Gerrit 進行代碼審核以便更好的對代碼進行集中式管理,還開發了 Repo 命令行工具,對 Git 部分命令封裝,將 百多個 Git 庫有效的進行組織。要想克隆和管理這百多個 Git 庫,還真不是一件簡單的事情。
在研究 Repo 的過程中,發現很多文檔在 Google Group 上,非“翻牆”不可看。非法的事情咱不干,直接閱讀 repo 的代碼吧。
創建本地 Android 版本庫鏡像的思路
如果了解了 Repo 的實現,參考 《Using Repo and Git》 , 建立一個本地的 android 版本庫鏡像還是不難的:
- 下載 repo bootstrap 腳本
$ curl http://android.git.kernel.org/repo >~/bin/repo $ chmod a+x ~/bin/repo $ export PATH=$PATH:~/bin
- 提供 –mirror 參數調用 repo init ,建立 git 版本庫克隆
$ repo init -u git://android.git.kernel.org/platform/manifest.git --mirror
- 使用 –morror 則下一步和源同步的時候,本地按照源的版本庫組織方式進行組織,否則會按照 manifest.xml 指定的方式重新組織並檢出到本地
- 開始和源同步
$ repo sync
- 修改 manifest ,修改 git 庫地址,指向本地的 git 服務器
- 修改 platform/manifest.git 庫中現有的 xml 文件,或者創建一個新的 xml 文件
- 將 git 的地址改為本地地址,提交並 push
- 本地 repo 鏡像建立完畢之后,就可以在執行 repo init 時,使用本地更改后的 manifest 庫,之后執行 repo sync 就是基於本地版本庫進行同步了。
- 也可以改造 repo,使得不必為 repo 工具初始化,也在本地網絡完成操作…
Repo init 干了些什么?
實際上,得到客戶使用 repo 的信息后,首先下載 repo 執行腳本開始研究。
curl http://android.git.kernel.org/repo >~/bin/repo
難道只有 600 行的 python 代碼么?要是這樣應該很簡單的呀。可以看下來,卻發現遠非如此。
Shell script or python?
首先 repo 腳本使用了一個魔法:從腳本第一行的 shebang 來看應該是 shell 腳本,但是滿眼卻都是 python 語法,怎么回事?
1 #!/bin/sh 2 3 ## repo default configuration 4 ## 5 REPO_URL='git://android.git.kernel.org/tools/repo.git' 6 REPO_REV='stable' 7 8 # Copyright (C) 2008 Google Inc. ... 22 magic='--calling-python-from-/bin/sh--' 23 """exec" python -E "$0" "$@" """#$magic" 24 if __name__ == '__main__': 25 import sys 26 if sys.argv[-1] == '#%s' % magic: 27 del sys.argv[-1] 28 del magic
魔法就在第 23 行,巧妙的通過 python 三引號字串寫出了一個能被 python 和 shell script 都能理解的代碼,以此為界,代碼由 Shell 腳本進入了 Python 的世界。
Bootstrap 和真正的 repo
通過 curl 下載的的 repo 並非完整的 repo 腳本,只是一個 bootstrap。當 repo 執行時,會負責下載完整的 repo 代碼,並將控制權轉移給真正的 repo。
通過 main 函數,可以看到 repo 運行的開始,就試圖發現本地真正的完整的 repo 代碼,以便移交控制權:
544 def main(orig_args): 545 main, dir = _FindRepo() 586 try: 587 os.execv(main, me)
其中 545 行的 _FindRepo() 會在當前目錄開始向上遞歸查找 “.repo/repo/main.py”,如果找到則移交控制權(587行)。
Repo bootstrap 腳本調用 init 只完成第一階段的初始化
Repo 的 bootstrap 腳本只支持兩個命令 help 和 init,而 init 也只完成 repo 版本庫克隆(即安裝 repo 完整工具),之后就轉移控制權。
在 Repo bootstrap 執行 init 可以提供很多參數,但實際上第一階段初始化,只用到兩個參數(而且都有默認值)
- 參數:–repo-url=URL
repo 工具本身的 git 庫地址。缺省為:git://android.git.kernel.org/tools/repo.git - 參數:–repo-branch=REVISION
使用 repo 的版本庫,即 repo git 庫的分支或者里程碑名稱。缺省為 stable
第二階段的 repo init
執行第二階段的 repo init,控制權已經移交給剛剛克隆出來的 repo git 庫的腳本。
Repo git 庫被克隆/檢出到執行 repo init 命令當前目錄下的 .repo/repo 子目錄中,主要的執行腳本為 .repo/repo/main.py。main.py 接着執行 repo init 命令。
Repo 的代碼組織的非常好,在 .repo/repo/subcmds/ 子目錄下,是各個 repo 命令的處理腳本。repo init 的第二階段腳本正是由 .repo/repo/subcmds/init.py 負責執行的。第二階段主要完成:
- 克隆由 -u 參數提供的 manifest Git 庫,如克隆 android 庫時:
$ repo init -u git://android.git.kernel.org/platform/manifest.git
- 如果不提供 -b REVISION 或者 –manifest-branch=REVISION參數,則檢出 manifest Git 庫的 master 分支
- 如果不提供 -m NAME.xml 或者 –manifest-name=NAME.xml 參數,則使用缺省值 default.xml
- 如果提供 –mirror 參數,則后續同步操作會有相應的體現
Repo start 干了些什么?
Android 源碼網站在介紹 repo 的使用模型中,有一個圖片: http://source.android.com/images/git-repo-1.png , 介紹了 repo 的使用流程。其中 “repo start” 是緊接着 “repo sync” 后的第一個動作。那么這個動作是干什么的呢?
得益於 repo 對 git 操作的封裝,”repo start” 命令的處理代碼只有區區 68 行。
37 def Execute(self, opt, args): 41 nb = args[0] 47 projects = [] 48 if not opt.all: 49 projects = args[1:] 54 all = self.GetProjects(projects) 57 for project in all: 59 if not project.StartBranch(nb): 60 err.append(project)
看到第 59 行了么,就是對 repo 同步下來的項目的多個 Git 版本庫,逐一執行 project.StartBranch 操作。 nb 是 repo start 的第一個參數,即分支名稱。
關於 StartBranch 的代碼,在 project.py 中:
857 def StartBranch(self, name): 858 """Create a new branch off the manifest's revision. 859 """ 894 if GitCommand(self, 895 ['checkout', '-b', branch.name, revid], 896 capture_stdout = True, 897 capture_stderr = True).Wait() == 0: 898 branch.Save() 899 return True
原來如此, repo start <branch_name> 就是逐一為各個版本庫創建工作分支,以便在此分支下進行工作。
讀者可以按圖索驥,找到 repo 各個命令的實現,破解心中的疑惑。
repo的用法(zz)
注:repo只是google用Python腳本寫的調用git的一個腳本,主要是用來下載、管理Android項目的軟件倉庫。(也就是說,他是用來管理給git管理的一個個倉庫的)
下載 repo 的地址: http://android.git.kernel.org/repo ,可以用以下二者之一來下載 repo
wget http://android.git.kernel.org/repo
或者
curl http://android.git.kernel.org/repo > ~/bin/repo
下載完成后須修改repo的權限: chmod a+x ~/bin/repo
用repo sync 在抓去 android source code 的時候,會經常出現一些錯誤導致 repo sync 中斷,每次都要手動開始。 可以用如下的命令,來自動重復
$?=1;
while [ $? -ne 0 ] ;
do repo sync ;
done
獲取幫助:
repo help [ command ] //顯示command 的詳細的幫助信息內容
示例: repo help init 來獲取 repo init 的其他用法
repo init -u URL 用以在當前目錄安裝 repository ,會在當前目錄創建一個目錄 ".repo" -u 參數指定一個URL, 從這個URL 中取得repository 的 manifest 文件。
示例:repo init -u git://android.git.kernel.org/platform/manifest.git
獲取的manifest文件放在.repo目錄中。命名為manifest.xml。這個文件的內容其實就是所有被git管理的倉庫的列表!
可以用 -m 參數來選擇獲取 repository 中的某一個特定的 manifest 文件,如果不具體指定,那么表示為默認的 namifest 文件 (default.xml)
repo init -u git://android.git.kernel.org/platform/manifest.git -m dalvik-plus.xml
(有諸多供我們選擇的manifest文件,所有的manifest文件都放在目錄.repo/manifests中,該目錄本身亦被git所管理,你可以cd進去看看)
可以用 -b 參數來指定某個manifest 分支。
repo init -u git://android.git.kernel.org/platform/manifest.git -b release-1.0
你會發現.repo/manifests是個被git管理的倉庫,這里放的是所有的manifest文件(*.xml),因為被git管理,固然有分支,-b可以切換到你想要的分支然后再下載相關的xml文件,當然具體下載那個xml還要看-m參數了,所以如果你僅僅指定-b而沒有-m的話,就是下載-b指定分支下的default.xml文件
如果不指定-b參數,那么會默認使用master分支
4. repo sync [project-list]
下載最新本地工作文件,更新成功,這本地文件和repository 中的代碼是一樣的。 可以指定需要更新的project , 如果不指定任何參數,會同步整個所有的項目。
如果是第一次運行 repo sync , 則這個命令相當於 git clone ,會把 repository 中的所有內容都拷貝到本地。 如果不是第一次運行 repo sync , 則相當於 git remote update ; git rebase origin/branch . repo sync 會更新 .repo 下面的文件。 如果在merge 的過程中出現沖突, 這需要手動運行 git rebase --continue
5. repo update[ project-list ]
上傳修改的代碼 ,如果你本地的代碼有所修改,那么在運行 repo sync 的時候,會提示你上傳修改的代碼,所有修改的代碼分支會上傳到 Gerrit (基於web 的代碼review 系統), Gerrit 受到上傳的代碼,會轉換為一個個變更,從而可以讓人們來review 修改的代碼。
6. repo diff [ project-list ]
顯示提交的代碼和當前工作目錄代碼之間的差異。
7. repo download target revision
下載特定的修改版本到本地, 例如: repo download pltform/frameworks/base 1241 下載修改版本為 1241 的代碼
8. repo start newbranchname .
創建新的branch分支。 "." 代表當前工作的branch 分支。
9. repo prune [project list]
刪除已經merge 的 project
10. repo forall -c
這個命令會遍歷所有的git倉庫,並在每個倉庫執行-c所指定的命令(這個被執行的命令就不限於僅僅是git命令了,而是任何被系統支持的命令,比如:ls 、 pwd 、cp 等等的 )
當我想通過這個命令遍歷所有的倉庫並在每個倉庫執行"git checkout . "用以將每個倉庫的改動都清除的時候,我這么輸入命令:
repo forall -c git checkout .
我發現這樣根本不行。看來repo不能遍歷執行checkout這個命令。今天我終於想到了另外一個命令"git reset --hard HEAD" 哈哈
repo forall -c git git reset --hard HEAD
再說一個新發現:以前用repo forall 執行一些命令的時候,可能再遍歷到某個倉庫的時候出了問題,但是我卻苦於不知道這個倉庫到底是哪個!一直也沒有解決。今天終於找到了。。。。 關鍵時候還是要看命令自己帶的幫助手冊呀。。。
repo help forall 用這個命令查看下針對forall的幫助吧。說的很清楚,repo執行的時候加上-p參數就可以在遍歷到每個倉庫的時候先打印出當前的pwd,然后再繼續執行-c所指定的命令。舉例如下:
repo forall -p -c git branch
//該命令會遍歷所有倉庫並打印每個倉庫的分支情況,由於有了-p參數,這樣便會打印出每個倉庫的路徑!!!
11. repo status
顯示 project 中每個倉庫的狀態,並打印倉庫名稱