Android源代码仓库及其管理工具Repo分析

本文深入探讨了Android源代码仓库AOSP的管理工具Repo,它基于Git但针对大型项目进行了优化。Repo通过Python脚本管理多个Git子项目,自动化创建分支、更新代码等任务。此外,文章介绍了Repo仓库、Manifest仓库和AOSP子项目仓库的结构及相互关系,阐述了Repo的初始化和同步过程,以及如何在AOSP上创建Topic分支。
摘要由CSDN通过智能技术生成

分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

               

       软件工程由于需要不断迭代开发,因此要对源代码进行版本管理。Android源代码工程(AOSP)也不例外,它采用Git来进行版本管理。AOSP作为一个大型开放源代码工程,由许许多多子项目组成,因此不能简单地用Git进行管理,它在Git的基础上建立了一套自己的代码仓库,并且使用工具Repo进行管理。工欲善其事,必先利其器。本文就对AOSP代码仓库及其管理工具repo进行分析,以便提高我们日常开发效率。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       现代的代码版本管理工具,SVN和Git是最流行的。SVN是一种集中式的代码管理工具,需要有一个中心服务器,而Git是一种分布式的代码管理工具。不需要一个中心服务器。不需要中心服务器意味着在没有网络的情况下,Git也能进行版本管理。因此,单从这一点出发,Git就比SVN要方便很多。当然,Git和SVN相比,还有许多不同的理念设计,但是总的来说,Git越来越受到大家的青睐,尤其是在开源社区。君不见,Linux是采用Git进行版本管理,而越来越火的GitHub,提供也是Git代码管理服务。本文不打算分析Git与SVN的区别,以及Git的使用方法,不过强烈建议大家先去了解Git,然后再看下面的内容。这里推荐一本Git书籍《Pro Git》,它是GitHub的员工Scott Chacon撰写的,对Git的使用及其原理都介绍得非常详细和清晰。

       前面提到,AOSP是由许许多项目组成的,例如,在Android 4.2中,就包含了329个项目,每一个项目都是一个独立的Git仓库。这意味着,如果我们要创建一个AOSP分支来做feature开发,那么就需要到每一个子项目去创建对应的分支。这显然不能手动地到每一个子项目里面去创建分支,必须要采用一种自动化的方式来处理。这些自动化处理工作就是由Repo工具来完成的。当然,Repo工具所负责的自动化工作不只是创建分支那么简单,查看分支状态、提交代码、更新代码等基础Git操作它都可以完成。

       Repo工具实际上是由一系列的Python脚本组成的,这些Python脚本通过调用Git命令来完成自己的功能。比较有意思的是,组成Repo工具的那些Python脚本本身也是一个Git仓库。这个Git仓库在AOSP里面就称为Repo仓库。我们每次执行Repo命令的时候,Repo仓库都会对自己进行一次更新。

       上面我们讨论的是Repo仓库,但是实际上我们执行Repo命令想操作的是AOSP。这就要求Repo命令要知道AOSP都包含有哪些子项目,并且要知道这些子项目的名称、仓库地址是什么。换句话说,就是Repo命令要知道AOSP所有子项目的Git仓库元信息。我们知道,AOSP也是不断地迭代法变化的,例如,它的每一个版本所包含的子项目可能都是不一样的。这意味着需要通过另外一个Git仓库来管理AOSP所有的子项目的Git仓库元信息。这个Git仓库在AOSP里面就称为Manifest仓库。

        到目前为止,我们提到了三种类型的Git仓库,分别是Repo仓库、Manifest仓库和AOSP子项目仓库。Repo仓库通过Manifest仓库可以获得所有AOSP子项目仓库的元信息。有了这些元信息之后,我们就可以通过Repo仓库里面的Python脚本来操作AOSP的子项目。那么,Repo仓库和Manifest仓库又是怎么来的呢?答案是通过一个独立的Repo脚本来获取,这个Repo脚本位于AOSP的一个官方网站上,我们可以通过HTTP协议来下载。

        现在,我们就通过一个图来来勾勒一下整个AOSP的Picture,它由Repo脚本、Repo仓库、Manifest仓库和AOSP子项目仓库组成,如图1所示:


图1 Repo脚本、Repo仓库、Manifest仓库和AOSP子项目仓库

       接下来我们就看看上述脚本和仓库是怎么来的。

       1. Repo脚本

       从官方网站可以知道,Repo脚本可以通过以下命令来获取:

