Skip to content

repo初始化与同步

解释 repo init 与 repo sync 的职责、manifest 选择、project 同步阶段、常用参数和安全恢复方法。

基于android-17.0.0_r1
AndroidAOSPrepoGit

repo初始化与同步 ​

AOSP 由许多 Git project 组成。repo init 决定“这棵工作区受哪份 manifest 管理”, repo sync 决定“哪些 project 要取得哪些 Git 对象,并把哪个 revision 检出到工作区”。 两条命令名字简单,但它们共同控制版本边界、磁盘占用、网络流量和本地修改的安全性。

最常见的误解是把 repo init 当作“下载源码”,把 repo sync 当作“等价于多个 git pull”。实际情况更精确:

  • repo init 初始化 .repo/,准备 git-repo 和 manifest project,并选择 manifest。
  • repo sync 解析当前 manifest,更新远端对象,再按需要更新每个 project 的 worktree。
  • manifest revision 与 project 的本地 topic branch 是两个概念。
  • --force-sync、--force-checkout 等参数可能覆盖本地状态,不能作为常规故障修复开关。

本文从一次新的 checkout 开始,解释 init 和 sync 的数据流、选择性同步、分支切换和故障恢复。 AOSP目录结构全景 已说明 project name、path 和 manifest 的关系。 本文在此基础上解释 init 与 sync 分别修改什么、如何判断失败发生在网络还是 worktree 阶段, 以及怎样在不丢失本地修改的前提下选择恢复动作。

1. repoinit ​

1.1 初始化输入 ​

创建一个新的 repo client 时,唯一必需的信息是 manifest repository URL:

bash
repo init \
  -u https://android.googlesource.com/platform/manifest \
  -b android-17.0.0_r1

-u 指定 manifest project,-b 指定 manifest branch、tag、commit 或其他可解析 revision。 如果不传 -b,repo 使用 manifest remote 的默认分支。源码研究应显式指定稳定 tag,避免工作区 随着默认分支继续漂移。

常用 manifest 参数如下:

参数作用什么时候使用
-u URL指定 manifest repository创建或更换上游 manifest
-b REVISION选择 manifest revision固定 release tag、branch 或 commit
-m NAME.xml选择非默认 manifest 文件同一 manifest 仓库提供多套产品清单时
-g GROUPS选择 project groups只初始化指定类别的 project
-p PLATFORM选择平台 group限制 darwin、linux 等平台相关 project
--manifest-depth=N浅克隆 manifest project只关心当前 manifest 历史时
--standalone-manifest使用静态 manifest 文件不需要后续更新 manifest project 时

-b 选择的是 manifest project 的 revision。manifest 文件再为其管理的 project 指定 默认或独立 revision。不能因为两者通常使用相同的 Android tag,就把它们理解成同一个 Git 仓库。

1.2 init主要产物 ​

初始化后,源码根目录会出现 .repo/。其中最值得认识的是:

text
.repo/
├── repo/               # git-repo 工具源码
├── manifests/          # 当前 manifest 工作树
├── manifests.git/      # manifest project 的 Git 数据
├── manifest.xml        # 当前使用的 manifest 入口
├── project-objects/    # 各 project 可共享的 Git 对象
├── projects/           # 工作区 project 的 Git 管理数据
└── local_manifests/    # 可选的本地 manifest 覆盖

这些目录解释了一个重要现象:AOSP project 的 .git 通常不是完全独立的大型对象库。 repo 会把对象和工作区管理信息放在 .repo 下,再让各 project worktree 使用这些数据。

下面的图展示 init 的输入和产物。它关注职责,不展开 git-repo 内部所有辅助文件。

1.3 manifest同步 ​

git-repo 的 Init.Execute 不会直接遍历 Android 平台 project。它先检查工具与 Git 版本, 处理 repo 自身的 URL/revision,判断是否复用已有 checkout,然后调用 _SyncManifest。

源码文件:git-repo/subcmds/init.py

相关函数/类型:Init.Execute

python
def Execute(self, opt, args):
    wrapper = Wrapper()

    # 说明:init 首先校验 repo 声明的 Git 版本要求。
    reqs = wrapper.Requirements.from_dir(WrapperDir())
    git_require(reqs.get_hard_ver("git"), fail=True)

    rp = self.manifest.repoProject

    # ...

    existing_checkout = self.manifest.manifestProject.Exists

    # 说明:核心动作是同步和选择 manifest,
    # 不是检出全部 Android platform project。
    self._SyncManifest(opt)

    if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
        # 说明:交互终端中还可以设置用户身份和颜色配置。
        if opt.config_name or self._ShouldConfigureUser(
            opt, existing_checkout
        ):
            self._ConfigureUser(opt)
        self._ConfigureColor()

    if not opt.quiet:
        self._DisplayResult()

