出于一些原因,我们期望更改 git 的历史记录,比如说
- 上传某个特定的提交
- 修改提交之间的顺序
- 删除某个特定的提交
现在我们来看一下这些操作是如何实现的。
为了便于讲解,我们假定当前本地 master 分支有如下四条历史,而远程 master 分支只有 base commit 一条记录。
1 | 8b4eefb contrib 3 |
上传某个特定的提交
假设 contrib 2 修复了一个 bug,需要立即上传至远程服务器。它处于 contrib 1 和 contrib 3 之间,如何能只上传 contrib 2 这个提交呢?
回想一下,git push 的语法是
1 | git push remote_address local_branch:remote_branch |
如果我们可以在本地创建一个分支,使它的历史为 base commit => contrib 2
,再上传到服务器就好了。
1 | 回到 base commit |
修改提交之间的顺序
上面的问题有一个更加优雅的解法。如果我们交换 contrib 1 和 contrib 2 的顺序,就可以避免以后 git pull --rebase
获取新记录时还要修复本地 master 分支。修改它们之间的顺序可以按如下步骤操作:
1 | 回到 master |
此时 git 会在编辑器中从上到下显示提交的顺序:
1 | pick ec94292 contrib 1 |
交换前两行,让 git 先处理 contrib 2 再处理 contrib 1:
1 | pick a3ca1ef contrib 2 |
保存后我们就得到了期望的历史记录:
1 | 7611175 contrib 3 |
删除某个特定的提交
如上一节所述,若选用 cherry pick 方法,将 contrib 2 上传到远程 master 分支,那么你还需要修复本地 master 分支。具体来说,在上传 contrib 1 和 contrib 3 前,需要先删除 contrib 2 的记录,然后将 contrib 1 和 contrib 3 一起 rebase 到从服务器获取的 contrib 2 之上。在本地删除一个特定提交又有 cherry pick 和 rebase 两种做法。
cherry pick 需要创建一个临时分支,收集应当保留的提交,然后写入本地 master 分支。
1 | 回到 contrib 1 |
如果 cherry pick 的提交比较多,上面的方法就不如 rebase 那么方便了:
1 | 回到 master |
随后如法炮制,删除第一行 "pick a3ca1ef contrib 2" 即可。
注意事项
本文中涉及的 cherry-pick,rebase 和 reset 指令都具有危险性。在做出修改之前,请确保当前修改不会影响已经上传到服务器的历史。如果你不慎动了那些记录,将无法进行 git push 操作。在多数情况下,force push 是被禁止的,因为这会使其他所有协作者丢失他们未上传的代码。