前言
Android 采用 Gerrit 提供代码评审服务,并且开发了一个客户端工具 repo,实现多仓库管理。Git 的开发者对服务端的 Git 源码做了扩展,使得基于 Git(cgit)的代码平台可以很容易引入新的集中式工作流。同样 git-repo 兼容 Android 的 repo 工具,支持对多仓库的协同管理。
git-repo
实现和安卓 repo
使用习惯上的兼容,两者的差异如下:
- Android repo 只支持 Gerrit 服务器。
git-repo
采用了一套新的服务发现协议,支持 AGit-Flow 及其兼容的工作流,也支持 Gerrit 工作流。 - Android repo 只支持 manifests 仓库管理下的多仓库,而
git-repo
在此基础上,还支持单一 Git 代码仓的集中式工作流协同,并提供快捷的别名命令git peer-review
,或git pr
。 - Android repo 使用 Python 脚本语言开发,
git-repo
使用 Go 语言开发,包含了完整测试用例。git-repo
安装简单,除了Git
外,别无其他软件依赖。
安装repo
首先下载repo的引导脚本,可以使用wget、curl甚至浏览器从地址http://android.git.kernel.org/repo下载。把repo脚本设置为可执行,并复制到可执 行的路径中。在Linux上可以用下面的指令将repo下载并复制到用户主目录的bin
目录下。
mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
## 如果上述 URL 不可访问,可以用下面的:
## curl -sSL 'https://gerrit-googlesource.proxy.ustclug.org/git-repo/+/master/repo?format=TEXT' |base64 -d > ~/bin/repo
chmod a+x ~/bin/repo
为什么说下载的repo只是一个引导脚本(bootstrap)而不是直接称为repo呢?因为repo的大部分功能代码不在其中,下载的只是一个帮助完成整个repo程序的继续下载和加载工具。
repo init
完成repo工具的完整下载,现在仅有的不过是repo 的引导程序。初始化操作会从repo脚本里设定的镜像地址中克隆repo.git库到当前的目录下。一个隐藏的.repo目录。
克隆创建的清单库manifest.git(地址来自与-u参数),清单库实际上只包含一个default.xml文件,这个XML文件定义了多个版本库和本地地址的映射关系,是repo工作的指引文件。
克隆的清单库位于.repo/manifests.git中,本地克隆到.repo/manifests。
repo巧妙的实现了多Git版本库的管理。因为 repo使用了清单版本库,所以repo这一工具并没有被局限于Android项目,可以在任何项目中使用。
下载repo.git
在repo引导脚本中,定义了缺省的repo.git的版本库位置以及要检出的缺省分支。
如果不想从缺省任务获取repo,或者不想获取稳定版(stable分支)的repo,可以在repo init命令中通过下面的参数覆盖缺省的设置,从指定的源地址克隆repo代码库。
- 参数
--repo-url
,用于设定repo的版本库地址。 - 参数
--repo-branch
,用于设定要检出的分支。 - 参数
--no-repo-verify
,设定不要对repo的里程碑签名进行严格的验证。
实际上,完成repo.git版本库的克隆,这个repo引导脚本就江郎才尽了,init命令的后续处理(以及其他命令)都交给刚刚克隆出来的.repo/repo/main.py来继续执行。
清单库是什么?从哪里下载?
清单库实际上只包含一个default.xml
文件。这个XML文件定义了多个版本库和本地地址的映射关系,是repo工作的指引文件。所以在使用repo引导脚本进行初始化的时候,必须通过-u
参数指定清单库的源地址。
清单库的下载,是通过repo init命令初始化时,用-u
参数指定清单库的位置。例如repo针对Android代码库进行初始化时执行的命令:
$ repo init -u git://android.git.kernel.org/platform/manifest.git
-
Repo引导脚本的
init
命令可以使用下列和清单库相关的参数:- 参数
-u
(--manifest-url
):设定清单库的Git服务器地址。 - 参数
-b
(--manifest-branch
):检出清单库特定分支。 - 参数
--mirror
:只在repo第一次初始化的时候使用,以和Android服务器同样的结构在本地建立镜像。 - 参数
-m
(--manifest-name
):当有多个清单文件,可以指定清单库的某个清单文件为有效的清单文件。缺省为default.xml
。
- 参数
-
Repo初始化命令(repo init)可以执行多次:
- 不带参数的执行repo init,从上游的清单库获取新的清单文件
default.xml
。 - 使用参数
-u
(--manifest-url
)执行repo init,会重新设定上游的清单库地址,并重新同步。 - 使用参数
-b
(--manifest-branch
)执行repo init,会使用清单库的不同分支,以便在使用repo sync时将项目同步到不同的里程碑。
- 不带参数的执行repo init,从上游的清单库获取新的清单文件
清单库和清单文件
当执行完毕repo init之后,工作目录内空空如也。实际上有一个.repo
目录。在该目录下除了一个包含repo的实现的repo库克隆外,就是manifest库的克隆,以及一个符号链接链接到清单库中的default.xml
文件。
当执行 repo init 命令来初始化仓库的时候首先执行的就是 Repo 的引导脚本,该脚本会到我们指定的地方去下载 Manifest 仓库,以及 Repo 命令主体部分。下载好之后工作目录内空空如也。实际上有一个.repo
目录。在该目录下除了一个包含repo的实现的repo库克隆外,就是manifest库的克隆,以及一个符号链接链接到清单库中的default.xml
文件。其中:
文件夹 | 用途 |
---|---|
manifests | 清单文件的仓库 |
manifests.git | 清单文件的 Git 裸仓库,不带工作区 |
manifest.xml | 这是一个链接文件,指向你的用于初始化工作区的清单文件,即manifests/default.xml |
project.list | 一个文本文件,里面包含所有项目名字的列表 |
projects | projects该文件夹下包含所有 git project 的裸仓库,文件夹的层次结构跟工作区的布局一样 |
repo | 这是 repo 命令的主体,其中也包含最新的 repo 命令,推荐使用这里面的 repo 命令 |
在工作目录下的.repo/manifest.xml
文件就是清单文件的 git 仓库。Repo命令的操作,都要参考这个清单文件。这些 xml 文件中包含了各个 git project 的名称,检出的 reversion,检出到哪个目录等等信息。Repo 就是利用这些 manifest 文件去分别获取各个 project,比如这样一个 manifest 文件:
元素 | 详情 |
---|---|
manifest 元素 | xml 文件的根元素 |
remote 元素 | 可以存在一个或者多个 remote 元素,remote 元素指定了使用 repo upload 命令的时候,会将改变提交到哪个服务器 |
default 元素 | default 元素中指定的属性都是一些缺省的属性。即如果 project 元素中不存在该属性,则使用在 default 元素中指定的属性。revision:Git 分支的名字。如果 project 元素没有指定 revision 属性,那么就使用 default 元素的该属性。revision 属性的值可以是一个 git branch,git tag,也可以是一个 commit id。sync-j:sync 的时候,并行工作的任务数。sync-c:如果设置为 true,则在同步代码的时候,将只会同步 project 元素中 revision 属性中指定的分支。如果 project 元素没有指定 revision 属性,则使用 default 元素的 revision 属性。 |
project 元素 | xml 文件中可以指定一个或者多个 project 元素。 每一个 project 元素都描述了一个需要 pull 到本地的 git 仓库。project 元素中有很多可以使用的属性,在此只介绍几个我们经常使用的属性。name:git project 的名字,path:该 project 的本地工作区的路径,revision:该 project 要跟踪的分支的名字即之后提交PR的目标分支。名字可以是相对于 refs/heads 命名空间的,如:master,或者绝对的,如:refs/heads/master。标签或者 48 位的 SHA-1 值理论上也可以工作。如果没有提供该属性,则使用 default 元素中的 revision 属性。 |
repo命令的工作流
repo的命令
Repo命令实际上是Git命令的简单或者复杂的封装。每一个repo命令都对应于repo源码树中subcmds
目录下的一个同名的Python脚本。每一个repo命令都可以通过下面的命令获得帮助。
repo help <command>
## repo init
repo init -u URL [OPTIONS]
常用选项参数如下,其它参数通过repo help init查询:
-u
(–manifest-url):指定要从中检索清单Git服务器代码库的网址
-m
(–manifest-name):当有多个清单文件时,在代码库中选择清单文件。如果未选择任何清单名称,则会默认选择 default.xml
-b
(–manifest-branch):指定修订版本,即特定的清单分支
命令repo init 要完成如下操作:
- 完成repo工具的完整下载,执行的Repo脚本只是引导程序
- 克隆清单版本库manifest.git (地址来自于-u 参数)
- 克隆的清单库位于manifest.git中,克隆到本地
.repo/manifests
。在之前的Repo版本中清单.repo/manifest.xml
只是符号链接,它指向.repo/manifests/default.xml
。在目前的repo版本中清单.repo/manifest.xml
是一个实际的文件,通过include的方式引用.repo/manifests/default.xml
- 如果
.repo/manifests
中有多个xml文件,repo init
可以任意选择其中一个,默认选择是default.xml - 在
.repo/manifests
中执行git branch -a | cut -d / -f 3
可以查看本地存放的已有的所有分支信息,但是该信息只是同步到你上次下载时的信息,如果需要最新信息需要先在该目录中执行git pull
操作。
实际上,完全可以进入到.repo/manifests
目录,用git命令操作清单库。对manifests
的修改不会因为执行repo init而丢失,除非是处于未跟踪状态。
repo sync
repo sync [<project>…]
-j
:开启多线程同步操作,一般8核心可以开到16任务,过多会起反作用。默认情况下,使用4个线程并发进sync
-c
:只同步指定的远程分支。默认情况下,sync会同步所有的远程分支,当远程分支比较多的时候,下载的代码量就大。使用该参数,可以缩减下载时间,节省本地磁盘空间
-d
:脱离当前的本地分支,切换到manifest.xml中设定的分支。在实际操作中这个参数很有用,当我们第一次sync完代码后,往往会切换到dev分支进行开发。如果不带该参数使用sync, 则会触发本地的dev分支与manifest设定的远程分支进行合并,这会很可能会导致sync失败
-f
:当有git库sync失败了,不中断整个同步操作,继续同步其他的git库
–no-clone-bundle
:在向服务器发起请求时,为了做到尽快的响应速度,会用到内容分发网络(CDN, Content Delivery Network)。同步操作也会通过CDN与就近的服务器建立连接, 使用HTTP/HTTPS的$URL/clone.bundle来初始化本地的git库,clone.bundle实际上是远程git库的镜像,通过HTTP直接下载,这会更好的利用网络带宽,加快下载速度
repo sync命令用于参照清单文件克隆或者同步版本库。如果某个项目版本库尚不存在,则执行repo sync命令相当于执行git clone。如果项目版本库已经存在,则相当于执行下面的两个命令:
-
git remote update
相当于对每一个remote源执行fetch
操作。 -
git rebase origin/branch
针对当前分支的跟踪分支,执行rebase
操作。不采用merge
而是采用rebase
,目的是减少提交数量,方便评审(Gerrit)。
如果直接执行repo sync
会同步所有project,如果后面指定project则只会同步指定的project。如repo sync platform/build
。
repo start
repo start <newbranchname> [--all | <project>…]
repo start命令实际上是对git checkout -b命令的封装。为指定的项目或者所有项目(若使用--all
参数),以清单文件中为项目设定的分支或里程碑为基础,创建特性分支。特性分支的名称由命令的第一个参数指定。相当于执行git checkout -b。
此命令会更新manifest xml文件中的信息给对应的远程分支创建相应的本地分支。
repo status
repo status [<project>...]
repo status命令实际上是对git diff-index、git diff-files命令的封装,同时显示暂存区的状态和本地文件修改的状态。
例输出:
project repo/ branch devwork
-m subcmds/status.py
...
上面示例输出显示了repo项目的devwork
分支的修改状态。
-
每个小节的首行显示项目名称,以及所在分支名称。
-
之后显示该项目中文件变更状态。头两个字母显示变更状态,后面显示文件名或者其他变更信息。
-
第一个字母表示暂存区的文件修改状态。
其实是git-diff-index命令输出中的状态标识,并用大写显示。
-
:没有改变A
:添加 (不在HEAD中, 在暂存区 )M
:修改 ( 在HEAD中, 在暂存区,内容不同 )D
:删除 ( 在HEAD中,不在暂存区 )R
:重命名 (不在HEAD中, 在暂存区,路径修改 )C
:拷贝 (不在HEAD中, 在暂存区,从其他文件拷贝)T
:文件状态改变 ( 在HEAD中, 在暂存区,内容相同 )U
:未合并,需要冲突解决
-
第二个字母表示工作区文件的更改状态。
其实是git-diff-files命令输出中的状态标识,并用小写显示。
-
:新/未知 (不在暂存区, 在工作区 )m
:修改 ( 在暂存区, 在工作区,被修改 )d
:删除 ( 在暂存区,不在工作区 )
-
两个表示状态的字母后面,显示文件名信息。如果有文件重命名还会显示改变前后的文件名以及文件的相似度。
repo checkout
repo checkout <branchname> [<project>...]
repo checkout命令实际上是对git checkout命令的封装。检出之前由repo start创建的分支。
repo branches命令
repo branches [<project>...]
repo branches命令读取各个项目的分支列表并汇总显示。该命令实际上是通过直接读取.git/refs
目录下的引用来获取分支列表,以及分支的发布状态等。
输出示例:
*P nocolor | in repo
repo2 |
-
第一个字段显示分支的状态:是否是当前分支,分支是否发布到代码审核服务器上?
-
第一个字母若显示星号(*),含义是此分支为当前分支
-
第二个字母若为大写字母
P
,则含义是分支所有提交都发布到代码审核服务器上了。 -
第二个字母若为小写字母
p
,则含义是只有部分提交被发布到代码审核服务器上。 -
若不显示
P
或者p
,则表明分支尚未发布。 -
第二个字段为分支名。
-
第三个字段为以竖线(|)开始的字符串,表示该分支存在于哪些项目中。
-
| in all projects
该分支处于所有项目中。
-
| in project1 project2
该分支只在特定项目中定义。如:
project1
、project2
。 -
| not in project1
该分支不存在于这些项目中。即除了project1
项目外,其他
-
repo diff
repo diff [<project>...]
epo diff命令实际上是对git diff**命令的封装,用以分别显示各个项目工作区下的文件差异。
repo stage
repo stage -i [<project>…]
-i
:代表git add --interactive
命令中的--interactive
,给出个界面供用户选择。
实际是对git add --interactive
命令的封装、用于挑选各个项目工作区中的改动以加入暂存区。
repo upload
repo upload [--re --cc] {[<project>]... | --replace <project>}
repo upload命令相当于git push,但是又有很大的不同。执行repo upload不是将版本库改动推送到克隆时的远程服务器,而是推送到代码审查服务器(由Gerrit软件架设)的特殊引用上,使用的是SSH协议(特殊端口)。代码审核服务器会对推送的提交进行特殊处理,将新的提交显示为一个待审核的修改集,并进入代码审查流程。只有当审核通过,才会合并到官方正式的版本库中。
-h, --help
:显示帮助信息。
-t
:发送本地分支名称到 Gerrit 代码审核服务器。
--replace
:发送此分支的更新补丁集。注意使用该参数,只能指定一个项目。
--re=REVIEWERS, --reviewers=REVIEWERS
:要求由指定的人员进行审核。
--cc=CC
:同时发送通知到如下邮件地址。
repo download
repo download {project change[/patchset]}...
repo download命令主要用于代码审核者下载和评估贡献者提交的修订。贡献者的修订在Git版本库中以refs/changes/<changeid>/<patchset>
引用方式命名(缺省的patchset为1),和其他Git引用一样,用git fetch获取,该引用所指向的最新的提交就是贡献者待审核的修订。使用repo download命令实际上就是用git fetch获取到对应项目的refs/changes/<changeid>/patchset>
引用,并自动切换到对应的引用上。
repo rebase
repo rebase {[<project>...] | -i <project>...}
-h, --help
:显示帮助并退出
-i, --interactive
:交互式的变基(仅对一个项目时有效)
-f, --force-rebase
:向 git rebase 命令传递 --force-rebase 参数
--no-ff
:向 git rebase 命令传递 -no-ff 参数
-q, --quiet
:向 git rebase 命令传递 --quiet 参数
--autosquash
:向 git rebase 命令传递 --autosquash 参数
--whitespace=WS
:向 git rebase 命令传递 --whitespace 参数
repo rebase命令实际上是对git rebase命令的封装,该命令的参数也作为git rebase命令的参数。但 -i 参数仅当对一个项执行时有效。
repo prune
repo prune [<project>...]
repo prune命令实际上是对git branch -d命令的封装,该命令用于扫描项目的各个分支,并删除已经合并的分支。
repo abandon
repo abandon <branchname> [<project>...]
相比repo prune命令,repo abandon命令更具破坏性,因为repo abandon是对git branch -D命令的封装。该命令非常危险,直接删除分支,请慎用。
repo grep
相当于对git grep的封装,用于在项目文件中进行内容查找。
repo smartsync
相当于用-s
参数执行repo sync,可以通过一些选项设置同步的方式,比如是否在遇到第一个错误的时候就停止,是否强制删除含有未提交修改的项目等等。
repo forall
repo forall [<project>…] –c <command>
迭代器,可以对repo管理的项目中执行同一个shell命令
-c
:后面所带的参数是shell指令
-p
:在shell指令输出之前列出项目名称
-v
:列出执行shell指令输出的错误信息
-r
:使用正则
额外的环境变量:
REPO_PROJECT
:指定项目的名称
REPO_PATH
:指定项目在工作区的相对路径
REPO_REMOTE
:指定项目远程仓库的名称
REPO_LREV
:指定项目最后一次提交服务器仓库对应的哈希值
REPO_RREV
:指定项目在克隆时的指定分支,manifest里的revision属性
另外,如果-c后面所带的shell指令中有上述环境变量,则需要用单引号把shell指令括起来。
repo forall -c 'echo $REPO_PROJECT' # 输出项目的名称
repo forall -p -c git merge aosp-my-dev # 把所有项目切换到master分支,该指令将会把aosp-my-dev分支合并到master分支
repo forall -c git tag aosp-mytag-1.0 # 在所有项目下打标签
repo forall -c 'git remote add origin_1 ssh://jiangxin@192.168.0.125/$REPO_PROJECT.git' # 引用环境变量REPO_PROJECT添加远程仓库
repo forall -c git remote add origin_1 # 删除远程仓库
repo forall –c git branch aosp-dev # 切换分支
repo forall –c git checkout –b aosp-dev # 创建分支
repo forall -c git reset --hard # 当repo sync时如果提示discarding xx commits时可以通过该命令废弃所有提交,然后继续repo sync
repo forall -c 'commitID=`git log --before "2020-01-31 00:00:00" -1 --pretty=format:"%H"`; git reset --hard $commitID' /* repo回退当前分支下所有仓库到指定日期前的最新代码版本*/
repo manifest
repo manifest –o android.xml
显示manifest文件内容,可以通过-o参数输出到指定的文件中。
repo version
显示repo的版本号,还会同时显示Git/Python等依赖的应用版本。
repo selfupdate
用于repo自身的更新。如果提供--repo-upgraded
参数,还会更新各个项目的钩子脚本。
repo官方教程:https://source.android.com/source/using-repo.html