Git 强制合并多个 Commit

使用git rebase -i命令合并commit

当我们在开发过程中,可能会产生许多临时性的、细碎的commit,这些commit在最终合并到主分支前最好合并成一个或几个有意义的commit。使用git rebase -i(交互式变基)是一种强大的方式来实现这一目标。

基本步骤

  1. 首先确定要合并的commit范围,使用以下命令:
git rebase -i HEAD~n

其中n是要合并的commit数量。例如,要合并最近的3个commit,就使用:

git rebase -i HEAD~3
  1. 执行命令后,会打开一个编辑器,显示类似如下内容:
pick f7f3f6d 修改A
pick 310154e 修改B
pick a5f4a0d 修改C

# 命令:
# p, pick = 使用该commit
# r, reword = 使用该commit,但修改提交信息
# e, edit = 使用该commit,但停下来修改
# s, squash = 使用该commit,但融合到前一个commit
# f, fixup = 类似squash,但丢弃提交信息
# x, exec = 执行shell命令
# d, drop = 删除该commit
  1. 要合并commit,将除第一个commit外的所有pick改为squash(或s)或fixup(或f):
pick f7f3f6d 修改A
s 310154e 修改B
s a5f4a0d 修改C
  • 使用squash会保留每个commit的信息,并让你编辑最终的commit信息
  • 使用fixup会直接使用第一个commit的信息,丢弃其他commit的信息
  1. 保存并关闭编辑器后,如果使用了squash,会再打开一个编辑器让你编辑最终的commit信息

  2. 保存并关闭编辑器,rebase操作完成

其他常用rebase命令

除了squashfixup外,交互式rebase还提供了其他有用的命令:

  1. reword (r):使用该commit,但修改提交信息

    r f7f3f6d 修改A
    

    选择此选项后,rebase过程会暂停并打开编辑器让你修改该commit的提交信息。

  2. edit (e):使用该commit,但暂停以便进行修改

    e f7f3f6d 修改A
    

    选择此选项后,rebase会在应用该commit后暂停,让你可以:

    • 修改commit的内容(使用git addgit commit --amend
    • 将该commit拆分成多个commit
    • 完成修改后,使用git rebase --continue继续
  3. drop (d):完全删除该commit

    d f7f3f6d 修改A
    
  4. exec (x):执行shell命令

    x echo "已处理到这个commit"
    

    可用于在rebase过程中执行测试或其他命令

  5. 重新排序:直接在编辑器中调整commit的顺序

    # 原始顺序
    pick f7f3f6d 修改A
    pick 310154e 修改B
    pick a5f4a0d 修改C
    
    # 调整后的顺序
    pick a5f4a0d 修改C
    pick f7f3f6d 修改A
    pick 310154e 修改B
    

处理冲突

如果在rebase过程中出现冲突:

  1. 解决冲突的文件
  2. 使用git add添加解决后的文件
  3. 执行git rebase --continue继续rebase过程
  4. 如果想取消rebase,可以使用git rebase --abort

强制推送

合并commit后,如果这些commit已经推送到远程仓库,需要使用强制推送:

git push --force

注意:强制推送会重写历史,可能影响其他协作者。在共享分支上使用时要特别小心,最好使用--force-with-lease选项:

git push --force-with-lease

实用技巧

  1. 可以使用git log查看commit历史,确定要合并的范围
  2. 也可以使用commit的SHA值指定范围:git rebase -i <oldest-commit-sha>^
  3. 在合并前,最好创建一个备份分支:git branch backup-branch

常见错误及解决方法

  1. 没有选择正确的commit范围:确保包含了所有需要合并的commit
  2. 编辑器中的顺序错误:最早的commit在最上面,最新的在最下面
  3. 合并后push失败:使用--force--force-with-lease选项

通过合理使用git rebase -i命令,可以保持一个干净、有意义的commit历史,使项目更易于维护和理解。

实际操作案例

下面通过一个实际案例来演示如何合并多个commit。

场景描述

假设我们在开发一个新功能时,创建了以下几个commit:

$ git log --oneline -5
a1b2c3d 修复按钮样式问题
e4f5g6h 添加表单验证
i7j8k9l 实现提交功能
m0n1o2p 更新README
q3r4s5t 初始化项目结构

我们希望将前三个commit(a1b2c3d、e4f5g6h、i7j8k9l)合并为一个完整的功能提交。

操作步骤

  1. 启动交互式rebase

    git rebase -i HEAD~3
    
  2. 编辑器打开后,将显示

    pick i7j8k9l 实现提交功能
    pick e4f5g6h 添加表单验证
    pick a1b2c3d 修复按钮样式问题
    
    # 命令:
    # ...各种命令说明...
    

    注意commit的顺序是从旧到新(与git log显示的相反)。

  3. 修改为

    pick i7j8k9l 实现提交功能
    s e4f5g6h 添加表单验证
    s a1b2c3d 修复按钮样式问题
    
  4. 保存并关闭编辑器

  5. 新的编辑器窗口打开,用于编辑合并后的commit信息

    # This is a combination of 3 commits.
    # The first commit's message is:
    实现提交功能
    
    # This is the 2nd commit message:
    添加表单验证
    
    # This is the 3rd commit message:
    修复按钮样式问题
    
    # ...
    
  6. 编辑为一个完整的commit信息

    完成表单提交功能
    
    - 实现表单提交逻辑
    - 添加客户端和服务器端验证
    - 修复按钮样式问题
    
  7. 保存并关闭编辑器

  8. rebase完成后,查看日志

    $ git log --oneline -3
    f9e8d7c 完成表单提交功能
    m0n1o2p 更新README
    q3r4s5t 初始化项目结构
    

    可以看到三个commit已经合并为一个。

  9. 推送到远程仓库(如果需要):

    git push --force-with-lease
    

处理可能的冲突

如果在rebase过程中出现冲突,会看到类似以下信息:

CONFLICT (content): Merge conflict in src/form.js
error: could not apply e4f5g6h... 添加表单验证

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

解决步骤:

  1. 打开冲突文件,寻找并解决冲突(冲突部分会被标记为):

    <<<<<<< HEAD
    // 当前代码
    =======
    // 要应用的代码
    >>>>>>> e4f5g6h... 添加表单验证
    
  2. 解决冲突后,添加文件:

    git add src/form.js
    
  3. 继续rebase过程:

    git rebase --continue
    
  4. 如果还有其他冲突,重复上述步骤。