【问题标题】:Git rebase loses history, then why rebase?Git rebase 失去了历史,那为什么要 rebase 呢?
【发布时间】:2011-07-12 04:06:15
【问题描述】:

在过去的几天里,我一直在研究使用 Git 进行变基。大多数关于变基的论点都说它清理了历史并使其更加线性。如果你做简单的合并(例如),你会得到一个历史记录,显示历史记录何时分歧以及何时重新组合在一起。据我所知,变基删除了所有历史记录。问题是:为什么不希望 repo 历史反映代码开发的所有方式,包括它在哪里以及如何分歧?

【问题讨论】:

  • 这就是为什么有人说你不应该经常使用git rebasepaul.stadig.name/2010/12/… 可能值得您阅读。
  • @MatrixFrog 谢谢。我认为我倾向于同意他的观点,但我想在某些情况下此类工具(git rebase 和朋友)也很有用。

标签: git version-control git-rebase git-merge


【解决方案1】:

组织你的历史是使用 rebase 而不是合并的重点,它非常有价值。

准确反映过去每一次代码更改的 git 历史记录有什么用?您是否需要这样的东西来进行某种认证工作?如果你不这样做,你为什么想要那个?真实发生的过去是混乱且难以理解的。我的意思是,为什么不包括您在编辑文件时编写然后删除的每个字符?

使用 git 历史的最常见方式是阅读它。 查找导致问题的提交和探索文件的不同版本可能是两个最常见的用例。 当你的 git 历史是直截了当(而且干净!)时,这两个用例都会变得更加简单和方便。

也许比使用 rebase 与团队其他成员共享更改更重要的是,每个成员都应该使用 rebase 将他们的更改格式化为自包含提交的逻辑集合。 开发不会自然而然地发生在彼此直接遵循的逻辑步骤中。 有时你只是在你的分支上推送一个提交,只是因为它是一天的结束,你必须离开。 将此类信息放入您的 git 历史记录是纯粹的噪音。 我经常将一个需要 20 次提交的功能压缩到只有一两次,因为没有必要显示任何最终没有成为成品的一部分。

即使您的功能的开发历史是一团糟,您也可以并且绝对应该制作一个乌托邦的 git 历史。 你第一次以正确的顺序把所有东西都做好了,你在第一天做了功能 A,在第二天做了功能 B,没有错误或临时打印语句。 你为什么要这样做? 因为阅读您的更改的人更容易理解。

如果你将这个想法与 git bisect 结合起来,那么整理你的主历史记录以仅包含通过当时定义的所有测试的提交变得更有帮助。 找到错误的起源点很简单,因为 git bisect 可以正常工作。 如果您使用合并并将每个分支的整个开发历史上传到 master,则 bisect 没有实际帮助的机会。

