第 9 章 出错急救
按现象查表、对症处理,无需通读。三条基本原则:
- 先确认再操作:先跑
git status与git log --oneline看清当前状态。 - 不执行
git push --force:会改写远程历史、覆盖他人提交。认为必须强推时先停下,与维护者确认。 - 不确定先备份:有风险的操作前用
git branch backup-<临时名>存一个备份点。
9.1 遇到冲突(最常见)
合并或 pull 时提示 CONFLICT,部分文件被暂停。这不是错误,需要你决定保留哪个版本。
第一步:确认冲突文件。
git status
冲突文件列于 Unmerged paths 之下。打开这些文件,可见 Git 插入的冲突标记:
<<<<<<< HEAD
当前分支(本方)的内容
=======
待合并一方的内容
>>>>>>> feature/other
第二步:手动编辑取舍。 保留某一方或融合两方,删除三行标记(<<<<<<<、=======、>>>>>>>)及不需要的内容。不确定保留哪一方时,与改动另一方的成员确认,不要猜。
第三步:标记解决并完成合并。
git add <已解决的文件>
git commit --no-edit # 直接采用 Git 生成的默认合并信息
若执行的是不带
--no-edit的git commit并弹出了编辑器(通常是 vim),无需惊慌:按Esc,输入:wq再回车,即可保存并退出。
若中途需要放弃、回到合并前的状态:
git merge --abort # 放弃本次 merge
# 若冲突来自 pull
git rebase --abort # 视情况而定,可先用 git status 查看提示
9.2 push 被拒绝(rejected)
! [rejected] main -> main (fetch first)
原因:远程有本地没有的新提交(他人先推了)。先 pull 再 push,不要 --force:
git pull origin <当前分支> # 可能触发冲突(见 9.1)
# 解决冲突后
git push
本手册已在 0.2 全局设置
pull.rebase=true,所以这里的git pull实际走 rebase。若 pull 过程出现冲突,按 9.1 编辑冲突文件后,用git rebase --continue继续(不是git commit --no-edit),需要放弃时git rebase --abort。
9.3 提交信息写错了
尚未 push——修改上一条 commit 信息:
git commit --amend -m "正确的提交信息"
--amend 只在未 push 时使用。已 push 则不改历史,在 PR 描述或后续提交中补充说明即可。
9.4 提交到了错误的分支
本应在 feature/x 却提交到了 main(尚未 push)。
# 1. 查看最近的提交
git log --oneline -3
# 2. 将当前分支(main)指针退回上一个提交,改动保留在暂存区
git reset --soft HEAD~1
# 3. 切换到正确分支后重新提交
git checkout -b feature/x
git commit -m "feat(x): 提交信息"
reset --soft HEAD~1 撤销最近一次提交但把改动保留在暂存区,是最安全的选择。不要用 git reset --hard,它会连同改动一并丢弃。
9.5 想撤销本地修改
情况一:某文件改错,恢复到最近一次提交(改动会丢失,确认后再执行)。
git restore <文件名> # 新版 Git
# 或
git checkout -- <文件名> # 旧版 Git
情况二:git add 有误,把文件移出暂存区(改动保留)。
git restore --staged <文件名> # 新版 Git
# 或
git reset HEAD <文件名> # 旧版 Git
情况三:改到一半要先切分支。 用 stash 暂存:
git stash # 暂存全部改动,工作区恢复整洁
git stash pop # 回来后取出改动
9.6 误提交了大文件或敏感文件
尚未 push:
git reset --soft HEAD~1 # 撤销本次提交,改动退回
git restore --staged <误加的文件> # 将其移出暂存区
随后把该文件加入 .gitignore,再重新提交需要的内容。
已 push,尤其涉及密钥、密码、内部地址:立即停止并通知维护者。 不要自行 --force 清理历史(破坏性操作,仅管理员在授权下执行,见《管理员层手册》第 3 章)。已推送的密钥 / 密码视同泄露,须走作废与更换流程,仅删文件不够。
9.7 想撤销一个已经 push 的提交
已推送的提交不能用 reset / --amend 改历史(会覆盖远程、影响他人)。正确做法是用 revert 新建一个反向提交来抵消它,历史完整保留:
git log --oneline # 找到要撤销的提交哈希
git revert <commit哈希> # 生成一个撤销该提交的新提交
git push
revert 只新增提交、不改写历史,是已 push 场景下最安全的撤销方式,也对应 commit 规范里的 revert 类型。若撤销的是敏感信息(密钥、密码),revert 不够——历史里仍能查到,须按 9.6 走作废更换流程。
9.8 本地和远程乱了,想重来
最稳妥的办法:保留需要的改动,重新克隆一份干净的仓库。
# 1. 先将需要保留的改动备份至仓库外的临时文件夹
# 2. 更换位置重新 clone
cd ..
git clone <仓库地址> lab-fresh
# 3. 将备份的改动手动放回新克隆的仓库,再按正常流程操作
重新 clone 往往比逐条排查 reset / rebase 更安全快捷。
9.9 急救速查表
| 现象 | 先做 | 处理 |
|---|---|---|
| 合并/pull 提示 CONFLICT | `git status` | 编辑冲突文件删标记 → `add` → `commit --no-edit`(见 9.1) |
| push 被 rejected | `git status` | `git pull` 后解决冲突再 push,不得 force(见 9.2) |
| 上一条 commit 信息写错(未 push) | `git log --oneline` | `git commit --amend`(见 9.3) |
| 提交到错误分支(未 push) | `git log --oneline` | `git reset --soft HEAD~1` 后切分支重提(见 9.4) |
| 改乱某个文件想还原 | `git status` | `git restore <文件>`(见 9.5) |
| add 有误想撤销 | `git status` | `git restore --staged <文件>`(见 9.5) |
| 改到一半想先切分支 | — | `git stash` / `git stash pop`(见 9.5) |
| 误提交大文件(未 push) | `git status` | `reset --soft` + `.gitignore`(见 9.6) |
| 误提交敏感信息(已 push) | 停止操作 | **立即通知维护者**,不得自行 force(见 9.6) |
| 想撤销一个已 push 的提交 | `git log --oneline` | `git revert <commit>`,不改历史(见 9.7) |
| 状态彻底混乱 | 备份改动 | 重新 clone 一份干净的仓库(见 9.8) |
只要没执行
push --force或reset --hard,Git 绝大多数操作都可恢复。遇事先status、先备份、先询问。
9.10 练手
必做演练:用 revert 撤销一个"已推送"的提交。演练放在练手仓库的 .quest-scratch/rescue 目录(已被 .gitignore 忽略,属一次性练习,不会污染仓库):
mkdir -p .quest-scratch/rescue
cd .quest-scratch/rescue
git init
git branch -M main
"line1" | Out-File -Encoding utf8 demo.txt
git add demo.txt; git commit -m "docs: 初始化 demo"
"oops" | Add-Content demo.txt
git add demo.txt; git commit -m "docs: 加了一行不该加的内容"
git log --oneline # 找到"不该加"那次提交
git revert HEAD --no-edit # 生成一个反向提交,抵消上一次改动
git log --oneline # 顶部出现 Revert 提交
Get-Content demo.txt # oops 那行已被撤销
cd ../..
revert 只新增提交、不改写历史,对应第 4 章的 revert 类型,是"已 push"场景下最安全的撤销方式。表中其余急救(冲突、rejected、amend、reset --soft、restore、stash、重克隆)可在同一个 rescue 仓库里对照 9.1–9.8 逐条试练——先造出对应场景,再按处理列操作,全程离线、不影响正式仓库。
完成后自检:
python scripts/check-quest.py 9 # 检查 rescue 仓库含 revert 提交且无遗留冲突标记
python scripts/check-quest.py all # 通关总检:所有关卡是否全部通过
all 全部通过、且你已在自己的 Gitea Fork 上真实提过 1 个 PR、1 个 Issue 并自己合入 main,即完成全部练手。各关的考核方式与覆盖范围见附录 A。