TW●汤伟的数字生活NOTES ON A DIGITAL LIFE
个人数字生活文库始于 2026 · 持续修订总目录 ↗
全部文章

前端与 Git / 实战手册

Git 日常改动与安全撤销:先找准状态,再选择退路

从工作区、暂存区和提交记录判断改动在哪一层,掌握按块暂存、保留内容的撤销、stash 恢复和共享提交的反向修复。

阅读设置字号仅保存在当前浏览器

开始前:确认位置和保护范围

适用于已经有至少一次提交的普通 Git 仓库,示例文件为 src/demo.ts。命令可在 macOS、Linux 和 Windows Git 终端使用;restore 需要 Git 2.23 或更新版本,本文 stash show --include-untracked 示例需要 Git 2.32+。本文在 2026-09-08 核对官方文档,所有输出均为说明用途的示例,不代表作者在你的项目上执行过。第一次提交之前没有 HEAD,撤销暂存的处理不同,不应照搬本文依赖 HEAD 的操作。

遇到“撤销一下”先补全目标:是不要这次暂存,还是不要文件内容,还是让已经提交的错误失效?编辑器里的文件是工作区,下一次提交的候选快照是暂存区,HEAD 指向当前提交。一个文件可以同时存在已暂存和未暂存修改。操作前先保存编辑器缓冲区,确认终端在正确仓库;重要的未跟踪文件和忽略文件另外复制到受控备份目录,分支本身不会备份这些内容。

风险标记按影响划分:read 表示查看信息或约定只读的网络请求,可能留下工具缓存或服务日志;write 表示修改暂存区、依赖、配置、产物或启动进程;danger 表示可能覆盖、丢弃现有编辑。代码块若包含多条操作,按最高风险标记。示例路径需要按当前项目确认,代码和响应样本不会被本资料自动执行。

确认仓库和状态sh · macOS / Linux / Windows(已安装对应工具)
查询 / 诊断
git --version
git rev-parse --show-toplevel
git status --short --branch

根目录应是目标项目。示例“## feature/demo”是分支,“ M src/demo.ts”是未暂存改动;此处的 read 表示不主动更改业务内容。 示例未实机验证。

第一步:分别检查两份差异

先看 status,再看两种 diff。普通 diff 比较工作区和暂存区,带 --cached 则比较暂存区和 HEAD。示例状态“MM src/demo.ts”表示两处都有变化;只看普通 diff 会漏掉已经准备提交的部分。“?? notes.txt”表示尚未跟踪,普通 diff 不展示它的内容,应在编辑器单独检查。遇到“UU”说明文件冲突尚未解决,此时不要进入常规撤销流程。

差异中的加号和减号是相对于比较基准的增删,不是安全程度。检查导入路径、调试输出、环境配置和意外格式化,尤其留意一个小修复是否把整个文件换了换行符。路径前的双横线用于分隔选项与文件名,路径含空格时加引号。若输出为空,只能说明当前比较没有差异,不能证明整个工作目录干净。

检查工作区和暂存区sh · macOS / Linux / Windows(已安装对应工具)
查询 / 诊断
git diff -- src/demo.ts
git diff --cached -- src/demo.ts
git diff --check

前两条显示不同层的补丁;第三条检查工作区差异中的空白错误和冲突标记,不能替代编译或功能测试。 示例未实机验证。

第二步:只暂存本次需要的改动

使用按块暂存把修复和顺手重构分开。交互界面中的 y 接受当前块,n 跳过,s 在可拆分时拆成更小块,q 结束;不确定就跳过,回到编辑器整理后重试。新文件还未进入跟踪时,先检查其完整内容,再用带明确文件路径的 add 纳入。不要为了省事把所有目录一股脑加入提交。

发现暂存错了文件时,只撤销暂存,保留工作区内容继续编辑。预期“ M”重新出现,暂存差异消失而普通差异仍在。如果原来是“MM”,撤销暂存会把两轮改动都留在工作区,因此下一次要重新检查完整补丁。检查通过后再按项目规定提交;提交钩子也可能修改文件,提交结束仍需重新看状态。

按块暂存sh · macOS / Linux / Windows(已安装对应工具)
会修改状态
git add -p -- src/demo.ts
git diff --cached --check
git diff --cached -- src/demo.ts

