Skip to content

12|代码对照——单文件与全项目

“看差异”其实有三种尺度:

  1. 当前文件相对 Git index 有什么变化;
  2. 任意两份文件有哪些不同;
  3. 整个项目的 staged、unstaged 和文件历史长什么样。

这一章会把这三种场景分别练一遍。开始对比前记得先保存,因为 Diffview 主要读取磁盘上的工作树,没保存的内容可能根本不会显示。

工作流一的新按键:当前文件对 Git index

按键效果
Space h d当前文件与 Git index 并排对照
] c / [ c在 diff 模式跳到下一处 / 上一处差异

上一章留下了一份半暂存状态。打开 src/pocket_tasks/service.py,保存以后按:

半暂存的 service.py 同时显示 staged 与 unstaged 标记
半暂存的 service.py 同时显示 staged 与 unstaged 标记

Space h d

屏幕上会并排出现 Git 快照和当前文件两个窗口。因为 add_task 已经进了 index,这次对照里主要应该看到还没暂存的 completed_count

service.py 与 Git index 并排对照
service.py 与 Git index 并排对照
  1. ] c 跳到下一处差异。
  2. [ c 回上一处。
  3. Ctrl+wh/l 看清两个窗口的文件名。
  4. 焦点放到 Git 快照窗口,按 Ctrl+w,再按 q 关闭它。

若剩余窗口仍保留 diff 折叠或高亮,按 F1,输入 diffoff,连按两次 Enter

这套简单的对比视图,最适合确认一件事:

当前 Buffer 还有哪些内容没进 index?

工作流二的新按键:任意两个文件的原生 diff

按键效果
Picker 中 Ctrl+v在竖向分屏打开候选
F1diffthis → 两次 Enter让当前窗口加入 diff
d o从另一窗口取得当前差异块
d p把当前差异块送到另一窗口
F1diffoff → 两次 Enter让当前窗口离开 diff

这套流程不依赖 Git。配置文件、生成结果、不同版本的文案,甚至接口响应,都可以直接摆在一起比较。

创建两份一次性对照文件

Space e 打开 Explorer,在项目根目录按 a,输入 compare/,按 Enter

在项目根目录创建 compare 目录
在项目根目录创建 compare 目录

进入新目录,创建 left.conf,输入:

ini
[display]
theme = light
show_help = yes
show_count = yes

[limits]
max_items = 5
trim_titles = no

[sorting]
primary = created
stable = yes
完成并保存 left.conf
完成并保存 left.conf

保存。回到 Explorer,创建 right.conf,输入:

ini
[display]
theme = dark
show_help = yes
show_count = yes

[limits]
max_items = 10
trim_titles = no

[sorting]
primary = title
stable = yes
完成并保存 right.conf
完成并保存 right.conf

保存。现在两份文件都已经成了 Buffer,最后停在 right.conf 就行。

用 Buffer Picker 竖向并排

  1. Space ,
  2. 输入 left.conf
Buffer Picker 筛选并预览 left.conf
Buffer Picker 筛选并预览 left.conf
  1. 候选高亮到它时按 Ctrl+v

现在两个代码窗口分别显示 left 和 right。新分屏究竟出现在左边还是右边,会受当前布局影响,所以要看文件名确认内容,别只凭位置判断。

left.conf 与 right.conf 竖向并排
left.conf 与 right.conf 竖向并排

让两个窗口都进入 diff

  1. 在当前窗口按 F1
  2. 输入 diffthis,连按两次 Enter
  3. Ctrl+w,再按 hl,移动到另一份文件。
  4. 再执行一次 F1diffthisEnterEnter

完成后应该会看到:

  • 相同行会对齐;
  • 不同内容带有 diff 高亮;
  • 大段相同内容可能折叠;
  • ] c[ c 在三个差异块之间跳转。
两个配置窗口进入原生 diff
两个配置窗口进入原生 diff

使用 d od p 合并差异

先找到显示 left.conf 的窗口。

  1. 搜索 /theme,按 Enter
  2. d o
  3. 当前窗口从另一侧取得这一块,light 变成 dark
用 do 把 dark 取得到 left.conf
用 do 把 dark 取得到 left.conf
  1. u,把练习修改撤销。

接着仍在 left 窗口:

  1. 搜索 /max_items,按 Enter
  2. d p
  3. 另一窗口收到当前块,10 变成 5
用 dp 把 max_items 发送到 right.conf
用 dp 把 max_items 发送到 right.conf
  1. 移到 right 窗口,按 u 撤销。

可以这样记:

d o:obtain,把差异取到当前窗口。d p:put,把差异放到另一个窗口。

这两条命令都会修改目标 Buffer,但不会替你保存。真拿它们改文件时,先检查结果,再决定是保存还是撤销。

退出双文件 diff

在第一个窗口执行 F1diffoffEnterEnter,移动到第二个窗口,再执行一次。随后可用 Ctrl+wq 关闭其中一个分屏。

compare 目录只是这次练习的临时场地。想清理的话:

  1. 确认两份文件没有想保留的内容。
  2. Space ,,筛选 compare,用 Ctrl+x 删除两个临时 Buffer。
Buffer Picker 列出两个 compare Buffer
Buffer Picker 列出两个 compare Buffer
  1. 在 Explorer 把光标放到 compare 目录。
  2. d 并确认。
删除 compare 目录后的项目树
删除 compare 目录后的项目树

如果当前系统没有可用的回收站后端,Explorer 会永久递归删除整个目录。这里只删名字明确的练习目录;碰到真实目录时,一定要把路径和内容确认清楚以后再按 d

工作流三:Diffview 全项目审查

先按 F1wallEnterEnter,随后按:

Space g s

Diffview 会新建一个 Tab。默认界面大致如下:

text
┌─ 文件面板 ─────────┬──────────── 对照区域 ───────────┐
│ Staged changes    │    左侧版本    │    右侧版本     │
│ Unstaged changes  │               │                │
└───────────────────┴────────────────────────────────┘

同一条路径可能同时出现在 Staged 和 Unstaged 两个分组里。上一章留下的两个 MM 文件,正好能展示这种状态。

Diffview 同时列出已暂存与未暂存变化
Diffview 同时列出已暂存与未暂存变化

第一步:浏览所有变化

按键效果
Tab打开下一份变更文件
Shift+Tab打开上一份变更文件
Space e聚焦 Diffview 文件面板
文件面板 j/k上下移动
文件面板 Enter打开所选文件的 diff
Space b隐藏或显示文件面板
g f在先前的普通 Tab 打开真实工作文件

先反复按 Tab 把所有文件过一遍,再按 Space e 回到文件面板,用 j/kEnter 精确选择。

查看 test_service.py 的未暂存变化
查看 test_service.py 的未暂存变化

在 Diffview 里,Space e 只在当前界面里生效:它负责把焦点切到文件面板,不会打开或关闭普通的 Explorer。

要修代码时,在对照窗口按 g f。Diffview 会继续留在自己的 Tab,真实文件则会在之前的工作 Tab 里打开。改完并保存以后,用 g tg T 回到 Diffview,按 Space e,再按 R 刷新文件列表。

两侧到底代表谁

  • Unstaged 条目通常比较 index 与工作树;
  • Staged 条目通常比较 HEAD 与 index;
  • 左右标题和文件面板分组比颜色更值得信任;
  • 对照区里的 Git 快照与 index 视图不适合作为日常编辑入口。
查看 service.py 的已暂存变化
查看 service.py 的已暂存变化

如果不小心在 Changes 视图的 index 一侧进入插入模式并保存,暂存区也会跟着变化。需要修改文件时,优先按 g f 回到普通工作 Tab。

第二步:按文件暂存

Diffview 文件面板的暂存动作处理整份文件:

按键效果
s-暂存 Unstaged 条目;对 Staged 条目执行时会取消暂存
S暂存列表里的全部变化
U取消暂存全部变化

这次只练小写 s

  1. Space e 聚焦文件面板。
  2. 在 Unstaged 分组找到 service.py,按 s
service.py 的变化已全部暂存
service.py 的变化已全部暂存
  1. test_service.py 重复一次。
  2. 观察它们的未暂存条目消失,暂存条目保留或更新。
两个文件的变化已全部暂存
两个文件的变化已全部暂存

这样会把上一章留下的 completed_count 及其测试也放进 index。如果暂存错了文件,把光标移到它的 Staged 条目,再按一次 s,就能取消整份文件的暂存。

一个需要了解但不必练习的高风险操作

Diffview 文件面板的 X 键会直接恢复文件

X 会把选中的文件恢复到 diff 左侧的状态,而且不会确认。插件虽然会短暂显示一条可能用于恢复的 Git 命令,但那条消息很容易错过。平时审查时不要随手按;确实要丢弃内容,优先回到普通文件预览 hunk,再逐块处理。

正确关闭 Diffview

普通的 q 只会影响当前窗口或面板,无法关闭整个 Diffview。

  1. F1
  2. 输入 DiffviewClose
在 F1 Picker 中选择 DiffviewClose
在 F1 Picker 中选择 DiffviewClose
  1. 连按两次 Enter

你会回到原来的工作 Tab。

审查、测试、提交

打开终端,依次运行:

console
PYTHONPATH=src python -m unittest -v
git diff --cached --check
git diff --cached
完整测试套件全部通过
完整测试套件全部通过

测试应该全部通过;git diff --cached --check 什么也没输出,就表示没发现空白错误。第三条命令会打开补丁,按 q 退出 pager。

在 pager 中审查已暂存补丁
在 pager 中审查已暂存补丁

确认提交内容只有标题清理和完成数量以后,再运行:

console
git commit -m "clean task titles and count completions"
git status --short
提交创建后工作树保持干净
提交创建后工作树保持干净

最后应该会回到干净的工作树。

工作流四:当前文件对上一提交的父提交

现在项目至少有两个提交。打开 service.py,按:

Space h D

service.py 与父提交中的版本并排对照
service.py 与父提交中的版本并排对照

注意最后的 D 是大写。这个映射实际比较的是“当前文件”和 HEAD~,也就是当前提交的父提交。它只看眼前这个文件,不会打开全项目 diff。

] c 浏览刚提交的变化,之后关掉 Git 快照窗口。如果项目里只有根提交,HEAD~ 还不存在,这个动作就会报找不到 revision。

