5. Git 变基操作

变基 是指将一个分支开始的一系列提交对象或从某个提交对象开始的一系列提交对象的所有提交重新应用到一个新的提交对象上重新提交以实现整理提交结果的目的。

变基的目的:

  1. 合并提交历史:将当前分支的提交转入到目标分支的最新提交之后,形成一条直线的提交历史,方便提交的远程分支中,并能保留分支的提交历史。
  2. 整理本地提交:将本地的多个分支整理成一个分支。
  3. 合并或删除同一个分支之前的提交节点。

在 Git 中整合来自不同分支的修改主要有两种方法:git merge(合并)和 git rebase(变基)。

变基操作需要使用 git rebase 命令。

git rebase 命令

作用: 在另一个分支上重新应用提交。

命令格式

git rebase [选项] [旧基准] [基底分支]

说明

  1. 基底分支是要作为变基后作为基底的分支,新分支以此分支的最后一个提交对象作为基础。默认是当前分支。
  2. 旧基准 可能是 需要变基分支 或者 提交对象
    • 旧基准 是需要变基分支时,表示该分支从基底分支分叉处的所有提交。
    • 旧基准 是提交对象时,表示该提交对象到 基底分支 的所有提交。

常用选项

选项
说明
-i--interactive
交互式变基,重新提交历史。
--onto <新基底>
变基到某个新基底(某个分支或提交对象)。
--abort
中止变基,恢复到变基之前的状态。
--continue
继续没有完成的变基操作。
--skip
跳过导致合并冲突的提交。

下面我们举例说明变基的思想和变基的方法。

示例1:

现在我们有如下图的六次提交 ABCDEF,其中每次提交 都会提交一个文件。如: A 这次提交增加一个 a.txtA 这次提交增加一个 b.txt,以此类推。

A---B---C  master
     \
      D---E---F  myb2

现在分支 master 的最终状态是有三个文件 a.txtb.txtc.txt,而 myb2 分支的最终状态则有五个文件 a.txtb.txtd.txte.txtf.txt

myb2 分支是在 B 提交后进行的分支,因此 myb2 分支不存在 c.txt 这个文件。

下载上述示例仓库

myb2 分支变基到 master 分支

如果此时你在 myb2 分支中,你可以使用 git rebase master 命令将 myb2 分支变基到 master 分支。

如果你不在 myb2 分支中,你可以使用 git rebase master myb2 命令同样可以将 myb2 分支变基到 master 分支。

变基完成后会切换到 myb2 分支。

执行结果如下:

$ git rebase master myb2
Successfully rebased and updated refs/heads/myb2.

变基完成后,即形成如下的结果:

A---B---C  master
         \
          D'---E'---F'  myb2

变基后的 D'E'F' 提交对象是是 DEF提交对象复制品,并提交哈希值也不相同。以前的提交对象 DEF 依旧存在,可以通过 git reflog 查看。

变基后 myb2 分支的最终状态已经包含了 C 提交的 c.txt 这个文件(此时是 6 个文件)。

上述操作如果在 master 分支的 C 提交和 myb2 分支的 DEF 提交修改的公共的文件,变基操作可能会失败。出现如下提示。

$ git rebase master myb2
Auto-merging b.txt
CONFLICT (content): Merge conflict in b.txt
error: could not apply 8e7f7c7... 修改了 b.txt
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 8e7f7c7... 修改了 b.txt

上述提示是在 变基时 因为两个分支同时修改了 b.txt 而产生了变基的冲突。因此要手动解决变基后 b.txt 的冲突。方法有如下几种:

  1. 如果你想放弃这次变基,则使用 git rebase --abort 恢复到变基之前的状态。
  2. 如果你手动解决了 b.txt 的冲突,你可以使用 git add/rm 将冲突文件提交到暂存区,然后运行 git rebase --continue 继续你的变基操作。
  3. 也可以使用 git rebase --skip 命令跳过导致合并冲突的提交。

master 分支变基到 myb2 分支

重新解压缩上述示例代码

如果此时你在 master 分支中,你可以使用 git rebase myb2 命令将 master 分支变基到 myb2 分支。

如果你在其它分支中,你可以使用 git rebase myb2 master 命令同样可以将 master 分支变基到 myb2 分支。

变基完成后会切换到 master 分支。

git rebase myb2 master 等同于 git switch master 然后在执行 git rebase myb2

