Git 翻车现场:那些年我 git push --force 毁掉的东西

上周五下午 6 点 50,我正准备收拾东西下班。

leader 突然私信我:"你那个需求今天能上线吗?"

我看了眼代码,✅ 完美,rebase 完还没 push 呢。

二话不说,git push --force --------- 啪!覆盖了远程。

三分钟后,同事小王在群里发了一条:

"谁把我今天写的代码覆盖了???"

我的手指悬在键盘上,僵住了。

那是我周五下班前的最后一分钟,也是我最想原地消失的一分钟。

这就是 Git 的魔力时刻。

它让协作变得丝滑,也让翻车变得猝不及防。

Git 很强大,但越强大的工具,翻车现场越惨烈。

今天我就来给大家盘一盘,那些年我们用 Git 挖的坑。

翻车现场一:git push --force 覆盖远程历史

场景还原

周五下班前,你 rebase 完本地分支,准备 push。

bash

复制代码
git push --force

然后你发现,同事张三的提交没了。

原来他今天下午 push 了三个 commit,而你 rebase 的时候把它们都"消灭"了。

办公室气氛瞬间凝固。

翻车原理

plaintext

复制代码
远程仓库原本的样子:
A - B - C - D (张三的提交)
      ↑
      你的分支

你 rebase 后:
A - B - E - F (你的新提交)
      ↑
      远程

force push 后:
A - B - E - F  ← 张三的 C - D 没了 😭

rebase 会"重写历史",你的 commit 变成了全新的 commit。

force push 直接告诉服务器:别管原来的历史,用我的!

然后张三今天的工作就消失在了时间的长河里。

救场方案

如果你的仓库有权限,还有救:

bash

复制代码
# 1. 找到张三消失的提交
git reflog

# 输出类似:
abc123 HEAD@{5}: rebase (finish): returning to refs/heads/main
def456 HEAD@{6}: commit: 张三的提交

# 2. 创建一个临时分支保存他的提交
git branch zhangsan-backup def456

# 3. 合并回主分支
git checkout main
git merge zhangsan-backup
git push

正确做法

永远用 --force-with-lease 代替 --force

bash

复制代码
git push --force-with-lease

这个参数会检查远程是否有新的提交。如果有,它会拒绝 push 并告诉你:

"远程有新提交,请先拉取!"

翻车现场二:git reset --hard 误删本地提交

场景还原

"我想撤销上一次提交",我想。

bash

复制代码
git reset --hard HEAD~1

等等,我刚才说的是撤销最后一次提交对吧?

对吧???

屏幕上显示:HEAD is now at abc123 昨天的提交

我刚才一周写的代码呢???

翻车原理

Git 的三个"区域":

plaintext

复制代码
工作区    →    暂存区    →    仓库
(你的文件)   (git add)    (git commit)

--soft  : 只动仓库,暂存区和工作区不变
--mixed : 动仓库+暂存区,工作区不变
--hard  : 全动!仓库没了,暂存区没了,工作区也没了! 💥

--hard 会把你的工作目录直接清空,包括未提交的代码。

救场方案

只要你没 gc,这个世界还有救:

bash

复制代码
git reflog
# 找到 hard reset 之前的 commit

git reset --hard HEAD@{1}
# 回来了!🎉

💡 小技巧:reflog 默认保留 90 天的操作记录

正确做法

永远不要在未备份的情况下 hard reset

方案 A:用 git stash 先暂存

bash

复制代码
git stash
git reset --hard HEAD~1
# 后悔了?
git stash pop

方案 B:用 --soft--mixed

bash

复制代码
# 如果只是想修改 commit 消息
git commit --amend

# 如果想让代码保留在暂存区
git reset --soft HEAD~1

翻车现场三:merge 时的冲突灾难

场景还原

终于等到 feature 分支合并到 main 了!

打开冲突文件一看:

<<<<<<<

复制代码
    user.setName("张三");
=======
    user.setName("李四");