因此,init 成功只表示 repo client 和 manifest 已准备好。平台源码是否已经出现,取决于后续 repo sync。

2. Manifest范围 ​

2.1 name ​

sync 解析 manifest 后,至少需要知道:

字段决定什么
name从哪个远端 Git project 获取对象
path在本地工作区放到哪里
revision检出 branch、tag 或 commit
remote使用哪个 remote 配置
groups该 project 是否被当前 group 选择
clone-depth 等是否采用 project 级同步优化

如果 project 没有独立 revision,它通常继承 manifest 的 default revision。project 级覆盖 优先于 default,因此同一工作区可以同时包含 tag project、特定 branch project 和固定 commit。

2.2 Groups ​

repo init -g 保存 project group 选择。常见表达包括:

bash
# 只选择两个 group
repo init -u <manifest-url> -b <revision> -g pdk,tradefed

# 选择一个 group,同时排除另一个 group
repo init -u <manifest-url> -b <revision> -g default,-notdefault

groups 适合由 manifest 维护者定义的产品或职责集合。它与 repo sync <project> 的区别是:

  • groups 改变当前 client 的 manifest project 选择;
  • sync 参数只决定本次命令处理哪些已选 project。

如果只是临时同步一个模块,通常不需要重新 init groups,直接按 path 或 name 执行 sync 更清晰。

2.3 project同步 ​

repo sync 的位置参数既可以是 project name,也可以是相对或绝对工作区路径:

bash
# 按本地路径同步
repo sync frameworks/base frameworks/native

# 按 manifest project name 同步
repo sync platform/frameworks/base platform/frameworks/native

# 在 project 目录中同步当前路径
cd frameworks/base
repo sync .

没有位置参数时,sync 处理当前 manifest 选中的全部 project。大型工作区中,明确目标能减少网络、 检出和错误排查范围。

3. reposync ​

3.1 网络阶段 ​

网络阶段负责确认所需 revision 是否存在于本地 Git 对象库,不存在时从 remote 获取。它会处理 clone、fetch、浅克隆、partial clone、tag、prune、重试和认证等问题。

源码文件:git-repo/subcmds/sync.py

相关函数/类型:Sync._SyncOneProject

python
sync_result = project.Sync_NetworkHalf(
    quiet=opt.quiet,
    verbose=opt.verbose,
    use_superproject=opt.use_superproject,
    current_branch_only=Sync._GetCurrentBranchOnly(
        opt, project.manifest
    ),
    force_sync=opt.force_sync,
    clone_bundle=opt.clone_bundle,
    tags=opt.tags,
    optimized_fetch=opt.optimized_fetch,
    retry_fetches=opt.retry_fetches,
    prune=opt.prune,
    # ...
)

# 说明:网络阶段单独记录成功、是否实际发生 fetch,
# 以及 Git 或连接错误;它不会在失败时继续强制检出 worktree。
fetch_success = sync_result.success
remote_fetched = sync_result.remote_fetched

--network-only(-n)会在网络阶段后停止。它适合提前取得对象,但不会把工作区更新到目标 revision。

3.2 本地阶段 ​

只有网络阶段成功,且当前 project 确实有 worktree 时,repo 才进入本地阶段:

源码文件:git-repo/subcmds/sync.py

相关函数/类型:Sync._SyncOneProject

python
if fetch_success:
    if opt.network_only or not project.worktree:
        checkout_success = True
    else:
        syncbuf = SyncBuffer(
            project.manifest.manifestProject.config,
            detach_head=opt.detach_head,
        )

        # 说明:本地阶段处理当前分支、manifest revision、
        # rebase、checkout 和 worktree 冲突。
        project.Sync_LocalHalf(
            syncbuf,
            force_sync=opt.force_sync,
            force_checkout=opt.force_checkout,
            force_rebase=opt.rebase,
            verbose=opt.verbose,
        )
        checkout_success = syncbuf.Finish()

--local-only(-l)跳过网络阶段,只利用本地已有对象更新 worktree。如果目标 revision 对应的 对象尚未取得,local-only 无法凭空补齐它。

3.3 project同步 ​