只纳入确认过的块。后两条验证即将提交的内容;add 改写暂存区。 示例未实机验证。

撤销暂存,保留编辑内容sh · macOS / Linux / Windows(已安装对应工具)
会修改状态
git restore --staged -- src/demo.ts
git status --short
git diff -- src/demo.ts

从 HEAD 恢复该文件的暂存状态,不覆盖工作区。若没有初始提交,停止并采用适用于未出生分支的流程。 示例未实机验证。

第三步:暂存现场后再尝试恢复

需要中断当前工作时,可以用带说明的 stash 保存已跟踪修改,并用 -u 纳入未跟踪文件。该操作会清理被收纳的工作区内容,所以不是只读备份。忽略文件默认不在范围内,子模块内部改动也要单独处理。执行前后检查文件清单,确认重要素材确实另有副本,不能仅凭“Saved working directory”一句输出认为所有文件都被保护。

恢复时先 list 找到对应条目,再 show 检查范围,优先 apply,让 stash 条目继续保留。例子中的 stash@{0} 只是当前最新条目,新增 stash 会改变编号,实际使用应核对说明和内容。普通 apply 不保证原来的暂存分层恢复;确实需要恢复暂存状态可了解 --index,但存在冲突时也可能失败。

如果 apply 报冲突,条目仍然存在。先看 status,在编辑器逐个合并并保留双方需要的内容,验证后再 add。不要重复 apply 企图冲掉冲突,也不要假设存在 stash --abort。回退最稳妥的前提是应用前工作区干净且已有备份;不满足时先把当前现场复制保存,再制定按文件恢复方案。确认恢复成功之前保留 stash,避免提前删除最后一份修改。

保存现场,包含未跟踪文件sh · macOS / Linux / Windows(已安装对应工具)
会修改状态
git stash push -u -m "before-demo-investigation"
git stash list

会改变工作区和暂存区;不包含忽略文件,不负责子模块内部现场。 示例未实机验证。

检查并恢复指定现场sh · macOS / Linux / Windows(已安装对应工具)
会修改状态
git stash show --stat --include-untracked 'stash@{0}'
git stash apply 'stash@{0}'
git status --short

执行 apply 前必须确认该条目与当前分支兼容;apply 保留条目,发生冲突先处理现场。 示例未实机验证。

仅在确定不要内容时:恢复单个文件

如果目标确实是放弃某个已跟踪文件的未提交编辑,先在编辑器保存一份仓库外副本,再确认源版本中的内容。下面命令明确从 HEAD 恢复工作区,属于会覆盖内容的危险操作;它不修改暂存区,因此可能产生工作区与暂存区不一致的状态。不要把路径替换成点号扩大到整个仓库,也不要把这一步当成常规清理方式。

执行后重新查看两份 diff。如果覆盖错了未提交内容,Git 的 reflog 通常无法救回从未提交或 stash 的文字,只能依靠先前副本、编辑器历史等。若想保留当前内容只是取消暂存,应回到第二步;这是两种完全不同的意图。

先看源版本;确认已有副本后才覆盖sh · macOS / Linux / Windows(已安装对应工具)
高风险操作
git show HEAD:src/demo.ts
git restore --source=HEAD --worktree -- src/demo.ts
git diff -- src/demo.ts
git diff --cached -- src/demo.ts

第二条会覆盖该文件工作区内容;源提交不存在该已跟踪路径时还可能删除路径。 示例未实机验证。

已经提交:用反向提交保留历史

对已共享的错误提交,通常选择 revert 新建一个抵消改动的提交。先确保现场干净,用 show 确认要反向处理的是普通提交;示例选择 HEAD,只在当前最后一次提交恰好是目标时使用。合并提交需要明确主线父提交,不能随便猜一个数字。错误涉及后续依赖时,也不能仅凭命令成功判断业务已恢复。

发生冲突后,status 会列出未合并路径。决定继续则编辑文件、移除冲突标记、完成验证后 add 并 continue;决定放弃则使用 abort 回到此次序列开始前。两种分支只选其一。完成后检查最新提交和相关功能。若反向提交本身也错了,可以在干净现场重新审查并反向处理那次反向提交,仍然保留可追溯历史。

