【问题标题】:Git rebasing not working correctly?Git rebase 不能正常工作?
【发布时间】:2015-10-13 22:55:12
【问题描述】:

Git rebase 似乎没有按我预期的方式工作,基于我的 对变基的理解,以及基于我如何看到变基在 水银。我已经生成了一个示例来说明奇怪的行为,并且我 希望有人能解释为什么 Git 会这样。考虑这个 DAG 的状态:

在这种情况下,我在 master 上提交了 f7 和 f8,但我想移动 而是将这些新提交放到功能分支上。即我做了提交 错误的分支,并希望纠正错误。我可以从 源树:

SourceTree 证实了我的意图:

但结果完全不是我所期望的:

虽然节点在 DAG 中的位置正确,但分支头在 不正确!通过将 f7 和 f8 重新设置为 f6,我希望看到 master 重置为 原点上的位置,并期望 feature/AAA-1 前进到 f8。像这样:

这是我所期望的行为,基于 Mercurial 中的变基,并且只是 通常基于变基正在做什么。为什么 Git 这样做,我该怎么做 让它正常运行?

【问题讨论】:

  • “我怎样才能让它正常运行?”假设 you 是错误的,而不是工具,通常是一个好主意。与 Git 世界中 rebase 的(同样有效的)想法相比,您对 rebase 所做的事情(这在 Mercurial 世界中很可能是正确的)有不同的心理模型。
  • 感谢互联网巨魔先生为这个问题添加了无效评论:)
  • 相反,我相信我的评论富有成效的。如果你反对你的工具,坚持认为它做错了事,你将继续遇到问题。在 Git 和 Mercurial 中,分支是完全不同的东西。 Git 没有;它只是不同。 Mercurial 与 Subversion 的工作方式不同,Subversion 与 Perforce 的工作方式不同……它们中的任何一个比其他的更客观“正确”吗?这种思维方式的改变很重要。
  • 相反,我曾使用过 CVS、SVN、SourceSafe、TFS、Perforce、Mercurial、Git 和 Bazaar。我精通这些传统概念和 DVCS 概念,并且为其中不止一个提供了源代码。我几乎在所有这些问题中都遇到了错误、缺陷和设计缺陷(毕竟没有 VCS 是完美的),并且曾多次提供补丁来解决这些问题。你坚持认为 Git 的当前行为是绝对正确的行为,或者认为 Git 没有错误,没有改进的余地,都是幼稚的。这是 Git 变基的一个缺陷。
  • “你坚持认为 Git 的当前行为是绝对正确的行为,或者认为 Git 没有错误,没有改进的余地,都是幼稚的。”我同意这太天真了,这就是为什么我没有说任何接近这个的原因。我的观点是 Git 模型和 Mercurial 模型是不同的,要问你如何“使 [Git] 行为正确”,意思是“Mercurial 的方式”,是一个糟糕的选择。没有什么能使 Mercurial 模型客观地“正确”。不幸的是,这个评论线程变得有点傻,所以我会在这里停下来。

标签: git mercurial rebase atlassian-sourcetree


【解决方案1】:

来自 Mercurial,您期望提交永久附加到特定分支。也就是说,如果你设法单独提取一些提交,你就会有这样的说法:

I am a commit on branch foo.
I change file bar.

Git 不是这样工作的:提交独立于任何分支(名称),事实上,分支名称——标签,如果你愿意的话——可以被剥离并卡在其他地方- 不高兴。它们没有用1,除了人类试图解释混乱。

在 Mercurial 中,当您“重新设置”某些变更集时,您(至少实际上)将它们作为与它们的基础的差异转储出来,然后切换到您希望它们所在的另一个分支并在其上进行新的提交另一个分支。 Mercurial 过去(也许现在仍然如此)称这第一步为“嫁接”。这些新提交现在永久附加到(并且仅附加到)另一个不同的分支:

master:    f1 - f2 - f3 - f4 - f7 - f8
                             \
feature/AAA-1:                 f5 - f6

变成:

master:    f1 - f2 - f3 - f4 - f7 - f8
                             \
feature/AAA-1:                 f5 - f6 - 9 - 10

此时您可以“安全地撤消” f7 和 f8,将它们从 master 行中删除,并且您的 rebase 仅在另一个分支上完成了副本。

注意我在这里画了左边的分支标签。这是安全的,因为所有提交都永久地粘在它们的分支上,所以一旦变更集在其分支的行上,它总是在其分支的行上。唯一违反“变更集在其分支的(单)行上”规则的情况是合并,当变更集附加到(完全)两个分支时:它位于其主分支上,但在一个连接中到另一个分支。

另一方面,在 git 中,提交可以被认为是“开启”零个或多个 分支(没有“正好 1 或 2”的约束),并且 set提交“on”的分支是动态的,因为可以随时添加或删除分支名称。 (还要注意“分支”这个词有at least two meanings in git。)

Git 的 rebase 的工作方式与 Mercurial 的非常相似:它实际上 复制 提交。但是要开始有一个重要的区别:副本不是专门“在”任何分支上的(实际上,rebase 过程在 no 分支上运行,使用 git 所谓的“分离的 HEAD”)。然后,最后还有一个更重要的区别。

和之前一样,我们可以从绘图开始,但这次我会画一点不同的:

                     f7 <- f8   <-- master
                    /