>>>>>>> feature/add-validation

我心里想:"张三是对的,选这个!"

然后点了"接受当前分支"。

一周后,用户发现所有名字都显示成了别人的。

翻车分析

冲突的本质是:两个分支都修改了同一段代码

Git 不知道谁的改动是正确的,只能让你来选择。

但选择需要理解上下文,而不是凭直觉。

救场方案

bash

复制代码
# 还没 commit,先停!
git merge --abort

然后重新审视冲突:

  1. 理解两个分支分别改了什么
  2. 咨询改动涉及的同事
  3. 确定正确的合并结果

正确做法

解决冲突的 checklist:

plaintext

复制代码
□ 理解每个冲突的来源
□ 知道为什么两边都会改这段代码
□ 确定哪个改动是"对的",或者都需要
□ 手动编辑合并,而不是盲目选择
□ 测试通过后再 commit

翻车现场四:在错误的分支上开发了一整天

场景还原

周一早上,需求评审完,开始干活。

我自信地敲下:

bash

复制代码
git checkout main
git pull
# 开始写代码...

下午 5 点,代码写完了,准备提交:

bash

复制代码
git status
On branch main

等等,main???

需求说要开新分支 feature/user-profile 的吧???

我今天写的 20 个文件,全在 main 上。

救场方案

方案一:stash 大法(推荐)

bash

复制代码
# 1. 暂存所有改动
git stash

# 2. 创建新分支并切换
git checkout -b feature/user-profile

# 3. 恢复改动
git stash pop

方案二:cherry-pick(如果你已经有 commit)

bash

复制代码
# 1. 提交到 main
git add .
git commit -m "今天的改动"

# 2. 创建新分支
git branch feature/user-profile

# 3. 在 main 上 reset
git checkout main
git reset --hard HEAD~1

# 4. 在新分支上 cherry-pick
git checkout feature/user-profile
git cherry-pick main

正确做法

每次开发前,必做三件事:

plaintext

复制代码
1. git branch        # 看下当前在哪个分支
2. git branch -a     # 看下远程有哪些分支
3. 确认无误后再开始写代码

翻车现场五:把密码/密钥提交到了仓库

场景还原

凌晨 2 点,deadline 前最后一小时。

".env 文件?先提交上去,明天再改!"

bash

复制代码
git add .env
git commit -m "add config"
git push

第二天醒来,AWS 控制台收到警报:有人在用你的密钥挖矿。

救场方案

方案一:使用 BFG Repo-Cleaner(推荐)

bash

复制代码
# 1. 安装 BFG
# brew install bfg  (macOS)

# 2. 删除文件的所有历史记录
bfg --delete-files .env

# 3. 强制更新所有分支
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --force

方案二:使用 git filter-branch(慢但自带)

bash

复制代码
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch .env' \
  --prune-empty --tag-name-filter cat -- --all

重要:改完密钥!

密钥已经泄露,光删除历史没用,必须重新生成。

正确做法

三道防线:

plaintext

复制代码
防线 1:.gitignore
     echo ".env" >> .gitignore

防线 2:pre-commit 钩子
     npx husky add .husky/pre-commit "git diff --cached --name-only | grep -q '\.env$' && echo 'No .env files!' && exit 1"

防线 3:git-secrets(AWS 官方工具)
     git secrets --install
     git secrets --register-aws

翻车现场六:rebase 半途而废,代码处于混乱状态

场景还原

bash

复制代码
git rebase -i HEAD~5

进入了交互式界面,改了一些 commit 顺序。

然后发现:"好像搞乱了?"

按 Ctrl+C 退出?

不管用了。整个 rebase 处于中间状态,不知道怎么办。

救场方案

万能命令:

bash

复制代码
git rebase --abort

是的,就这么简单。

它会放弃当前 rebase 的一切操作,恢复到 rebase 开始之前的状态。

正确做法

rebase 前先备份:

bash

复制代码
# 1. 创建备份分支
git branch backup-before-rebase

