Git:rebase 是什么

原文:Part 3 - What is a rebase?

接上文 Git: commit 中的 hash 是什么

在上文中,我们讨论了commit hash是什么。我们了解到的关于commit的一个重要方面是它们不能被改变。hash本身是由存储在commit中的信息生成的,因此要修改commitcommit hash,必须创建一个全新的commit。我们还讨论了每个commit都存储上一个commithash。我们没有讨论的是这对我们的Git历史有什么影响。

由于commit hash是根据存储的信息生成的,并且部分信息是前一次commithash,因此几乎不可能修改提交历史记录。每一个提交就像是一个链条中的一个链环,它是依据前一个链环锻造的。
在这里插入图片描述

如果你像上面那样从金属链上拆下一个链环,就不可能在不破坏它们的情况下重新连接上一个链环和下一个链环。然而,在Git的上下文中,情况更糟;这个类比在这里被打破了,因为在一个金属链中,你可以建立一个新的链环,将前一个链环和下一个链环连接在一起。在Git中不能这样做;在这里可以重新锻造的唯一链环必须是完全相同的链环,包含完全相同的信息,包括以前的提交哈希。只有精确的提交才能具有链中的下一个链环所引用的commit hash

如果要在提交历史记录中间删除一个提交,那么下一个提交将引用一个不再存在的commit hash。由于在不更改commit的hash的情况下无法更改该提交,因此不能简单地生成引用上一次提交的新提交,因为链中的下一次提交引用了原始提交的hash

如果您更改了一个提交的一小部分,那么为它生成的hash将是不同的,并且链中的下一个提交将不再引用新的提交。您必须更改下一个提交以引用新生成的commit hash,这也会导致该提交的哈希发生更改。一直到链子的末端。

这就是rebase发挥作用的地方。如果您还记得第2部分中的内容,在我们将feature1分支合并为master之后,在我们的图表中留下了一个叉,显示了我们所有的提交是如何与彼此相关的。

在这里插入图片描述

合并可以很好地工作,但是存储库图可以很快失去对所有分支和交叉提交关系的控制。下面只是我的一个仓库的一个小片段。
在这里插入图片描述

如果您使用Git的图形界面,那么很有可能看到类似的东西。合并是在分支之间移动更改的最简单方法,因为它们避免破坏提交历史链,并减少了令人头疼的问题。然而,一旦你对rebase的工作有了一个强有力的理解,你就开始对它有所欣赏。例如,如果我们将演示存储库中的feature1分支以master分支为基rebase,那么我们将得到一个干净的历史记录,如下所示:
在这里插入图片描述

请注意,我们的历史又是一条简单的直线。Git做了什么魔术才能让这成为可能?如果您还记得之前的内容,我们的commit 3和commit 4将共享commit 2作为一个共同的祖先提交。提交3引用了提交2,因为它是前一次提交。现在您可能想知道为什么commit 3commit 4列为它的previous commit

还记得我说过的话吗?如果你在中间断了锁链,那么你就必须从头到尾重建所有的commit。这正是rebase所做的。

在这里插入图片描述

如果你仔细观察,会发现commmit 3commit 5commit 6commit hash都已更改。这是对我们的feature1分支作出的三项commit。通过将我们的feature1分支rebasemaster上,Git能够将我们的分支倒回到它最初与master分离的地方。它将分支上每个提交的diff存储在一个临时文件中。然后它开始重写我们的分支历史,但这次是从master上最新的commit,也就是commit 4开始。

Git为我们分支上的每个提交创建了全新的提交,并彻底使用全新的commit hash 。当它创建新的commit时,它会更改我们分支上的第一个提交,以便它现在引用master上的最新提交作为previous commit。将更改重新提交为新提交的过程称为以master为基础重新commit

注意:不要被术语混淆。以master 变基 不会修改master。这意味着你的分支提交将在master的最后一个commit之后立即执行。

在这里插入图片描述

在上面的屏幕截图中,您会注意到master指针仍然指向commit 4,其commit hash没有更改。如果我们现在切到master并将feature1合并到master,我们将无法获得合并提交。这将是一个简单的快进合并,这意味着Git只需将主分支指针向上移动我们现在的直线提交引用,以便它指向与feature1分支指针相同的commit
在这里插入图片描述
如果不将feature1合并到master中,我们决定做更多的工作并进行更多的提交,我们将在图中再次创建一个分叉。我们的下一个提交给master将引用commit 4作为它的祖先,而我们的feature1分支的第一个提交也将引用commit 4作为它的祖先。为了再次得到一条直线,我们需要检查feature1,并再次rebasemaster上。如果您曾经通过Github提交了一个pull request并过时的时候,那么基本上就是这样。如果项目维护人员没有将拉请求合并进来,而是继续在项目上做更多的工作,那么这个pull request需要rebase来包含完整的的Git历史记录。将您的工作rebaserepository/branch 上使得 Pull request简单地将其分支指针快速转移到分支接受后指向的同一提交。接受 Pull request只是一个简单的合并。如果您在提交请求之前进行了rebase,并且在提交更多请求之前将其合并,那么合并将是一个 fast-forward 的合并,并将保持原始存储库的整洁。

