Git学习笔记(六)——repo 极简操作指南

引言

repo 极简操作指南…

一、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命令初始化一个项目时,就会从远程把manifestsrepo这两个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- TARGETPRODUCTTARGET_BUILD_VARIANT。
    • GetManifest(tag)—— 返回指定tag的manifest
  • 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)。

如果需要新增或替换一些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 的代码

未完待续…

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/115279758