文章大纲
引言
repo 极简操作指南…以后更多精选系列文章以高度完整的系列形式发布在公众号,真正的形成一套完整的技术栈,欢迎关注,目前第一个系列是关于Android系统启动系列的文章,大概有二十几篇干货:
一、repo 概述
Git是一种分布式的代码管理工具。不需要一个中心服务器。在Android中,就包含了上百个项目,每一个项目都是一个独立的Git仓库。这意味着,如果我们要创建一个分支来做feature开发,那么就需要到每一个子项目去创建对应的分支。这显然不能手动地到每一个子项目里面去创建分支,必须要采用一种自动化的批量的方式来处理,repo应运而生。
repo
是Android为了方便管理多个git库而开发的一系列Python脚本(Python通过调用git命令来完成自己的功能),repo的出现,并非为了取代git,而是让Android开发者更为有效的利用git。(Android源码包含数百个git库,仅仅是下载这么多git库就是一项繁重的任务,所以在下载源码时,Android就引入了repo)官方推荐通过Linux curl命令下载repo,下载完后,为repo脚本添加可执行权限。
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
$ chmod a+x ~/bin/repo
$ export PATH=$PATH:~/bin
#初始化一个repo库。其中,-u 表示清单库的地址, -m表示使用清单库中的哪份清单文件。
$ repo init -m <manifest.xml> -u <manifest-url>
1、repo的工作原理概述
repo需要关注当前git库的数量、名称、路径等,有了这些基本信息,才能对这些git库进行操作。通过集中维护所有git库的清单,repo可以方便的从清单中获取git库的信息,清单会随着版本升级而产生变化,同时也有一些本地的修改定制需求,所以repo是通过一个名为**manifests
的git库来管理项目的清单文件的,当打开repo这个可执行的python脚本后,发现代码量并不大(不超过1000行),难道仅这一个脚本就完成了AOSP数百个git库的管理吗?并非如此。 repo是一系列脚本的集合,这些脚本也是通过名为repo的git库来维护的**。当客户端使用repo命令初始化一个项目时,就会从远程把manifests
和repo
这两个git库拷贝到本地(但对于Android开发来说,一般通过文件管理器,是无法看到这两个git库的)repo将自动化的管理信息都隐藏根目录的.repo子目录中。
2、repo 库的主要结构:repo库,manifest库,子项目仓库
所有的这些脚本,repo使用了一个单独的git仓库去负责维护它,即repo库。repo库中有一个脚本——repo,在将其添加到系统环境变量PATH中后,就可以执行repo命令,而repo库的下载,可以使用命令repo init --repo-url=< repo库地址 >,repo实现主要由三部分构成:repo库,manifest库,子项目仓库。
2.1、项目清单库(.repo/manifests)
一份位于工作目录(.repo/manifests)的.git目录下,另一份独立存放于.repo/manifests.git,清单库
- 指定不同的清单库文件,可以将各仓库代码切换到对应的不同节点
- 指定清单库不同的分支,可以切换不同的清单库文件
repo仓库可通过manifest库可以获得所有AOSP子项目仓库的元信息,通过这些元信息我们就可以通过repo仓库里面的Python脚本来操作AOSP的子项目。而记录这些元信息的文件即以xml格式保存的清单文件,对这些清单文件的管理,repo又是单独使用一个git库去负责维护它,即清单库。AOSP项目清单git库下,只有一个文件default.xml,是一个标准的XML,描述了当前repo管理的所有信息。 AOSP的default.xml的文件内容如下:
<?xml version="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" >
<copyfile src="core/root.mk" dest="Makefile" />
</project>
<project path="abi/cpp" name="platform/abi/cpp" groups="pdk" />
<project path="art" name="platform/art" groups="pdk" />
...
<project path="tools/studio/translation" name="platform/tools/studio/translation" groups="notdefault,tools" />
<project path="tools/swt" name="platform/tools/swt" groups="notdefault,tools" />
</manifest>
可以包含以下节点:
-
remote
——描述了远程仓库的基本信息。- name——远程git服务器的名字,默认命名为origin,直接用于git fetch, git remote 等操作
- alias—— 远程git服务器的别名,如果指定了,则会覆盖name的设定。在一个manifest中,name不能重名,但alias可以重名。
- fetch—— 所有projects的git URL 前缀
- review——用作code review的server地址,指定Gerrit的服务器名,用于repo upload操作。如果没有指定,则repo upload没有效果。
-
default
——default标签的定义的属性,将作为project标签的默认属性,在project标签中,也可以重写这些属性。-
remote—— 默认使用的远程仓库名称,之前定义的某一个remote元素中name属性值,用于指定使用哪一个远程git服务器。
-
revision—— git分支的名字,表示当前的版本,例如master或者refs/heads/master
-
sync_j—— 在repo sync中sync-j表示在同步远程代码时,并发的任务数量,配置高的机器可以将这个值调大。
-
sync_c—— 如果设置为true,则只同步指定的分支(revision 属性指定),而不是所有的ref内容。
-
sync_s—— 如果设置为true,则会同步git的子项目
-
-
manifest-server
元素——只能有一个该元素。它的url属性用于指定manifest服务的URL,通常是一个XML RPC 服务,它要支持一下RPC方法:- GetApprovedManifest(branch, target)—— 返回一个manifest用于指示所有projects的分支和编译目标。
target参数来自环境变量TARGET_PRODUCT和TARGET_BUILD_VARIANT,组成 T A R G E T P R O D U C T − TARGET_PRODUCT- TARGETPRODUCT−TARGET_BUILD_VARIANT。 - GetManifest(tag)—— 返回指定tag的manifest
- GetApprovedManifest(branch, target)—— 返回一个manifest用于指示所有projects的分支和编译目标。
-
project
——每一个repo管理的git库,就是对应到一个project标签,path描述的是项目相对于远程仓库URL的路径,同时将作为对应的git库在本地代码的路径; name用于定义项目名称,命名方式采用的是整个项目URL的相对地址。 譬如,AOSP项目的URL为https://android.googlesource.com/,命名为platform/build的git库,访问的URL就是https://android.googlesource.com/platform/build- name—— 唯一的名字标识project,同时也用于生成git仓库的URL。格式如下:
r e m o t e f e t c h / {remote_fetch}/ remotefetch/{project_name}.git - path—— 可选的路径。指定git clone出来的代码存放在本地的子目录。如果没有指定,则以name作为子目录名。
- remote—— 指定之前在某个remote元素中的name。
- revision—— 指定需要获取的git提交点,可以是master, refs/heads/master, tag或者SHA-1值。
- groups—— 列出project所属的组,以空格或者逗号分隔多个组名。所有的project都自动属于"all"组。每一个project自动属于
name——‘name’ 和path——'path’组。例如,它自动属于default, name——monkeys, and path——barrel-of组。如果一个project属于notdefault组,则,repo sync时不会下载。 - sync_c—— 如果设置为true,则只同步指定的分支(revision 属性指定),而不是所有的ref内容。
- sync_s—— 如果设置为true,则会同步git的子项目。
- upstream—— 在哪个git分支可以找到一个SHA1。用于同步revision锁定的manifest(-c 模式)。该模式可以避免同步整个ref空间。
- annotation—— 可以有多个annotation,格式为name-value pair。在repo forall 命令中这些值会导入到环境变量中。
- remove-project—— 从内部的manifest表中删除指定的project。经常用于本地的manifest文件,用户可以替换一个project的定义。
- include—— 通过name属性可以引入另外一个manifest文件(路径相对与manifest repository’s root)。
- name—— 唯一的名字标识project,同时也用于生成git仓库的URL。格式如下:
如果需要新增或替换一些git库,可以通过修改default.xml来实现,repo会根据配置信息,自动化管理。但直接对default.xml的定制,可能会导致下一次更新项目清单时,与远程default.xml发生冲突。 因此,repo提供了一个种更为灵活的定制方式local_manifests
:所有的定制是遵循default.xml规范的,文件名可以自定义,譬如local_manifest.xml, another_local_manifest.xml等, 将定制的XML放在新建的.repo/local_manifests子目录即可。repo会遍历.repo/local_manifests目录下的所有*.xml文件,最终与default.xml合并成一个总的项目清单文件manifest.xml。
$ ls .repo/local_manifests
local_manifest.xml
another_local_manifest.xml
$ cat .repo/local_manifests/local_manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<project path="manifest" name="tools/manifest" />
<project path="platform-manifest" name="platform/manifest" />
</manifest>
2.2、repo脚本库(.repo/repo)
repo对git命令进行了封装,提供了一套repo的命令集(包括init, sync等),所有repo管理的自动化实现也都包含在这个git库中。 在第一次初始化的时候,repo会从远程把这个git库下载到本地。所有的这些脚本,repo使用了一个单独的git仓库去负责维护它,即repo库。repo库中有一个脚本——repo,在将其添加到系统环境变量PATH中后,就可以执行repo命令,而repo库的下载,可以使用命令repo init --repo-url=< repo库地址 >
2.3、子项目仓库(.repo/repo/projects仓库目录和工作目录)
仓库目录保存的是历史信息和修改记录,工作目录保存的是当前版本的信息。子项目本身就是git管理的完成仓库,因此都有一个.git目录,就在子项目的根目录。repo管理模式下,每一个AOSP子项目的工作目录也有一个.git目录,不过这个.git目录是一个符号链接,实际链接到.repo/repo/projects对应的Git目录。即子项目的工作目录和Git目录都是分开存放的,其中,工作目录位于AOSP根目录下,Git目录位于.repo/repo/projects目录下。这样我们就既可以在AOSP子项目的工作目录下执行Git命令,也可以在其对应的Git目录下执行Git命令。一般要访问到工作目录的命令(例如git status)需要在工作目录下执行,而不需要访问工作目录(例如git log)可以在Git目录下执行。
二、repo 基本操作
1、repo init -u ssh://xxxx/manifest -m 初始化repo仓库
2、repo sync 同步远程仓库代码
要取得 upstream 最新的 code,只要执行下 repo sync 就行。等价于对每个 project 做 git pull 的批量处理。不过如果你本地曾对 source tree 进行过一些修改,repo sync 可能遇到不同的問題。
-
-d
——同步repo所管理的各个git工程的代码。要不丢失本地修改,强制同步远端服务器代码并将各子工程的当前分支detach到no branch状态。repo sync -d
命令会将 HEAD 强制指向 repo manifest 版本,而忽略本地的改动。即使同时提供-d
和--force-sync
两个选项,还是不能强制覆盖本地修改。若第一次运行 repo sync ,则相当于 git clone ,会把 repository 中的所有内容都拷贝到本地。若不是第一次运行 repo sync ,则相当于 git remote update ; git rebaseorigin/branch . repo sync 会更新 .repo 下面的文件。如果在merge 的过程中出现冲突,需要手动运行 git rebase --continue
-
-c
——
repo sync的正确使用姿势:
2.1、本地修改后,未commit 此次修改
若只是单纯地修改,还未进行 git commit 操作时,执行 repo sync 时会將 upstream 和你的本地修改做合併(merge);如果若有冲突(conflict),则repo sync 就会失败(不过,你的修改依然存在,不會被蓋掉)。为了避免因冲突导致的失败,最好是先用 git stash 缓存你的修改,再 repo sync,最后再git stash pop 将修改apply到repo sync 后的结果上(若有冲突可能需要手动修正)。假如你修改的是frameworks/base:
$ cd frameworks/base
$ git stash
$ cd ../..
$ repo sync frameworks/base
$ cd frameworks/base
$ git stash pop
2.2、本地修改后,已经commit 此次修改
若已將修改 commit ,执行 repo sync 时会根据你的 branch 是否为 remote-tracking branch 而有所不同。若不是 remote-tracking branch,则 repo sync 的等价于 git checkout 至 upstream 相对应 branch 的 tip (即最新的 commit),可能会显示以下的信息:
$ repo sync frameworks/base
frameworks/base/: discarding 3 commits
不要担心,它只代表你的 HEAD 已被切换到 upstream,你原来的 commit 并沒有真的被丟掉,你仍然可以通过以下命令切换回來
$ cd frameworks/base
$ git checkout ORIG_HEAD
其中ORIG_HEAD 指 repo sync 切换前的 HEAD。然后再用 git rebase把你的修改 apply 到 upstream tip 上:
# (如果你在弄的是 froyo-x86、… 就把 gingerbread-x86 換成 froyo-x86、…)
$ git rebase m/gingerbread-x86
以上这些操作能不能自动完成? 當然可以,这就是 remote-tracking branch 的意义。若你的 commit 是在一个remote-tracking branch 上,则repo sync 就会將你的 commit apply 到 upstream tip 上,例如:
$ repo sync frameworks/base
project frameworks/base/
First, rewinding head to replay your work on top of it...
Applying: fix wifi issues
git branch -v 时显示(no branch)
$ git checkout -b mybranch m/gingerbread-x86
Branch mybranch set up to track remote branch gingerbread-x86 from x86.
Switched to a new branch ‘mybranch’
3、repo forall -c <git 命令> 批量执行git 命令
4、repo 分支管理
repo上创建分支很简单,就是git创建新分支一样,repo只不过是利用git(manifest仓库)来记录管理多个git仓库而已。因此我们利用repo创建一个新的分支,即给repo管理的每个git仓库创建一个一样的新分支。
4.1、repo 创建新分支
4.1.1、repo start new_branch_name创建。
“.” 代表当前工作的branch 分支
4.1.2、repo forall -c “git checkout -b new_branch_name”
repo forall -c 意思是遍历所有的git仓库(除了管理的仓库manifest外),并在每个仓库(除了管理的仓库manifest外)执行-c后面所指定的命令,-c即command。
repo forall -c "git checkout -b new_branch_name"
如上面就是在每个仓库(除了管理的仓库manifest外)都执行git checkout -b new_branch_name命令,那么这样就可以给每个仓库创建了一个一样的新分支了。
4.2、repo 推送到远程代码仓库
repo forall -c "git push origin local_branch_name"
4.3、新分支添加到manifest仓库
manifest仓库是管理repo下面其他仓库的,因此我们要让repo知道我们创建了新分支,好让同步的时候可以根据分支名来同步分支。
- 为manifest仓库创建新分支new_branch_name
因为repo forall -c的命令是除了.repo/manifest下的仓库都进行git命令,所以我们还需要在.repo/manifest也创建一个分支.
.repo/manifests$ git checkout -b new_branch_name
- 修改清单default.xml
<default remote="origin" revision="new_branch_name" sync-j="4"/>
-
把新分支推上服务器
git add default.xml git commit -m "add a new branch" git push origin new_branch_name
5、repo管理的仓库怎么切换
repo forall -c + git 命令
,可以实现,但不建议。我们推荐使用基于manifest的分支切换,好处是敲的命令更短,而且不容易出错。
repo init -m xxx.xml
repo sync -d
使用前保证本地代码是都已经提交了,也就是干净的代码。
5.1、如何得知应该是哪个xxx.xml?
xxx.xml实际是清单库,记录的是repo管理的各个子代码仓库的分支信息。一般保存在工程根目录 .repo/manifests/ 文件夹。 里面可以看到各种xxx.xml。为了方便记忆,我们一般清单库的名字和分支的名字是一样的。示例:打开platform.xml,内容如下,关注 <default revision=“platform” 这一行。其中的revision="platform"表示默认分支是platform。
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote name="origin" review="review.source.android.com" fetch="../../" />
<default revision="platform" remote="origin" sync-j="4" />
<project path="bionic" name="xinyun/qcom/bionic" />
<project path="hardware" name="xinyun/qcom/hardware" />
<project path="device" name="xinyun/qcom/device" />
5.2、如何得知当前代码的具体提交节点信息呢
repo manifest -r
结果如下,注意revision字段,全部是具体的hash
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote fetch=".." name="origin" review="http://review.omapzoom.org/"/>
<default remote="origin" revision="mpt320" sync-j="4"/>
<project name="xxxx/DVRIP" path="DVRIP" revision="21446707a55885b0d85c4251841bb6fc0beaebd6" upstream="mpt320"/>
<project name="xxxx/MPT" path="MPT" revision="02dea0e19fa224c46760d075137c7fe9ec24ab00" upstream="mpt320">
<copyfile dest="build.sh" src="Tools/build.sh"/>
<copyfile dest="ssh-login.sh" src="Tools/ssh-login.sh"/>
</project>
<project name="xxxx/MPTApp" path="MPTApp" revision="04b0734cf2888759ba28be70d683f25401aca7bf" upstream="mpt320"/>
<project name="xxxx/MPTRpcServer" path="MPTRpcServer" revision="d1ebb9d4676abc287c127e47baa65cac8c85a92f" upstream="mpt320
<project name="xxxx/Manager" path="Manager" revision="4e3803aa0984158cf2511b2efd2b4384d2c52cae" upstream="mpt320"/>
<project name="xxxx/Media" path="Media" revision="28a3bba254b30c29b74527e4d7132c044c345176" upstream="mpt320"/>
<project name="xxxx/PAL" path="PAL" revision="23986988975fed2f0b0b2ee6bcefea1cd2040f6d" upstream="mpt320"/>
<project name="xxxx/Record" path="Record" revision="e6594eb6f7942365c0157e47d7020b11b3ac084e" upstream="mpt320"/>
</manifest>
6、repo download target_revision 下载指定版本
下载特定的版本到本地,例如: repo downloadpltform/frameworks/base 1241 下载修改版本为 1241 的代码
未完待续…