Git 高级技巧:变基、挑拣和交互式暂存
大多数开发者熟悉 git add、git commit、git push 这些基础命令。但 Git 还有许多强大的高级功能,掌握它们能显著提升代码管理效率和代码库质量。
本文覆盖:交互式变基、cherry-pick、交互式暂存、stash 进阶、bisect 调试、reflog 救援
一、交互式变基(Interactive Rebase)
git rebase -i 是整理提交历史的利器:
# 交互式变基最近 4 个提交
git rebase -i HEAD~4
# 或基于某个提交
git rebase -i <base-commit>
常用操作
# 打开的编辑器中会显示类似内容:
pick a1b2c3d 添加用户登录功能
pick e4f5g6h 修复登录验证 bug
pick i7j8k9l 优化登录性能
pick m0n1o2p 添加登出功能
# 修改命令前缀来操作提交:
# pick - 保留提交
# reword - 保留提交,但修改提交信息
# edit - 保留提交,但停下来修改
# squash - 合并到前一个提交,保留提交信息
# fixup - 合并到前一个提交,丢弃提交信息
# drop - 删除提交
实战场景
场景 1:合并多个小提交
# 将多个小修改合并为一个完整的提交
pick a1b2c3d 添加登录 API
squash e4f5g6h 修复 typo
squash i7j8k9l 添加日志
# 结果:一个干净的提交 "添加登录 API"
场景 2:修改历史提交
# 标记要修改的提交为 edit
pick a1b2c3d 添加用户模块
edit e4f5g6h 添加登录功能 # 停在这里修改
pick i7j8k9l 添加登出功能
# 执行后会停在该提交,你可以:
git add <files> # 添加修改
git commit --amend # 修改当前提交
git rebase --continue # 继续变基
黄金法则:永远不要变基已推送到远程仓库的提交。变基会改写历史,可能导致协作混乱。
二、Cherry-pick(挑拣提交)
git cherry-pick 用于将特定提交应用到当前分支:
# 挑拣单个提交
git cherry-pick <commit-hash>
# 挑拣多个提交
git cherry-pick <commit1> <commit2>
# 挑拣提交范围(不包含 start)
git cherry-pick <start>..<end>
# 只挑拣但不提交(可以修改后再提交)
git cherry-pick -n <commit-hash>
实战场景
场景:将 hotfix 从开发分支应用到生产分支
# 当前在 develop 分支
git log --oneline -5
# abc1234 修复登录验证漏洞
# def5678 添加新功能
# ...
# 切换到 main 分支
git checkout main
# 只挑拣修复提交
git cherry-pick abc1234
# 推送到远程
git push origin main
处理冲突
# cherry-pick 遇到冲突时
# 1. 手动解决冲突
# 2. 标记为已解决
git add <resolved-files>
# 3. 继续 cherry-pick
git cherry-pick --continue
# 或放弃
git cherry-pick --abort
三、交互式暂存(git add -p)
git add -p(patch mode)允许你选择性地暂存文件的部分修改:
git add -p <file>
# Git 会逐块显示修改,询问操作:
# y - 暂存此块
# n - 不暂存此块
# q - 退出
# a - 暂存此块和后续所有块
# d - 不暂存此块和后续所有块
# e - 手动编辑此块
# s - 分割成更小的块
实战场景
场景:一个文件包含多个不相关的修改
# 你在 utils.js 中做了两件事:
# 1. 修复了一个 bug
# 2. 添加了一个新工具函数
# 想要分成两个提交
git add -p utils.js
# 第一块(bug 修复)
@@ -10,7 +10,7 @@
function validate(input) {
- return input !== null;
+ return input !== null && input !== undefined;
}
(1/2) Stage this hunk [y,n,q,a,d,e,?]? y
# 第二块(新函数)
@@ -50,6 +50,10 @@
return a + b;
}
+
+function multiply(a, b) {
+ return a * b;
+}
(2/2) Stage this hunk [y,n,q,a,d,e,?]? n
# 现在暂存区只有 bug 修复
git commit -m "fix: 修复 validate 函数的 undefined 检查"
# 暂存新函数
git add utils.js
git commit -m "feat: 添加 multiply 工具函数"
最佳实践:每次提交只做一件事。使用
git add -p 可以帮你保持提交的原子性。
四、Stash 进阶
基础 git stash 很常用,但 stash 有更多技巧:
# 基础用法
git stash # 暂存当前修改
git stash pop # 恢复并删除 stash
git stash list # 查看所有 stash
# 进阶用法
git stash save "描述信息" # 带描述的 stash
git stash push -m "描述" # 同上(新语法)
# 包含未跟踪的文件
git stash -u # 包含 untracked 文件
git stash --include-untracked # 同上
# 只暂存部分文件
git stash push -p # 交互式选择暂存内容
# 从 stash 创建分支
git stash branch <branch-name> <stash>
# 应用特定 stash
git stash apply stash@{2}
# 删除特定 stash
git stash drop stash@{2}
# 清空所有 stash
git stash clear
实战场景
场景:紧急修复生产问题
# 正在开发新功能
vim src/new-feature.js
# 收到紧急 bug 报告,需要立即修复
git stash push -m "WIP: 新功能开发"
git checkout main
git checkout -b hotfix/urgent
# 修复 bug
vim src/bug.js
git add . && git commit -m "fix: 紧急修复"
git push origin hotfix/urgent
# 回到开发分支
git checkout feature/new-feature
# 恢复工作进度
git stash pop
五、Bisect:二分查找 Bug
git bisect 用于定位引入 bug 的提交:
# 开始 bisect
git bisect start
# 标记当前提交为有 bug
git bisect bad
# 标记某个已知正常的提交
git bisect good v1.0.0
# Git 会自动 checkout 中间提交
# 测试后标记
git bisect good # 如果这个提交是好的
git bisect bad # 如果这个提交有问题
# 重复直到找到问题提交
# Git 会显示:
# abc1234 is the first bad commit
# 结束 bisect
git bisect reset
自动化 bisect
# 如果有自动化测试脚本
git bisect start
git bisect bad
git bisect good v1.0.0
git bisect run npm test
# Git 会自动运行测试并找到问题提交
六、Reflog:时光倒流
git reflog 记录所有 HEAD 的移动,可以找回"丢失"的提交:
# 查看操作历史
git reflog
# 输出示例:
# abc1234 HEAD@{0}: reset: moving to HEAD~2
# def5678 HEAD@{1}: commit: 添加新功能
# ghi9012 HEAD@{2}: commit: 修复 bug
# ...
# 恢复到某个状态
git reset --hard HEAD@{1}
# 或直接用提交 hash
git reset --hard def5678
实战场景
场景:误删分支或错误 reset
# 不小心 hard reset 丢了工作
git reset --hard HEAD~3
# 惊慌... 不怕!
git reflog
# 找到之前的状态
# abc1234 HEAD@{0}: reset: moving to HEAD~3
# def5678 HEAD@{1}: commit: 重要的工作
# 恢复
git reset --hard def5678
# 找回误删的分支
git reflog
# 找到分支删除前的提交
git checkout -b recovered-branch <commit-hash>
Reflog 有效期:默认 90 天。可以通过
gc.reflogExpire 配置。
七、其他实用技巧
1. 交互式重置
# 类似 add -p,但是针对重置
git reset -p
# 撤销部分暂存
git reset HEAD -p
2. 清理工作区
# 查看将被删除的文件(预览)
git clean -n
# 删除未跟踪的文件
git clean -f
# 删除未跟踪的文件和目录
git clean -fd
# 同时删除忽略的文件
git clean -fdx
3. 提交搜索
# 搜索提交信息
git log --grep="关键词"
# 搜索代码变更
git log -S "function_name"
# 搜索代码变更(正则)
git log -G "pattern"
# 查看某个文件的修改历史
git log -p -- path/to/file
# 谁修改了这行代码
git blame -L 10,20 path/to/file
4. 工作区 vs 暂存区 vs HEAD
# 工作区 vs 暂存区
git diff
# 暂存区 vs HEAD
git diff --staged
git diff --cached # 同上
# 工作区 vs HEAD
git diff HEAD
# 两个提交之间
git diff abc123..def456
总结
| 命令 | 用途 | 危险程度 |
|---|---|---|
| git rebase -i | 整理提交历史 | ⚠️ 不要变基已推送的提交 |
| git cherry-pick | 选择性应用提交 | ✅ 安全 |
| git add -p | 部分暂存 | ✅ 安全 |
| git stash -u | 暂存工作进度 | ✅ 安全 |
| git bisect | 二分查找 bug | ✅ 安全(只读) |
| git reflog | 找回丢失提交 | ✅ 救命神器 |
| git clean -fd | 清理未跟踪文件 | ⚠️ 不可恢复 |
危险操作清单:
•
•
•
•
•
git reset --hard - 丢弃所有未提交的修改•
git clean -fd - 删除未跟踪的文件•
git push --force - 强制推送,覆盖远程历史•
git rebase 已推送的提交 - 改写公共历史
学习资源:
Pro Git 书籍:git-scm.com/book/zh/v2
Git 官方文档:git-scm.com/docs
Learn Git Branching:learngitbranching.js.org
Pro Git 书籍:git-scm.com/book/zh/v2
Git 官方文档:git-scm.com/docs
Learn Git Branching:learngitbranching.js.org