【讨论】:

    【解决方案2】:

    如果您在公共存储库上犯了错误,并且还没有人从中分叉/合并/拉出,您可以挽回面子和混乱:

    git reset --hard [SHAnumber]
    
    git rebase -f master
    
    git push -f origin HEAD:master
    

    清空垃圾箱:

    git gc
    

    【讨论】:

      【解决方案3】:

      假设您正在从事一项统治世界的秘密计划。这个阴谋有三个主谋:

      • 天才
      • 将军
      • 电脑黑客

      他们都同意在 1 周内来到他们的秘密基地,每个人都有 1 个详细的计划。

      电脑黑客,作为一个务实的程序员,建议他们使用 Git 来存储计划的所有文件。每个人都会 fork 初始项目 repo,他们将在一周内合并。

      他们都同意,在接下来的几天里,故事是这样的:

      天才

      他总共进行了 70 次提交,每天 10 次。

      将军

      他监视了他们同志的回购并制定了击败他们的策略。他在最后一天做了 3 次提交。

      电脑黑客

      这位务实的程序员使用了分支。他制定了 4 个不同的计划,每个计划都在一个分支上。每个分支都被重新定位为一个提交。

      七天过去了,小组再次开会,将所有计划合并为一个杰作。他们都渴望开始,所以他们都试图自己合并所有的东西。

      故事是这样的:

      天才

      他合并了 General 的 repo 和 Computer Hacker 的所有更改。然后,作为一个逻辑爱好者,他看了看日志。他希望看到一个想法的逻辑演变,其中的东西是在以前的想法提交的基础上构建的。

      但是日志显示的是,在时间线上混合了无数不同想法的提交。仅通过阅读提交时间线,一位读者无法真正理解提交的演变和推理。

      所以他以一团糟,即使是天才也无法理解。

      将军

      一般思想:分而治之!

      因此他将 Genius 的 repo 合并到他的 repo 中。他查看了日志,看到了 Genius 想法的一堆提交,这些提交遵循一个不稳定的进展,直到最后一天。最后一天,将军和天才的想法混杂在一起。

      他正在监视计算机黑客并且知道 Rebase 解决方案。所以他对自己的想法做了一个rebase,然后再次尝试合并。

      现在日志每天都显示出一个合乎逻辑的进展。

      电脑黑客

      这位务实的程序员为 Genius 想法创建了一个集成分支,为 General 想法创建了另一个分支,为他自己的想法创建了另一个分支。他对每个分支都做了一个变基。然后他将所有内容合并到master中。

      他的所有队友都看到他的日志很棒。这很简单。乍一看,它是不稳定的。

      如果一个想法引入了一个问题,那么很清楚是在哪个提交中引入的,因为只有一个。

      他们最终征服了整个世界,并且不再使用 Subversion。

      大家都很开心。

      【讨论】:

      • "他对每个分支都做了一个 rebase。然后他将所有分支合并到 master 中。" - 他不应该变基然后一次合并一个吗?我的意思是,在合并一个分支之后,需要对其他分支进行 rebase 才能获得最简单的历史记录,对吗?
      • 随之而来的是一个人General Evil Hacker Genius,并设置了一个后门并以master为基础,所以看起来变化来自其他3个傀儡之一。没有人注意到,他们发布了代码……不受限制地访问数据库。
      【解决方案4】:

      据我所知,变基会删除所有历史记录。

      这是不正确的。顾名思义,变基改变了提交的 base。通常在该过程中不会丢失任何提交(除非您没有获得合并提交)。虽然您关于将开发过程的所有内容都保留在历史记录中的观点是正确的,但这通常会导致令人困惑的历史记录。

      尤其是与其他人一起工作时,每个人都在自己的分支上工作,同时需要其他人的某些更改才能继续(例如 A 要求 B 实现某些东西,以便 A 可以在自己的开发中使用该功能),这会导致许多合并。比如这样:

           #--#--#--#--*-----*-----------------*---#---\         Branch B
          /           /     /                 /         \
      ---#-----#-----#-----#-----#-----#-----#-----#-----*       Branch A
      

      在这个例子中,我们有一个分支,它暂时独立工作,但不断从原始分支中提取更改(# 是原始提交,* 是合并)。

      现在,如果我们在重新合并之前对分支 B 进行变基,我们可以得到以下结果:

                                   #--#--#--#--#---\         Branch B
                                  /                 \
      ---#---#---#---#---#---#---#---#---------------*       Branch A
      

      这表示相同的实际更改,但 B 已重新基于 A 上的某个较旧的提交,因此不再需要之前在 B 上完成的所有合并(因为这些更改已经存在于该较旧的提交中)。现在缺少的所有提交都是合并,通常不包含有关开发过程的任何信息。 (请注意,在此示例中,您还可以稍后在 A 上重新提交最后一次提交,以获得一条直线,从而有效地消除对第二个分支的任何提示)

      【讨论】:

      • 所以,有些数据 丢失了,但只有与开发无关的数据?没有理由看到这些额外的合并,因为它们没有提供任何有意义的信息。对吗?
      • 是的,他们基本上提供的唯一信息是它所基于的分支。并且在变基时,您将这些信息间接地放入其他提交中。除此之外,提交只是调整,但既没有删除也没有改变(内容)。
      • @SergioPulgarin 我从未见过这种行为。保留创作日期,这是最重要的。也许你在谈论提交日期?
      • 我不能说这个答案,虽然被接受,但完全正确:通过历史记录中的合并提交,您还可以找到冲突解决的痕迹:对于每个冲突,您可以检查前两个版本是以及它是如何在合并版本中解决的。变基时该信息会丢失。不过,我通常更喜欢 rebase 以及更清晰的历史记录。
      • @herman 通过变基也可以解决冲突。它只是集成到最初引入更改的提交中,使其在语义上比使用单个合并提交解决一整套(可能不相关的)提交的冲突更好。你失去的是针对旧更改的冲突解决方案,在我看来这并不是那么有用。 – 无论如何,我确实在我的回答中声明,rebase 时会丢失一些信息。
      【解决方案5】:

      您进行变基主要是为了在远程分支(您只是获取)之上重新处理您的本地提交(您尚未推送的提交),以解决任何冲突本地(即在将它们推回上游仓库之前)。
      请参阅“git workflow and rebase vs merge questions”,非常详细:“git rebase vs git merge”。

      但是 rebase 不限于这种情况,并且结合“--interactive”,它允许对您的历史进行一些本地重新排序和清理。另见“Trimming GIT Checkins/Squashing GIT History”。

      你为什么不希望 repo 历史反映代码开发的所有方式,包括它在哪里以及如何分歧

      • 在集中式 VCS 中,永远不要丢失历史记录很重要,它确实应该反映“代码开发的所有方式”。
      • 在分布式 VCS 中,您可以在 publishing 一些分支到上游之前进行所有类型的本地实验,将所有内容保留在其中就没有意义了历史:并非每个人都需要克隆并查看您的所有分支、测试、替代方案等。

      【讨论】:

      • 解决冲突也可以由你在本地通过合并上游来完成(通常更容易因为你解决所有提交的冲突,而不是一个和一个),所以你做 rebase 主要是为了使历史更清晰正如你稍后提到的。
      • @Marius:在本地合并上游?但是您并不总是可以本地访问上游存储库...
      • 不,但是如果你想推/请求拉,那么你不能离线。并且 rebase 很多产生冲突的提交可能很困难(你需要记住在特定提交之后 repo 应该是什么样子),因此在上游合并或压缩提交然后 rebase 可能更容易。
      猜你喜欢
      • 2016-04-13
      • 1970-01-01
      • 1970-01-01
      • 2015-03-21
      • 2016-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多