进阶教程 · Git

遇到脏工作树时怎样保住已有改动

先识别暂存区、工作区和未跟踪文件,再决定提交、分离或停止操作。

Git 所说的脏工作树,通常包含已修改的跟踪文件、已经暂存的改动,或者尚未纳入版本控制的新文件。这个词只描述当前状态。没有看清文件归属就切分支、rebase、清理或还原,才会伤到已有工作。

先把三类变化分开看

从短状态开始。

git status --short --branch

非冲突条目里,短格式的左列表示暂存区,右列表示工作区。 M 代表文件只在工作区修改,M 代表已有内容进入暂存区。MM 出现时,暂存以后又产生了新改动。?? 是未跟踪文件。未合并条目使用另一套含义,两列记录参与合并的两个 head 相对共同祖先的状态。看到 U 时,仓库可能停在冲突处理中,应先确认当前是 merge、rebase 还是 cherry-pick。

再分别读取真实内容。

git diff
git diff --cached
git ls-files --others --exclude-standard

第一条显示工作区相对暂存区的差异,第二条显示暂存区相对 HEAD 的差异,第三条列出没有被忽略的未跟踪文件。三份结果覆盖跟踪文件和没有被忽略的未跟踪文件,忽略文件还要单独检查。

未跟踪且命中忽略规则的文件不会出现在普通状态里。已经被 Git 跟踪的文件不受 .gitignore 影响,修改后仍会显示。怀疑某个配置或产物被规则隐藏时,可以运行 git status --ignored --short --untracked-files=all,再用 git check-ignore -v <path> 找到生效的忽略规则。忽略文件未必可以删除,本地数据库、调试记录和环境配置常常正好放在这里。

遇到脏工作树时怎样保住已有改动的入口、目标与准备条件操作示意图
图 1 · 进入任务前先确认目标、范围和准备条件

先确认改动归属

如果文件是当前任务刚做的,检查内容和测试后,在当前功能分支提交。文件来自另一个窗口或用户本人,就停止会重写工作区的操作,并把路径和状态如实报告。无法确认归属时也应停住,猜错一次可能抹掉几小时工作。

git stash 可以临时保存修改,使用 -u 时还会带上未跟踪文件。它适合归属明确、稍后会恢复的短暂停放。来源不明的改动不应被随手塞进 stash,因为后续的人很难知道该恢复到哪个分支。需要长期保留的完整任务更适合在正确分支形成小提交。

准备交给别人处理时,把证据留全。

git diff --name-status
git diff --cached --name-status
git stash list
git log -1 --oneline

文件清单说明变化落在哪里,stash 列表能暴露被临时收起的内容,最后一条提交说明当前分支已经保存到哪一步。只交一句“目录是脏的”,接手者仍要重新寻找这些事实。

遇到脏工作树时怎样保住已有改动的三步关键操作与请求骨架示意图
图 2 · 把关键操作拆成三步,并给每一步留下可观察结果

对还原和清理保持克制

git restore <file> 会用指定来源恢复工作区文件,默认来源通常是索引。它可能丢掉尚未提交的本地修改。git clean 面向未跟踪文件,带删除参数后也可能造成无法从 Git 找回的损失。运行这类命令以前,先解析出明确文件路径,确认文件属于当前任务,并查看预览能力。

脏工作树还没有安全分离以前,不要 pull、merge、rebase 或切到别的任务分支。可以继续做只读检查,例如查看日志、分支和差异。等现有修改被提交到正确分支,或者由所有者明确处理以后,再改变提交历史。

子模块需要多看一眼。短状态中的大写 M 可以表示子模块记录的提交改变,小写 m 表示子模块内部有修改,问号表示内部出现未跟踪文件。只在父仓库提交指针,不能保存子模块里的未提交内容。进入对应子模块重复检查,确认两层状态都清楚以后再操作。

最后运行 git status --porcelain=v1 --branch --untracked-files=all。输出只剩分支行,说明跟踪文件、暂存区和没有被忽略的未跟踪文件都没有变化。忽略文件仍要按前面的办法单独检查。这个结果只说明当前本地状态已经核对,推送、合并和部署仍需分别确认。

遇到脏工作树时怎样保住已有改动的结果验收与交付证据清单示意图
图 3 · 用结果、检查与交付证据确认任务真的完成

官方参考