# 把一次修改做成可审阅、可恢复的提交

原文: https://yishto.com/learn/developer-workflow/git-ssh-and-review/

作者: [Yishto](https://yishto.com/about/)

通过工作区与暂存快照的差异理解提交范围，结合分支恢复、SSH 身份检查和审阅说明完成一次修改。

首次发布: 2026-11-10
内容更新: 2026-11-10

一次修改同时包含检索修复、临时日志和个人配置。即使功能已经正常，直接把整个目录提交也会让审阅者难以判断改动范围。Git 能保存快照和历史，但哪些内容进入下一次提交，仍需要开发者明确选择。

本课使用一个合成资料应用的版本变化，解释工作区、暂存区、提交和分支之间的关系。随后把这些关系转成一次可审阅的协作流程。Python 示例只在内存里复制字典，不会创建仓库，也不会连接远程服务器。

## 工作区、暂存区与提交保存不同内容

工作区是正在编辑的文件；暂存区，也叫 index，记录准备进入下一次提交的内容；提交保存已记录的快照及历史关系。`git add` 之后继续修改同一个文件，新增的修改不会自动进入此前暂存的内容。因此一个文件可以同时出现已暂存和未暂存的变化。

`git status` 显示这些状态的区别。普通 `git diff` 主要查看工作区相对暂存区的变化，`git diff --staged` 查看暂存区相对当前提交的变化。提交前两者都要看：前者帮助发现遗漏和无关编辑，后者才说明下一次提交实际会包含什么。新建文件还要注意是否已被跟踪。

`.gitignore` 可以避免某些未跟踪文件被顺手加入，但不能抹掉已经进入历史的秘密。密钥一旦泄露，删除文件或新增忽略规则不够，需要撤销或轮换凭证，并按仓库流程处理历史。教学项目应从一开始只使用合成配置和占位值。

## 分支表达历史位置，远程状态要更新

分支是指向提交的可移动引用，不是一份独立文件夹。提交通过父提交组成历史图；同一工作区切换分支时，Git 根据目标历史更新文件，还会受到当前未提交编辑的影响。开始新任务前先检查状态，保留已有改动，不要把切换失败当成需要清空目录的理由。

`git fetch` 获取远程对象并更新相应远程跟踪引用，通常不会替你合并当前分支。之后才能比较本地修改与已获取的远程状态。merge 连接历史，rebase 把一组修改重新应用到另一基底，会产生新的提交身份。共享的已发布历史做 rebase，需要先协调协作者。

恢复也有不同含义。revert 通过新提交表达逆向修改，适合需要保留审计历史的情形；reset 可以移动引用，并依选项影响暂存区与工作区。不要把带有文件覆盖含义的 reset 当作日常清理命令。选择恢复方法前，写清要保留哪些编辑、历史是否已共享。

## 用快照模型验证暂存行为

假设 app 原来是 v1。暂存它之后，工作区又变成 v2；第一次提交仍应来自暂存快照。第二次明确更新暂存内容，才得到 v2。这里用浅复制模拟只有字符串值的文件映射，省略了 Git 对象、索引格式和父提交。

```python
working = {"app": "v1"}
index = working.copy()
working["app"] = "v2"
commit = index.copy()
assert commit["app"] == "v1"
index["app"] = working["app"]
next_commit = index.copy()
assert next_commit["app"] == "v2"
assert commit["app"] == "v1"
print(commit, next_commit)
```

输出是 `{'app': 'v1'} {'app': 'v2'}`。最后一个断言检查第二次暂存不会修改第一次保存的快照。这个结果解释“文件明明改了，为什么提交里没有”的一种原因，但不能代替在真实仓库查看 staged diff。

如果字典值换成可变嵌套对象，浅复制就不足以表达独立快照。这也是模型边界：Git 内容寻址对象并不是 Python 字典。练习时应验证它表达的关系，避免从这个小例子推导 Git 内部实现。

## 把修改做成可检查的提交

在一次性仓库练习时，先记录初始提交，再创建任务分支。只修改一项行为，例如让未知资料 ID 返回明确错误。查看 status 和 diff，移除自己添加的调试输出，选择需要的文件或补丁块暂存，再查看 staged diff。不要把不属于任务的编辑一并处理。

运行能覆盖原始失败和相邻边界的检查，再写 conventional commits 信息，例如 `fix: 修正未知资料的错误返回`。描述说清改变了什么；验证记录说明具体输入与观察结果。提交完成后检查工作区，剩余编辑可以是有意保留的，但要知道它们为何没有进入提交。

PR 或 MR 的说明应围绕最终行为：什么触发原来的错误，现在返回什么，如何验证，以及是否存在兼容性风险。审阅对象是结果与依据，不是所有尝试过的方案。一个聚焦的提交更容易比较、回退和讨论，文件数量少本身却不保证改动简单。

## SSH 检查服务器身份与用户身份

SSH 的主机密钥检查回答“连接的是不是预期服务器”，用户认证回答“你是否具有登录资格”。两者不能互相替代。公共密钥可以按服务要求登记，私钥留在本机并受保护；不要把私钥当附件或配置推到仓库。

遇到主机密钥变化，应通过可信渠道核对服务器变更和指纹。直接删除 known_hosts 条目再接受新值，会跳过原本要解决的身份问题。连接被拒绝时，分别检查主机、端口、用户名、选用的身份和服务端权限，不把所有失败都归结为 Git 密码。

访问服务器成功也不等于有仓库写入权限。开源协作还要阅读许可证、贡献指南和维护者要求；问题报告应包含最小复现、预期与实际结果，避免发布私有数据。讨论与提交可以关联，但认证机制不会代替代码审阅。

提交里还应避免混入生成产物与本机配置，除非仓库明确把它们作为交付物。对于新增文件，检查内容与忽略规则的作用范围；对于删除文件，确认是任务要求而非工具误操作。审阅时保留运行时版本与检查命令，方便别人区分代码行为变化和本地环境差异。

## 练习：留下遗漏，再证明它被发现

在临时仓库里暂存一次改动，然后继续修改同一行。先写出两种 diff 应分别显示什么，再运行检查验证。提交暂存版本后确认新编辑仍在工作区，明确暂存它后再创建第二个提交。保留两个提交的标识和 diff。

验收要求：读者能从 staged diff 预测下一次提交内容，从历史看到两个版本，并用新增的逆向提交恢复目标行为。另写一份 SSH 故障分类表，区分主机身份异常、用户认证失败和仓库权限不足。整个练习不需要发布真实私钥，也不需要改写共享历史。

## 资料来源与延伸阅读

- [Pro Git](https://git-scm.com/book/en/v2)
- [Git status command](https://git-scm.com/docs/git-status)
- [SSH client manual](https://man.openbsd.org/ssh)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [让每次发布都能检查，也能恢复](https://yishto.com/learn/delivery/cicd-monitoring-and-incidents/)
- [用测试和决策记录支持代码演进](https://yishto.com/learn/data-practice/testing-refactoring-and-decisions/)