$ curl http://commondatastorage.googleapis.com/git-repo-downloads/repo > ~/bin/repo$ chmod a+x ~/bin/repo
       也就是可以通过curl工具从http://commondatastorage.googleapis.com/git-repo-downloads/repo获得,并且保存在文件~/bin/repo中。由于~/bin/repo是一个python脚本,我们通过chmod命令赋予它可执行的权限,以便接下来我们可以通过repo命令来运行它。

       2. Repo仓库

       我们下载好Repo脚本之后,要选通过以下命令来安装一个Repo仓库:

$ repo init -u https://android.googlesource.com/platform/manifest
       这个命令实际上是包含了两个操作:安装Repo仓库和Manifest仓库。其中,Manifest仓库的地址由-u后来带的参数给出。这一小节我们先分析Repo仓库的安装过程,在接下来的第3小节中,再分析Manifest仓库的安装过程。

       我们看看Repo脚本是如何执行repo init命令的:

def main(orig_args):  repo_main, rel_repo_dir = _FindRepo()  cmd, opt, args = _ParseArguments(orig_args)  wrapper_path = os.path.abspath(__file__)  my_main, my_git = _RunSelf(wrapper_path)  if not repo_main:    if opt.help:      _Usage()    if cmd == 'help':      _Help(args)    if not cmd:      _NotInstalled()    if cmd == 'init':      if my_git:        _SetDefaultsTo(my_git)      try:        _Init(args)      except CloneFailure:        ......        sys.exit(1)      repo_main, rel_repo_dir = _FindRepo()    else:      _NoCommands(cmd)  if my_main:    repo_main = my_main  ver_str = '.'.join(map(str, VERSION))  me = [repo_main,        '--repo-dir=%s' % rel_repo_dir,        '--wrapper-version=%s' % ver_str,        '--wrapper-path=%s' % wrapper_path,        '--']  me.extend(orig_args)  me.extend(extra_args)  try:    os.execv(repo_main, me)  except OSError as e:    ......    sys.exit(148)if __name__ == '__main__':  main(sys.argv[1:])
        _FindRepo在从当前目录开始往上遍历直到根据目录。如果中间某一个目录下面存在一个.repo/repo目录,并且该.repo/repo存在一个main.py文件,那么就会认为当前是AOSP中执行Repo脚本,这时候它就会返回文件main.py的绝对路径和当前查找目录下的.repo子目录的绝对路径给调用者。在上述情况下,实际上是说明Repo仓库已经安装过了。

        _FindRepo的实现如下所示:

repodir = '.repo'               # name of repo's private directoryS_repo = 'repo'                 # special repo repositoryREPO_MAIN = S_repo + '/main.py' # main scriptdef _FindRepo():  """Look for a repo installation, starting at the current directory.  """  curdir = os.getcwd()  repo = None  olddir = None  while curdir != '/' \    and curdir != olddir \    and not repo:    repo = os.path.join(curdir, repodir, REPO_MAIN)    if not os.path.isfile(repo):      repo = None      olddir = curdir      curdir = os.path.dirname(curdir)  return (repo, os.path.join(curdir, repodir))
         _ParseArguments无非是对Repo的参数进行解析,得到要执行的命令及其对应的参数。例如,当我们调用“repo init -u https://android.googlesource.com/platform/manifest”的时候,就表示要执行的命令是init,这个命令后面跟的参数是“-u https://android.googlesource.com/platform/manifest”。同时,如果我们调用“repo sync”,就表示要执行的命令是sync,这个命令没有参数。

         _RunSelf检查Repo脚本所在目录是否存在一个Repo仓库,如果存在的话,就从该仓库克隆一个新的仓库到执行Repo脚本的目录来。实际上就是从本地克隆一个新的Repo仓库。如果不存在的话,那么就需要从远程地址克隆一个Repo仓库到本地来。这个远程地址是Hard Code在Repo脚本里面。

         _RunSelf的实现如下所示:

