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:
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/。其中最值得认识的是:
.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
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 选择。常见表达包括:
# 只选择两个 group
repo init -u <manifest-url> -b <revision> -g pdk,tradefed
# 选择一个 group,同时排除另一个 group
repo init -u <manifest-url> -b <revision> -g default,-notdefaultgroups 适合由 manifest 维护者定义的产品或职责集合。它与 repo sync <project> 的区别是:
- groups 改变当前 client 的 manifest project 选择;
- sync 参数只决定本次命令处理哪些已选 project。
如果只是临时同步一个模块,通常不需要重新 init groups,直接按 path 或 name 执行 sync 更清晰。
2.3 project同步
repo sync 的位置参数既可以是 project name,也可以是相对或绝对工作区路径:
# 按本地路径同步
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
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
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 控制总体并发数。高并发不一定更快:网络限速、服务端限制、磁盘随机写入和内存压力都可能 成为瓶颈。
# 日常同步,根据机器和网络设置并发
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 状态 |
一个可控的两步流程是:
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:
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 仍可能因本地状态失败。 切换前至少执行:
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-sync | Git 目录关联到错误的 object directory | 可能覆盖 Git 管理关系并移除 refs |
--force-checkout | index、worktree 或未跟踪文件阻止切换 revision | 可能丢弃未提交修改和冲突文件 |
--force-remove-dirty | manifest 已移除 project,但本地 project 有修改 | 可能删除整个脏 project |
使用前必须把目标缩小到明确 project,并先查看 git status、git diff 和未跟踪文件。
6.2 全局force风险
下面的决策图展示同步失败后的安全顺序。
force 参数不是问题分类器。只有先知道为什么失败,才能判断它是否适用。
7. 常见故障与排查
7.1 Fetch 失败
| 现象 | 常见原因 | 优先检查 |
|---|---|---|
| timeout、connection reset | 网络、代理、服务端限流 | 单 project 重试、代理和 DNS |
| repository not found | remote URL 或权限错误 | manifest remote、账号权限 |
| revision not found | branch/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:
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 本身, 也不适合用来掩盖版本不一致。
7.4 Link/copy失败
repo 在 project 检出后还会更新 manifest 声明的 copyfile/linkfile。如果目标路径已被普通文件、 目录或错误链接占用,project 本身可能同步成功,但工作区入口更新失败。
排查时需要区分:
- project fetch 是否成功;
- project checkout 是否成功;
- manifest project 列表是否更新;
- copyfile/linkfile 是否成功。
这四个阶段在错误报告中是独立的,不能只看到最终 repo sync failed 就认定网络失败。
7.5 测试路径
git-repo 的 tests/test_subcmds_sync.py 直接检查 sync 的阶段边界:
| 测试 | 关键断言 | 覆盖边界 |
|---|---|---|
test_worker_fetch_fails | fetch 失败后 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 失败后停止后续 batch | interleaved 模式遵守 fail-fast |
例如,fetch 失败测试明确检查了“没有继续 checkout”:
源码文件:git-repo/tests/test_subcmds_sync.py
# 符号: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。
处理真实同步问题时,应能回答以下问题:
- 解释为什么执行
repo init -b <new-tag>后仍需要repo sync。 - 给定 fetch 成功、checkout 失败的日志,指出 Git 对象和 worktree 分别处于什么状态。
- 说明
--network-only、--local-only和--detach的差异。 - 在使用任何 force 参数前,列出必须检查的 project、status、diff 和备份信息。
日常 project 查询、topic branch 和差异管理参见 repo日常命令实战。
