搜索 K
主题
git rebase 是 Git 中用于重新排列提交历史的强大命令。它的核心功能是将一系列提交「搬移」到另一个基础提交之上,从而产生一条线性的、更整洁的提交历史。它也可以用于交互式编辑提交(squash、reword、拆分等)。
永远不要对已推送到远程公共分支的提交执行 rebase。 rebase 会改写提交历史(生成新的 SHA),这会破坏其他协作者的工作。
git rebase [--onto <newbase>] [<upstream>] [<branch>]
git rebase -i [<upstream>]
git rebase --continue
git rebase --abort
git rebase --skip这是 Git 中最经典的争论之一。两者都能将分支的修改整合到一起,但方式完全不同。
| 对比项 | git merge | git rebase |
|---|---|---|
| 历史结构 | 保留分支结构(非线性) | 线性历史(无分支痕迹) |
| 新提交 | 创建合并提交(merge commit) | 不创建额外提交(重放已有提交) |
| 原始 SHA | 保留 | 全部改变(新的 SHA) |
| 安全性 | 安全(不改写历史) | 改写历史(已推送的提交不可 rebase) |
| 冲突处理 | 一次性解决所有冲突 | 逐个提交解决冲突 |
| 适用场景 | 公共分支合并、保留完整上下文 | 整理本地提交、保持线性历史 |
mergerebaserebase -imerge(更安全)git rebase 的内部过程可以分解为以下步骤:
详细过程:
Git 提交的 SHA 是根据内容、父提交、作者、时间等信息计算的哈希值。rebase 改变了父提交(从 C2 变成了 C3),所以即使提交内容不变,SHA 也会不同。
# 方式 1:先切换到 feature 分支
git checkout feature
git rebase main
# 方式 2:直接指定(无需切换分支)
git rebase main feature# 场景:main 有了新提交,feature 分支需要同步
# 方式 1:rebase
git checkout feature
git fetch origin
git rebase origin/main
# 方式 2:pull --rebase(更简洁)
git pull --rebase origin main# 全局设置
git config --global pull.rebase true
# 单个仓库设置
git config pull.rebase true设置后 git pull 自动使用 rebase 而不是 merge。
--onto:精确控制变基目标 --onto 允许你将一部分提交搬移到另一个基础上,是 rebase 最灵活的用法。
git rebase --onto <newbase> <upstream> [<branch>]# feature 分支是从 develop 创建的,现在想移到 main 上
#
# 之前:main → C1 → C2
# develop → C1 → C3 → C4
# feature → C1 → C3 → C4 → C5 → C6
#
# 目标:将 C5、C6 移到 main 上
git rebase --onto main develop feature
# 结果:main → C1 → C2 → C5' → C6' (feature)# 想删除 C3 这个提交(保留 C4、C5)
# C1 → C2 → C3 → C4 → C5 (HEAD)
git rebase --onto C2 C3
# 结果:C1 → C2 → C4' → C5' (HEAD)
# C3 被跳过了-i) 交互式 rebase 是 Git 中最强大的历史整理工具,允许你对提交进行重排序、合并、拆分、编辑、删除等操作。
# 交互式编辑最近 N 个提交
git rebase -i HEAD~N
# 交互式编辑从某个提交之后的所有提交
git rebase -i <commit>
# 编辑当前分支上所有不在 main 中的提交
git rebase -i main执行后 Git 会打开编辑器,显示类似以下内容:
pick abc1234 feat: 添加用户模型
pick def5678 fix: 修复用户验证
pick ghi9012 feat: 添加登录页面
pick jkl3456 fix: 修复拼写错误
pick mno7890 chore: 更新依赖
# Rebase 123456..mno7890 onto 123456 (5 commands)
#
# Commands:
# p, pick = 使用提交(不做修改)
# r, reword = 使用提交,但修改提交信息
# e, edit = 使用提交,但暂停让你修改内容
# s, squash = 合并到前一个提交,保留提交信息
# f, fixup = 合并到前一个提交,丢弃提交信息
# x, exec = 执行 shell 命令
# b, break = 在此处暂停
# d, drop = 删除提交| 命令 | 简写 | 说明 |
|---|---|---|
pick | p | 保留提交不变(默认) |
reword | r | 保留提交,但修改提交信息 |
edit | e | 暂停在该提交,允许修改文件内容或拆分提交 |
squash | s | 合并到前一个提交,合并两者的提交信息 |
fixup | f | 合并到前一个提交,丢弃当前提交的信息 |
exec | x | 在该提交后执行一个 shell 命令 |
break | b | 在此处暂停 rebase,可用 --continue 继续 |
drop | d | 删除该提交(也可以直接删除那一行) |
label | l | 为当前 HEAD 打标签(用于复杂 rebase) |
reset | t | 将 HEAD 重置到某个标签 |
merge | m | 创建合并提交(保留合并历史) |
将多个小提交合并成一个有意义的提交:
git rebase -i HEAD~4编辑器中修改:
pick abc1234 feat: 添加用户模型
squash def5678 fix: 修复用户验证
squash ghi9012 fix: 修复拼写错误
squash jkl3456 test: 添加用户测试squash vs fixupsquash:合并时会打开编辑器,让你编辑合并后的提交信息(包含所有被合并提交的信息)fixup:合并时直接丢弃当前提交的信息,只保留目标提交的信息对于修复拼写、格式调整等小修改,用 fixup 更高效。
git rebase -i HEAD~3reword abc1234 feat: 旧的提交信息
pick def5678 feat: 另一个提交
pick ghi9012 fix: 修复保存后 Git 会依次打开编辑器让你修改标记为 reword 的提交信息。
git rebase -i HEAD~3edit abc1234 feat: 需要修改内容的提交
pick def5678 feat: 其他提交保存后 Git 会暂停在 abc1234:
# 修改文件
vim src/user.ts
# 将修改加入到该提交
git add src/user.ts
git commit --amend
# 继续 rebase
git rebase --continuegit rebase -i HEAD~4pick abc1234 feat: 保留这个
drop def5678 debug: 删除这个调试提交
pick ghi9012 feat: 保留这个
pick jkl3456 feat: 保留这个直接在编辑器中调换行的顺序即可:
# 原始顺序
pick abc1234 feat: A
pick def5678 feat: B
pick ghi9012 feat: C
# 调整后
pick ghi9012 feat: C
pick abc1234 feat: A
pick def5678 feat: B如果提交之间有依赖关系(后面的提交修改了前面提交创建的文件),重排序可能导致冲突。
使用 edit 暂停后,将一个大提交拆分为多个小提交:
git rebase -i HEAD~3
# 将目标提交标记为 edit
# rebase 暂停后
git reset HEAD~1
# 所有修改回到工作区
# 分批提交
git add src/model/
git commit -m "feat: 数据模型"
git add src/api/
git commit -m "feat: API 接口"
git add tests/
git commit -m "test: 添加测试"
# 继续 rebase
git rebase --continueexec 自动运行测试 git rebase -i HEAD~5pick abc1234 feat: 功能 A
exec npm test
pick def5678 feat: 功能 B
exec npm test
pick ghi9012 fix: 修复
exec npm test在每个提交后插入 exec npm test(或其他测试命令),如果测试失败,rebase 会暂停,让你修复问题。这有助于确保每个提交都是可构建、可测试的。
--autosquash:自动整理 fixup 提交 配合 git commit --fixup 和 git commit --squash 使用,自动将修复提交合并到目标提交。
# 1. 正常提交
git commit -m "feat: 用户登录"
# 2. 发现需要修复上面的提交
git add src/auth.ts
git commit --fixup abc1234 # 自动生成消息 "fixup! feat: 用户登录"
# 3. 交互式 rebase 时自动排列
git rebase -i --autosquash main
# fixup 提交会自动移到目标提交下方并标记为 fixupgit config --global rebase.autoSquash true设置后 git rebase -i 自动启用 --autosquash,无需每次手动添加。
rebase 过程中,每个提交都可能产生冲突(因为是逐个重放的)。
# 1. 开始 rebase
git rebase main
# CONFLICT (content): Merge conflict in src/main.ts
# 2. 解决冲突
# 编辑冲突文件,删除冲突标记 <<<<<<<, =======, >>>>>>>
# 3. 标记冲突已解决
git add src/main.ts
# 4. 继续 rebase(不要用 git commit!)
git rebase --continue
# 如果同一个提交还有其他冲突,重复步骤 2-4--abort vs --skip vs --quit | 命令 | 效果 |
|---|---|
git rebase --continue | 冲突解决后继续重放下一个提交 |
git rebase --abort | 完全取消 rebase,恢复到 rebase 前的状态 |
git rebase --skip | 跳过当前有冲突的提交,继续下一个 |
git rebase --quit | 退出 rebase,保留已成功重放的提交 |
--skip 会丢失提交--skip 会完全跳过当前提交,该提交的所有修改都不会包含在最终结果中。只在你确定这个提交不需要时使用。
不要等到 main 分支有了几十个新提交后才 rebase。养成每天或每几个提交就同步一次的习惯:
git fetch origin
git rebase origin/mainrerere 自动记住冲突解决方案# 启用 rerere(Reuse Recorded Resolution)
git config --global rerere.enabled true启用后,Git 会记住你解决冲突的方式。如果下次遇到相同的冲突(比如 rebase 失败重试),Git 会自动应用之前的解决方案。
--rebase-merges:保留合并历史 默认的 rebase 会将合并提交扁平化(丢失分支合并的结构)。使用 --rebase-merges 可以保留合并提交:
git rebase --rebase-merges main--update-refs:自动更新中间分支引用 当你有多个相互依赖的分支时,rebase 一个分支会导致其他分支的引用过期。--update-refs 会自动更新所有受影响的分支引用:
git rebase --update-refs maingit config --global rebase.updateRefs truerebase 改变了提交的 SHA,推送到远程需要强制推送:
# 推荐:使用 --force-with-lease(安全的强制推送)
git push --force-with-lease origin feature
# 不推荐:使用 --force(可能覆盖他人的提交)
git push --force origin feature--force vs --force-with-lease--force:无条件覆盖远程分支,即使远程有其他人推送的新提交--force-with-lease:只在远程分支没有新提交时才推送,否则被拒绝始终使用 --force-with-lease,避免覆盖他人的工作。
# 每天开始开发前同步 main 的最新代码
git checkout feature
git fetch origin
git rebase origin/main
# 如果有冲突,解决后继续
git add .
git rebase --continue
# 推送到远程(feature 是个人分支,可以 force push)
git push --force-with-lease origin feature# 开发完成,准备合并前整理提交
git rebase -i main
# 在编辑器中:
# - squash 修复提交到功能提交中
# - reword 不清晰的提交信息
# - drop 不需要的调试提交
# - 调整提交顺序使逻辑更清晰
# 整理后推送
git push --force-with-lease origin feature
# 然后创建 PR / MRpull --rebase 避免无意义的合并提交 # 正常 git pull 可能产生合并提交
git pull origin main
# Merge branch 'main' of ... into main ← 无意义的合并提交
# 使用 --rebase 保持线性历史
git pull --rebase origin main
# 本地提交被移到远程新提交之后,不产生合并提交# 1. 发现之前某个提交有 bug
git log --oneline
# abc1234 feat: 用户登录
# def5678 feat: 注册页面
# ghi9012 初始化
# 2. 修复 bug 并创建 fixup 提交
git add src/auth.ts
git commit --fixup abc1234
# 3. rebase 时自动合并
git rebase -i --autosquash ghi9012
# fixup 提交自动排列到 abc1234 下方并标记为 fixup在创建 Pull Request 之前,整理提交历史是专业开发者的标准实践:
# 开发过程中的提交(很乱)
git log --oneline
# f1a2b3c fix: 又修了一个 typo
# d4e5f6a fix: 修复 typo
# a1b2c3d WIP: 试试另一个方案
# 7e8f9a0 WIP: 调试中
# 1a2b3c4 feat: 用户认证模块
# 9d8e7f6 (main) ...
# PR 前整理(合并 WIP、修复到功能提交中)
git rebase -i main编辑器中:
pick 1a2b3c4 feat: 用户认证模块
fixup 7e8f9a0 WIP: 调试中
fixup a1b2c3d WIP: 试试另一个方案
fixup d4e5f6a fix: 修复 typo
fixup f1a2b3c fix: 又修了一个 typo# 整理后的结果
git log --oneline
# abc1234 feat: 用户认证模块 ← 一个干净的提交
# 9d8e7f6 (main) ...
# 推送并创建 PR
git push --force-with-lease origin feature每个提交应该是一个逻辑完整的变更单元:
feat: 添加用户认证模块(包含模型、API、测试)fix: 修复登录时 token 过期问题WIP、fix typo、debug、试试看PR 审查后需要修改代码,有两种策略:
# 策略 1:追加新提交后 squash(推荐新手)
# 审查意见:修改变量命名、添加错误处理
git add .
git commit -m "review: 按审查意见修改"
# 然后 squash 到原始提交
git rebase -i main
# 将 review 提交 fixup 到对应的功能提交
# 策略 2:使用 fixup 自动关联(推荐进阶用户)
git add src/auth.ts
git commit --fixup abc1234 # abc1234 是需要修改的提交
# push 后,合并 PR 时选择 squash merge
# 或手动 rebase -i --autosquash大型功能拆分为多个依赖的 PR,使用 rebase 维护依赖关系:
# 结构:main → feature-base → feature-api → feature-ui
# 当 feature-base 合入 main 后,需要 rebase 后续分支
git checkout feature-api
git rebase main # feature-api 现在基于最新的 main
git checkout feature-ui
git rebase feature-api # feature-ui 基于更新后的 feature-api
# 使用 --update-refs 自动处理(Git 2.38+)
git checkout feature-ui
git rebase --update-refs main
# 自动更新 feature-api 和 feature-ui 的引用团队使用 merge 策略但想保持主分支线性历史:
# main 分支有很多合并提交,想清理
# ❌ 不要对公共分支做这个!
# ✅ 正确做法:对自己的 feature 分支使用 rebase 替代 merge
# 而不是事后清理 main
# 设置团队规范:feature 分支合入 main 时使用 squash merge
# GitHub PR 设置 → 只允许 "Squash and merge"
# GitLab MR 设置 → 只允许 "Squash commits"长期维护的 feature 分支如何高效同步主分支的更新:
# ❌ 错误做法:每次都 merge main 到 feature
git checkout feature
git merge main
# 结果:feature 的历史中充满了 merge commit
# ✅ 正确做法:定期 rebase
git checkout feature
git fetch origin
git rebase origin/main
# ✅ 最佳实践:每天早上第一件事
git fetch origin
git rebase origin/main
# 及时解决小冲突,而不是积累到最后变成大冲突| 主分支活跃度 | 建议同步频率 | 方式 |
|---|---|---|
| 每天 5+ 个合并 | 每天 | rebase |
| 每天 1-2 个合并 | 每 2-3 天 | rebase |
| 每周几个合并 | 每周 | rebase 或 merge |
| 主分支几乎不变 | 合并前 | rebase |
项目从 master 迁移到 main,或者需要将开发中的功能从一个发布分支移到另一个:
# 场景:你的 feature 基于 release/v2.0,但现在要移到 release/v3.0
git rebase --onto release/v3.0 release/v2.0 feature
# 场景:master 重命名为 main 后,更新所有 feature 分支
git checkout feature
git rebase --onto main master在交互式 rebase 中修改作者信息(常见于使用了错误的 Git 配置时):
# 修改最近 5 个提交的作者
git rebase -i HEAD~5
# 将需要修改的提交标记为 edit
# rebase 暂停时修改作者
git commit --amend --author="正确的名字 <correct@email.com>"
git rebase --continue
# 对每个 edit 的提交重复上述操作--autostash 自动保存未提交修改 # rebase 前如果有未提交的修改,会被拒绝
# 传统做法:手动 stash → rebase → stash pop
git stash
git rebase origin/main
git stash pop
# 更好的做法:使用 --autostash
git rebase --autostash origin/main
# Git 自动 stash → rebase → stash pop
# 设为默认行为
git config --global rebase.autoStash true--committer-date-is-author-date 保留原始时间 rebase 后提交的 committer date 会变成当前时间。如果你想保留原始的提交时间:
git rebase --committer-date-is-author-date main# 将所有 pick 改为 squash(保留第一个)
# 在编辑器中,可以使用搜索替换:
# 查找:pick
# 替换:squash(或 fixup)
# 但要保留第一行的 pick!
# 或者使用命令行一步完成 squash
git reset --soft main
git commit -m "feat: 合并所有提交为一个"
# 这比 rebase -i 然后逐个标记 squash 快得多# 在 ~/.gitconfig 中添加
[alias]
# 交互式 rebase(最常用)
ri = rebase -i
# rebase 到 origin/main
rom = "!git fetch origin && git rebase origin/main"
# 继续 rebase
rc = rebase --continue
# 放弃 rebase
ra = rebase --abort
# 整理最近 N 个提交
fixup = "!f() { git rebase -i HEAD~$1; }; f"
# 快速 fixup 提交
cf = commit --fixup
# 使用
git ri main # 交互式 rebase 到 main
git rom # 同步远程 main
git rc # 解决冲突后继续
git fixup 5 # 整理最近 5 个提交
git cf abc1234 # 创建 fixup 提交exec 确保每个提交可构建 # 在 rebase 过程中,对每个提交运行测试
git rebase -i main --exec "npm test"
# 或对每个提交运行 lint + test
git rebase -i main --exec "npm run lint && npm test"
# 如果某个提交导致测试失败,rebase 会暂停
# 你可以修复后 git rebase --continuegit bisect 需要每个提交都能编译运行才能正确定位 bug## Git Rebase 团队规范
### 允许 rebase 的情况
- 自己的 feature 分支(未与他人共享)
- PR 合并前整理提交历史
- 同步主分支更新到 feature 分支
### 禁止 rebase 的情况
- main / develop 等公共分支
- 已创建 PR 且有他人基于此分支工作
- release 分支(发布后)
### PR 提交规范
- 每个 PR 最多 5-10 个有意义的提交
- 不允许 WIP、fixup、debug 等临时提交出现在最终 PR 中
- 使用 `git rebase -i` 整理后再提交 PR
- PR 合并方式:Squash and merge(小 PR)或 Merge commit(大 PR)# ❌ 千万不要这样做
git checkout main
git rebase feature # 把 main rebase 到 feature 上
git push --force # 强制推送 main
# 后果:所有人的 main 都与远程不一致
# 所有人都要执行:
git fetch origin
git reset --hard origin/main记住:只 rebase 自己的分支,永远不要 rebase 公共分支。
# ✅ 正确:将 feature rebase 到 main 上
git checkout feature
git rebase main
# feature 的提交移到 main 最新提交之后
# ❌ 错误:将 main rebase 到 feature 上
git checkout main
git rebase feature
# main 的提交被改写!口诀:「站在 feature 上,rebase 到 main」。
git rebase -i main
git push origin feature
# error: failed to push, non-fast-forward
# 因为 rebase 改变了 SHA,需要 force push
git push --force-with-lease origin feature如果在编辑器中删除了所有行(或全部标记为 drop),rebase 会导致分支回退到基础提交,丢失所有修改。
补救方法:git reflog → git reset --hard <rebase 前的 SHA>
git commit# 解决冲突后
git add .
git commit -m "resolve conflict" # ❌ 错误!会创建新的提交
git add .
git rebase --continue # ✅ 正确!继续 rebase 流程二进制文件(图片、PDF 等)的冲突无法自动合并,每个涉及二进制文件的提交都可能需要手动处理。如果你的分支有大量二进制文件变更,考虑使用 merge 替代 rebase。
没有绝对的对错,取决于团队偏好和具体场景:
| 场景 | 推荐方式 |
|---|---|
| 将 feature 合入 main | merge |
| feature 分支同步 main 的更新 | rebase |
| 整理本地提交历史 | rebase -i |
| 多人协作的公共分支 | merge |
| 个人的 feature 分支 | rebase |
| 想保留完整的分支合并记录 | merge |
| 想要干净的线性历史 | rebase |
使用 ORIG_HEAD 或 reflog:
# 方式 1:刚刚 rebase 完
git reset --hard ORIG_HEAD
# 方式 2:通过 reflog
git reflog
# 找到 rebase 前的 SHA
git reset --hard <sha>详见 Git reset
几个建议:
rerere:git config --global rerere.enabled true,Git 会记住冲突解决方案git rebase -i HEAD~10 # 先合并提交
git rebase main # 再同步主分支git rebase -i 的编辑器看不懂 / 不会用?编辑器中的每一行格式是 <命令> <SHA> <提交信息>。你只需要修改行首的命令关键字:
pickrewordsquash 或 fixupdrop 或直接删除那一行保存并关闭编辑器后 Git 开始执行。如果搞砸了:git rebase --abort
可以,使用 --onto:
git rebase --onto target-branch source-branch feature-branch但这通常会导致大量冲突,因为两个分支的代码差异很大。
因为 rebase 会改变提交的 SHA。如果其他人基于旧的 SHA 做了工作(pull、branch、cherry-pick),rebase 后这些 SHA 就不存在了,导致:
所以规则是:只 rebase 自己的本地/个人分支,不要 rebase 多人共享的分支。
确保 Git 配置了编辑器:
# 使用 VS Code
git config --global core.editor "code --wait"
# 使用 Notepad++
git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"
# 使用 vim(Git Bash 默认)
git config --global core.editor vim| 目标 | 命令 |
|---|---|
| 变基到 main | git rebase main |
| 同步远程主分支 | git fetch && git rebase origin/main |
| pull 时使用 rebase | git pull --rebase origin main |
| 取消 rebase | git rebase --abort |
| 冲突解决后继续 | git rebase --continue |
| 跳过当前提交 | git rebase --skip |
| 撤销已完成的 rebase | git reset --hard ORIG_HEAD |
| 目标 | 命令 |
|---|---|
| 编辑最近 N 个提交 | git rebase -i HEAD~N |
| 合并提交(squash) | 编辑器中将 pick 改为 squash 或 fixup |
| 修改提交信息 | 编辑器中将 pick 改为 reword |
| 编辑提交内容 | 编辑器中将 pick 改为 edit |
| 删除提交 | 编辑器中将 pick 改为 drop 或删除该行 |
| 重排序提交 | 编辑器中移动行的位置 |
| 自动整理 fixup 提交 | git rebase -i --autosquash |
| 目标 | 命令 |
|---|---|
| 移动到其他基础 | git rebase --onto <newbase> <upstream> |
| 保留合并历史 | git rebase --rebase-merges |
| 自动更新分支引用 | git rebase --update-refs |
| 推送 rebase 后的分支 | git push --force-with-lease origin <branch> |
| 创建 fixup 提交 | git commit --fixup <sha> |
| 启用 rerere | git config --global rerere.enabled true |
| 默认 pull 使用 rebase | git config --global pull.rebase true |
| 默认启用 autosquash | git config --global rebase.autoSquash true |