Git fixup 与 squash 整理提交

在功能开发或 Code Review 修修补补时,往往会产生很多「修 typo」「补漏测」「按 review 改一版」之类的小提交。fixup / squash 用来把这些变更合并进更早的某次提交,让 git log 更干净,而不必手动 git reset 再重提。

与「调整提交顺序」相关的交互式 rebase 总览见 Git 提交记录顺序调整

核心概念

方式 合并时是否保留「被合并提交」的说明
squash 保留,rebase 时会打开编辑器让你编辑合并后的 message
fixup 丢弃被合并提交的 message,只保留前一个提交的说明

两者都会把变更内容(diff)并入正上方的那条提交(pick 下面的第一条有效提交)。

pick  a1b2c3  feat: 添加 UART 驱动    ← 保留这条的 message(fixup 时)
fixup d4e5f6  fix: 修编译警告          ← 内容并入上一条,本条 message 丢弃
fixup 789abc  fix: 改波特率默认值      ← 同上

方式一:git commit --fixup / --squash(推荐日常)

先找到要「并入」的目标提交的 hash(或相对引用):

git log --oneline -5
# a1b2c3 feat: 添加 UART 驱动
# d4e5f6 fix: 修编译警告

在改完工作区后,不直接 git commit,而是:

# 生成一条「待 fixup 到 a1b2c3」的提交(message 以 fixup! 开头)
git add .
git commit --fixup=a1b2c3

# 或:待 squash 到 a1b2c3(message 以 squash! 开头,合并时可编辑总 message)
git commit --squash=a1b2c3

生成的提交信息形如:

fixup! feat: 添加 UART 驱动

或:

squash! feat: 添加 UART 驱动

fixup! / squash! 前缀后的文字应与目标提交的 subject 一致(至少能匹配),这样 --autosquash 才知道挂到哪条提交下面。

自动合并:git rebase -i --autosquash

把栈里所有 fixup! / squash! 提交自动排到对应目标提交之后,并执行 fixup/squash:

# 整理最近 N 条(含 fixup 提交)
git rebase -i --autosquash HEAD~5

# 从某分支点之后全部整理
git rebase -i --autosquash main

也可设成默认行为,少打 --autosquash

git config --global rebase.autoSquash true
# 之后:git rebase -i HEAD~5  会自动排列 fixup/squash 行

建议流程(本地分支修 PR):

git commit --fixup=<目标hash>   # 可多次
git rebase -i --autosquash HEAD~10
git log --oneline               # 确认只剩一条 feat + 干净历史

方式二:交互式 rebase 里手写 f / s

不事先 commit --fixup 时,在 git rebase -i 编辑器里把后面提交的命令改成 fixupsquash

git rebase -i HEAD~4
pick a1b2c3 feat: 添加 UART 驱动
fixup d4e5f6 fix: 修编译警告
fixup 789abc fix: 改波特率
pick fedcba0 docs: 更新 README
  • fixup 简写 f:并入上一条,不要本条 message
  • squash 简写 s:并入上一条,保存后会弹出编辑器合并 message

多条 fixup 会按顺序依次并入同一个 pick 提交。

git commit --amend 的区别

--amend --fixup + rebase
作用对象 最近一次提交 任意历史中的某次提交
是否改 hash 改掉当前 HEAD 的 hash rebase 后整段历史 hash 可能都变
典型场景 刚提交发现漏文件、改 message 要把补丁并进 3 条之前的 feat
# 只改「上一笔」:补文件、改 message
git add .
git commit --amend --no-edit

若目标不是 HEAD~0,用 fixup 更合适。

squash 合并 message 的编辑

squashcommit --squash 后,rebase 结束时会打开编辑器,例如:

# This is a combination of 3 commits.
# The first commit's message is:
feat: 添加 UART 驱动

# This is the 2nd commit message:
fix: 修编译警告

# This is the 3rd commit message:
fix: 改波特率

删掉不需要的行,整理成一条 Conventional Commits 即可(参见 约定式提交)。fixup 不会弹出这一步。

其他相关 rebase 动作(简表)

命令 简写 说明
pick p 保留该提交
reword r 保留提交,只改 message
edit e 停在该提交,便于 git commit --amend 拆改
squash s 并入上一条,合并 message
fixup f 并入上一条,丢弃本条 message
drop d 删除该提交

已推送提交时的注意

  • fixup / squash 会重写历史,合并后提交的 hash 全部变化。
  • 未推送的个人分支:随意使用。
  • 已推送且他人可能基于该分支开发:需要 git push --force-with-lease(比 --force 安全),并和团队确认;共享的 main / master 上一般禁止 force push。
git push --force-with-lease origin feature/uart

冲突时:

git status
# 解决冲突后
git add .
git rebase --continue
# 放弃本次 rebase
git rebase --abort

常见问题

Q: autosquash 没有自动排列 fixup 行?

  • 确认 fixup! 后的 subject 与目标提交 title 一致(大小写、空格要能对上)。
  • 目标提交必须在本次 rebase -i 的范围内。
  • 是否启用了 rebase.autoSquash;旧版 Git 需显式加 --autosquash

Q: 想合并到「不是上一行」的那条提交?

git commit --fixup=<hash> + --autosquash,不要只靠手写顺序;autosquash 会把 fixup 提交移到正确目标之后。

Q: 和 git merge --squash 一样吗?

不一样。merge --squash 是把整个分支压成一次新提交再并入当前分支;本文的 fixup/squash 是在同一条线性的提交历史里整理已有 commit。

命令速查

# 创建待合并的补丁提交
git commit --fixup=<commit>
git commit --squash=<commit>

# 自动执行合并
git rebase -i --autosquash HEAD~N
git config --global rebase.autoSquash true

# 只改最近一次
git commit --amend

# 查看结果
git log --oneline -10
git show HEAD

小结

  • fixup:补丁并进指定提交,丢掉补丁自己的 commit message,适合 review 小改、修编译。
  • squash:同样合并 diff,但保留各条 message 供你编辑成一条。
  • 日常推荐:git commit --fixup=<hash>git rebase -i --autosquash,比纯手改 rebase 列表不易排错。
  • 合并前保证工作区干净;已推送分支慎用,必要时 --force-with-lease 并协调团队。