跳到主要内容

🛡️ Git 进阶操作

当你已经掌握日常工作流后,进阶操作能让你在复杂场景下依然游刃有余。本章介绍四个核心进阶命令。

🔄 Interactive Rebase

git rebase -i重写提交历史最强大的工具。它允许你编辑、重排、合并、拆分或删除已有的提交。

基本用法

# 对最近 3 个提交进行交互式 rebase
git rebase -i HEAD~3

# 对某个提交之后的所有提交进行 rebase
git rebase -i <commit-hash>

运行后会打开编辑器,列出每个提交及操作指令:

pick abc1234 feat: add user login
pick def5678 fix: correct login validation
pick ghi9012 docs: update README

# Commands:
# p, pick = 保留该提交(默认)
# r, reword = 保留提交但修改 message
# e, edit = 暂停,修改内容后可继续
# s, squash = 合并到前一个提交(保留 message)
# f, fixup = 合并到前一个提交(丢弃 message)
# d, drop = 删除该提交
# x, exec = 执行 shell 命令

常见场景

场景 1:合并多个「修复」提交

开发中常出现 fix: typofix: lint 等琐碎提交,合并到主分支前应整理:

# rebase 前
abc1234 feat: add user login
def5678 fix: typo in login
efg7890 fix: eslint warnings

# rebase 编辑(将后两个改为 fixup)
pick abc1234 feat: add user login
fixup def5678 fix: typo in login
fixup efg7890 fix: eslint warnings

# rebase 后(变成一个干净的提交)
abc1234 feat: add user login

场景 2:修改旧提交的 Message

# 对包含目标提交的历史进行 rebase
git rebase -i HEAD~5
# 将目标提交的操作改为 `reword`,保存退出后 Git 会逐个打开编辑器让你修改 message

场景 3:拆分一个提交

# 1. 对目标提交执行 rebase,操作改为 `edit`
git rebase -i HEAD~3
# edit abc1234 feat: add feature X(这个提交里混了多件事)

# 2. Git 会暂停在目标提交,此时撤销该提交的变更但保留在工作区
git reset HEAD~1

# 3. 逐个文件/代码块重新提交(原子化)
git add src/part-a.ts && git commit -m "feat: add part A"
git add src/part-b.ts && git commit -m "feat: add part B"

# 4. 继续 rebase
git rebase --continue
⚠️ Rebase 的黄金法则

永远不要 rebase 已经推送到远程的提交,除非你确定团队其他成员没有基于这些提交工作。改写公共历史会导致协作灾难。

如果必须 rebase 已推送的提交,使用 git push --force-with-lease 而非 git push --force

🔍 Git Bisect——二分查找 Bug

当发现了一个 Bug,但不知道是哪个提交引入的,git bisect 可以通过二分查找,快速定位到「第一个引入 Bug 的提交」。

基本流程

# 1. 开始 bisect 会话
git bisect start

# 2. 标记当前版本有 Bug(bad)
git bisect bad

# 3. 标记某个已知没 Bug 的版本(good)
git bisect good v2.0.0

# Git 会自动切换到中间的提交,你测试后标记
# (重复以下步骤直到 Git 告诉你答案)

# 4. 测试当前版本,标记 good 或 bad
git bisect bad # 这个版本有 Bug
git bisect good # 这个版本没问题

# 5. Git 找到答案后,会输出类似:
# Bisecting: 0 revisions left to test after this (roughly 0 steps)
# 8a3f2c1 is the first bad commit
# Author: ...
# Date: ...
# fix: update cache logic ← 这就是引入 Bug 的提交!

# 6. 结束 bisect 会话,回到原分支
git bisect reset

自动化 Bisect(高效方式)

如果有可自动运行的测试脚本,git bisect run 可以全自动完成查找:

# 运行自动化二分查找
# 脚本返回 0 = good,返回 1-127 = bad
git bisect run npm test

# 自定义脚本
git bisect run bash -c "node -e 'require(\"./dist/index.js\").test()'"

Bisect 最佳实践

要点说明
提交要原子化每个提交只做一件事,bisect 才能精准定位到具体代码行
Good 版本要准确选一个确实没 Bug 的历史版本,否则 bisect 会给出错误答案
run 自动化手动测试 10+ 个提交很累,写测试脚本让 Git 全自动跑

📜 Git Reflog——引用日志

git reflog 记录了 HEAD 和分支引用在过去的所有移动。它是你的「后悔药」——即使提交被删除或分支被重置,只要还在 reflog 里(默认保留 90 天),就能找回来。

查看 Reflog

# 查看 HEAD 的引用日志
git reflog

# 输出示例:
# abc1234 (HEAD -> main, origin/main) HEAD@{0}: commit: fix: auth timeout
# def5678 HEAD@{1}: commit: feat: add user dashboard
# ghi9012 HEAD@{2}: reset: moving to HEAD~2
# jkl3456 HEAD@{3}: commit: wip: broken feature
# mno7890 HEAD@{4}: pull: Fast-forward from main to origin/main

用 Reflog 恢复丢失的提交

# 场景:不小心 git reset --hard 丢掉了几个提交
# 解决:从 reflog 找到丢失的提交,reset 回去

git reflog
# 发现 abc1234 是丢失前的状态

git reset --hard abc1234
# 成功恢复!所有「丢失」的提交都回来了

Reflog 与 git log 的区别

git loggit reflog
显示内容提交历史(DAG)HEAD/分支的移动记录
包含已删除提交❌ 不包含✅ 包含(在过期前)
主要用途查看项目演变恢复误操作丢失的提交
数据保留期永久默认 90 天
💡 Reflog 不是永久的

Reflog 数据会在过期后自动清理(默认 90 天)。如果你发现重要提交「消失」了,越快用 reflog 恢复越好。

🍒 Cherry-pick——遴选提交

git cherry-pick某个(些)提交应用到当前分支,而不需要合并整个分支。适用于「只想拿某个提交,不要其他变更」的场景。

基本用法

# 将提交 abc1234 的变更应用到当前分支
git cherry-pick abc1234

# 遴选多个提交(按顺序排列)
git cherry-pick abc1234 def5678

# 遴选但不自动提交(让你先检查/修改)
git cherry-pick --no-commit abc1234

常见场景

场景 1:将 Hotfix 同时应用到多个分支

# 假设 main 分支发现了一个紧急 Bug,你在 main 上提交了修复:
# fix: patch security vulnerability (commit: sec1234)

# 但 staging 分支也需要这个修复,又不想合并整个 main...
git checkout staging
git cherry-pick sec1234
# staging 分支现在也有了 sec1234 的修复,但没有 main 上的其他变更

场景 2:从错误分支上「抢救」提交

# 你不小心在 main 上直接提交了功能代码(应该提交到 feature 分支)
# 解决:在 feature 分支 cherry-pick 这个提交,然后在 main 上 revert

git checkout feature-x
git cherry-pick abc1234 # 把提交拿到 feature 分支

git checkout main
git revert abc1234 # 在 main 上撤回这个提交

Cherry-pick 冲突处理

Cherry-pick 可能产生冲突,处理方式与 merge 冲突类似:

# 发生冲突时
git status # 查看冲突文件

# 手动解决冲突后
git add <conflicted-files>
git cherry-pick --continue

# 或者放弃此次 cherry-pick
git cherry-pick --abort

📝 下一步

掌握进阶操作后,建议继续学习: