图解Git——变基《Pro Git》

简介: 变基(rebase)是Git中用于将一系列提交“重新播放”到另一个分支上的操作,使提交历史更加线性整洁。其基本操作为`git rebase <目标分支>`,可将当前分支的修改基于目标分支重新应用;复杂场景下使用`git rebase --onto <新基底> <旧基底> <分支>`,将特定提交从旧基底移动到新基底。

变基

1. 变基的由来

  1. 回顾之前分支合并
    1. 分叉的提交历史

      编辑

    2. 通过合并操作来整合分叉的历史

      编辑

  1. 有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。 你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。

2. 变基的基本操作

  1. 切换到想要变基的分支:git checkout experiment
  2. 变基:git rebase master

3. 变基的原理

编辑

  1. 确定共同祖先:Git 首先寻找 experiment 分支和 master 分支的共同祖先提交,此例中为 C2,因为 experiment 分支从 C2 处分叉。
  2. 保存提交序列:Git 把 experiment 分支上从共同祖先 C2 之后的提交(即 C4)保存成一个序列。这些提交记录了 experiment 分支相对共同祖先的变化。
  3. 调整分支基础:Git 将 experiment 分支的指针移开,把它的基础设置为 master 分支的最新提交 C3。这一步为后续重新应用提交做准备,让 experiment 分支基于 master 分支的最新状态。
  4. 生成新提交:Git 依照保存的提交序列,将 C4 重新应用到新基础 C3 上。通过模拟 C4C3 基础上的变更效果,Git 创建一个新的提交对象 C4'。由于 C4' 的父提交是 C3(而原 C4 的父提交是 C2),尽管功能效果与 C4 相同,但它是一个全新的提交,拥有不同的哈希值。
  5. 过程图:
    1. 变基后:

      编辑

    2. 回到 master 分支,并进行一次快进合并后:

      编辑


