my-git

Git Troubleshooting Playbook

动手恢复前,先回答三个问题:

  1. 代码是否已经提交
  2. commit 是否已经 push
  3. 是否已经有人基于这些 commit 继续开发

不同答案,处理方式完全不同。

急救决策树

当前状态 先做什么 推荐处理 避免什么
代码还没提交 git status、必要时 git stash push -u 用 git restore、git stash、手工移动文件保留现场 直接 git reset --hard
已提交,还没 push git log --oneline -5、git reflog -10 可以用 reset、commit --amend、rebase -i 整理本地历史 整理前不留备份分支
已 push,但没人基于它开发 先确认远端分支和团队状态 优先 git revert,确实要整理历史时先沟通再 push --force-with-lease 直接 push --force
已 push,且有人基于它开发 先通知协作者,确认影响范围 用新 commit 修正,公共分支优先 revert 改写公共历史
secret 已经提交 立即废弃并轮换 secret 再清理 Git 历史和平台缓存 只做 revert 就结束
force push 覆盖远端 找到仍保留正确提交的人或本地 reflog 用正确 commit 恢复远端分支 继续在错误分支上叠加提交

如果不确定自己属于哪一类,先只执行只读命令:

git status
git log --oneline --decorate -10
git reflog -10
git branch -vv

不要急着执行会改写工作区或历史的命令。

修复前先保留现场

先保存现场:

git status
git log --oneline --decorate -10
git reflog -10

如果工作区还有未提交代码,先确认全部内容都属于自己并准备稍后恢复,才可临时保存:

git stash push -u -m "backup-before-recovery"

状态中有其他 owner 的编辑或来源不清的未跟踪文件时,停止在这个工作区操作,保留现场或使用独立 worktree。

场景 1:commit 提交到了错误分支

现象

你在名为 main 的分支上提交了本来应该在 feature 分支里的 commit。main 是示例,实际以检查到的错误分支名为准。

先检查

git status --porcelain
git log --oneline -5

安全处理:还没 push

以下流程只适用于最后一个 commit 尚未 push、没有协作者依赖,并且 git status --porcelain 没有输出的情况。有 staged、unstaged 或 untracked 内容时,停止在 reset 前并保留现场。

git branch feat/right-branch
git reset --hard HEAD~1
git switch feat/right-branch

这会把当前 commit 保留到新分支,并让原分支回到提交前。

如果已经 push

如果已经 push,优先新建 PR 修正,不要直接重写公共分支历史。

可以:

git switch -c feat/right-branch <commit-sha>
git push -u origin feat/right-branch

然后在原分支上用 revert 撤销:

git revert <commit-sha>

场景 2:reset 到了错误位置

先检查

git reflog

恢复方式

找到 reset 前的位置后,先确认工作区干净并保留当前位置,再恢复:

git status --porcelain
git branch backup-before-recover HEAD
git reset --hard HEAD@{1}

git status --porcelain 有输出时停止。HEAD@{1} 必须来自刚才检查的 reflog 条目,不能按位置猜测。

场景 3:commit 丢了

先检查

git reflog

恢复方式

git branch recovered-work <commit-sha>
git switch recovered-work

只要 commit 还在 reflog 里,通常可以救回来。

场景 4:错误代码已经 push

如果还没人基于它继续开发

可以和团队确认后修正历史:

git revert <commit-sha>

优先用 revert,因为它不会改写公共历史。

如果 commit 里包含 secret

不要只 revert。

先废弃并轮换密钥,再清理历史,见 Remove Secret from History。

场景 5:force push 覆盖了远端分支

先检查

git reflog
git log --oneline --decorate -10

<integration-branch> 是被覆盖的实际集成分支名,例如 main、master 或 develop。让仍保留旧提交的同事执行:

git log --oneline origin/<integration-branch> -10
git reflog -10

恢复远端分支

找到正确 commit、确认远端权限和分支规则,并得到相关 owner 的授权后:

git push origin <good-sha>:<integration-branch>

如果分支受保护,需要管理员或平台 owner 处理。

场景 6:合并了错误分支

如果已经产生 merge commit

git log --oneline --merges -10
git revert -m 1 <merge-commit-sha>

-m 1 表示保留第一父分支的视角,撤销被合入分支带来的变化。

避免

不要对已经 push 且被别人使用的主分支随意 reset --hard。

场景 7:提交了 secret

第一动作

立即废弃并轮换 secret。

Git 历史清理不能让已经泄露的 token 重新安全。

然后清理历史

推荐使用 git filter-repo 或平台提供的安全处理流程。

见 Remove Secret from History。

场景 8:rebase 处理乱了

还在 rebase 中,先中止

git rebase --abort

冲突解决后继续

git status
git add <files>
git rebase --continue

恢复到 rebase 前

git reflog
git status --porcelain
git branch backup-before-rebase-recover HEAD
git reset --hard <before-rebase-sha>

这里同样要求 git status --porcelain 没有输出,<before-rebase-sha> 来自已检查的 reflog,不能使用猜测的提交。

场景 9:撤销公共 commit

公共 commit 优先用:

git revert <commit-sha>

这样会新增一个反向 commit,历史清楚,协作者也安全。

场景 10:清理本地改动

先看会删除什么

git status --porcelain
git clean -nd

git clean -nd 只列出候选文件,不能证明它们可以删除。

删除未跟踪文件

确认路径是 owner 授权丢弃的未跟踪文件后,精确指定路径:

git clean -f -- <owned-untracked-path>

丢弃已跟踪文件改动

确认路径归属、预期基线和授权后:

git restore --worktree -- <owned-tracked-path>

高风险操作

先运行:

git status --porcelain

git status --porcelain 有输出时停止。只有工作区干净、<verified-ref> 已核实且本地历史允许重写时,才保留当前位置并执行 reset:

git branch backup-before-hard-reset HEAD
git reset --hard <verified-ref>

出现任何需要保留的 staged、unstaged 或 untracked 内容时,停止,不执行广泛的 restore、clean 或 reset。

快速判断表

状态 优先方案
未提交 git stash 或 git restore
已提交未 push reset、commit --amend、交互式 rebase
已 push 无人基于它开发 可以协商后重写历史
已 push 且别人基于它开发 优先 git revert
提交了 secret 先轮换密钥,再清理历史

延伸阅读