Skip to content

repo日常命令实战

面向 AOSP 日常开发,说明 repo list、status、forall、start、branches、diff 和 manifest 的组合用法与安全边界。

基于android-17.0.0_r1
AndroidAOSPrepoGit

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 listproject 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 默认输出字段 ​

bash
repo list

默认格式为:

text
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

python
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 常用筛选方式 ​

bash
# 只显示本地路径
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 比较:

  1. HEAD:当前提交;
  2. index:暂存区;
  3. worktree:工作目录。

典型输出:

text
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 前先执行:

bash
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_PROJECTmanifest project name
REPO_PATH相对 repo client 根目录的 path
REPO_REMOTEmanifest remote 名称
REPO_LREVmanifest revision 转换后的本地 tracking ref
REPO_RREVmanifest 中原始 revision
REPO_COUNT本次 project 总数
REPO_I当前 project 序号

变量必须在子 shell 中展开,所以命令通常使用单引号:

bash
repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'

如果使用双引号,外层 shell 可能在 repo 启动子命令之前就展开变量。

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

相关函数/类型:DoWork

python
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 常用只读查询 ​

bash
# 输出每个 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:

bash
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 ​

不推荐直接运行:

bash
repo forall -c 'git reset --hard'
repo forall -c 'git clean -fdx'

这些命令会跨 project 丢弃修改或删除文件。需要批量写操作时:

  1. 用 repo list 或 repo forall 只读命令确认 project 集合;
  2. 用 -r、-g 或显式 path 缩小范围;
  3. 先在一个 project 验证;
  4. 再决定是否并行执行;
  5. 操作后立即运行 repo status。

5. repo start ​

5.1 manifest分支 ​

bash
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

python
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 ​

bash
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入口 ​

bash
repo diff

# 只查看指定 project
repo diff frameworks/base frameworks/native

repo diff 为每个选中 project 调用工作树 diff,再按 project 顺序输出。它适合快速浏览多仓修改, 但不替代以下检查:

  • repo status:更适合确认新增、删除、暂存和冲突状态;
  • git diff --cached:查看某个 project 已暂存内容;
  • git log:查看已经提交到 topic branch 的修改。

7.2 差异路径 ​

repo diff -u 让路径相对 repo client 根目录,适合将输出保存为跨 project patch:

bash
repo diff -u > workspace.patch

补丁是否能直接应用仍取决于目标工作区的 project revision 和文件路径。正式迁移修改时,逐 project 提交或生成明确的 patch 集通常更容易追踪来源和应用结果。

8. repo manifest ​

8.1 manifest输出 ​

bash
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 ​

bash
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 开始修改前 ​

bash
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 修改过程中 ​

bash
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 提交前 ​

bash
repo status
repo diff
repo branches
repo manifest -r -o source-baseline.xml

代码审查和上传步骤属于 Gerrit 工作流,应在确认每个 project 的 commit、Change-Id 和目标分支后 单独执行。

9.4 文件归属 ​

bash
cd path/to/file/parent
repo list .

git rev-parse --show-toplevel
git remote -v

repo list . 给出 manifest project 映射,Git 命令确认当前 worktree 和 remote。两者一起使用, 可以避免在错误 project 中提交修改。

10. 命令链 ​

repo 的命令不是彼此独立的速查项,而是一条可以还原的操作链:list 先确定 project,status 和 diff 描述修改,branches 说明修改落在哪个 topic branch,manifest -r 固定未修改部分的提交 集合。forall 只是在 project 集合已经明确后批量执行同类查询。

例如发现 frameworks/base 中出现意外修改时,可以按下面的顺序回答问题:

bash
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定制。