如果分支引用意外移动,reflog 能帮助找到本机记录的旧提交。先 show 核实,再给找到的提交创建救援分支以固定引用;它记录的是引用变动,不是每次键盘编辑,而且记录会过期。最终验收要回答三个问题:想保留的文件还在吗,暂存区是否只包含预期内容,针对改动的检查是否通过。

确认并反向处理最后一个普通提交sh · macOS / Linux / Windows(已安装对应工具)
会修改状态
git status --short
git show --stat HEAD
git revert HEAD

仅在状态干净且 HEAD 是目标普通提交时运行最后一条;将创建新提交并可能调用编辑器及钩子。 示例未实机验证。

冲突解决后的继续分支sh · macOS / Linux / Windows(已安装对应工具)
会修改状态
git add -- src/demo.ts
git revert --continue

仅适用于正在进行的 revert,且全部冲突已经人工解决并验证。 示例未实机验证。

放弃本次 revert 的替代分支sh · macOS / Linux / Windows(已安装对应工具)
高风险操作
git revert --abort

只在 revert 序列仍进行时使用;会丢弃本次冲突处理过程的编辑,需要的内容先另存。 示例未实机验证。

查找本机引用历史sh · macOS / Linux / Windows(已安装对应工具)
查询 / 诊断
git reflog --date=iso -n 15
git log -5 --oneline

输出中的提交号用于后续人工核对;不保证能恢复未提交文件。 示例未实机验证。

案例起点:修文案时夹进了一次重构

假设个人博客的 src/demo.ts 同时包含标题修正和筛选函数重构,标题已经暂存,随后又改了筛选逻辑;另有尚未跟踪的 notes.txt 存着写作提纲。这是构造案例,未实机验证。读者的任务是只提交标题,保留重构与提纲,发现标题不妥时还能解释如何恢复。先把这三个意图写下来,再对照文件状态。不要为了得到干净状态而牺牲本来想保留的内容,干净只是工具状态,完成任务才是目的。

先打开工作区文件,确认两项编辑都还在;再阅读暂存差异,确认里面只有标题。如果暂存差异混入筛选逻辑,就取消该文件暂存,再按块选择标题。两项改动紧挨着而无法自动拆块时,可以先把完整工作区副本保存到仓库外,在编辑器把文件整理为仅标题修改,暂存后再恢复保留的重构。这个过程涉及覆盖文件,必须逐步核对副本及差异,不能当作一条自动脚本。

三层状态对照与预期输出text · 普通文本编辑器 / macOS / GNU/Linux / DSM 浏览器客户端
查询 / 诊断
# 示意,非真实运行结果
HEAD: 标题=旧标题;筛选=原逻辑
index: 标题=新标题;筛选=原逻辑
worktree: 标题=新标题;筛选=新逻辑
git status --short 的示意:
MM src/demo.ts
?? notes.txt
普通 diff:只应显示筛选改动
cached diff:只应显示标题改动

示意模板,未实机验证;复制到自己的受控笔记后填写待测项,不会自动修改线上配置。

提交前后核对顺序sh · macOS / GNU/Linux Git 2.23+;DSM 需先核对 Git 版本
会修改状态
git status --short --branch
git diff -- src/demo.ts
git diff --cached -- src/demo.ts
git diff --cached --check
# 独立确认暂存快照可用后,提交这份已审查的暂存内容
git commit -m "修正文章标题"
git show --stat HEAD
git show HEAD -- src/demo.ts
git status --short

在普通已有提交的个人仓库使用;commit 会运行配置的钩子。逐条执行,错误即停,未实机验证。

案例推进:验证的究竟是哪份代码

一个容易遗漏的边界是:普通构建读取工作区,而提交读取暂存区。如果工作区含有尚未暂存的修复,构建成功并不能证明暂存快照独立可用。本案例中的标题改动不应依赖筛选重构,因此先审查这种依赖是否存在;若两个补丁实质不可分,应调整提交范围,而不是为了形式上的小提交强行拆开。对于需要严格验证的代码,可在独立开发副本验收对应提交,避免在当前混合现场反复覆盖。

