一、单步悔棋
考虑这样一种情况,你在工作区添加了若干个文件,且修改了若干个文件。此时,你认为工作区的内容可以提交了,于是进行了提交操作,但是,刚提交上去,发现有个文件不应该被提交,那么需要进行单步悔棋。
Git权威指南上对单步悔棋的操作建议是使用git commit --amend,修补式提交,字面上解释是将此次的错误提交进行修补,但是使用后可以发现,commit的SHA-1值变化了,是一个新的commit对象。所以感觉它实际上是,先进行重置reset,再进行一次新的commit。
故 ①修改 + ②git commit --amend -m "xxx" = ① git reset HEAD^ + ②修改 + ③ git commit -m "xxx"。
初始图——图1:
该图中,依次进行了commit 1、2、3、4。
对于单步悔棋,基本上可以用下面的图进行说明,图2:
解释一下,commit 3有了新的孩子提交commit 5,commit 4和commit 3断绝了关系离家出走了。是简单的git log看不到它的存在,git reflog可以看到它。该commit对象在.git/objects/目录下好好的待着呢。
Ps:请无视图2中的Ps,这个Ps不该加到此图中,还有,本博客中的的所有图,箭头我都画反了,呃。。。。
二、多步悔棋
多步悔棋可以应用于这种场合:某开发人员领受某个特性开发的任务,于是在本地版本库进行了一系列开发、测试、修补、再测试的流程,最终在本地版本库中留下了多次提交。在将本地版本库改动推送到团队协同工作的核心版本库时,这个开发人员就想用多步悔棋的操作,将多个试验性的提交合并为一个完整的提交。
例如:从初始图1 中,合并commit 2,commit 3和commit 4,然后提交或修改一下再提交,新的提交为commit 5。
具体的可以先 git reset --soft HEAD~3,然后修改或直接提交,git commit -m "xxx",完成,而初始图1,变为图3:
git reset 如此强大,简直万能了。
三、git cherry-pick
但是,还是有它做不了的,git cherry-pick(拣选),意义在于从众多的提交中挑选出一个提交应用在当前的工作分支中。
Git权威指南中,对git cherry-pick是这么举例的,有了A、B、C、D、E、F六个提交,其中D是个“坏蛋”,于是想将提交变为A、B、C、E、F,那么 git cherry-pick 将发挥它的作用,但是有没有这种情况的实际应用,我表示怀疑,啊哈。
顺应它的例子,可以用我们前面的初始图1来说明一下,假设commit 3就是它说的“坏蛋”,那么可以有:
git checkout commit2 // 将HEAD指向commit2
git cherry-pick commit4 // 将commit 4拣选出来,接到commit 2之后
git cherry-pick commit5 // 再将commit 5拣选出来,接到commit 4之后
实际上在进行 git cherry-pick commit4 和 git cherry-pick commit5 这样的操作时,会产生新的commit对象,你可以将这个新的commit对象看作是commit 4对应的commit对象的一个复制体(尤里复制出来的)。
Ps:这里再对上一篇《Git step by step 13之对象》作个补充,该篇中提到:“Git在处理提交时,会把每次提交的文件全部内容(snapshot)都记录下来。 例如,file在修改之后,再进行提交时,会新建一个完整地对应于它的blob对象,而不是覆盖掉之前提交时所产生的,也不是只对修改的部分(例如:添加了某些行)建立blob对象。 ”如果对file进行修改,那么Git会认为目录的内容也发生了变化,因此会新建tree对象,进一步的,提交时,commit对象也是新建的。
在执行完上面的命令之后,会有图4:
四、git rebase
git rebase是对提交执行变基操作,即可以实现将指定范围的提交“嫁接”到另外一个提交之上。其实它就是对git cherry-pick的一个扩展,git cherry-pick一次只能拣选一个commit,而git rebase一次可以拣选出连续的若干个commit。
例如前面的
git checkout commit2 // 将HEAD指向commit2
git cherry-pick commit4 // 将commit 4拣选出来,接到commit 2之后
git cherry-pick commit5 // 再将commit 5拣选出来,接到commit 4之后
用git rebase就这么执行:
git checkout commit2
git rebase --onto commit2 commit3^ commit5 // 注意commit3后面还有个“^”,commit3^也可以用commit2代替,即git rebase --onto commit2 commit2 commit5。
当然git rebase的用处不止于此,后面会对它再建专题。more:
rebase