4. 更复杂的变基应用——对两个分支进行变基(git rebase --onto <newbase> <upstream> [branch]

  1. git rebase --onto <newbase> <upstream> [branch]命令格式:
    1. <newbase>:新的基底
      1. 意义: 指定你希望“重放的提交”应用到的目标分支或提交。
      2. 作用: 这些提交会被重放到 <newbase> 指定的位置。
      3. 类型:
        1. 可以是分支名(如 master)。
        2. 可以是提交哈希(如 abc123)。
        3. 可以是一个引用(如 HEAD~2)。
    1. <upstream>:基准分支
      1. 意义: 确定从哪里开始选取要重放的提交。
      2. 作用: Git 会找到 <upstream>[branch] 的共同祖先,并选取从这个分叉点到 [branch] 之间的提交进行“重放”。
      3. 类型:
        1. 分支名(如 dev)。
        2. 提交哈希。
        3. 可以省略,默认使用 [branch] 的上游分支(即用 git branch --set-upstream-to 设置的分支)。
    1. [branch]:当前分支 :
      1. 意义: 指定操作的分支,默认为当前分支。
      2. 作用: 表示重放哪个分支的提交。
      3. 类型:
        1. 分支名(如 feature)。
        2. 如果省略,则默认使用当前所在的分支。
  1. 模拟你创建了一个主题分支 server,为服务端添加了一些功能,提交了 C3C4。 然后从 C3 上创建了主题分支 client,为客户端添加了一些功能,提交了 C8C9。 最后,你回到 server 分支,又提交了 C10

    编辑

  2. 截取主题分支上的另一个主题分支,然后变基到其他分支:

    1. 假设你希望将 client 中的修改合并到主分支并发布,但暂时并不想合并 server 中的修改。

    2. 这时使用 git rebas 命令的 --onto选项,选中在 client 分支里但不在 server 分支里的修改(即 C8C9),将它们在 master 分支重放:git rebase --onto master server client

    3. 翻译一下这个命令: “取出 client 分支,找出它从 server 分支分歧之后的补丁, 然后把这些补丁在 master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样”。这理解起来有一点复杂,不过效果非常酷。

    4. 编辑

    5. 快进合并 master,使之包含来自 client 分支的修改

      1. git checkout master + git merge client

      2. 快进合并后:

        编辑

  1. server 中的修改变基到 master 上—— $ git rebase master server
    1. git rebase <basebranch> <topicbranch>简化了先切换到 server分支,再对其执行变基。

    2. server 变基后:

      编辑

  1. 最终的历史提交并删除 clientserver 分支
    1. 快进合并主分支 mastergit checkout master + git merge server

    2. 删除 clientserver 分支:git branch -d client + git branch -d server

    3. 最终的提交历史:

      编辑


5. 变基的风险

奇妙的变基也并非完美无缺,要用它得遵守一条准则:

如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。(

  • 如果你的提交已经被其他开发者获取并使用,避免用 rebase 改变历史。
  • 使用 rebase 的原则是: 只能在自己控制的范围内操作 ,避免影响其他人。)

如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。

5.1. 变基问题示例

让我们来看一个在公开的仓库上执行变基操作所带来的问题。 假设你从一个中央服务器克隆然后在它的基础上进行了一些开发。 你的提交历史如图所示:

编辑

Figure 44. 克隆一个仓库,然后在它的基础上进行了一些开发

然后,某人又向中央服务器提交了一些修改,其中还包括一次合并。 你抓取了这些在远程分支上的修改,并将其合并到你本地的开发分支,然后你的提交历史就会变成这样:

编辑

Figure 45. 抓取别人的提交,合并到自己的开发分支

接下来,这个人又决定把合并操作回滚,改用变基;继而又用 git push --force 命令覆盖了服务器上的提交历史。 之后你从服务器抓取更新,会发现多出来一些新的提交。

编辑

Figure 46. 有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交

结果就是你们两人的处境都十分尴尬。 如果你执行 git pull 命令,你将合并来自两条提交历史的内容,生成一个新的合并提交,最终仓库会如图所示:

编辑

Figure 47. 你将相同的内容又合并了一次,生成了一个新的提交

此时如果你执行 git log 命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。 此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来,这会令人感到更加混乱。 很明显对方并不想在提交历史中看到 C4C6,因为之前就是他把这两个提交通过变基丢弃的。

5.2. 用变基解决变基

如果你 真的 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。 如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。

实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和——即 “patch-id”。

如果你拉取被覆盖过的更新并将你手头的工作基于此进行变基的话,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。

举个例子,如果遇到前面提到的 有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交 那种情境,如果我们不是执行合并,而是执行 git rebase teamone/master, Git 将会:

  • 检查哪些提交是我们的分支上独有的(C2,C3,C4,C6,C7)
  • 检查其中哪些提交不是合并操作的结果(C2,C3,C4)
  • 检查哪些提交在对方覆盖更新时并没有被纳入目标分支(只有 C2 和 C3,因为 C4 其实就是 C4')
  • 把查到的这些提交应用在 teamone/master 上面

从而我们将得到与 你将相同的内容又合并了一次,生成了一个新的提交 中不同的结果,如图 在一个被变基然后强制推送的分支上再次执行变基 所示。

编辑

Figure 48. 在一个被变基然后强制推送的分支上再次执行变基

要想上述方案有效,还需要对方在变基时确保 C4'C4 是几乎一样的。 否则变基操作将无法识别,并新建另一个类似 C4 的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。

在本例中另一种简单的方法是使用 git pull --rebase 命令而不是直接 git pull。 又或者你可以自己手动完成这个过程,先 git fetch,再 git rebase teamone/master

如果你习惯使用 git pull ,同时又希望默认使用选项 --rebase,你可以执行这条语句 git config --global pull.rebase true 来更改 pull.rebase 的默认配置。

如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。

如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 git pull --rebase 命令,这样尽管不能避免伤痛,但能有所缓解。

6. ⭐变基总结

  1. 什么是变基:
    • 将一系列提交“重新播放”到另一个分支上,改变提交历史,使其更线性。
  1. 基本操作:
    • git rebase <目标分支>:将当前分支的修改基于目标分支重新应用。
    • git rebase --onto <新基底> <旧基底> <分支>:将特定提交从旧基底移动到新基底。
  1. 优点:
    • 提交历史更加整洁、直线化。
    • 避免了不必要的合并提交,便于代码审查。
  1. 风险:
    • 如果变基已推送的提交,可能破坏其他开发者的工作(引发冲突和混乱)。
  1. 使用建议:
    • 可以使用: 对未推送的本地分支清理历史,或整理提交后再推送。
    • 避免使用: 对已共享的公共分支执行变基。
  1. 常用配置:
    • 默认拉取使用变基:git config --global pull.rebase true
  1. 注意事项:
    • 若必须在公共分支变基,请通知团队使用 git pull --rebase
    • rebase 是一种改写历史的操作,改写的历史如果已经被其他人使用,会造成混乱。

目录
相关文章
|
9天前
|
安全 Shell Linux
Git 基础——《Pro Git》
Git 是一个分布式版本控制系统,支持多种操作来管理和跟踪代码库的变化。以下是常用的操作和命令:
82 37
|
13小时前
|
安全 开发工具 git
图解Git——分支管理《Pro Git》
分支管理是 Git 中的重要机制,支持并行开发和清晰的工作流。常用命令包括:`git branch` 列出所有分支,`git branch -v` 查看最后一次提交,`git branch --merged` 和 `git branch --no-merged` 分别查看已合并和未合并的分支。创建新分支用 `git branch &lt;branch-name&gt;`,删除分支用 `git branch -d`(已合并)或 `-D`(强制删除)。建议定期清理已完成任务的分支,保持代码库整洁,并使用有意义的分支命名规范。注意强制删除未合并分支时可能丢失数据。
13 0
|
13小时前
|
开发工具 git 开发者
图解Git——分支简介《Pro Git》
Git 分支是其核心特性之一,允许开发者从主开发线分离工作,避免干扰主线。传统版本控制系统创建分支效率低,而Git的分支创建和切换非常轻量高效。
15 3
|
13小时前
|
网络安全 Apache 开发工具
图解Git——服务器上的Git《Pro Git》
Git 远程仓库及通信协议简介:远程仓库为团队协作提供平台,支持共享代码。常见形式为裸仓库,仅保存 Git 元数据。Git 支持多种协议,包括本地协议(适合局域网)、HTTP/HTTPS(推荐智能 HTTP,安全易用)、SSH(企业内部协作首选)和 Git 协议(高效只读访问)。选择协议需根据协作需求、安全性和配置难度权衡。此外,搭建 Git 服务器涉及创建裸仓库、上传至服务器、初始化共享仓库等步骤。生成 SSH 公钥、配置服务器及使用 GitWeb 或 GitLab 等工具可进一步增强功能。第三方托管服务如 GitHub 提供便捷的托管选项,适合快速启动和开源项目。总结而言,自行运行服务器提
11 1
|
13小时前
|
开发工具 git 开发者
图解Git——分布式Git《Pro Git》
分布式工作流程主要分为三种模式:集中式工作流、集成管理者工作流和主管与副主管工作流。集中式工作流中,所有开发者同步同一个中央仓库,通过拉取和提交协作;集成管理者工作流中,开发者拥有自己的仓库,通过 `fork` 和请求合并进行协作;主管与副主管工作流适用于大型项目,由主管最终合并代码,副主管负责各自模块。贡献代码时,需考虑活跃贡献者数量、工作流程、提交权限等因素,确保代码合并成功并遵循提交准则。
11 1
|
13小时前
|
存储 缓存 Java
图解Git——远程分支《Pro Git》
远程分支是 Git 中用于管理分布式协作的关键概念。远程引用指向远程仓库中的分支和标签,常用 `git ls-remote` 或 `git remote show` 查看。日常开发中,通常使用远程跟踪分支(如 `origin/main`)与远程分支交互,简化远程仓库状态的管理和使用。远程跟踪分支记录远程分支的状态,但本身只读。
9 0
|
Java Unix Linux
Git学习
Git学习
82 0
YI
|
安全 IDE Java
Git学习总结(上)
以前在学校提交作业时使用过Git这个工具,感觉自己的使用没有发挥出它的全部优势,所以最近利用假期时间认真学习了一下Git。Git的分布式特性和回滚、分支等操作十分实用,特此记录下Git学习过程。
YI
104 1
YI
|
Ubuntu 开发工具 git
Git学习总结(下)
以前在学校提交作业时使用过Git这个工具,感觉自己的使用没有发挥出它的全部优势,所以最近利用假期时间认真学习了一下Git。Git的分布式特性和回滚、分支等操作十分实用,特此记录下Git学习过程。
YI
64 0
|
Shell 网络安全 开发工具