注意事项
-
变基操作的 实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。
-
使用变基操作的原则是:只对尚未推送或分享给别人的本地修改执行变基操作,从不对已推送至别处的提交执行变基操作。这是因为变基会丢弃一些提交。如果这些提交已经分享给别人,那么再执行变基操作后将导致别人需要整合。
-
rebase 不会合并提交结点。假设 rebase 之前有三个结点, rebase 时会生成三个相应的结点,并不会将三个结点合并成一个。
-
变基命令是在推送前清理提交使之整洁的工具,千万不要对已经共享的结点进行变基操作。
与 merge 比较
rebase 与 merge 一样,都用于分支合并。
-
merge 执行的是三方合并。merge 会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。如下图:
merge 合并
-
rebase (变量)会将某一分支上的所有修改都移至另一分支上。对上图来说,你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。
rebase 合并 图二
当变基成功后, 再切回 master 分支,进行一次快进合并即可。
$ git branch * d2 dev $ git rebase dev First, rewinding head to replay your work on top of it... Applying: udev2 $ git checkout dev Switched to branch 'dev' $ git merge d2
通过 branch 命令看出当前处在 d2 分支中。通过 rebase 将 d2 变基到 dev 分支中。在切换回 dev 分支后合并 d2 分支。
-
merge 时主分支会有并行结构,即某个结点可能会有多个父结点。但使用 rebase 时,所有的结点都是串行的。
-
rebase 时,rebase 后跟的是目标基底,会将当前分支的修改应用到目标基底分支上。要注意的是:rebase 后,目标基底指向并不会发生变化,仍需要
git merge
进行快进合并。
原理
以图二来说
-
首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2。
-
然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件。
-
然后将当前分支 ( experiment ) 指向目标基底 C3。
-
最后以此将之前另存为临时文件的修改依序应用,得到新的提交结点 C4‘,并将当前分支指向 C4'。
由于新的提交结点以目标基底为父结点,所以切换到目标基底后进行 merge 操作时,执行的是快进合并。
--onto
剪切指定范围内的提交结点,并在指向的分支上对这些节点执行变基操作。
其命令格式为:
git rebase --onto base from to
其含义是:将 (from, to] 范围内的所有提交结点在 base 指向的结点之后重建
假设现在提交记录为 ( 来源于参考 2 ):
H---I---J topicB
/
E---F---G topicA
/
A---B---C---D master
执行 git rebase --onto master topicA topicB
后,其结果为:
H'--I'--J' topicB
/
| E---F---G topicA
|/
A---B---C---D master
注意上图提交后的结果:master 的指向并没有发生变化,但变基生成的新提交结点以 master 为祖先结点。因此,master 与 topicB 合并时,会执行快进合并 —— 直接将 master 的指向 J’ 结点即可。
另外,执行 rebase 后,原有的 J 结点没有任何指针指向它。为避免原有的结果丢失,可以在 topicB 处新建一分支,然后再执行 rebase。
冲突的解决
在进行变基操作时,如果出现文件冲突,解决步骤如下:
-
手动解决冲突后,通过
git add <filename>
暂存冲突文件。注意:这步不需要进行 commit。 -
使用
git rebase --continue
继续本次变基操作;或使用git rebase --abort
放弃本次变基操作。$ git rebase dev d # 此处出现冲突时的文字提示,也可以通过 git status 进行查看 $ git status rebase in progress; onto bc4cd42 You are currently rebasing branch 'd' on 'bc4cd42'. (fix conflicts and then run "git rebase --continue") (use "git rebase --skip" to skip this patch) (use "git rebase --abort" to check out the original branch) Unmerged paths: (use "git reset HEAD <file>..." to unstage) (use "git add <file>..." to mark resolution) both modified: a.txt no changes added to commit (use "git add" and/or "git commit -a") $ vim a.txt $ git add a.txt $ git rebase --continue
--continue 与 --abort
冲突解决中,最后使用 continue ,表示继续本次变基操作。
也可以使用 git rebase --abort
表示放弃本次 rebase 操作。
$ git rebase dev d
# 冲突的提示
$ git branch
* (no branch, rebasing d)
d
dev
master
$ git rebase --abort
$ git branch
* d
dev
master
可以发现出现冲突时,Git 类似于新建了一个匿名分支。当使用 --abort 选项时,Git 会直接切回原分支,那么该匿名分支将丢失,也就是放弃了本次变基操作。
-i 选项
以交互模式进行变基操作。
-
对指定范围内的提交进行变基,相当于修改指定结点范围内的所有提交。
-
指定的范围是前开后闭。如:
git rebase -i HEAD~3 HEAD~1
会以 HEAD~3 为基底,对 HEAD2,HEAD1 进行变基操作。
因为是变基,所以在执行完命令之后,当前分支会指向新生成的结点,原有的结点将丢失。因此,如果还要保留原有的结点指向,可以在执行 rebase 之前新建一个指针,或在执行命令之后通过
git reflog
找到原有的结点的 sha1 值,并建立指向(如分支,标签等)。
使用上述命令后,会出现如下界面(这个界面是 vim 格式,可以编辑保存):
pick e81ab54 add test fun
pick a686383 update test func
# Rebase 3a47216..a686383 onto 3a47216 (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
前两行未加 #,表示未注释内容。在编辑保存之后, git 会读取未注释行内容,并根据内容对每一次提交结点进行操作。
提交结点按从旧到新依次排序,即最上面的结点最先提交。另外,可以 调整行的顺序,用以修改对不同结点进行变基操作的顺序。
每一行代表一次提交。行前面的 pick 表示要对此次提交进行的操作,操作后的 sha1 值表示此次提交对应的 commit 对象,最后是本次提交时输入的提交信息。
其中 e81ab54 指向的是 HEAD~2,a686383 指向的是 HEAD~1,而 3a47216 指的是 HEAD~3。
第一行注释中 3a47216..a686383
指包含在 a686383 中,却不包含中 3a47216 中的结点,也就是 HEAD~2 与 HEAD~1,即 2 个结点。
假设 删除了某一结点所对应的行,则该结点不会进行变基操作 —— 即放弃了这个结点对应的修改。
常用操作
操作 | 含义 |
---|---|
pick | 对结点进行变基 |
reword | 对结点进行变基,但可以重新编辑提交信息 |
edit | 将变基操作暂停在指定的结点处,可以进行 修改信息,拆分结点 等操作 |
squash | 将本结点压缩到前一个结点中 |
-
使用 reword 后,会在保存变基指令后重新弹出界面,在该界面中输入指定结点的提交信息。
-
edit 类似于断点,变基进行到断点时会自动暂停,直到通过
git rebase --continue
命令放行为止。使用 edit 操作后,界面如下:$ git rebase -i HEAD~3 HEAD~1 Stopped at e81ab54bae45a53ea681ba0096085833bb5c6843... add test fun You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue $ git commit --amend [detached HEAD 350071f] add test fun2 Date: Thu Mar 29 22:00:23 2018 +0800 1 file changed, 3 insertions(+), 4 deletions(-)
当前变基操作暂定在
e81ab
结点上。此时可以修改本次结点的提交信息,还可以将本次结点拆分成多个。注意:修改完成之后,需要运行git rebase --continue
放行;如果不需要修改可以直接使用git rebase --continue
放行。-
直接使用
git commit --amend
修改该结点提交信息。 -
也可以重置暂存区,然后将修改分多次提交。如:
$ git reset HEAD^ $ git add README $ git commit -m 'updated README formatting' $ git add lib/simplegit.rb $ git commit -m 'added blame' $ git rebase --continue
上述命令首先重置暂存区(git reset HEAD^),然后将 README 与 lib/simplegit.rb 分两次提交,最后通过
git rebase --continue
放行断点,继续进行变基操作。拆解结束后,原来的一个提交结点会被拆成两个,而原来的结点将不存在。
-
-
使用 squash 操作时,第一行不能使用 squash。因为第一行没有前结点,无法合并。使用 squash 操作后,通过 git log 查看提交历史如下:
$ git log -4 commit 069e62223017380c9ec46565acb0340f99af82c3 commit 3a4721694a888a876d062ac90bca03115b3394b7 $ git log -4 commit a6863836d1d54c00035511824758a8da6dfed83b commit e81ab54bae45a53ea681ba0096085833bb5c6843 commit 3a4721694a888a876d062ac90bca03115b3394b7
第一个命令是使用 squash 时的提交记录,第二个是不使用 squash 时的提交记录。可以看出,使用 squash 时,Git 会将两个结点合并成一个。因此,上面的 log 在 3a47216 前只有一条记录,而下面的却有两条记录。
网友评论