Git 多段提交合并为一条

将多段 Git 提交合并为一条(Squash Commits)

本文档介绍如何把一段提交历史压缩为一条干净的提交。适用于在合并分支 / 提交 PR / 发布版本前精简历史。

适用读者:日常使用 Git 的开发者
适用范围:本地分支(多人协作请先沟通,改写历史需要强制推送)

总体流程图

flowchart TD
A[确定合并范围<br/>识别最早提交 commitA] --> B{选择方式}
B -->|推荐| C[交互式 rebase<br/>git rebase -i commitA^]
B -->|替代| D[软重置法<br/>git reset --soft commitA^]

C --> E[编辑待办列表:<br/>第一条 pick/reword,其余 squash/fixup]
E --> F{是否冲突?}
F -->|是| G[逐个解决冲突<br/>git add ...<br/>git rebase --continue]
F -->|否| H[编辑最终提交信息<br/>(如使用 squash)]
G --> F
H --> I[强制推送<br/>git push --force]

D --> J[一次性提交<br/>git commit -m '...']
J --> I

一、选择合并范围

查看历史,找到要合并段落中最早的那条提交(记为 commitA):

git log --oneline --decorate --graph --all

接下来示例都以 表示该起点。不要把真实哈希写进文档中。

二、交互式 Rebase(推荐)

2.1 打开交互式 rebase

git rebase -i <commitA>^

^ 表示从 commitA 的前一个提交开始变基,确保列表中包含 commitA。

2.2 编辑待办列表(pick / squash / fixup)

编辑器会打开从老到新的提交列表,例如:

pick <commitA>   描述 A
pick <commitB> 描述 B
pick <commitC> 描述 C

将第一条保留为 pick(或改为 reword 以修改最终说明),其余全部改为 squash(合并并保留说明进行编辑)或 fixup(合并但丢弃说明):

pick  <commitA>  描述 A
fixup <commitB> 描述 B
fixup <commitC> 描述 C

想把中间说明合并整理进最终提交 → squash

想让历史更干净简短,只保留第一条说明 → fixup

2.3 编辑最终提交信息

使用了 squash 时,Git 会提示你合并并编辑 message。建议使用统一规范(如 Conventional Commits):

feat: 初始化核心模块并完善配置加载/状态更新/推理接口

- 支持配置解析与模型加载
- 新增观测量处理逻辑与缓存
- 增加控制参数下发与限幅
保存并继续。

2.4 解决冲突与继续

遇到冲突时:

# 解决冲突
git add <文件1> <文件2> ...

# 继续 rebase
git rebase --continue

# 放弃本次 rebase(回到开始前状态)
git rebase --abort

2.5 推送(强制)

改写了历史,需要强制推送:

git push origin <分支名> --force
# 或更安全:
git push origin <分支名> --force-with-lease

三、软重置法(快速合并)

当列表很长或只想“粗暴合并”时可用:

# 回退到 commitA 的上一个,但保留工作区/暂存区
git reset --soft <commitA>^

# 将当前所有变更一次性提交为一条
git commit -m "feat: (你的最终提交说明)"

# 推送(改写历史)
git push origin <分支名> --force

特点:过程短、一步到位;但不会逐条整理中间提交的说明。

四、故障排查与回滚流程图

flowchart TD
S[开始] --> A{rebase 进行中?}
A -->|是| B{冲突?}
B -->|是| C[解决冲突<br/>git add ...<br/>git rebase --continue]
C --> A
B -->|否| D[完成 rebase]
A -->|否| D

D --> E{推送失败?}
E -->|是| F[改写历史导致拒绝<br/>使用 --force/--force-with-lease]
F --> G[git push --force(-with-lease)]
E -->|否| H[结束]

G --> H[结束]

%% 回滚支线
subgraph 回滚/救援
I[误操作?] --> J{要回到 rebase 前?}
J -->|是| K[git rebase --abort]
J -->|否| L[通过 reflog 找回]
L --> M[git reflog → 找到安全点]
M --> N[git reset --hard <安全点SHA>]
end

五、最佳实践

在私有分支上频繁整理历史;提交 PR/合并前保持 1~数条语义清晰的提交。

与协作者沟通后再进行改写历史操作;推送时优先用 --force-with-lease。

善用 fixup! 工作流:

git commit --fixup=<基准SHA>
git rebase -i --autosquash <基准SHA>^

让 Git 自动把修复提交贴到目标提交后,减少手动编辑。

使用统一提交规范(如 Conventional Commits),方便生成变更日志。

常见问题 FAQ

Q1:rebase 列表里最新的提交在最上面还是最下面?
A:一般最上面是最早,最下面是最新。如果只出现一条,检查你是否使用了 ^ 作为起点。

Q2:squash 和 fixup 如何选择?
A:希望整合并编辑中间信息 → squash;希望直接合并且不保留中间信息 → fixup。

Q3:强制推送有什么风险?
A:会覆盖远端历史。多人协作时建议先沟通,或使用 --force-with-lease 以避免覆盖他人最新提交。

Q4:合并后发现说明不完整怎么办?
A:使用 git commit --amend 修改最近一次提交;或通过 git reflog 回到合并前重新整理。

附录:常用命令速查

# 查看历史
git log --oneline --decorate --graph --all

# 交互式合并一段历史
git rebase -i <commitA>^

# 继续 / 放弃 rebase
git rebase --continue
git rebase --abort

# 软重置到 commitA 之前
git reset --soft <commitA>^

# 修订最近一次提交(信息或内容)
git commit --amend

# 强制推送(或更安全的强推)
git push origin <branch> --force
git push origin <branch> --force-with-lease

# 从 reflog 找回历史
git reflog
git reset --hard <sha>
知识共享许可协议