# 2. 放心 rebase
git rebase -i HEAD~5

# 3. 如果搞砸了
git rebase --abort
git checkout backup-before-rebase

翻车现场七:git cherry-pick 导致重复提交

场景还原

有个 bug 在 release-1.0 分支修复了,需要合到 main。

bash

复制代码
git checkout release-1.0
git log --oneline
# abc123 fix: 修复用户无法登录的 bug

git checkout main
git cherry-pick abc123

两周后,release-1.0 合到 main:

bash

复制代码
git checkout main
git merge release-1.0

同一个 bug 修复,被合进来两次。 代码开始出现奇怪的冲突。

翻车原理

plaintext

复制代码
cherry-pick 的本质是:创建一个"内容相同但 SHA 不同"的新 commit

release-1.0: abc123 "fix: 修复用户无法登录"
main 上的:   def456 "fix: 修复用户无法登录"  ← 不同的 SHA

merge 的时候,Git 不知道这两个是同一个改动

救场方案

方案一:revert 掉重复的那个

bash

复制代码
git revert def456

方案二:用 merge 而不是 rebase 来同步 release 分支

bash

复制代码
# 不要 cherry-pick release 分支的 commit
# 直接 merge release 分支
git merge release-1.0

正确做法

cherry-pick vs merge 的选择策略:

plaintext

复制代码
cherry-pick: 需要挑几个特定 commit
merge:       需要把整个分支的改动都合过来

如果要把 release 的改动合到 main:
→ 直接 merge release,不要 cherry-pick

终极救场指南:Git 急救箱

翻车场景 急救命令 预防措施
force push 覆盖同事代码 git reflog + 恢复分支 --force-with-lease
hard reset 清空代码 git reflog + git reset --hard git stash 或用 --soft
merge 选错冲突方向 git merge --abort 理解上下文,不要盲选
在错误分支开发 git stash + 切换分支 开发前先 git branch
密码/密钥泄露 BFG Repo-Cleaner + 改密码 .gitignore + pre-commit
rebase 搞乱了 git rebase --abort rebase 前备份分支
cherry-pick 导致重复 git revert 重复的 commit 整分支用 merge
误删分支 git reflog 找回来 不要用 -D 删除分支

万能保命原则

永远先 reflog 看一眼再操作!

bash

复制代码
git reflog

它会记录你所有的操作历史,包括"你以为丢了"的东西。

在 Git 的世界里,很少有真正的删除。

翻车不可怕,不知道怎么救才可怕

写这篇文章的时候,我回忆起每一个翻车现场。

有在客户面前 force push 覆盖 leader 代码的尴尬。

有凌晨 3 点发现 .env 被 push 出去的心跳加速。

有 merge 完才发现线上出了 bug 的绝望。

但后来我发现,翻车本身不可怕。

可怕的是:

  • 不知道 force push 会覆盖别人代码
  • 不知道 hard reset 会清空工作目录
  • 不知道 reflog 能救命

技术路上的坑,踩过才知道疼。

但知道怎么爬起来,比什么都重要。

相关推荐
GitCode官方2 小时前
投稿|Git + Docker 零基础入门攻略
git·docker·容器
_可乐无糖2 小时前
Windows本地安装git
git
2301_780029042 小时前
.gitignore不可以忽略文件问题
git·gitee·开源
饕餮争锋3 小时前
PR中的P为什么是pull而非push?
git
水云桐程序员3 小时前
Git是什么?怎样使用?
git·学习方法
Allen_LVyingbo3 小时前
面向医疗群体智能的协同诊疗与群体决策支持系统(上)
数据结构·数据库·人工智能·git·python·动态规划
deng-c-f4 小时前
配置(14):git创建分支,跟确保正确提交分支
git
callJJ4 小时前
Git 分支合并到测试分支(dep-qa)教程
大数据·git·elasticsearch
爱钓鱼的程序员小郭5 小时前
Git 使用文档
git