背景和要解决的问题

git-commit 子命令可以用来创建一个 commit, 一个 commit 指向一个 tree 对象,并且包含开发人员手工编写的修改记录文本信息以及时间戳。

有时会遇到这种情况,那就是此次创建的 commit 指向的 tree 和父 commit 指向的 tree 的差异过大,导致这种情况的原因主要是编码人员在一次 commit 中积攒了太多的变更,这种情况会导致诸多坏处,包括不限于:

为了重新得到一个清晰、合理的提交历史记录,我们希望对现已创建的 commit 进行重新组合,或者是对任意相邻的已经创建好了的 commit 进行重新组合,我们将演示如何通过灵活地应用 git-checkout, git-branch, git-rebase 等一系列 git 子命令来快速实现这一需求。

什么是理想的提交历史记录

理想的历史提交记录应该是这样的,即每个提交对应一个独立的修改主题,提交与提交之间的改动内容从语义上来说互不重合,或者说提交与提交之间的主题的界限尽可能清晰明确。

假设现在需要实现两个功能/需求/变更,分别记做 A, B, 假设现在有 a, b, c 三个文件,假设功能 A 的实现需要修改文件 a 和 文件 b, 功能 B 的实现需要修改文件 c, 现在,对比这两个不同的提交历史记录:

  1. 在提交 c0 中修改文件 a, 完成一部分功能 A, 然后在 c0 的基础上创建提交 c1, 在提交 c1 中修改 a, b, c 三个文件,完成 A, B 两个功能;
  2. 在提交 c0 中修改文件 a, b, 并且完成功能 A, 在 c0 的基础上创建提交 c1, 在提交 c1 中修改文件 c 完成功能 B.

显然,第 2 种提交历史记录优于第 1 种提交历史记录,并且第 2 种更加接近理想的提交历史记录。

对于第 1 种提交历史记录已经造成了的情形,可以尝试使用 git-rebase 结合其他子命令的方式来对提交历史记录进行重新组合,这也是本文主要讲述的。

操作过程讲解

文件级颗粒度的 commit 重新组合

作为演示,我们先创建一个空文件夹,然后进入到该演示目录,初始化 git 仓库并且创建三个文件:

mkdir git-rebase-demo
cd git-rebase-demo
git init
echo "// file1.js" >> file1.js
echo "// file2.js" >> file2.js
echo "// file3.js" >> file3.js