文章目錄
- Shell script or python?
- Bootstrap 和真正的 repo
- Repo bootstrap 指令碼調用 init 只完成第一階段的初始化
- 第二階段的 repo init
- Related Posts
本文轉載自:http://www.worldhello.net/2010/08/31/1915.html
本文是repo的進階說明,講述repo的實現原理,一般可以不用理會。
Android repo 魔法Android 為企業提供一個新的市場,無論大企業,小企業都是處於同一個起跑線上。研究 Android 尤其是 Android 系統核心或者是驅動的開發,首先需要做的就是本地複製建立一套 Android 版本庫管理機制。Android 使用 Git 作為代碼管理工具,開發了 Gerrit 進行代碼審核以便更好的對代碼進行集中式管理,還開發了 Repo 命令列工具,對 Git 部分命令封裝,將 百多個 Git 庫有效進行組織。要想複製和管理這百多個 Git 庫,還真不是一件簡單的事情。在研究
Repo 的過程中,發現很多文檔在 Google Group 上,非“翻牆”不可看。非法的事情咱不幹,直接閱讀 repo 的代碼吧。
建立本地 Android 版本庫鏡像的思路
如果瞭解了 Repo 的實現,參考 《Using Repo and Git》 , 建立一個本地的 android 版本庫鏡像還是不難的:
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 sys26 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=URLrepo 工具本身的 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
負責執行的。第二階段主要完成:
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 各個命令的實現,破解心中的疑惑。
Related Posts
Git中文本地化 |
28 Feb 2012 |
1 Comment 和 5 Reactions |
Gitolite 管理員自訂命令 |
30 Nov 2011 |
4 Comments 和 0 Reactions |
Gitolite 萬用字元版本庫自訂授權 |
30 Nov 2011 |
0 Comments 和 0 Reactions |