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__':
25import sys
26if sys.argv[-1] == '#%s' % magic:
27del 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):
545main, dir = _FindRepo()
586try:
587os.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 行。
37def Execute(self, opt, args):
41 nb = args[0]
47 projects = []
48if not opt.all:
49projects = args[1:]
54 all = self.GetProjects(projects)
57for project in all:
59if not project.StartBranch(nb):
60err.append(project)
看到第 59 行了么,就是对 repo 同步下来的项目的多个 Git 版本库,逐一执行 project.StartBranch 操作。 nb 是 repo start 的第一个参数,即分支名称。
关于 StartBranch 的代码,在 project.py 中:
857def StartBranch(self, name):
858"""Create a new branch off the manifest's revision.
859"""
894if GitCommand(self,
895['checkout', '-b', branch.name, revid],
896capture_stdout = True,
897capture_stderr = True).Wait() == 0:
898branch.Save()
899return 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
done
获取帮助:
repo help [ command ]
示例: repo help init 来获取 repo init 的其他用法
repo init -u URL 用以在当前目录安装 repository ,会在当前目录创建一个目录 ".repo"
示例: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 ;
5. repo update[ project-list ]
上传修改的代码 ,如果你本地的代码有所修改,那么在运行 repo sync 的时候,会提示你上传修改的代码,所有修改的代码分支会上传到 Gerrit (基于web 的代码review 系统), Gerrit 受到上传的代码,会转换为一个个变更,从而可以让人们来review 修改的代码。
6. repo diff [ project-list ]
7. repo download
8. repo start newbranchname .
9.
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
再说一个新发现:以前用repo forall 执行一些命令的时候,可能再遍历到某个仓库的时候出了问题,但是我却苦于不知道这个仓库到底是哪个!一直也没有解决。今天终于找到了。。。。
repo help forall
repo forall -p -c git branch
//该命令会遍历所有仓库并打印每个仓库的分支情况,由于有了-p参数,这样便会打印出每个仓库的路径!!!