执行结果如下:

$ git rebase myb2
Successfully rebased and updated refs/heads/master.

变基前

A---B---C  master
     \
      D---E---F  myb2

变基后

A---B           C‘  master
     \         /
      D---E---F  myb2

此次变基后 myb2 分支没有变化,但 master 分支的的最终结果是含有 6 个文件a.txt ... f.txt

此时分支 master 的历史记录是 A-B-D-E-F-C'myb1 分支的提交历史是 A-B-D-E-F

示例2

变基到上游分支

在使用 git rebase 进行变基时,可以使用 --onto <新基底> 选项将变基后的结果应用到某个新基底的提交对象上。

假设现在有这样一个提交情况:

                        H---I---J myb4
                      /
              E---F---G  myb3
            /
A---B---C---D  master

下载此示例仓库

则使用 git rebase --onto master myb3 myb4 进行变基,变基后的结果如下:

如果当前分支是 myb4 则可以使用 git rebase --onto master myb3 命令。

             H'--I'--J'  myb4
            /
            | E---F---G  myb3
            |/
A---B---C---D  master

使用 git log --graph --oneline --all 可以查看结果如下:

weimingze@mzstudio:~/rebase_demo2$ git log --graph --oneline --all
* d474c64 (HEAD -> myb4) J
* c74a2de I
* 640ca4d H
* 5f9fd7d (myb3) G
* 9c3c5c9 F
* 6628d53 E
* 1c200fe (master) D
* 80d8724 C
* f29bb2c B
* 355d87c A
weimingze@mzstudio:~/rebase_demo2$ git rebase --onto master myb3
Successfully rebased and updated refs/heads/myb4.
weimingze@mzstudio:~/rebase_demo2$ git log --graph --oneline --all
* 419fbc3 (HEAD -> myb4) J
* 077ac87 I
* a036bff H
| * 5f9fd7d (myb3) G
| * 9c3c5c9 F
| * 6628d53 E
|/
* 1c200fe (master) D
* 80d8724 C
* f29bb2c B
* 355d87c A

如果 原来的 myb4 分支几乎没有在 myb3 分支上进行改动,则此中做法很高效且实用。

上述命令 git rebase --onto master myb3 myb4master 是指 master 分支的最后提交对象 D 作为新基底,代替默认的 myb4myb3 是指 myb3 最后的提交对象 Gmyb4 是指 myb4 最后的提交对象 J,上述会将从 G 开始(不包含G)到 J 的结束(包含J)分支移动到 D 提交对象之后。

示例3

同一分枝变基

现在有这样一组分支。

A---B---C---D---E---F---G  master

下载此示例仓库

上述提交中的 C 中,由于操作失误,错将中间生成的占用空间很大的临时资源文件错误提交到了 C 中。然后在 D 提交时又删除了此资源文件。后续 EFG是正常提交。如果将此分支直接推送到远处的合作分支,则远处合作分支会多出一些垃圾文件,到时所有拉取此远处仓库的合作者的本地仓库也会变大。

这种情况可以将 E 及之后的所有提交变基到 B即可,而后的 CD 则会失去引用。

在使用 git rebase 进行变基时,可以使用 --onto <新基底> 选项将分支的后续节点变基到之前的节点达到上述目的。

在当前分支 master 下执行 git rebase --onto HEAD~5 HEAD~3git rebase --onto HEAD~5 HEAD~3 master 命令。

变基后的结果

A---B---E'---F'---G'  master

使用 git log --graph --oneline --all 可以查看结果如下:

weimingze@mzstudio:~/rebase_demo3$ git log --graph --oneline --all
* 8498821 (HEAD -> master) G
* 18b5f87 F
* 0aee6a4 E
* 6753c83 D
* 2052505 C
* 95a244a B
* 297e5a2 A
weimingze@mzstudio:~/rebase_demo3$ git rebase --onto HEAD~5 HEAD~3
Successfully rebased and updated refs/heads/master.
weimingze@mzstudio:~/rebase_demo3$ git log --graph --oneline --all
* 2959bca (HEAD -> master) G
* dd86199 F
* dbc69b1 E
* 95a244a B
* 297e5a2 A

变基的限制

不要对已推送到远程仓库且与它人共享的分支进行变基。

实验:

下载上述示例的仓库,尝试使用 git rebase 命令达成你自己的变基目标。