← 开发工具与工作流 · 第 2 篇

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

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

学完这一篇,你能做到

从两种 diff 预测提交内容,保留无关编辑,并写出可验证的提交与审阅说明。

一次修改同时包含检索修复、临时日志和个人配置。即使功能已经正常,直接把整个目录提交也会让审阅者难以判断改动范围。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 对象、索引格式和父提交。

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 故障分类表,区分主机身份异常、用户认证失败和仓库权限不足。整个练习不需要发布真实私钥,也不需要改写共享历史。

资料来源与延伸阅读

资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。