4

本文需要有对git repo gerrit的基本使用,
这里不提及过多的基本用法.

00. Books

01. Repo 的产生

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 sync # 建议还是加上-c 参数

建立一个本地的镜像,
然后下载代码时引用这个镜像

repo init .... --reference=镜像路径
repo sync -c

这个时候,这些有共同主线的N套代码占用空间约为
100G + N * 几兆
比之前的100G * N 是不是节约空间多了?

如果之后主服务器库容量增加到120G,而你的镜像没更新,
那么理论上新下载的代码只需要下载20G的数据,
新的N套代码占用的空间=100G(镜像的) + 20G*N(新代码)
而不用镜像功能,理论上其占用空间=120G*N

更通用的公式为:
用镜像功能新N套代码理论占用空间=镜像占用空间 + (服务器库占用空间-镜像占用空间) * N

(所以从公式上也可看出,还是建议定期的更新镜像,以减少服务器与镜像占空间差,这样新下代码时就更节省时间和空间。对于定时更新Linux可建个crontab任务)

大家可用

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;

Change-Id由git commit时调用.git/hooks/commit-msg钩子生成的,
gerrit靠该id来区分同一分支下是否为同一个提交,
事实上,只要gerrit未submit,可以在本地git commit ... --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

    来更新。


Atom
26 声望29 粉丝

带着问题看code