ToolkitX
知识库工具箱

Git 高级技巧

cherry-pick, bisect, reflog

25min·高级

01. cherry-pick——挑拣提交

cherry-pick 把你想要的一个或几个 commit 从别的分支复制到当前分支。你不需要 merge 整个分支,只要选其中的几个 commit。 典型场景:修了一个 bug 在 develop 分支上,但这个修复也需要应用到生产分支上。用 cherry-pick 把那个修复 commit 摘到生产分支。 cherry-pick 会在当前分支创建一个新的 commit,内容跟来源 commit 一样,但 commit hash 不同(因为是不同的分支上创建的)。这意味着后续 merge 两个分支时可能会有重复内容冲突,需要手动解决。 操作:git cherry-pick commit-hash。可以一次挑多个:git cherry-pick hash1 hash2 hash3,或者一个范围:git cherry-pick hash1..hash3。
bash
# 把某个 commit 复制到当前分支
git cherry-pick abc1234

# 一次挑多个
git cherry-pick abc1234 def5678

# 挑一个范围(不含 hash1,从 hash2 到 hash3)
git cherry-pick hash1..hash3

# 发生了冲突?跟 merge 一样解决
# 解决后 git add && git cherry-pick --continue

git cherry-pick --abort   # 放弃本次 cherry-pick
cherry-pick 之后记得沟通——两个分支现在有内容相同的不同 commit,后面 merge 时可能会冲突。最好 cherry-pick 后就清理好。

02. bisect——二分法找 bug

bisect 是 Git 最神奇的命令——用二分查找算法帮你找出是哪个 commit 引入了 bug。你不用一个个 commit 试,几十上百个 commit 只需测试几次就能定位。 用法:git bisect start——开始二分。git bisect bad——标记当前版本有问题。git bisect good commit-hash——标记某个旧版本是好的(没 bug)。Git 自动切到中间版本让你测试。每次测试完告诉 Git 是好(good)还是坏(bad),反复直到找到元凶。 你可以手动测试,也可以写个自动测试脚本:git bisect run npm test。Git 自动二分法查找,找到坏 commit 就停止。 找到后 git bisect reset 回到二分之前的状态。
bash
# 二分查找的完整流程
git bisect start
git bisect bad                  # 当前版本有 bug
git bisect good v1.0.0          # v1.0.0 是好的

# Git 自动切到中间版本
# 你测试一下,然后告诉 Git:
# git bisect good    (如果这个版本没 bug)
# git bisect bad     (如果这个版本有 bug)

# 反复几次后 Git 告诉你第一个出问题的 commit
git bisect reset              # 退出二分模式

# 自动化二分(有测试脚本的话)
git bisect run npm test
bisect 是找回归 bug 的神器。当你说"之前还是好的,不知道哪个改动坏了",bisect 几分钟就帮你揪出来。

03. reflog——后悔药

reflog 记录了你在 Git 仓库里 HEAD 的每一次移动。就算你 git reset --hard 把 commit 丢了,只要它曾经在本地仓库存在过,reflog 里就能找到。默认保存 90 天。 git reflog 列出 HEAD 的所有移动记录,每条有一个编号(HEAD@{0} 是最新的,HEAD@{1} 是上一次)。找到你误删的那条记录对应的 commit hash,然后用 git reset --hard 或 git checkout 恢复。 reflog 不只救 reset 误操作,还能找回删掉的分支(merge 后删了分支,后来发现还需要?去 reflog 找最后一次 commit 的 hash 然后 git branch recover-branch hash)。
bash
# 查看 reflog
git reflog

# 输出示例
# abc1234 HEAD@{0}: commit: fix login bug
# def5678 HEAD@{1}: merge feature/branch
# 7890abc HEAD@{2}: reset: moving to HEAD~1

# 恢复到 reset 之前的状态
git reset --hard def5678

# 找回误删的分支
git reflog | grep "feature/old"
git branch recovered-branch abc1234
reflog 是本地的,push 不到远程仓库。用来救你本地的误操作就够了。远程仓库的误操作需要用 GitHub 的 protected branch 之类的机制保护。

04. rebase——变基的深入理解

rebase 和 merge 做的是同一件事——把一个分支的改动整合到另一个分支——但方式不同: merge 创建一个特殊的 merge commit,把两个分支的历史连在一起。历史保留原有的分叉结构,能看到哪个 commit 来自哪个分支。 rebase 把当前分支的 commit 依次摘出来,放到目标分支的最新 commit 之后重新提交。历史变成一条直线——看起来像你一直在目标分支上顺序开发。 rebase 的金科玉律:不要 rebase 已经 push 到共享仓库的 commit。rebase 会改变 commit hash,别人基于旧 hash 的工作就会乱掉。只 rebase 你自己本地的、还没 push 的分支。 交互式 rebase(rebase -i)可以编辑历史——压缩多个 commit 为一个(squash)、重新排序、修改 commit message、拆分 commit。这是保持 commit 历史干净的重要工具。
bash
# 将当前分支 rebase 到 main 上
git checkout feature
git rebase main

# 交互式 rebase——修改最近 3 个 commit
git rebase -i HEAD~3
# 编辑器里可以:
# pick → 保留
# squash → 合并到上一个 commit
# reword → 改 message
# drop → 删除
# edit → 暂停让你修改

# rebase 冲突后
git add .                # 解决后 add
git rebase --continue    # 继续
git rebase --abort       # 放弃 rebase
rebase 后 push 需要 --force。如果别人在这期间 pull 了你的旧分支,他的仓库会出问题。rebase 前确保只有你一个人在改这个分支。

05. submodule 与 subtree——管理项目中的外部代码

当你的项目依赖其他 Git 仓库的代码时(如共享库、主题),两种方案: submodule——在你的仓库里记录外部仓库的引用(指向某个 commit)。git clone 时默认不会下载 submodule,需要 git submodule update --init。 优点:外部仓库独立维护,版本明确。 缺点:操作复杂——clone 多一步、切换分支要注意 submodule 是否匹配、pull 了主仓库还要进 submodule 里 pull。 subtree——把外部仓库的代码合并到你的仓库的一个子目录里。没有额外的 clone 步骤,外人看就是你这个仓库的代码。 优点:外人 clone 一步到位。缺点:仓库变大(包了外部代码),历史记录混杂。 现代项目更推荐用包管理器(npm、pip)管依赖,而不是 submodule。

知识测验

1/5正确 0

cherry-pick 做什么?