DANGER, DANGER WILL ROBINSON!

在这里插入图片描述

到目前为止,我已经向您展示了如何将一个功能分支以master为基准rebase,而master不会修改已经在master上的任何提交。rebase只会修改功能分支的所有提交。但是,如果你试图通过以功能分支为基来变基master,那么你就是在自找苦吃

假设你的主分支要通过克隆仓库的方式与所有人共享。如果你在master或任何其他共享分支上push一堆提交,然后我将这些提交pull到我的机器上,那么现在我可以继续执行我的工作,并在你的提交之上提交新的更改。我的第一次提交现在将引用你的最后一次提交作为它的previous commit

假设您对本地master分支进行了五次提交,因为feature1分支与之不同,有三次提交。我们还将假装您将master上的提交推送到了remote repository(远程仓库)中。

在这里插入图片描述

现在你做了一个错误的操作,以feature1为基本变基master而不是以master为基本变基feature1,现在你有了一个整洁的历史。但是,你会注意到,origin/master似乎位于一个奇怪的分叉上。
在这里插入图片描述

如果你现在试图把你的主分支push上去,Git会拒绝。Git认为你的远程master上有五个个commit 需要你pull。它还认为有八个commit需要push
在这里插入图片描述

如果你试图在这种情况下pull,事情会很快令你发毛。你拉下来的五个commit的是旧的commit,它们的的commit hash和你rebase之前的commit hash 是一样的。
在这里插入图片描述

讨厌!(Yuck!)

"Aha!" you say, "I’ll simply git push --force and all will be well!"

在这里插入图片描述

DON’T YOU DARE!

通过强制推送,您只是将这个问题丢给了别人,因为别人正在基于你的旧的commit hash工作。当前,我对您的存储库的克隆如下所示。
在这里插入图片描述

--force将告诉远程存储库只需丢弃那些旧的提交,并使用你的提交。现在,下次我pull你的仓库的时候,我会处理你所造成的混乱。
在这里插入图片描述

我现在有奇怪的合并冲突和奇怪的重复提交。我不知道我应该在哪里继续我的工作,也不知道我的工作将如何与你的工作同步。

这就是人们需要谨慎使用rebase的原因,同样也需要谨慎使用git push --force。解开一根钢筋并不容易,而且常常是不可能的,所以你真的需要注意你在做什么。rebase的好处是很好的,除非你不知道自己在做什么的。如果不小心,可能会导致大量工作丢失,因为您必须将仓库回退到没有rebase时的提交,并且无法从其他地方恢复它们。

在我们设想的糟糕场景中,修复方法是将存储库resetfeature1分支上的最后一次提交。然后,您需要从远程存储库中进行一次pull,以恢复您在rebase之前推到那里的五个主提交。然后,您可以切到feature1分支,并正确地执行REBASE。

我们假设的情况很容易恢复,但是如果你失去冷静,开始做事情而不知道你在做什么,那么你很容易永远失去这五项commit。如果你git push --force并丢弃你的远程存储库中最初的五个提交,那么在你看来,一切都会像在前一个屏幕截图中一样漂亮。也就是说,直到你的项目参与者开始在issue或消息列表中诅咒你。这五个旧的commit将是敬酒,你恢复它们的唯一方法,将是找一找,谁之前pull过这五个原始commit,并从他那里恢复。

底线:除非您知道没有其他人在远程存储库或分支上工作,否则不要强制推送。在我工作的地方,每个开发人员都有自己的远程仓库的fork,我们将提交包含我们自己修改的pull request。在我已经有一个挂起的pull request之后,解决一个小bug是很常见的。

我经常会修复这个bug,并使用commmit--amend修改最后一个与git commit,这相当于rebase最近的commit,并给它一个新的commit hash。我没有问题强制将更改推到我的fork,并用旧的hash清除旧的commit,因为我知道我是唯一一个处理该fork的人,我的pull request尚未合并。如果它已经被合并了,那么我就不能再将这个经过修改的commit 推到我们的主仓库了,因为原来的提交已经被合并了,而其他人可能会将其拉下来。如果我在这一点上push --force并打开一个新的pull request,我可能会给我的团队中的其他人带来问题。

小心翼翼rebase。一旦你掌握了它的窍门,你就不会再掉进这样的陷阱了。小心点,注意你在做什么。rebase的障碍起初几乎总是很小的障碍;当用户试图“修复”它并且不知道他们在做什么时,它们只会变成厄运的黑洞。

记住,将一个分支rebase到另一个分支上的简单方法是:断开当前分支分叉的链环,并开始将全新的链环锻造到另一个分支的末端,以便每个链环接仅连接到一个先前的链环,而没有两个链环连接到同一个先前的链环。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值