rebase

作者: 一江碎月 | 来源:发表于2018-03-11 14:34 被阅读0次

注意事项

  1. 参考1,图片也来源于该链接;参考2

  2. 变基操作的 实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交

  3. 使用变基操作的原则是:只对尚未推送或分享给别人的本地修改执行变基操作,从不对已推送至别处的提交执行变基操作。这是因为变基会丢弃一些提交。如果这些提交已经分享给别人,那么再执行变基操作后将导致别人需要整合。

  4. rebase 不会合并提交结点。假设 rebase 之前有三个结点, rebase 时会生成三个相应的结点,并不会将三个结点合并成一个。

  5. 变基命令是在推送前清理提交使之整洁的工具,千万不要对已经共享的结点进行变基操作。

与 merge 比较

rebase 与 merge 一样,都用于分支合并。

  1. merge 执行的是三方合并。merge 会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。如下图:

    merge 合并
  2. 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 分支。

  3. merge 时主分支会有并行结构,即某个结点可能会有多个父结点。但使用 rebase 时,所有的结点都是串行的。

  4. rebase 时,rebase 后跟的是目标基底,会将当前分支的修改应用到目标基底分支上。要注意的是:rebase 后,目标基底指向并不会发生变化,仍需要 git merge 进行快进合并。


原理

以图二来说

  1. 首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2。

  2. 然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件。

  3. 然后将当前分支 ( experiment ) 指向目标基底 C3。

  4. 最后以此将之前另存为临时文件的修改依序应用,得到新的提交结点 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。


冲突的解决

在进行变基操作时,如果出现文件冲突,解决步骤如下:

  1. 手动解决冲突后,通过 git add <filename> 暂存冲突文件。注意:这步不需要进行 commit。

  2. 使用 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 选项

以交互模式进行变基操作。

  1. 对指定范围内的提交进行变基,相当于修改指定结点范围内的所有提交

  2. 指定的范围是前开后闭。如:

    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 将本结点压缩到前一个结点中
  1. 使用 reword 后,会在保存变基指令后重新弹出界面,在该界面中输入指定结点的提交信息。

  2. 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 放行断点,继续进行变基操作。

      拆解结束后,原来的一个提交结点会被拆成两个,而原来的结点将不存在。

  3. 使用 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 前只有一条记录,而下面的却有两条记录。

相关文章

  • 三、git merge与rebase区别

    merge与 rebase 都是作为合并分支 a. rebase:多一步rebase;b.rebase:不能有代...

  • Git~rebase

    git rebase rebase简介 rebase, 意思为变基,即改变分支的的根支。提到rebase就不得不说...

  • git整体学习

    基础 1. git ... 3. git rebase 第二种合并分支的方法是 git rebase。Rebase...

  • git8~rebase

    2019.06.25 git rebase git stash git pull --rebase git sta...

  • Git错误信息解决记录

    Git error: previous rebase directory .git/rebase-apply st...

  • git rebase和merge区别

    merge和rebase 标题上的两个命令:merge和rebase都是用来合并分支的。 这里不解释rebase命...

  • Git 批量合并Commit或commit信息

    git rebase -i HEAD~3 git rebase -i [commitId-a] [commitId...

  • 如何正确rebase

    git rebase -i origin/分支 合并冲突 git status git rebase --cont...

  • Git使用教程

    拉取代码的正确步骤 git fetch git rebase git rebase --continue(遇到冲突...

  • git merge conflict

    git pull --rebase编辑confilct的文件,修改完毕后git addgit rebase --c...

网友评论

      本文标题:rebase

      本文链接:https://www.haomeiwen.com/subject/ioqwfftx.html