def _RunSelf(wrapper_path):  my_dir = os.path.dirname(wrapper_path)  my_main = os.path.join(my_dir, 'main.py')  my_git = os.path.join(my_dir, '.git'if os.path.isfile(my_main) and os.path.isdir(my_git):    for name in ['git_config.py',                 'project.py',                 'subcmds']:      if not os.path.exists(os.path.join(my_dir, name)):        return None, None    return my_main, my_git  return None, None
        从这里我们就可以看出,如果Repo脚本所在的目录存在一个Repo仓库,那么要满足以下条件:

        (1). 存在一个.git目录;

        (2). 存在一个main.py文件;

        (3). 存在一个git_config.py文件;

        (4). 存在一个project.py文件;

        (5). 存在一个subcmds目录。

        上述目录和文件实际上都是一个标准的Repo仓库所具有的。

        我们再回到main函数中。如果调用_FindRepo得到的repo_main的值等于空,那么就说明当前目录还没有安装Repo仓库,这时候Repo后面所跟的参数只能是help或者init,否则的话,就会显示错误信息。如果Repo后面跟的参数是help,就打印出Repo脚本的帮助文档。

        我们关注Repo后面跟的参数是init的情况。这时候看一下调用_RunSelf的返回值my_git是否不等于空。如果不等于空的话,那么就说明Repo脚本所在目录存一个Repo仓库,这时候就调用_SetDefaultsTo设置等一会要克隆的Repo仓库源。

         _SetDefaultsTo的实现如下所示:

GIT = 'git'REPO_URL = 'https://gerrit.googlesource.com/git-repo'REPO_REV = 'stable'def _SetDefaultsTo(gitdir):  global REPO_URL  global REPO_REV  REPO_URL = gitdir  proc = subprocess.Popen([GIT,                           '--git-dir=%s' % gitdir,                           'symbolic-ref',                           'HEAD'],                          stdout = subprocess.PIPE,                          stderr = subprocess.PIPE)  REPO_REV = proc.stdout.read().strip()  proc.stdout.close()  proc.stderr.read()  proc.stderr.close()  if proc.wait() != 0:    _print('fatal: %s has no current branch' % gitdir, file=sys.stderr)    sys.exit(1)

         如果Repo脚本所在目录不存在一个Repo仓库,那么默认就将https://gerrit.googlesource.com/git-repo设置为一会要克隆的Repo仓库源,并且要克隆的分支是stable。否则的话,就以该Repo仓库作为克隆源,并且以该Repo仓库的当前分支作为要克隆的分支。

         从前面的分析就可以知道,当我们执行"repo init"命令的时候:

         (1). 如果我们只是从网上下载了一个Repo脚本,那么在执行Repo命令的时候,就会从远程克隆一个Repo仓库到当前执行Repo脚本的目录来。

         (2). 如果我们从网上下载的是一个带有Repo仓库的Repo脚本,那么在执行Repo命令的时候,就可以从本地克隆一个Repo仓库到当前执行Repo脚本的目录来。

         我们再继续看main函数的实现,它接下来调用_Init在当前执行Repo脚本的目录下安装一个Repo仓库:

def _Init(args):  """Installs repo by cloning it over the network.  """  opt, args = init_optparse.parse_args(args)  ......  url = opt.repo_url  if not url:    url = REPO_URL    extra_args.append('--repo-url=%s' % url)  branch = opt.repo_branch  if not branch:    branch = REPO_REV    extra_args.append('--repo-branch=%s' % branch)  ......  if not os.path.isdir(repodir):    try:      os.mkdir(repodir)    except OSError as e:      ......      sys.exit(1)  _CheckGitVersion()  try:    if NeedSetupGnuPG():      can_verify = SetupGnuPG(opt.quiet)    else:      can_verify = True    dst = os.path.abspath(os.path.join(repodir, S_repo))    _Clone(url, dst, opt.quiet)    if can_verify and not opt.no_repo_verify:      rev = _Verify(dst, branch, opt.quiet)    else:      rev = 'refs/remotes/origin/%s^0' % branch    _Checkout(dst, branch, rev, opt.quiet)  except CloneFailure:    ......
       如果我们在执行Repo脚本的时候,没有通过--repo-url和--repo-branch来指定Repo仓库的源地址和分支,那么就使用由REPO_URL和REPO_REV所指定的源地址和分支。从前面的分析可以知道,REPO_URL和REPO_REV要么指向远程地址https://gerrit.googlesource.com/git-repo和分支stable,要么指向Repo脚本所在目录的Repo仓库和该仓库的当前分支。

       _Init函数继续检查当前执行Repo脚本的目录是否存在一个.repo目录。如果不存在的话,那么就新创建一个。接着是否需要验证等一会克隆回来的Repo仓库的GPG。如果需要验证的话,那么就会在调用_Clone函数来克隆好Repo仓库之后,调用_Verify函数来验证该Repo仓库的GPG。

       最关键的地方就在于函数_Clone函数和_Checkout函数的调用,前者用来从url指定的地方克隆一个仓库到dst指定的地方来,实际上就是克隆到当前目录下的./repo/repo目录来,后者在克隆回来的仓库中将branch分支checkout出来。

       函数_Clone的实现如下所示:

def _Clone(url, local, quiet):  """Clones a git repository to a new subdirectory of repodir  """  try:    os.mkdir(local)  except OSError as e:    _print('fatal: cannot make %s directory: %s' % (local, e.strerror),           file=sys.stderr)    raise C
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
To make edits to changes after they have been uploaded, you should use a tool like git rebase -i or git commit --amend to update your local commits. After your edits are complete: Make sure the updated branch is the currently checked out branch. For each commit in the series, enter the Gerrit change ID inside the brackets: # Replacing from branch foo [ 3021 ] 35f2596c Refactor part of GetUploadableBranches to lookup one specific... [ 2829 ] ec18b4ba Update proto client to support patch set replacments # Insert change numbers in the brackets to add a new patch set. # To create a new change record, leave the brackets empty. After the upload is complete the changes will have an additional Patch Set. If you only want to upload the currently checked out Git branch, you can use the flag --current-branch (or --cbr for short). diff repo diff [<PROJECT_LIST>] Shows outstanding changes between commit and working tree using git diff. download repo download <TARGET> <CHANGE> Downloads the specified change from the review system and makes it available in your project's local working directory. For example, to download change 23823 into your platform/build directory: repo download platform/build 23823 A repo sync should effectively remove any commits retrieved via repo download. Or, you can check out the remote branch; e.g., git checkout m/master. Note: There is a slight mirroring lag between when a change is visible on the web in Gerrit and when repo download will be able to find it for all users, because of replication delays to all servers worldwide. forall repo forall [<PROJECT_LIST>] -c <COMMAND> Executes the given shell command in each project. The following additional environment variables are made available by repo forall: REPO_PROJECT is set to the unique name of the project. REPO_PATH is the path relative to the root of the client. REPO_REMOTE is the name of the remote system from the manifest. REPO_LREV is the name of the revision from the manifest, translated to a local tracking branch. Used if you need to pass the manifest revision to a locally executed git command. REPO_RREV is the name of the revision from the manifest, exactly as written in the manifest. Options: -c: command and arguments to execute. The command is evaluated through /bin/sh and any arguments after it are passed through as shell positional parameters. -p: show project headers before output of the specified command. This is achieved by binding pipes to the command's stdin, stdout, and sterr streams, and piping all output into a continuous stream that is displayed in a single pager session. -v: show messages the command writes to stderr. prune repo prune [<PROJECT_LIST>] Prunes (deletes) topics that are already merged. start repo start <BRANCH_NAME> [<PROJECT_LIST>] Begins a new branch for development, starting from the revision specified in the manifest. The <BRANCH_NAME> argument should provide a short description of the change you are trying to make to the projects.If you don't know, consider using the name default. The <PROJECT_LIST> specifies which projects will participate in this topic branch. Note: "." is a useful shorthand for the project in the current working directory. status repo status [<PROJECT_LIST>] Compares the working tree to the staging area (index) and the most recent commit on this branch (HEAD) in each project specified. Displays a summary line for each file where there is a difference between these three states. To see the status for only the current branch, run repo status. The status information will be listed by project. For each file in the project, a two-letter code is used: In the first column, an uppercase letter indicates how the staging area differs from the last committed state. letter meaning description - no change same in HEAD and index A added not in HEAD, in index M modified in HEAD, modified in index D deleted in HEAD, not in index R renamed not in HEAD, path changed in index C copied not in HEAD, copied from another in index T mode changed same content in HEAD and index, mode changed U unmerged conflict between HEAD and index; resolution required In the second column, a lowercase letter indicates how the working directory differs from the index. letter meaning description - new/unknown not in index, in work tree m modified in index, in work tree, modified d deleted in index, not in work tree Was this page helpful? Let us know how we did:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值