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 文件,发现后立即删除并提交了一次“删除密钥”。
原因:删除只是在新的存档点里没有了这个文件,旧的存档点里它依然完整存在。任何能访问仓库的人,都可以切换到那个旧提交看到密钥。如果已经推送到了远程,就应该当作密钥已经泄露。
修复:
- 立即作废并更换这个密钥(修改数据库密码、在第三方平台重新生成 key)。这是最重要的一步,清理历史只是补救;
- 如果需要从历史中彻底清除,可以用
git filter-repo等工具重写历史,这会改变所有相关提交的哈希,需要和团队协调后强制推送; - 检查有没有其他地方(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找回提交