Git 操作常见工况问题与处理办法
写在前面
这份文档不是简单罗列 Git 命令,而是按照实际开发里最容易遇到的"工况"来组织内容:
- 先判断你当前处在什么状态。
- 再选择风险最小的处理办法。
- 最后给出可直接执行的正确指令。
本文整合了海内外常见 Git 实战资料,重点参考:
- Git 官方文档
git-scm.com - Pro Git 电子书
- GitHub 官方文档
- GitLab 官方文档
- 中文社区高频问题整理与工程实践文章
我的核心判断是:
- 公共历史优先保守处理 :已经推送给别人使用的提交,优先考虑
git revert,不要随便git reset --hard。 - 恢复问题先找引用,再做破坏性操作 :绝大多数"我代码没了"的问题,第一反应应该是
git reflog。 - 冲突不是异常,而是决策点:冲突意味着 Git 无法替你判断业务语义,必须由人完成最终裁决。
- 强推并非绝对禁止,但必须可控 :如果确实要改写远程历史,优先用
git push --force-with-lease,不要裸用--force。
一、先记住这 7 条救命原则
1. 出问题先看状态,不要先乱回退
第一时间执行:
bash
git status
git log --oneline --graph --decorate -n 15
git reflog -n 20
这 3 条命令能分别回答:
- 工作区和暂存区发生了什么
- 当前分支最近提交结构是什么
HEAD最近移动过哪些位置
2. 已推送公共分支,优先 revert
适用场景:
- 代码已经推到远端
- 别人可能已经基于这段历史继续开发
推荐:
bash
git revert <commit>
不推荐直接:
bash
git reset --hard <old-commit>
git push --force
3. 任何重写历史操作前,先做本地备份分支
在执行 rebase、reset --hard、filter-repo、强推之前,建议先做备份:
bash
git branch backup/my-feature
4. reflog 是后悔药,不是装饰品
恢复误删分支、误回退提交、误切换 detached HEAD,常用:
bash
git reflog
git checkout -b recover-branch <commit>
5. --force-with-lease 比 --force 更安全
bash
git push origin my-branch --force-with-lease
它会先检查远端是否出现你本地不知道的新提交,避免把别人的更新直接覆盖掉。
6. 冲突处理结束后一定要重新验证
至少执行:
bash
git diff --check
git status
如果项目支持编译和测试,还应继续执行构建、单测、静态检查。
7. 敏感信息泄露时,删文件不等于删历史
如果密钥、口令、证书已经进入 Git 历史:
- 仅删除文件并再次提交,不足以消除风险
- 必须先吊销/轮换密钥
- 再做历史重写
- 最后通知团队重新同步仓库
二、最常见工况总览
| 工况 | 推荐思路 | 核心命令 |
|---|---|---|
| 提交信息写错 | 修改最近一次提交 | git commit --amend |
| 少提交了文件 | 补 add 后 amend |
git add + git commit --amend --no-edit |
| 提交到了错误分支 | 分支迁移或 cherry-pick | git branch / git cherry-pick |
| 想撤销未 push 的提交 | reset,按保留程度选模式 |
--soft / --mixed / --hard |
| 想撤销已 push 的提交 | 新建反向提交 | git revert |
| push 被拒绝 | 先同步远端再处理 | git fetch + git merge / git rebase |
| merge/rebase 冲突 | 手动解决并继续/中止 | git add + git merge --continue / git rebase --continue |
| 误删分支/误 reset | 用 reflog 找回 | git reflog |
| 进入 detached HEAD | 新建分支或切回正常分支 | git switch -c |
| 工作做一半要切分支 | 临时存档 | git stash |
| SSH / HTTPS 认证失败 | 检查 token、权限、key、remote | git remote -v / ssh -T git@github.com |
| 提交了敏感信息 | 先轮换,再重写历史 | git filter-repo |
三、工况详解与处理办法
1. 还没初始化仓库,或提示 fatal: not a git repository
典型现象
text
fatal: not a git repository (or any of the parent directories): .git
原因判断
- 当前目录不是 Git 仓库
- 你进错了目录
.git被删除或损坏
正确处理办法
先确认当前目录:
bash
pwd
ls
如果本来就要新建仓库:
bash
git init
git add .
git commit -m "chore: initialize repository"
如果本来应该是现有仓库:
bash
git rev-parse --show-toplevel
若失败,说明大概率不在仓库里,切到正确目录即可。
我的建议
这个报错通常不是 Git 出故障,而是路径上下文错了。先校验目录,再做仓库操作。
2. 修改了文件,但切分支 / pull 时提示"本地改动会被覆盖"
典型现象
text
error: Your local changes to the following files would be overwritten
原因判断
- 工作区有未提交修改
- 这些修改与即将切换/拉取的内容冲突
处理办法 A:这些修改要保留,直接提交
bash
git add .
git commit -m "wip: save local changes before branch switch"
处理办法 B:这些修改暂时不想提交,用 stash
bash
git stash push -u -m "temp: before switch branch"
git switch <target-branch>
恢复时:
bash
git stash list
git stash pop
处理办法 C:这些修改不要了,直接丢弃
仅丢弃工作区改动:
bash
git restore .
同时丢弃暂存区与工作区:
bash
git restore --staged --worktree .
我的建议
如果你不能 100% 确认改动不重要,不要直接用全量丢弃命令。优先 stash,这是最稳妥的过渡操作。
3. git add . 加错了文件,或者提交里混入了不该提交的内容
场景 1:只是加错到暂存区,还没 commit
取消单个文件暂存:
bash
git restore --staged <file>
取消全部暂存:
bash
git restore --staged .
场景 2:已经 commit,但还没 push
保留修改,仅撤销最近一次提交:
bash
git reset --mixed HEAD~1
然后重新选择性暂存:
bash
git add <right-file>
git commit -m "feat: commit only intended files"
场景 3:已经 push 到公共分支
如果是一般错误文件:
bash
git rm --cached <file>
echo <file> >> .gitignore
git add .gitignore
git commit -m "chore: remove unintended tracked file"
git push
如果是密钥、密码、证书,跳转到后面的"敏感信息泄露"章节处理,不能只靠普通删除提交解决。
我的建议
工程里尽量少用"无脑全量暂存",更推荐:
bash
git add -p
它能从源头降低"把调试代码、临时文件、配置密钥一起提交上去"的概率。
4. 最近一次提交信息写错了,或者漏加了文件
适用前提
- 通常适用于最近一次提交
- 如果已推送到共享分支,要格外谨慎
修改最近一次提交信息
bash
git commit --amend -m "fix: correct commit message"
漏加文件但不改提交信息
bash
git add <forgotten-file>
git commit --amend --no-edit
如果已经 push 但只是你个人分支
bash
git push origin <branch> --force-with-lease
如果已经 push 到公共分支
不建议再改写历史。优先接受这次小缺陷,后续用新提交修正。
我的建议
amend 是"精修最后一个提交"的利器,但不适合拿来改公共历史。
5. 提交到了错误的分支
场景 1:刚提交到 main,其实应该去 feature,且还没 push
当前分支在错误提交上,直接把这个提交挂到新分支,再把当前分支退回去:
bash
git branch feature/my-work
git reset --hard HEAD~1
git switch feature/my-work
场景 2:目标分支已经存在
bash
git switch <right-branch>
git cherry-pick <wrong-commit>
git switch <wrong-branch>
git reset --hard HEAD~1
场景 3:已经 push 到远程
如果是公共分支,优先新建"撤销提交"而不是直接改历史:
bash
git switch main
git revert <wrong-commit>
git push origin main
git switch <right-branch>
git cherry-pick <wrong-commit>
git push origin <right-branch>
我的建议
"改错分支"是高频工况。团队协作里最稳的做法是:
- 公共分支上用
revert - 正确分支上用
cherry-pick
这样最符合审计与协作预期。
6. 想撤销最近一次提交,但希望保留代码
三种模式区别
--soft:撤销提交,保留到暂存区
bash
git reset --soft HEAD~1
适合:
- 只是想重写提交信息
- 想把多个提交重新组织为一个提交
--mixed:撤销提交,保留到工作区,取消暂存
bash
git reset HEAD~1
适合:
- 想重新选择哪些文件进入下一次提交
--hard:撤销提交,并丢弃改动
bash
git reset --hard HEAD~1
适合:
- 你明确确认最近提交和改动都不要了
我的建议
默认优先考虑:
--soft--mixed- 最后才是
--hard
原因很简单:代码能不丢,就先别丢。
7. 想撤销已经 push 的提交
正确处理思路
已推送提交,尤其是公共分支,优先使用:
bash
git revert <commit>
撤销最近一次提交:
bash
git revert HEAD
撤销某个历史提交:
bash
git revert <commit-hash>
撤销 merge 提交:
bash
git revert -m 1 <merge-commit>
为什么不优先用 reset
因为 reset 会移动分支指针,改写历史;revert 会创建一个新的反向提交,更适合共享历史。
我的建议
如果你的代码已经进入:
mainrelease- 团队共享功能分支
那就把 revert 当默认答案。
8. git push 被拒绝,提示 non-fast-forward
典型现象
text
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs
原因判断
- 远程分支上出现了你本地没有的新提交
- 你的推送会覆盖远端已有历史,因此被拒绝
稳妥处理办法 A:拉取并合并
bash
git fetch origin
git merge origin/<branch>
git push origin <branch>
常用处理办法 B:拉取并变基
bash
git fetch origin
git rebase origin/<branch>
git push origin <branch>
如果 rebase 过的是你自己的功能分支,并且之前已经 push 过:
bash
git push origin <branch> --force-with-lease
我的建议
- 团队公共分支更偏向
merge或平台要求的流程 - 个人功能分支可用
rebase - 无论哪种方式,先
fetch再判断,不要闭眼强推
9. 合并时出现冲突
典型现象
text
CONFLICT (content): Merge conflict in app.js
冲突文件通常会出现标记:
text
<<<<<<< HEAD
你的内容
=======
对方内容
>>>>>>> branch-name
正确处理步骤
先确认冲突文件:
bash
git status
然后手工编辑冲突文件,删除冲突标记,保留最终正确内容。
完成后:
bash
git add <resolved-file>
git commit
如果当前是新的 git merge --continue 流程,也可用:
bash
git merge --continue
处理原则
- 不要只看语法,必须看业务逻辑
- 不要偷懒地"全选 ours / theirs"后直接提交
- 冲突解决完成后必须重新验证编译和测试
我的建议
冲突解决本质上是"业务裁决"。Git 只能标出冲突位置,不能替你理解需求。
10. rebase 过程中出现冲突,不知道继续还是退出
常见命令
查看状态:
bash
git status
解决冲突后继续:
bash
git add .
git rebase --continue
当前这步提交跳过:
bash
git rebase --skip
完全放弃这次 rebase:
bash
git rebase --abort
推荐决策
- 你明确知道如何合并冲突内容:
--continue - 当前冲突提交本身没有价值:
--skip - 整个 rebase 方向不对,或者越修越乱:
--abort
rebase 前的安全动作
bash
git branch my-branch-backup
git fetch origin
git rebase origin/main
我的建议
rebase 最大的问题不是命令难,而是会改写历史。在共享分支上,除非团队约定明确,否则谨慎使用。
11. 进入了 detached HEAD
典型现象
text
HEAD detached at abc1234
发生原因
- 你切到了某个具体提交,而不是分支
- 例如:
bash
git checkout <commit>
如果只是看看历史
看完直接切回分支:
bash
git switch main
如果在 detached HEAD 上已经做了提交
必须先创建分支保存这些提交:
bash
git switch -c recover/detached-work
或者:
bash
git checkout -b recover/detached-work
如果已经切走,担心刚才提交丢了
bash
git reflog
git switch -c recover/detached-work <commit>
我的建议
detached HEAD 不是灾难,它只是说明你当前不在分支引用上。真正危险的是:做了提交却没及时建分支保存引用。
12. 误删了分支,或者 reset --hard 退过头了
第一反应
bash
git reflog
Git 会记录 HEAD 和引用的移动历史。找到你想恢复的提交后:
bash
git switch -c recover/branch <commit>
或者把当前分支直接恢复到那个位置:
bash
git reset --hard <commit>
如果是误删本地分支,但远程还在
bash
git fetch origin
git switch -c <branch> origin/<branch>
我的建议
很多人以为 reset --hard 是"不可恢复"的,其实通常短时间内仍可通过 reflog 找回 。所以误操作后不要继续做大量清理动作,先查 reflog。
13. 临时保存现场:stash 怎么用才不容易坑自己
最常用写法
保存当前修改,包括未跟踪文件:
bash
git stash push -u -m "temp: before urgent hotfix"
查看列表:
bash
git stash list
恢复并从列表删除:
bash
git stash pop
恢复但保留条目:
bash
git stash apply stash@{0}
如果恢复时有冲突
流程与普通冲突一样:
bash
git status
git add .
然后继续你的正常提交流程。
我的建议
stash 适合短期中断,不适合长期存档。超过一天还没恢复的 stash,建议尽快转成正式分支或提交,否则后续容易忘。
14. 认证失败:HTTPS、Token、SSH Key 出问题怎么办
常见现象
Authentication failedPermission denied (publickey)Permission to user/repo denied
先检查远端地址
bash
git remote -v
如果是 HTTPS 方式
当前主流平台通常不再建议直接用账号密码推送,需改用 Personal Access Token。
可重新设置 remote:
bash
git remote set-url origin https://github.com/<user>/<repo>.git
然后在认证时使用 token。
如果是 SSH 方式
先测试连接:
bash
ssh -T git@github.com
查看本机 key:
bash
ssh-add -l
没有 key 时生成:
bash
ssh-keygen -t ed25519 -C "your_email@example.com"
还要检查的点
- 当前 key 是否绑定到了正确账号
- 当前账号是否有仓库权限
- 远端地址是否写错组织或仓库名
- 是否把 deploy key 错当成个人账号 key 使用
我的建议
认证问题 80% 不是 Git 命令问题,而是凭据、账号、remote URL、权限四者不一致。顺着这四项排查最快。
15. 合并两个仓库时报 refusing to merge unrelated histories
典型现象
text
fatal: refusing to merge unrelated histories
原因
两个历史没有共同祖先,例如:
- 本地先
git init - 远程仓库也独立初始化过 README
- 双方不是从同一祖先演化而来
处理办法
确认你就是要强行合并两个独立历史时:
bash
git pull origin main --allow-unrelated-histories
或:
bash
git merge <other-branch> --allow-unrelated-histories
我的建议
这个参数不该被滥用。只有在你明确知道"就是两套独立历史要并到一起"时才用。
16. 误提交了敏感信息
常见错误认知
错误做法:
bash
rm .env
git add .
git commit -m "remove secret"
git push
这只能让最新版本看起来没了,但密钥仍在 Git 历史中。
正确处理步骤
第一步:立刻轮换 / 吊销密钥
例如:
- 重置云服务 AK/SK
- 废弃数据库密码
- 替换 SSH 私钥
- 重发 API Token
第二步:重写历史,移除敏感内容
推荐工具:
git filter-repo(需要提前安装,且它不是 Git 默认内置命令)
例如删除文件:
bash
git filter-repo --path .env --invert-paths
例如替换历史中的敏感字串,通常需要先准备替换规则文件。
第三步:强推重写后的历史
bash
git push origin --force --all
git push origin --force --tags
如果团队流程要求更稳妥,也可以在确认后改成:
bash
git push origin --force-with-lease --all
git push origin --force-with-lease --tags
第四步:通知所有协作者重新同步
协作者通常需要:
bash
git fetch --all
git reset --hard origin/<branch>
或者重新克隆仓库。
我的建议
敏感信息问题的优先级永远是:
- 先止血
- 再清理历史
- 最后修复流程
别把注意力只放在 Git 命令上,真正的风险控制在"密钥轮换"。
17. 想让分支历史更整洁:merge、rebase、squash 怎么选
merge
优点:
- 不改写已有历史
- 更安全,适合共享分支
命令:
bash
git merge origin/main
rebase
优点:
- 历史线性,review 更干净
风险:
- 改写历史
- 需要强推
- 共享分支风险高
命令:
bash
git fetch origin
git rebase origin/main
git push origin <branch> --force-with-lease
squash
适合:
- 一个功能分支上有很多零碎提交
- 合入主干前想压成 1 个或少量语义清晰的提交
命令:
bash
git rebase -i HEAD~5
将后续提交改成 squash 或 fixup。
我的建议
- 团队主干分支:优先安全与可审计
- 个人功能分支:可以适度
rebase+squash - 共享中的长期协作分支:谨慎改写历史
18. 想撤销文件改动,但分不清 restore、reset、revert
一句话区分
restore:还原文件内容reset:移动分支指针 / 调整暂存区状态revert:新增一个"反向提交"
典型对照
只撤销工作区文件改动
bash
git restore <file>
把文件从暂存区拿出来,但保留工作区修改
bash
git restore --staged <file>
撤销最近一次提交,但保留改动
bash
git reset HEAD~1
撤销一个已经推送的提交
bash
git revert <commit>
我的建议
很多 Git 事故,本质是把"修改文件内容"和"修改提交历史"混在了一起。只要先分清这两类问题,命令选择会清晰很多。
四、错误处理决策表
| 你的真实诉求 | 是否已 push | 推荐命令 | 风险说明 |
|---|---|---|---|
| 改最后一次提交信息 | 否 | git commit --amend |
低 |
| 改最后一次提交信息 | 是,个人分支 | git commit --amend + git push --force-with-lease |
中 |
| 撤销最近提交但保留代码 | 否 | git reset --soft HEAD~1 / git reset HEAD~1 |
中低 |
| 撤销已共享提交 | 是 | git revert <commit> |
低 |
| 找回误删分支 | 无所谓 | git reflog + git switch -c |
低 |
| 清空最近改动 | 否 | git restore . / git reset --hard |
高 |
| 个人分支重写历史 | 是,个人分支 | git rebase + git push --force-with-lease |
中 |
| 公共分支重写历史 | 是,公共分支 | 尽量避免 | 高 |
| 清理泄露密钥 | 是 | git filter-repo + 轮换密钥 |
很高 |
五、推荐排障顺序
当你不确定怎么处理时,按下面顺序来:
1. 看状态
bash
git status
git branch -vv
git log --oneline --graph --decorate -n 20
2. 看最近操作轨迹
bash
git reflog -n 20
3. 先备份当前指针
bash
git branch backup/troubleshoot
4. 再执行破坏性命令
例如:
git reset --hardgit rebasegit push --force-with-lease
5. 最后验证结果
bash
git status
git log --oneline --graph --decorate -n 20
必要时补充:
bash
git diff
git diff --staged
六、我认为最值得团队统一下来的规范
1. 公共分支禁止裸 --force
统一要求:
bash
git push --force-with-lease
2. 主干分支撤销用 revert
不要在 main / release 上习惯性 reset --hard + 强推。
3. Pull 前先看本地是否脏
bash
git status
4. 高频切换任务时优先 stash push -u -m
比"先随手 commit 一个垃圾 WIP"更整洁。
5. 敏感文件默认进 .gitignore
例如:
.env*.pem*.keysecrets/*.json
6. 大改历史前先建备份分支
bash
git branch backup/<branch-name>
7. 冲突解决后必须过一轮验证
至少做差异检查、编译、测试、代码审阅。
七、常用指令速查
bash
# 查看当前状态
git status
# 查看提交图
git log --oneline --graph --decorate -n 20
# 查看操作历史
git reflog -n 20
# 修改最近一次提交
git commit --amend
# 撤销最近一次提交但保留修改
git reset HEAD~1
# 撤销已推送提交
git revert <commit>
# 暂存当前工作
git stash push -u -m "temp work"
# 恢复暂存
git stash pop
# 与远端同步后 rebase
git fetch origin
git rebase origin/main
# 更安全的强推
git push origin <branch> --force-with-lease
# 处理 rebase 冲突
git rebase --continue
git rebase --abort
# 找回误操作
git reflog
git switch -c recover/<name> <commit>
八、参考资料
以下资料是本文整合时重点参考的海内外来源:
官方文档
- Git 官方总文档:https://git-scm.com/docs/git
- Git Reset:https://git-scm.com/docs/git-reset
- Git Rebase:https://git-scm.com/docs/git-rebase
- Git Restore:https://git-scm.com/docs/git-restore
- Git Stash:https://git-scm.com/docs/git-stash
- Git Reflog:https://git-scm.com/docs/git-reflog
- Git Cheat Sheet:https://git-scm.com/cheat-sheet
- Pro Git, Data Recovery:https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery
GitHub 官方
- Non-fast-forward 错误处理:https://docs.github.com/en/enterprise-cloud@latest/get-started/using-git/dealing-with-non-fast-forward-errors
- Merge Conflicts:https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts
- Resolve Merge Conflicts on GitHub:https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-on-github
- Authentication 文档:https://docs.github.com/en/authentication
- Permission denied (publickey):https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey
GitLab 官方
- Rebase and resolve merge conflicts:https://docs.gitlab.com/topics/git/git_rebase/
- Merge conflicts:https://docs.gitlab.com/user/project/merge_requests/conflicts/
中文社区资料
- Git 完整指南(中文实践整理):https://github.com/JiaXtian/computer-basic-skills/blob/main/docs/git.md
- Git 操作 10 个常见问题排查:https://blog.csdn.net/zhangxianhau/article/details/157023118
- 全网最详细的 Git 指南:https://blog.csdn.net/wll0417yj/article/details/147860127
九、结语
Git 真正难的地方,从来不是记住多少命令,而是理解三件事:
- 你现在改的是文件内容
- 还是在改暂存区状态
- 还是在改提交历史
一旦把这三层拆开理解,90% 的 Git 问题都能自己判断方向。
如果你想把本文继续扩展成博客文章,我建议下一步可以再补两部分:
- Git 工作区 / 暂存区 / 提交历史的图解版
reset/restore/revert的可视化对比图