Skip to content

Git 协作与提交边界 ​

Git 用久了,几乎每个人都遇到过这些时刻:

  • 想回退某个功能,却发现它和别的改动混在同一个提交里,回退一个就会坏另一个;
  • 不小心把密钥文件提交了,赶紧删掉又提交了一次,以为没事了;
  • 执行了 git reset --hard,几个小时的工作“消失”了;
  • 强制推送后,同事昨天的提交不见了。

这些问题,一部分靠理解 Git 的工作原理就能解决,另一部分靠好的提交习惯来预防。

读完这篇你会

  • 理解提交、分支、HEAD 的本质
  • 写出小而完整、便于评审和回退的提交
  • 找回“丢失”的提交,正确处理误提交的密钥

1. 原理:存档点和书签 ​

Git 的核心概念其实很简单:

  • 提交(commit)就像游戏的存档点:它保存了项目在那一刻的完整快照,并记录着“上一个存档点是谁”。所有提交连起来,就是项目的历史。
  • 分支(branch)只是一张书签,夹在某个存档点上。在分支上提交时,书签会自动移到新的存档点。
  • HEAD 表示“你现在在哪里”,通常指向你当前所在的分支。
text
        A ── B ── C        ← main(书签)
                   \
                    D ── E ← feature(书签)← HEAD(你在这里)

理解了这一点,很多操作就不神秘了:

  • 创建分支,只是新建了一张书签,成本几乎为零;
  • git reset,是把书签挪到另一个存档点;
  • 存档点本身不会因为书签挪走而立即消失,这就是后面能“找回丢失提交”的原因。

2. 一个提交,只做一件事 ​

好的提交能用一句话说清楚,比如“修复切换筛选条件时旧请求覆盖新结果”。

text
✅ 修复切换筛选条件时旧请求覆盖新结果
✅ 统一订单状态的显示文案
❌ 修改页面、接口、样式和配置
❌ update

为什么要这么做:

  • 评审:小提交能被认真看完;
  • 回退:出了问题,可以只撤销这一件事(git revert),不影响其他改动;
  • 追溯:半年后用 git blame 看到这一行,能通过提交说明知道当初为什么这么改;
  • 定位:用 git bisect 二分查找问题时,能精确定位到引入问题的那一件事。

提交说明写清楚改了什么、为什么改,而不是“update”“fix bug”。

3. 坑一:一个文件里有两件事的改动,怎么拆开提交 ​

现象:调试一个 bug 时顺手改了一处无关的文案,两处改动在同一个文件里,git add 只能整个文件一起加。

修复:用 git add -p 按块(hunk)选择要暂存的改动:

bash
git add -p src/pages/OrderList.vue
# Git 会逐块展示改动,输入 y 暂存、n 跳过、s 把大块拆成更小的块

提交前再用 git diff --staged 确认暂存区里只有这次要提交的内容。格式化、重命名这类大范围的机械改动,单独提交,不要和逻辑改动混在一起。

4. 坑二:把密钥提交进了仓库 ​

现象:不小心提交了包含数据库密码的 .env 文件,发现后立即删除并提交了一次“删除密钥”。

原因:删除只是在新的存档点里没有了这个文件,旧的存档点里它依然完整存在。任何能访问仓库的人,都可以切换到那个旧提交看到密钥。如果已经推送到了远程,就应该当作密钥已经泄露。

修复:

  1. 立即作废并更换这个密钥(修改数据库密码、在第三方平台重新生成 key)。这是最重要的一步,清理历史只是补救;
  2. 如果需要从历史中彻底清除,可以用 git filter-repo 等工具重写历史,这会改变所有相关提交的哈希,需要和团队协调后强制推送;
  3. 检查有没有其他地方(CI 日志、镜像、备份)也包含了这个密钥。

怎么避免再犯:

  • .env、证书、私钥文件加入 .gitignore,仓库里只提交 .env.example 这样的占位模板;
  • 使用 gitleaks 等密钥扫描工具,在提交前或 CI 中自动检查;
  • 提交前养成看一眼 git status 和 git diff --staged 的习惯。

5. 坑三:git reset --hard 之后,代码“丢了” ​

现象:想撤销最近几次提交,执行了 git reset --hard HEAD~3,结果发现其中有一个提交是需要的,git log 里已经看不到了。

原因:reset 只是把分支这张书签挪回了三个存档点之前。那三个存档点并没有被删除,只是没有书签指向它们了,所以 git log 看不到。

修复:用 git reflog 查看 HEAD 最近去过的每一个位置:

bash
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 9f8e7d6 HEAD@{1}: commit: 完成订单导出功能   ← 就是它
# ...

git branch rescue 9f8e7d6   # 给它夹上一张新书签,找回来了

reflog 是本地的“后悔药”,默认保留几十天的记录。不过要注意:从未提交过的修改被 reset --hard 清除后是找不回来的,所以重要的改动要及时提交(哪怕是临时提交)或用 git stash 暂存。

6. 坑四:强制推送覆盖了同事的提交 ​

现象:本地整理了提交历史后执行 git push --force,同事早上推送到同一分支的两个提交消失了。

原因:--force 会用你本地的分支状态直接覆盖远程分支,不管远程上有没有你没见过的新提交。

修复与预防:

  • 公共分支(main、master、多人共用的分支)禁止强制推送,并在代码托管平台上开启分支保护;
  • 只在自己独占的分支上改写历史;确实需要强制推送时,用 git push --force-with-lease:如果远程分支有你没拉取过的新提交,它会拒绝推送;
  • 同事的提交被覆盖后,可以让同事从他本地的仓库重新推送,或者在推送者的 reflog 里找到原来的提交。

7. 坑五:分支开发太久,合并时冲突一大片 ​

现象:一个功能分支开发了一个月,准备合并时和主线冲突了几十个文件,解决冲突花了一整天,还引入了新的 bug。

修复:

  • 分支要短命:大功能拆成多个小步骤,每一步都可以单独合并(未完成的功能用功能开关隐藏);
  • 经常同步主线:每天或每隔几天把主线的最新改动合并进来,冲突在小的时候就解决掉。
bash
git fetch origin
git merge origin/main      # 或者 git rebase origin/main(仅限自己独占的分支)

8. 提交前的最小清单 ​

bash
git status            # 1. 只有这次要提交的文件吗?有没有调试文件、个人配置?
git diff --staged     # 2. 逐行看一遍暂存的改动,特别留意删除、配置和敏感信息
npm run lint && npm test   # 3. 运行和改动相称的检查
git commit            # 4. 写清楚改了什么、为什么改

排查思路 ​

  • 找出某行代码是谁、为什么改的:git blame 文件名,再用 git show 提交哈希 看完整改动和说明。
  • 找出哪次提交引入了 bug:git bisect,用二分法在历史中定位,见 前端调试技巧。
  • 看清分支关系:git log --oneline --graph --all。
  • 找回丢失的提交:git reflog。
  • 不确定一个操作的后果:先 git branch backup 夹一张备份书签,再操作,出问题可以随时回到备份。

检查清单 ​

  • [ ] 一个提交只做一件事,说明里写清改了什么、为什么
  • [ ] 提交前检查了暂存区和 diff,没有调试文件和敏感信息
  • [ ] .env、证书、私钥在 .gitignore 中,并有密钥扫描
  • [ ] 误提交密钥后第一时间作废并更换
  • [ ] 公共分支开启保护,禁止强制推送
  • [ ] 功能分支短命,经常同步主线
  • [ ] 知道用 git reflog 找回提交

延伸阅读 ​

为复用而记录,为理解而整理。