repo日常命令实战
完成 repo init 和 repo sync 后,日常工作不应继续依赖“进入目录后凭感觉执行 Git 命令”。 AOSP 是多 project 工作区,同一个功能修改可能同时落在 frameworks/base、 frameworks/native、system/core 和某个 Mainline 模块中。需要先回答:
- 当前文件属于哪个 project;
- 哪些 project 有本地修改;
- 同名 topic branch 出现在哪些 project;
- 如何在多个 project 中执行同一条只读命令;
- 怎样导出一份可以复现当前提交集合的 manifest。
本文按“定位、检查、批量查询、分支管理、差异导出、基线记录”的顺序介绍常用命令。 AOSP目录结构全景 和 repo初始化与同步 已说明 project 映射与同步边界。本文继续处理 文件归属、repo status 两列状态、限定范围的 forall、topic branch 和基线记录。 repo upload、repo download 和 Gerrit 工作流留给后续代码审查专题。
1. 工作区状态
1.1 只读命令
以下命令通常只读取 project、Git 或 manifest 状态:
| 命令 | 主要用途 |
|---|---|
repo list | project name 与本地 path 映射 |
repo status | 汇总 HEAD、index 和 worktree 差异 |
repo diff | 输出多个 project 的工作树 diff |
repo branches | 汇总 topic branch 分布 |
repo forall -c '<read-only-command>' | 在多个 project 中执行查询 |
repo manifest -o - | 输出当前组合后的 manifest |
“只读”只表示 repo 命令自身不主动改工作树。repo forall 执行的是任意 shell 命令,如果传入 git reset、rm 或格式化命令,它当然会产生修改。
1.2 状态命令
| 命令 | 修改范围 |
|---|---|
repo start | 在选定 project 中创建并切换 topic branch |
repo sync | 取得对象并更新 project worktree |
repo abandon | 删除指定 topic branch |
repo prune | 清理已合并 topic branch |
repo forall -c '<write-command>' | 由传入命令决定 |
下面的图把日常命令按“发现、检查、批量查询和修改”分类。执行批量修改前,应先用只读命令确认 project 集合。
2. repo list
2.1 默认输出字段
repo list默认格式为:
frameworks/base : platform/frameworks/base
frameworks/native : platform/frameworks/native
system/core : platform/system/core冒号左边是工作区 path,右边是 manifest project name。这个映射在写源码引用、同步指定 project 和核对上游地址时都很重要。
repo list 的实现会先根据参数和 group 选择 project,再按选项输出 name、path 或完整路径:
源码文件:git-repo/subcmds/list.py
相关函数/类型:List.Execute
if not opt.regex:
# 说明:普通模式按 manifest project/path 解析参数。
projects = self.GetProjects(
args,
groups=opt.groups,
missing_ok=opt.all,
all_manifests=not opt.this_manifest_only,
)
else:
# 说明:-r 模式改用正则或通配表达式查找 project。
projects = self.FindProjects(
args,
all_manifests=not opt.this_manifest_only,
)
# ...
for project in projects:
if opt.name_only and not opt.path_only:
lines.append(project.name)
elif opt.path_only and not opt.name_only:
lines.append(_getpath(project))
else:
lines.append(f"{_getpath(project)} : {project.name}")2.2 常用筛选方式
# 只显示本地路径
repo list -p
# 只显示 project name
repo list -n
# 查当前目录属于哪个 project
repo list .
# 使用正则或通配表达式查找
repo list -r 'frameworks/*'
# 包含 manifest 中存在但尚未 checkout 的 project
repo list --all默认只显示当前存在于 checkout 中的 project。--all 改变的是显示范围,不会自动同步缺失 project。
3. repo status
3.1 Status状态
repo status 对每个 project 比较:
- HEAD:当前提交;
- index:暂存区;
- worktree:工作目录。
典型输出:
project frameworks/base/ branch feature-x
-m services/core/java/com/android/server/Example.java
A- core/tests/example/ExampleTest.java
-- temporary-note.txt第一列是 index 相对 HEAD 的状态,使用大写字符;第二列是 worktree 相对 index 的状态,使用小写 字符。
| 字符 | 位置 | 含义 |
|---|---|---|
A | 第一列 | 已暂存新增 |
M | 第一列 | 已暂存修改 |
D | 第一列 | 已暂存删除 |
R | 第一列 | 已暂存重命名 |
U | 第一列 | 未解决冲突 |
m | 第二列 | worktree 修改未暂存 |
d | 第二列 | worktree 删除未暂存 |
- | 第二列 | 新文件或未知文件 |
3.2 Status输出
在 sync、切换 release、批量格式化或创建 patch 前先执行:
repo status
# 只检查指定 project
repo status frameworks/base frameworks/native
# 同时显示不属于任何 repo project 的文件
repo status --orphans--orphans 能发现源码根目录或 project 之间的未管理文件。它适合检查临时脚本、下载文件和误放的 构建产物。
tests/test_subcmds_status.py 在临时 Git worktree 中检查这两类输出:
test_status_without_orphans修改已跟踪的 README,并断言输出为-m README;test_orphans_basic创建 project 外文件与目录,并断言只有使用-o时出现 orphan block。
这些测试只覆盖 status 的输出分类,不会判断任意真实工作区是否干净。读者仍需根据实际 project header 和文件路径判断修改来源。
4. repo forall
4.1 repo forall
repo forall 会把当前目录切换到 project worktree,并提供一组环境变量:
| 变量 | 含义 |
|---|---|
REPO_PROJECT | manifest project name |
REPO_PATH | 相对 repo client 根目录的 path |
REPO_REMOTE | manifest remote 名称 |
REPO_LREV | manifest revision 转换后的本地 tracking ref |
REPO_RREV | manifest 中原始 revision |
REPO_COUNT | 本次 project 总数 |
REPO_I | 当前 project 序号 |
变量必须在子 shell 中展开,所以命令通常使用单引号:
repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'如果使用双引号,外层 shell 可能在 repo 启动子命令之前就展开变量。
源码文件:git-repo/subcmds/forall.py
相关函数/类型:DoWork
env = os.environ.copy()
env["REPO_PROJECT"] = project.name
env["REPO_OUTERPATH"] = project.manifest.path_prefix
env["REPO_INNERPATH"] = project.relpath
env["REPO_PATH"] = project.RelPath(local=opt.this_manifest_only)
env["REPO_REMOTE"] = project.remote.name
try:
env["REPO_LREV"] = "" if mirror else project.GetRevisionId()
except ManifestInvalidRevisionError:
env["REPO_LREV"] = ""
env["REPO_RREV"] = project.revisionExpr
env["REPO_I"] = str(cnt + 1)
cwd = project.gitdir if mirror else project.worktree
result = subprocess.run(cmd, cwd=cwd, env=env, check=False,
shell=shell, stdout=subprocess.PIPE)这些变量由 Forall 在启动子命令前注入,cwd 则把 owner 切换到具体 project worktree;因此命令中的 git status 读取的是当前 project,而不是 repo client 根目录。验证时可选两个 project,断言每行的 REPO_PATH 与 REPO_PROJECT 成对对应;该实验检查变量注入和工作目录边界,不检查并行输出顺序。
4.2 常用只读查询
# 输出每个 project 的 HEAD
repo forall -p -c 'git rev-parse HEAD'
# 输出精确 tag;没有 tag 的 project 不输出
repo forall -p -c 'git describe --tags --exact-match HEAD 2>/dev/null || true'
# 查找包含本地提交的 project
repo forall -p -c 'git log --oneline "@{upstream}..HEAD" 2>/dev/null || true'
# 查找某个符号出现在哪些 project
repo forall -p -c 'git grep -n "SurfaceFlinger" -- "*.cpp" "*.h" | head'
# 只对匹配的 project 执行
repo forall -r 'frameworks/.*' -p -c 'git status --short'-p 只在命令产生输出时增加 project 标题,适合搜索和归属检查。-e 会在某个子命令失败时中止后续 迭代,适合所有 project 都必须成功的检查。
4.3 并行输出
forall 默认并行处理 project。project 的执行顺序不保证,但输出顺序会被稳定汇总。交互式命令 应使用 --interactive,它会强制串行执行。
下面的时序图展示批量查询的边界。
forall 的环境变量映射可以用只读实验直接验证。下面只选择两个明确 project:
repo forall frameworks/base frameworks/native \
-c 'printf "%s : %s\n" "$REPO_PATH" "$REPO_PROJECT"'实验分别输出 frameworks/base : platform/frameworks/base 和 frameworks/native : platform/frameworks/native,验证了 path、project name 与子 shell 环境变量 的映射。这个实验只检查显式选择的两个 project,不覆盖并行调度的全部行为。
4.4 repo list
不推荐直接运行:
repo forall -c 'git reset --hard'
repo forall -c 'git clean -fdx'这些命令会跨 project 丢弃修改或删除文件。需要批量写操作时:
- 用
repo list或repo forall只读命令确认 project 集合; - 用
-r、-g或显式 path 缩小范围; - 先在一个 project 验证;
- 再决定是否并行执行;
- 操作后立即运行
repo status。
5. repo start
5.1 manifest分支
repo start feature-display frameworks/base frameworks/native这条命令在两个 project 中创建并切换 feature-display。默认起点来自各 project 的 manifest revision,而不是所有 project 共用的某个 commit。
repo start 的核心实现会逐 project 调用 StartBranch:
源码文件:git-repo/subcmds/start.py
相关函数/类型:Start.Execute
projects = self.GetProjects(
args,
all_manifests=not opt.this_manifest_only,
)
def _ExecuteOne(project_idx):
project = self.get_parallel_context()["projects"][project_idx]
# 说明:未显式指定 --rev 时,从该 project 的 manifest revision 创建分支。
revision = opt.revision or project.revisionExpr
return project.StartBranch(
nb,
branch_merge="",
revision=revision,
)不同 project 的 feature-display 名称相同,但它们仍是彼此独立的 Git branch。
5.2 --all
repo start feature-x --all 会在所有选中 project 中创建分支。大多数功能修改只涉及少量 project, 不需要让整个工作区出现同名分支。显式列出 project 更容易审查和清理。
--rev 可以指定其他起点,--head 是从当前 HEAD 创建的简写。使用它们时应明确为什么不从 manifest revision 开始。
6. repo branches
repo branches
repo branches frameworks/base frameworks/native输出的四类信息包括:
*:该 branch 是否在至少一个 project 中被检出;P:该 branch 的提交是否全部通过 repo upload 发布;p:是否只有部分提交发布;- project 列表:branch 存在哪些 project,或缺失于哪些 project。
这比逐个执行 git branch 更适合回答“同名 topic branch 到底涉及哪些 project”。
repo branches 不会替你判断这些 branch 是否应该合并,也不会自动同步不同 project 的提交。 它只是聚合各 project 的 branch metadata。
7. repo diff
7.1 repo diff入口
repo diff
# 只查看指定 project
repo diff frameworks/base frameworks/nativerepo diff 为每个选中 project 调用工作树 diff,再按 project 顺序输出。它适合快速浏览多仓修改, 但不替代以下检查:
repo status:更适合确认新增、删除、暂存和冲突状态;git diff --cached:查看某个 project 已暂存内容;git log:查看已经提交到 topic branch 的修改。
7.2 差异路径
repo diff -u 让路径相对 repo client 根目录,适合将输出保存为跨 project patch:
repo diff -u > workspace.patch补丁是否能直接应用仍取决于目标工作区的 project revision 和文件路径。正式迁移修改时,逐 project 提交或生成明确的 patch 集通常更容易追踪来源和应用结果。
8. repo manifest
8.1 manifest输出
repo manifest -o current-manifest.xml
# 输出到标准输出
repo manifest -o -
# JSON 格式并美化
repo manifest --format=json --pretty -o current-manifest.json默认输出会合并当前 manifest 与 local manifests,适合检查 repo client 实际看到的 project 集合。 --no-local-manifests 可以只观察基础 manifest。
8.2 -r
repo manifest -r -o locked-manifest.xml-r 将 project revision 写成当前 HEAD commit,而不是继续引用 branch。生成的 revision-locked manifest 适合:
- 记录一次源码研究或构建的精确输入;
- 在另一台机器复现 project 提交集合;
- 比较两次工作区升级涉及哪些 project;
- 为问题复现记录当前 project 与 commit 基线。
它不会复制未提交修改,也不会记录被忽略文件。锁定 manifest 只能复现 Git commit 集合,不能替代 repo status 和构建环境记录。
9. 命令组合
9.1 开始修改前
repo list frameworks/base frameworks/native
repo status frameworks/base frameworks/native
repo start feature-x frameworks/base frameworks/native先确认 project 映射和脏状态,再在明确范围创建 topic branch。
9.2 修改过程中
repo status frameworks/base frameworks/native
repo diff frameworks/base frameworks/native
repo forall frameworks/base frameworks/native -p -c 'git log -1 --oneline'9.3 提交前
repo status
repo diff
repo branches
repo manifest -r -o source-baseline.xml代码审查和上传步骤属于 Gerrit 工作流,应在确认每个 project 的 commit、Change-Id 和目标分支后 单独执行。
9.4 文件归属
cd path/to/file/parent
repo list .
git rev-parse --show-toplevel
git remote -vrepo list . 给出 manifest project 映射,Git 命令确认当前 worktree 和 remote。两者一起使用, 可以避免在错误 project 中提交修改。
10. 命令链
repo 的命令不是彼此独立的速查项,而是一条可以还原的操作链:list 先确定 project,status 和 diff 描述修改,branches 说明修改落在哪个 topic branch,manifest -r 固定未修改部分的提交 集合。forall 只是在 project 集合已经明确后批量执行同类查询。
例如发现 frameworks/base 中出现意外修改时,可以按下面的顺序回答问题:
repo list frameworks/base
repo status frameworks/base
repo branches frameworks/base
repo diff frameworks/base
git -C frameworks/base log -1 --oneline这组输出能够区分“文件属于哪个 project”“当前是否在 topic branch”“修改在 index 还是 worktree” 以及“HEAD 基于哪个提交”。恢复动作只有在这些事实明确后才有确定目标。
只读查询可以扩大范围,写操作则应显式列出 project,并在前后各运行一次 repo status。需要改变 project 组成而不是 project 内部状态时,继续阅读 本地Manifest定制。
