VPNT Technical Report Series
Vol. 2026, No. 1 · 用户层手册
← 目录 / 用户层手册

第 9 章 出错急救

按现象查表、对症处理,无需通读。三条基本原则:

9.1 遇到冲突(最常见)

合并或 pull 时提示 CONFLICT,部分文件被暂停。这不是错误,需要你决定保留哪个版本。

第一步:确认冲突文件。

git status

冲突文件列于 Unmerged paths 之下。打开这些文件,可见 Git 插入的冲突标记:

<<<<<<< HEAD
当前分支(本方)的内容
=======
待合并一方的内容
>>>>>>> feature/other

第二步:手动编辑取舍。 保留某一方或融合两方,删除三行标记(<<<<<<<=======>>>>>>>)及不需要的内容。不确定保留哪一方时,与改动另一方的成员确认,不要猜。

第三步:标记解决并完成合并。

git add <已解决的文件>
git commit --no-edit   # 直接采用 Git 生成的默认合并信息

若执行的是不带 --no-editgit 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 --forcereset --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。


← 第 8 章 配合 Code Review附录 A 练手考核对照 →