跳到父提交对照中的下一处变化
跳到父提交对照中的下一处变化
可选工作流:翻项目提交历史
  1. F1
  2. 输入 DiffviewFileHistory
在 F1 Picker 中选择 DiffviewFileHistory
在 F1 Picker 中选择 DiffviewFileHistory
  1. 连按两次 Enter
Diffview 展示项目的提交历史
Diffview 展示项目的提交历史
  1. 在历史面板用 j/k 选择提交,按 Enter 查看该提交的文件差异。
查看当前提交中的 test_service.py 差异
查看当前提交中的 test_service.py 差异
展开上一条提交的文件清单
展开上一条提交的文件清单
  1. F1DiffviewClose → 两次 Enter 退出。

不带参数的 DiffviewFileHistory 会查看整个项目的历史。那些需要精细 Git 参数的高级查询,等真正用到时再学也不迟,这份教程不要求提前掌握。

三种代码对照方式的选择

你想回答的问题工具
当前文件还有什么没进 indexSpace h d
两份任意文件哪里不同,并在两边传递差异双分屏 + diffthis
项目里哪些文件 staged/unstagedSpace g s
当前文件相对父提交改了什么Space h D
提交历史里每次动了什么DiffviewFileHistory

下一章进入 13:合并冲突演练。我们会制造两处冲突,并分别练习在不同冲突中选择 ours 和 theirs。

本文档采用 知识共享 署名-相同方式共享 4.0 协议 进行许可。