f1 <- f2 <- f3 <- f4
                    \
                     f5 <- f6   <-- feature/AAA-1

这一次,标签在右侧,带有箭头。 master这个名字实际上直接指向commit f8,而f8又指向f7f7又指向f4等等。

这意味着,现在,f1f4 的提交都是“开启”两个分支。使用 git,最好说这些提交都“包含在”(历史)两个分支中。这些提交中没有任何内容可以说明它们最初是“在哪个分支上创建的”:它们带有父指针、源树 ID、作者和提交者名称(以及时间戳等),但没有“源分支名称”。 (我认为,从 hg 到 git 的新手经常觉得这很令人沮丧。)

如果您现在要求 git 将 f7f8 重新设置为 feature/AAA-1,git 将复制这两个提交:

                     f7 <- f8
                    /
f1 <- f2 <- f3 <- f4
                    \
                     f5 <- f6 <- f7' <- f8'

' 标记,或 f7prime 和 f8prime,表示这些是原件的副本——git cherry-picks,类似于 hg 的移植物)。但是现在我们找到了关键的区别,那个让你绊倒的地方:git 现在“剥离”了原来的 master 标签,并让它指向最尖端的新提交。这意味着最终的图表如下所示:

                     f7 <- f8   [abandoned -- was master]
                    /
f1 <- f2 <- f3 <- f4
                    \
                     f5 <- f6   <-- feature/AAA-1
                             \
                              f7' <- f8'   <-- master

Mercurial不能做到这一点:不能将分支标签撕下、随意移动和重新粘贴到别处。所以它没有,这就是它的 rebase 工作方式不同的原因。

在 git 中,您要做的只是简单地将两个提交挑选到 feature/AAA-1 分支中,然后将它们从 master 分支中删除:

$ git checkout feature/AAA-1
$ git cherry-pick master~2..master   # copy the commits
$ git checkout master
$ git reset --hard master~2          # back up over the originals

这里的想法是,您根本没有变基 master,甚至也没有真正变基您的功能分支:相反,您只是将两个提交复制到您的功能分支,然后将它们从主人。


1这有点夸大其词,因为在 存储库之间传输——git fetchgit push——也使用分支和标签标签。此外,您需要 一些 对提交的引用以使它们保持活动状态,否则 git 的垃圾收集器最终会将它们视为“无法访问”。

【讨论】:

  • 谢谢,你已经很好地描述了 Git 在做什么,以及为什么。我个人认为 Git 做错了......当我告诉 Git 重新设置 f7 / f8 时,我希望 Git 将 master 倒带指向其先前的位置,然后将 f7 更改为指向其父级的 f6,然后移动功能/AAA-1 转发,因为该分支已收到新的提交。这不是 Git 的行为方式,而且无论当前行为如何,这似乎与变基的概念不一致。
  • 因为 git 的分支是短暂的,它必须为“rebase”做出某种相当任意的定义。 Git 的定义是“找到提交 DAG 的某个部分,将这些提交重播到某个目标提交上,然后移动当前分支标签。”
  • 是的,这似乎是准确的 :) 我认为这只是一个简单的变基实现。缺少“倒带源”和“快进目标”步骤,这确实是使变基成为变基的原因,恕我直言。无论如何,感谢您的回答。
  • 一夜之间考虑了这个问题,以及你如何解释 Git 的当前行为,我意识到当你将跟踪分支重新定位到源分支时,你不会看到这个问题,因为在这种情况下,生成的分支头将是正确的。然而,在所有其他情况下,由 rebase 产生的分支头都是不正确的。似乎这个问题可以更好地描述为“跨分支变基时在 Git 中变基不能正常工作”。
【解决方案2】:

看起来您对变基并不真正感兴趣。您只想移动分支以指向新的头。在这种情况下,这很容易。我不使用 SourceTree,但在 CLI 中很简单:

git checkout feature/AAA-1
git reset --hard master
git checkout master
git reset --hard origin/master

【讨论】:

    【解决方案3】:

    您的期望是正确的,但我认为混淆只是在术语上。来自git-scm

    在本例中,您将运行以下命令:

    $ git checkout experiment
    $ git rebase master
    First, rewinding head to replay your work on top of it...
    Applying: added staged command
    

    它通过转到两个分支的共同祖先(你所在的一个和你正在变基的一个)的共同祖先来工作......

    注意在这种情况下“on”和“onto”的用法。在 git 中,您要变基的分支是变基后将成为子树的分支。我怀疑你对 SourceTree 的使用对于 git 来说只是倒退。

    【讨论】:

      【解决方案4】:

      git rebase 存在的目的是:你向feature/AAA 提交了一些东西,同时其他人将更改推送到origin/feature/AAA。现在feature/AAA(当前)和origin/feature/AAA 是分歧的。您可以合并它们,但项目规则规定历史应该是线性的。因此,您改为运行 git rebase,您当前的分支 feature/AAA 变为 基于 最新的 origin/feature/AAA

      你的情况不同。可能有命令将提交从一个分支移动到另一个分支,明确命名两者,但它不是变基。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-11-09
        • 2013-12-10
        • 1970-01-01
        • 2013-12-23
        • 1970-01-01
        • 2019-01-19
        • 2013-03-30
        • 2010-11-30
        相关资源
        最近更新 更多