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>
CMakeLists Eigen FCPX GNU Gazebo Git Interest KDL Life Linux Matrix ODE ROS Ros UML Ubuntu VcXsrv algorithm algorithms axis-angle bode calibration chrome control cpp dB data_struct dots figure gdb git latex launch life linux mac math matlab memory motor moveit operator optimal algorithm python robot robotics ros ros2 rtb simulation stl thread tools twist urdf velocity vim web work wsl
知识共享许可协议