《回到未来-第二集》布朗博士改进的时间旅行车使用了未来科技,是陆天两用的飞车,而且燃料不再依赖核物质,而是使用无所不在的生活垃圾。而此次实践使用的工具也进行了升级,采用强大的
git rebase 命令。
命令
git rebase 是对提交执行变基操作,即可以实现将指定范围的提交“嫁接”到另外一个提交之上。其常用的命令行格式有:
用法1: git rebase --onto <newbase> <since> <till>
用法2: git rebase --onto <newbase> <since>
用法3: git rebase <newbase> <till>
用法4: git rebase <newbase>
用法5: git rebase -i ...
用法6: git rebase --continue
用法7: git rebase --skip
用法8: git rebase --abort
不要被上面的语法吓到,用法5会在下节(时间旅行三)中予以介绍,后三种用法则是变基运行过程被中断时可采用的命令 —— 继续变基或终止等。
- 用法6是在变基遇到冲突而暂停后,当完成冲突解决后(添加到暂存区,不提交),恢复变基操作的时候使用。
- 用法7是在变基遇到冲突而暂停后,跳过当前提交的时候使用。
- 用法8是在变基遇到冲突后,终止变基操作,回到之前的分支时候使用。
而前四个用法如果把省略的参数补上(方括号内是省略掉的参数),看起来就都和用法1就一致了。
用法1: git rebase --onto <newbase> <since> <till>
用法2: git rebase --onto <newbase> <since> [HEAD]
用法3: git rebase [--onto] <newbase> [<newbase>] <till>
用法4: git rebase [--onto] <newbase> [<newbase>] [HEAD]
下面就介绍一下
git rebase 的用法。命令格式:
git rebase --onto <newbase> <since> <till>
变基操作的过程:
- 首先会执行 git checkout 切换到 <till> 。
因为会切换到 <till> ,因此如果 <till> 指向的不是一个分支(如 master),则变基操作是在 detached HEAD (分离头指针)状态进行的,当变基结束后,还要像在“时间旅行一”中那样,对 master 分支执行重置以实现把变基结果记录在分支中。
- 将 <since>..<till> 所标识的提交范围写到一个临时文件中。
还记得前面介绍的版本范围语法, <since>..<till> 是指包括 <till> 的所有历史提交排除 <since> 以及 <since> 的历史提交后形成的版本范围。
- 当前分支强制重置(git reset --hard)到 <newbase> 。
相当于执行: git reset --hard <newbase> 。
- 从保存在临时文件中的提交列表中,一个一个将提交按照顺序重新提交到重置之后的分支上。
- 如果遇到提交已经在分支中包含,跳过该提交。
- 如果在提交过程遇到冲突,变基过程暂停。用户解决冲突后,执行 git rebase --continue 继续变基操作。或者执行 git rebase --skip 跳过此提交。或者执行 git rebase --abort 就此终止变基操作切换到变基前的分支上。
很显然为了执行将 E 和 F 提交跳过提价 D,“嫁接”到 C 提交上。可以如此执行变基命令:
git rebase --onto C E^ F
因为 E^ 等价于 D,并且 F 和当前 HEAD 指向相同,因此可以这样操作:
git rebase --onto C D
有了对变基命令的理解,就可以开始新的“回到未来”之旅了。
确认舞台已经布置完毕。
$ git status -s -b
## master
$ git log --oneline --decorate -6
b6f0b0a (HEAD, tag: F, master) modify hello.h
48456ab (tag: E) add hello.h
3488f2c (tag: D) move .gitignore outside also works.
b3af728 (tag: C) ignore object files.
d71ce92 (tag: hello_1.0, tag: B) Hello world initialized.
c024f34 (tag: A) README is from welcome.txt.
现在演出第一幕:干掉坏蛋D
- 执行变基操作。
因为下面的变基操命令行使用了参数 F。F 是一个里程碑指向一个提交,而非 master,会导致后面变基完成还需要对 master 分支执行重置。在第二幕中会使用 master,会发现省事不少。
$ git rebase --onto C E^ F
First, rewinding head to replay your work on top of it...
Applying: add hello.h
Applying: modify hello.h
- 最后一步必需的操作,就是要将 master 分支指向变基后的提交上。
下面的切换操作使用了reflog的语法,即 HEAD@{1} 相当于切换回 master 分支前的HEAD指向,即 3360440。
$ git checkout master
Previous HEAD position was 3360440... modify hello.h
Switched to branch 'master'
$ git reset --hard HEAD@{1}
HEAD is now at 3360440 modify hello.h
- 经过检查,操作完毕,收工。
$ git log --oneline --decorate -6
3360440 (HEAD, master) modify hello.h
1ef3803 add hello.h
b3af728 (tag: C) ignore object files.
d71ce92 (tag: hello_1.0, tag: B) Hello world initialized.
c024f34 (tag: A) README is from welcome.txt.
63992f0 restore file: welcome.txt
幕布拉上,后台重新布景
为了第二幕能够顺利演出,需要将 master 分支重新置回到提交 F 上。执行下面的操作完成“重新布景”。
$ git checkout master
Already on 'master'
git reset --hard F
HEAD is now at b6f0b0a modify hello.h
布景完毕,大幕即将再次拉开。
现在演出第二幕:坏蛋D被感化,融入社会
- 执行 git checkout 命令,暂时将 HEAD 头指针切换到坏蛋 D。
切换过程显示处于非跟踪状态的警告,没有关系,因为剧情需要。
$ git checkout D
Note: checking out 'D'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at 3488f2c... move .gitignore outside also works.
- 悔棋两次,以便将C和D融合。
$ git reset --soft HEAD^^
- 执行提交,提交说明重用 C 提交的提交说明。
$ git commit -C C
[detached HEAD 2d020b6] ignore object files.
1 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 .gitignore
- 记住这个提交ID: 2d020b6。
用里程碑是最好的记忆提交ID的方法:
$ git tag newbase
$ git rev-parse newbase
2d020b62034b7a433f80396118bc3f66a60f296f
- 执行变基操作,将 E 和 F 提交“嫁接”到 newbase 上。
下面的变基操命令行没有像之前的操作使用使用了参数 F,而是使用分支 master。所以接下来的变基操作会直接修改 master 分支,而无须再进行对 master 的重置操作。
$ git rebase --onto newbase E^ master
First, rewinding head to replay your work on top of it...
Applying: add hello.h
Applying: modify hello.h
- 看看提交日志,看到提交 C 和提交 D 都不见了,代之以融合后的提交 newbase 。
还可以看到最新的提交除了和 HEAD 的指向一致,也和 master 分支的指向一致。
$ git log --oneline --decorate -6
2495dc1 (HEAD, master) modify hello.h
6349328 add hello.h
2d020b6 (tag: newbase) ignore object files.
d71ce92 (tag: hello_1.0, tag: B) Hello world initialized.
c024f34 (tag: A) README is from welcome.txt.
63992f0 restore file: welcome.txt
- 当前的确已经在 master 分支上了,操作全部完成。
$ git branch
* master
- 清理一下,然后收工。
前面的操作中为了方便创建了标识提交的新里程碑 newbase ,将这个里程碑现在没有什么用处了删除吧。
$ git tag -d newbase
Deleted tag 'newbase' (was 2d020b6)
别忘了后台的重新布景
为了接下来的时间旅行三能够顺利开始,需要重新布景,将 master 分支重新置回到提交 F 上。
$ git checkout master
Already on 'master'
$ git reset --hard F
HEAD is now at b6f0b0a modify hello.h