Appearance
Git Tag 固定重构基准:如何随时拿到完整 Diff?

在大型功能重构中,我们经常遇到一个痛点:重构还没结束,但想随时看到"从重构开始到现在"的完整变动,而不是被中间的各种 WIP commit、reset 搅乱基准,导致 git diff 只能看到零碎的增量。
本文记录一个简单可靠的方案:用 git tag 固定重构起点。
问题背景
git diff 是相对比较,它的基准决定了你看到什么:
| 命令 | 对比基准 | 你看到的 |
|---|---|---|
git diff | 工作区 vs 暂存区 | 只有未暂存的增量 |
git diff HEAD | 工作区 vs 最后一次 commit | 未提交的变动,commit 后基准就变了 |
git diff <base> | 工作区 vs 指定 commit | 相对 base 的全部变动 |
在重构过程中,如果我们:
- 先做了一次 WIP commit 保存进度
- 又
git reset --soft HEAD~1撤销 commit 继续改
这时 git diff 会变得空(因为改动都在暂存区),而你只想要"整个重构的完整 diff",却怎么都拿不到。
根本原因:基准点没有固定下来。git diff 的基准随着 commit、reset 不断漂移。
解决方案:git tag 固定基准
第一步:重构开始前打 tag
确保工作区干净,然后在当前 commit 打一个标记:
bash
git status # 确认工作区干净
git tag refactor-base # 给当前 HEAD 打标记💡 如果工作区已有未提交改动,建议先处理干净再打 tag,否则 tag 只标记 commit,不含工作区改动。
第二步:放心重构
之后无论:
- commit 多少次
reset --soft/reset --mixed多少次- 改了多少文件
都不影响基准。
第三步:随时查看完整 diff
bash
git diff refactor-base > git.diff.all.txt含义:refactor-base 那个 commit vs 当前工作区。
输出:从 tag 那一刻到现在的全部变动,中间过程完全不影响。
第四步:包含未跟踪的新文件
git diff 默认不显示未跟踪文件(?? 状态)。如果需要包含:
bash
git add -A # 全部加入暂存区
git diff refactor-base > git.diff.all.txt或者只想标记意图但不想真正暂存:
bash
git add -N .
git diff refactor-base > git.diff.all.txt第五步:重构完成后清理
bash
git tag -d refactor-base意外情况:忘记打 tag 怎么办?
如果开始重构前忘记打 tag,但改动还在工作区/暂存区、从未 commit,也有补救办法。
方案 A:用 HEAD 作为基准(最简单)
只要仓库里有过至少一次 commit,HEAD 天然就是"改动前的状态":
bash
git diff HEAD > git.diff.all.txtgit diff HEAD 会输出所有未提交的变动(工作区 + 暂存区),等价于"从上次 commit 到现在"的完整 diff。
如果你担心之后 commit 后基准丢失,现在补一个 tag 即可:
bash
git tag refactor-base HEAD # 基准 = 你开始改之前的那次 commit
git diff refactor-base # 与 git diff HEAD 等价之后就算 commit 十次、reset 五次,git diff refactor-base 依然给你从重构起点到现在的完整变动。
方案 B:先提交当前改动,再软撤销
如果想让基准更明确,可以先把当前改动提交,再用 git reset --soft 还原:
bash
# 1. 把当前所有改动提交(给"重构起点"拍快照)
git add -A
git commit -m "base: 重构前快照"
# 2. 打 tag 固定基准
git tag refactor-base
# 3. 软撤销这个 commit,改动回到暂存区,继续重构
git reset --soft HEAD~1⚠️ 注意:此时 refactor-base 指向的是"你改动后的状态",适合"从现在开始追踪后续变动",而不是"包含之前改动在内的完整 diff"。要包含之前的改动,用方案 A。
情况三:从没 commit 过,仓库是全新的
如果这是全新仓库,一次 commit 都没有,那没有"之前"可比——所有文件都是新增的。
bash
git add -A
git diff --cached # 看暂存区全部内容(相对空仓库)建议先做一次 commit 固定状态,之后才有基准可谈:
bash
git add -A
git commit -m "chore: 初始快照"
git tag refactor-base小结
| 你的状态 | 基准怎么找 | 命令 |
|---|---|---|
| 有过 commit,改动未提交 | HEAD 就是基准 | git diff HEAD |
| 有过 commit,忘了哪个是起点 | git reflog 找 | git tag refactor-base <commit> |
| 从未 commit,仓库已有历史 | 当前 HEAD | git diff HEAD |
| 从未 commit,全新仓库 | 无基准 | 先 commit 一次 |
只要仓库里有过至少一次 commit,
HEAD天然就是你的基准,git diff HEAD就能拿到所有未提交变动——不需要 tag。tag 的价值在于"跨多次 commit 还能锁定同一个基准"。如果你压根没 commit,
HEAD就够用了。
完整流程示例
bash
# 1. 重构开始前,固定基准
git status
git tag refactor-base
# 2. 重构过程中随便改、随便 commit
git add -A && git commit -m "WIP: 重构 part 1"
# ... 继续改 ...
git reset --soft HEAD~1
# ... 继续改 ...
# 3. 任何时候想看完整 diff
git diff refactor-base > git.diff.all.txt
# 4. 重构完成
git add -A
git commit -m "feat: 完整重构说明"
# 5. 清理基准标记
git tag -d refactor-base方案对比
| 方式 | 基准 | 结果 | 适用场景 |
|---|---|---|---|
git diff | 工作区 vs 暂存区 | 只有增量,基准乱飘 | 日常小改动 |
git diff HEAD | 工作区 vs 最后一次 commit | commit 后基准就变 | 单次提交前检查 |
git diff refactor-base | 工作区 vs 固定 tag | 始终完整 ✅ | 大型重构 |
git stash -u | 临时保存 | 需 git diff stash@{0}^ stash@{0} | 短时离开 |
| WIP commit | 保存进度 | 基准变成 WIP commit | 需要存档点 |
常见误区
| 误区 | 真相 |
|---|---|
git diff 默认看全部变动 | 错,只看"工作区 vs 暂存区" |
git reset --soft HEAD~1 后 git diff 能看到全部 | 错,改动在暂存区,git diff 为空,要用 git diff HEAD |
WIP commit 后 git diff 能看到整个重构 | 错,基准变成 WIP commit,只看到增量 |
git stash 默认包含新文件 | 错,要加 -u 才包含未跟踪文件 |
核心结论
git diff是相对比较,必须在重构开始前固定一个 base。用
git tag refactor-base作为锚点,之后无论 commit 多少次、reset 多少次,git diff refactor-base永远给你"从重构起点到现在的完整变动"。
这个方案尤其适合:
- 大型功能重构,需要随时向 AI(如 DeepSeek)提交完整 diff 生成 commit message
- 分阶段提交,但想随时掌握整体改动
- 代码审查前,想一次性看到完整变更集
小技巧:给 tag 起个有意义的名字
bash
git tag refactor/license-agent-v2
git diff refactor/license-agent-v2 > git.diff.all.txt或者用分支代替 tag(效果相同):
bash
git branch refactor-base
git diff refactor-base > git.diff.all.txt一句话记住:tag 是固定基准的锚点,打一次,之后 diff 永远完整。
