Android版本库众多的原因,主要原因是版本库太大以及Git不能部分检出。
如果所有的东西都放在一个库中,而某个开发团队感兴趣的可能就是某个驱动,
或者是某个应用,却要下载如此庞大的版本库,是有些说不过去。
git也有submodule供多个库下载,但这功能使用不方便,
其局限性和麻烦可参看
http://www.worldhello.net/got...
如果是你,有什么方案来管理这么多的库?
Repo是Google开发的用于管理Android版本库的一个工具。
Repo并不是用于取代Git,是用Python对Git进行了一定的封装,简化了对多个Git版本库的管理。
对于repo管理的任何一个版本库,都还是需要使用Git命令进行操作
02. Repo 如何组织这么多库 --manifest文件?
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote name="aosp"
fetch=".." />
<default revision="refs/tags/android-7.1.0_r4"
remote="aosp"
sync-j="4" />
<project path="build" name="platform/build" groups="pdk,tradefed" >
<copyfile src="core/root.mk" dest="Makefile" />
</project>
<project path="build/blueprint" name="platform/build/blueprint" groups="pdk,tradefed" />
......
从上看出,repo用清单文件来管理, 其内容为版本的地址,默认分支名,远程库和本地路径对应关系。
一个project对应一个库,path为本地切出来的工作目录的路径,也就是我们看到的代码路径,
name为对应的远程git库的名字/路径。
注意,一般说来,一套安卓代码都由好几百个库组成,上面只截取了一部分。
关于manifests更多的信息可查看你代码根目录的
.repo/repo/docs/manifest-format.txt
这里只说下revision,
你可以在project里通过revision指定与清单文件default里不一样的分支,
revision的值可能是分支/tag/commitID等形式
你也可以用repo manifest -r命令生成带revision信息的清单文件,可用于代码发布
该命令生成的一个例子:
<project name="device/qcom/common" revision="3a83da1dff148dd709caac602693d3295bd0a18b" upstream="refs/heads/省略">
03. repo init 在干什么?
$repo init --help
Usage: repo init [options]
Options:
...... Manifest options:
-u URL, --manifest-url=URL
manifest repository location
-b REVISION, --manifest-branch=REVISION
manifest branch or revision
-m NAME.xml, --manifest-name=NAME.xml
initial manifest file
--mirror create a replica of the remote repositories rather
than a client working directory
--reference=DIR location of mirror directory
--depth=DEPTH create a shallow clone with given depth; see git clone
...... repo Version options:
--repo-url=URL repo repository location
--repo-branch=REVISION
repo init用法如上, 我们一般下载code的方式为
repo init -u xxx -b yyy -m zzz.xml
repo sync -c
注意:
-m 当有多个清单文件,可以指定清单库的某个清单文件为有效的清单文件,如果省略该参数,则用该库里默认的清单文件
repo init命令执行后主要干了两件事
下载repo到.repo/repo里
下载并检出-u所指定的manifest库,并建立.repo/manifest.xml链接
之后用repo sync 才是下载库信息到 .repo/里,并根据manifest.xml里path和name等信息切出本地代码,建立对应关系。
对于第一步大家可能比较疑问,不是已经有个repo了吗?为什么还在下载repo?
实际上,我们执行的repo命令只是相当于个主函数,真正的执行命令(如repo sync)
都是.repo/repo/subcmds里的,
该子命令都用python写的, 所以当有需要,或者执行出错时想查看源码的话可在此目录下查看。
04. --mirror --reference作用
让我们想个问题,假设你一套代码有100G,
你们是异地协作协作,如需要上海/北京两地办公,主服务器在北京,网络很慢,
如何为上海的同事减少下代码时间? 如何缓解主服务器的压力?
你们都在一个地办公,多人公用一个服务器, 如何节省下载代码时间? 节省服务器空间?
你作为SCM,每天都要完全clean编译版本, 如何更快的构建?
当然你可能有别的方法,我们这只讲repo本身提供的方法,即镜像功能。该方法操作也简单,见效快。
主要用到的命令参数为mirror reference:
--mirror 建立和上游Android的版本库一模一样的镜像
--reference 通过引用已下载的mirror或者代码加快下载速度,常用使用场景:
DailyBuild
一套代码兼容多个机型
一套代码需要多个副本
多个人共用服务器
具体操作为
先通过
repo init .... --mirror
建立一个本地的镜像,
然后下载代码时引用这个镜像
repo init .... --reference=镜像路径
repo sync -c
如果之后主服务器库容量增加到120G,而你的镜像没更新,
那么理论上新下载的代码只需要下载20G的数据,
新的N套代码占用的空间=100G(镜像的) + 20G*N(新代码)
而不用镜像功能,理论上其占用空间=120G*N
更通用的公式为:
用镜像功能新N套代码理论占用空间=镜像占用空间 + (服务器库占用空间-镜像占用空间) * N
(所以从公式上也可看出,还是建议定期的更新镜像,以减少服务器与镜像占空间差,这样新下代码时就更节省时间和空间)
大家可用
du -sh .repo/
命令统计下代码repo库所占的空间。
注意:
即使不更新镜像,也不用担心你用reference下的代码和主服务器的不同步,
git会自动进行三方对比的,保证能获取到-u 指定的地址里的代码是最新的.
05. Gerrit Change-Id
graph TD;
title(repo gerrit简单工作流程);
init(repo init/sync) --> start(repo start xxx --all);
start --> gitmodify(单个库的修改和git流程一样);
gitmodify --> upload(上传到Gerrit repo upload/git push);
upload --> review(审核 +1 +2);
review-- 通过 --> submit(Gerrit submit);
review-- 不通过 --> gitmodify;
st=>start: Start
e=>end: End
op1=>operation: My Operation
sub1=>subroutine: My Subroutine
cond=>condition: Yes or No?
io=>inputoutput: catch something..
st->op1->cond
cond(yes)->io->e
cond(no)->sub1(right)->op1
Change-Id由git commit时调用.git/hooks/commit-msg钩子生成的,
gerrit靠该id来区分同一分支下是否为同一个提交,
事实上,只要gerrit未submit,可以在本地git commit XX --amend修正提交后(注意不要更改commit信息里的Change-Id),
可以再次push到gerrit,成为新的patchset,但提交号(即你那个提交的网址)不变.
06. repo upload时gerrit帐号名与邮箱前缀不一致
repo upload时默认是用配置的邮箱前缀做为push的用户名,
如果您的邮箱前缀和gerrit账户名不一致,需要做如下配置
$ cat ~/.gitconfig
[review "您的地址"]
username = 您的帐号名
07. Gerrit command
该功能可能SCM用得多,用于实现批处理脚本.
此处只列下用法,有兴趣的可以研究研究
$ ssh -p 端口号 用户名@地址 gerrit
Available commands of gerrit are:
apropos Search in Gerrit documentation
ban-commit Ban a commit from a project's repository
create-account Create a new batch/role account
create-branch Create a new branch
create-group Create a new account group
create-project Create a new project and associated Git repository
flush-caches Flush some/all server caches from memory
gc Run Git garbage collection
gsql Administrative interface to active database
ls-groups List groups visible to the caller
ls-members List the members of a given group
ls-projects List projects visible to the caller
ls-user-refs List refs visible to a specific user
plugin
query Query the change database
receive-pack Standard Git server side command for client side git push
rename-group Rename an account group
review Verify, approve and/or submit one or more patch sets
set-account Change an account's settings
set-members Modify members of specific group or number of groups
set-project Change a project's settings
set-project-parent Change the project permissions are inherited from
set-reviewers Add or remove reviewers on a change
show-caches Display current cache statistics
show-connections Display active client SSH connections
show-queue Display the background work queues
stream-events Monitor events occurring in real time
test-submit
version Display gerrit version
See 'gerrit COMMAND --help' for more information.
08. cherry-pick rebase
这个可看下git书箱学习下,工作中也用得多,提高效率.
09. repo sync (-n -l)
-n -l参数解释如下:
-l, --local-only only update working tree, don't fetch
-n, --network-only fetch only, don't update working tree
其实repo sync = repo sync -n + repo sync -l
如果你只想获取更新到.repo库里,即更新库信息,不更新本地代码, 可加上 -n 参数。
那 -l 参数有啥实际的用处呢?
如果你之前已经获取了更新,只有一两个库更新出错,
而你重新repo sync -c一次都得半小时,
那可以先repo sync -l 更新工作目录, 然后再单独的更新出错的库(命令为repo sync -c 库路径1 库路径2...)。
大部分情况下,下载代码后再用repo sync -c更新代码都是很快的,
但以我经历,碰到过服务器性能不行导致更新一次要10多个小时,
也有不知啥原因本地库出问题从而导致sync出问题。
出现这种情况我一般先强制更新能更新的库(默认更新出错会停止),
实在不知道啥原因导致某库更新出错,删了该库再继续更新步骤。
用到的命令可参考如下,这些命令或步骤可能需要重复或组合直到更新完成
# 强制更新
repo sync -c -f
# 仅更新本地工作目录
repo sync -c -l
# 删除某库
.repo/project-objects/具体某库路径
.repo/projects/具体某库路径
rm -rf 库的本地工作目录
-l参数对我来说还有个用处,那就是更新时可能信息很多,中间有些出错,
而我有不想慢慢看终端找出错信息,所以干脆再让他更新下
本地工作目录,这样方便查找出错信息。
(对于该情形,可能用-q参数是个更好的解决方法)
10. git pushInsteadOf
试想一下这样一个场景, 您的代码下载自A服务器, 现在要往B服务器push代码,该咋办?
当然, 您可用git remote添加远程信息,然后再用git push 新添加的远程名 ...
我这里讲另一个方法,就是pushInsteadOf,
eg:
vi ~/.gitconfig
[url "testB_address"]
pushInsteadOf = testA_address
即,我们仅需要在gitconfig里配置一下,
push的时候用B服务器的地址,替换A服务器的地址,
这样我们push的时候,代码就提交到B服务器了.
同样的,可用
insteadOf 替换push和pull时的地址
BTW,对 ~/.gitconfig文件的修改,您也可用命令
eg:
git config --global url.testB_address.insteadOf testA_address
11. Debug
有时候有些运行命令出错了,可加--trace参数查看更多信息
(git的调试方法请查看progit一书)
eg:
repo --trace sync -c
12. 建议
新下载代码后,分支处于no branch状态,建议下载代码后用
repo start 本地分支名 --all
建个分支,该分支名为本地分支名,可任意取名。
该分支仅用于和服务同步,更新代码,要修改的话用 repo start 新分支名 建立个新分支,完成提交后再回到那个分支,重复...同步...建新分支..
不然你本地分支上在修改,服务器别人提交也会修改,更新时会造成历史记录很乱,
甚至更新时git自动合并策略等因素可能会导致编译出来的代码有啥隐含bug。
BTW,当然,您也可用
git pull --rebase