提交之后重新看最新提交所含路径以及工作区状态。预期标题属于历史记录,重构仍显示未暂存,提纲仍是未跟踪;这三个结果同时满足才算完成。若提交钩子重新格式化了文件,先辨认新增差异来自钩子还是原编辑,再决定是否需要另一轮暂存。不要看到提交成功提示就立刻推送,尤其要核对提交内容是否包含本来只打算本机保存的提纲。

症状推理:没有差异与文件消失分别意味着什么

如果普通 diff 没输出而编辑器明显有改动,先保存缓冲区,再检查文件是否已经暂存、是否未跟踪、终端是否位于另一个工作副本。若 cached diff 有输出,这不是改动丢失,而是比较基准选错。若两处都没有输出但文件是未跟踪,应直接阅读文件并检查忽略规则;不要在没有证据时运行恢复命令,因为恢复会改变正在调查的现场。

如果 stash 后提纲从目录消失,先核对是否使用了包含未跟踪文件的选项,再检查对应条目的文件清单。能在条目里找到它,说明临时收纳可能符合预期;找不到则检查保存前的位置和仓库外副本。相反,被忽略的本地环境文件依旧存在并不表示 stash 失败,而是保存范围不同。恢复发生同名冲突时,先保留当前同名文件再分析,不要删除它来让命令通过。

已共享提交的恢复案例与停止边界

假设标题提交已经推送,读者发现新标题误导,需要恢复旧表述。先用提交内容确认目标,不要只凭时间排序猜测。若目标之后还有别人的修订,应考虑反向补丁与后续修改的关系:保留后来正确的文字,只抵消错误部分。正常反向提交成功后,历史中应同时保留原提交和修正提交;页面是否更新还要经过发布流程,Git 成功不等于线上完成。

如果反向操作停在冲突,先读冲突两边的文字和相邻上下文,再决定保留的最终段落。手工处理完不代表冲突已登记解决,还要按流程暂存并继续;若决定放弃,先另存本轮有价值的冲突编辑,再结束反向序列。此处的停止条件是无法确认目标提交、现场已有无关修改,或反向补丁牵涉未知依赖。先回到可解释状态,比猜参数更能保住写作成果。

把恢复能力记录成可交接的检查结果

留下记录时写明仓库副本、当前分支、目标提交的实际标识、想保留的文件和采用的恢复方式。公开笔记用占位符表示标识,私有工作记录可以保留真实提交引用;不要把“可以通过历史恢复”写得无限宽泛,未提交编辑和忽略文件仍需要其他副本。还应说明是否恢复了暂存分层,避免下一次把保留下来的工作区改动误当成待提交内容。

macOS 与 GNU/Linux 使用这里的 Git 子命令语义一致,但文件系统大小写、换行设置和外部编辑器行为可能不同;DSM 上预装或套件提供的 Git 版本不能默认满足本文门槛,也不能把普通仓库步骤直接用于套件管理目录。验收结束只整理本轮产生的临时副本,确认原改动完整后再按个人保留策略归档。本文没有执行任何恢复、提交或远程推送,案例结果均为应观察的判据。

恢复工作记录markdown · 普通文本编辑器 / macOS / GNU/Linux / DSM 浏览器客户端
会修改状态
# 标题修订恢复记录
- 目标提交:<实际提交标识>
- HEAD 核对结果:<待填>
- 必须保留:筛选重构、notes.txt
- 仓库外副本:<受控位置,不写私人内容>
- 本轮操作:取消暂存 / 单文件恢复 / 反向提交(只选适用项)
- 暂存差异:<待填>
- 工作区差异:<待填>
- 独立验证命令与结果:<待测>
- 远程与线上版本:<尚未推送或尚未发布>

示意模板,未实机验证;复制到自己的受控笔记后填写待测项,不会自动修改线上配置。

延伸阅读

Git status Git diff Git add Git restore Git stash Git revert Git reflog Git branch Git commit Git show
同一专题 · 继续阅读Vue 3 + TypeScript 排错:从首个错误追到数据边界pnpm、Node 与 Vite 启动构建诊断:沿着失败阶段查证据浏览器请求、CORS 与环境变量排查:沿一条请求找到断点查看整个专题 ↗