git-repo 支持两种调度方式:

  • 默认的 interleaved 模式可以让不同 project 的 fetch 和 checkout 交错执行;
  • --no-interleaved 使用明确的网络阶段,再统一进入本地阶段。

无论调度方式如何,单个 project 仍遵守“先取得对象,再更新 worktree”的因果关系。

下面的时序图展示一次 project sync 的关键状态。

4. 常用同步参数约束 ​

4.1 实现入口 ​

-j 控制总体并发数。高并发不一定更快:网络限速、服务端限制、磁盘随机写入和内存压力都可能 成为瓶颈。

bash
# 日常同步,根据机器和网络设置并发
repo sync -j8

# 定位失败时,串行并在首个错误处停止
repo sync -j1 --fail-fast <project>

错误日志中常见的“重新使用 -j1 --fail-fast”不是修复动作,而是为了得到更清晰的第一个错误。 确认原因后再选择网络重试、清理锁、处理本地修改或重新同步。

4.2 -c 与 tag ​

--current-branch(-c)只获取 manifest revision 所在分支相关对象,可以减少网络流量。 但是否适用取决于 revision 类型和后续是否需要其他历史或 tag。

--tags 控制是否取得 tag。源码研究通常通过 manifest 固定 revision,不应把“本地是否获取所有 tag”与“当前 worktree 是否处于正确 commit”混为一谈。

4.3 -n、-l ​

参数网络获取更新 worktree典型用途
-n / --network-only是否预取对象,稍后再检出
-l / --local-only否是使用已有对象完成本地检出
-d / --detach按正常规则是让 project 回到 manifest revision 的 detached 状态

一个可控的两步流程是:

bash
repo sync -n <project>
repo sync -l <project>

第一步失败不会改变 worktree;第二步只依赖已经取得的对象。日常使用通常不需要拆成两步,但在 网络不稳定或需要控制检出时很有帮助。

4.4 清理 ​

--retry-fetches=N 只适合临时网络错误。认证失败、仓库不存在、revision 错误或代理配置错误不会 因为重复请求自动解决。

--prune 删除远端已经不存在的 refs,默认启用。它影响 refs 清理,不等于删除工作区里的普通 文件或本地提交。

5. Release 切换 ​

5.1 切换manifest ​

在已有 client 中切换 manifest revision:

bash
repo init -b <new-release-tag>
repo sync -d

第一条命令只更新 manifest 选择。git-repo 的帮助文档明确说明:切换 manifest branch 后,需要 继续执行 sync,工作区文件才会更新。-d 让 project 回到 manifest revision 的 detached 状态, 适合固定 release 的只读研究工作区。

5.2 Manifest状态 ​

repo sync -d 不等于无条件删除修改,但 branch、rebase 和 checkout 仍可能因本地状态失败。 切换前至少执行:

bash
repo status
repo forall -c 'git status --short'

需要保留的修改应提交到 topic branch、导出 patch 或明确备份。不要把 --force-checkout 当成 “让 sync 通过”的默认参数。

5.3 同步测试 ​

即使所有 project 都成功同步到新 release,文章中的类型、函数、配置和测试仍可能变化。版本升级 后的正确工作是生成 project diff 和文章影响矩阵,然后逐篇复核路径与结论。

6. 参数保护 ​

6.1 force ​

参数解决的问题数据风险
--force-syncGit 目录关联到错误的 object directory可能覆盖 Git 管理关系并移除 refs
--force-checkoutindex、worktree 或未跟踪文件阻止切换 revision可能丢弃未提交修改和冲突文件
--force-remove-dirtymanifest 已移除 project,但本地 project 有修改可能删除整个脏 project

使用前必须把目标缩小到明确 project,并先查看 git status、git diff 和未跟踪文件。

6.2 全局force风险 ​

下面的决策图展示同步失败后的安全顺序。

force 参数不是问题分类器。只有先知道为什么失败,才能判断它是否适用。

7. 常见故障与排查 ​

7.1 Fetch 失败 ​

现象常见原因优先检查
timeout、connection reset网络、代理、服务端限流单 project 重试、代理和 DNS
repository not foundremote URL 或权限错误manifest remote、账号权限
revision not foundbranch/tag/commit 不存在project revision、remote refs
TLS/SSH 错误证书、代理、SSH 配置Git 直连同一 remote

先对失败 project 执行 repo sync -j1 --fail-fast <path>,再使用普通 Git 命令验证 remote。 不要一开始清空整个 .repo,那会扩大恢复成本。

7.2 Checkout失败 ​

checkout 失败通常已经取得了对象,问题发生在本地 worktree:

bash
cd <failed-project>
git status --short
git diff
git branch --show-current
git rev-parse HEAD

如果修改需要保留,先提交或导出 patch;如果 project 应作为固定版本研究源,处理修改后可用 repo sync -d <path> 回到 manifest revision。

7.3 Manifest失败 ​

manifest 失败会影响后续 project 选择。优先检查:

  • -u 指定的 manifest URL;
  • -b 指定的 revision;
  • -m 指定的 XML 是否存在;
  • manifest XML 是否有效;
  • local manifest 是否引用不存在的 project 或 remote。

使用 --no-manifest-update 可以临时沿用现有 manifest checkout,但它不修复 manifest 本身, 也不适合用来掩盖版本不一致。

repo 在 project 检出后还会更新 manifest 声明的 copyfile/linkfile。如果目标路径已被普通文件、 目录或错误链接占用,project 本身可能同步成功,但工作区入口更新失败。

排查时需要区分:

  1. project fetch 是否成功;
  2. project checkout 是否成功;
  3. manifest project 列表是否更新;
  4. copyfile/linkfile 是否成功。

这四个阶段在错误报告中是独立的,不能只看到最终 repo sync failed 就认定网络失败。

7.5 测试路径 ​

git-repo 的 tests/test_subcmds_sync.py 直接检查 sync 的阶段边界:

测试关键断言覆盖边界
test_worker_fetch_failsfetch 失败后 Sync_LocalHalf 不被调用网络失败不会继续检出该 project
test_worker_local_only不调用 network half,只调用 local half--local-only 的阶段边界
test_worker_network_only调用 network half,不调用 local half--network-only 的阶段边界
test_interleaved_fail_fast首批 project 失败后停止后续 batchinterleaved 模式遵守 fail-fast

例如,fetch 失败测试明确检查了“没有继续 checkout”:

源码文件:git-repo/tests/test_subcmds_sync.py

python
# 符号:InterleavedSyncTest.test_worker_fetch_fails
result_obj = self.cmd._SyncProjectList(opt, [0])
result = result_obj.results[0]

# 说明:fetch 失败与 checkout 失败分别记录,便于调用方定位阶段。
self.assertFalse(result.fetch_success)
self.assertFalse(result.checkout_success)
self.assertEqual(result.fetch_errors, [fetch_error])

# 说明:网络阶段失败后不会继续本地检出。
project.Sync_NetworkHalf.assert_called_once()
project.Sync_LocalHalf.assert_not_called()

这组测试覆盖的是模拟失败下的调度控制,不包括真实网络、代理或文件系统的全部错误。 现场问题仍需结合 Git 错误、project 状态和失败发生的阶段诊断。

8. 后续边界 ​

工作区建立后,日常查询、topic branch 和差异导出由 repo日常命令实战 负责。新增、移除和覆盖 project 的 XML 配置由 本地Manifest定制 负责。本文不再重复这些操作清单。

9. 同步测试 ​

repo init 与 repo sync 分别解决两个不同问题:

  • init 选择并同步 manifest,建立 repo client 的管理边界;
  • sync 根据 manifest 选择 project,先取得 Git 对象,再更新 project worktree。

理解这个边界后,很多命令行为会变得清晰:

  • 切换 manifest revision 后还需要 sync;
  • network-only 与 local-only 分别控制同步的两个阶段;
  • 按 path 同步只是缩小本次 project 范围,不改变 manifest 本身;
  • detach 用于回到 manifest revision,force 参数则可能破坏本地状态;
  • 同步失败应先分类,再对明确 project 采取恢复动作。

repo 的价值不只是批量下载。它把 manifest、Git 对象、工作区和多 project 错误处理组织成一个 可复现流程,而安全使用它的前提,是始终知道当前命令会修改 manifest、对象库还是 worktree。

处理真实同步问题时,应能回答以下问题:

  1. 解释为什么执行 repo init -b <new-tag> 后仍需要 repo sync。
  2. 给定 fetch 成功、checkout 失败的日志,指出 Git 对象和 worktree 分别处于什么状态。
  3. 说明 --network-only、--local-only 和 --detach 的差异。
  4. 在使用任何 force 参数前,列出必须检查的 project、status、diff 和备份信息。

日常 project 查询、topic branch 和差异管理参见 repo日常命令实战。