【问题标题】:Git commits are duplicated in the same branch after doing a rebase执行 rebase 后,Git 提交在同一分支中重复
【发布时间】:2012-03-05 01:53:29
【问题描述】:

我了解 Pro Git 中关于 The Perils of Rebasing 的场景。作者基本上告诉你如何避免重复提交:

不要对已推送到公共存储库的提交进行变基。

我将告诉你我的特殊情况,因为我认为它不完全适合 Pro Git 场景,而且我仍然会出现重复提交。

假设我有两个远程分支和本地对应分支:

origin/master    origin/dev
|                |
master           dev

所有四个分支都包含相同的提交,我将在 dev 开始开发:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

在几次提交后,我将更改推送到origin/dev

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

我必须返回master 进行快速修复:

origin/master : C1 C2 C3 C4 C7  # (2) git push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6

然后回到dev,我重新调整更改以在我的实际开发中包含快速修复:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

如果我使用 GitX/gitk 显示提交历史,我注意到 origin/dev 现在包含两个相同的提交 C5'C6',它们与 Git 不同。现在,如果我将更改推送到 origin/dev,结果如下:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git push
dev           : C1 C2 C3 C4 C7 C5' C6'

可能我对Pro Git中的解释不太了解,所以想了解两点:

  1. 为什么 Git 在变基时会重复这些提交?是否有特别的理由这样做,而不是在C7 之后应用C5C6
  2. 如何避免这种情况?这样做是否明智?

【问题讨论】:

  • 这个问题有很多优秀的答案。我想知道是否有人有一个简洁的要点或文章或维基,而不是一本书,它总结了一些后来的最佳实践?我已经多次使用 GitLab 和 GitHub 获得 OP 和其他分支/合并问题,似乎有太多相互矛盾的建议导致重复出现问题。

标签: git branch rebase


【解决方案1】:

简答

你忽略了你运行git push的事实,得到以下错误,然后继续运行git pull

To git@bitbucket.org:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

尽管 Git 试图提供帮助,它的“git pull”建议很可能不是您想要做的

如果你是:

  • 单独在“功能分支”或“开发者分支”上工作,然后您可以运行 git push --force 以使用您的后变基提交 (as per user4405677's answer) 更新远程。
  • 同时与多个开发人员一起在一个分支上工作,那么您可能一开始就不应该使用git rebase。要使用master 的更改更新dev,您应该在dev (as per Justin's answer) 上运行git merge master,而不是运行git rebase master dev

稍微长一点的解释

Git 中的每个提交哈希都基于许多因素,其中之一是之前提交的哈希。

如果您重新排序提交,您将更改提交哈希;变基(当它做某事时)会改变提交哈希。这样,运行git rebase master dev 的结果(其中devmaster 不同步)将创建与dev 上的内容相同的 提交(因此是哈希)但是在master 上的提交插入到它们之前。

您可能会以多种方式最终陷入这种情况。我能想到的两种方法:

  • 您可以在 master 上提交您想要在 dev 工作的基础
  • 您可以在 dev 上的提交已经被推送到远程,然后您继续更改(改写提交消息、重新排序提交、压缩提交等)

让我们更好地理解发生了什么——这里有一个例子:

你有一个仓库:

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

然后您继续更改提交。

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(这是您必须相信我的话的地方:在 Git 中有多种更改提交的方法。在此示例中,我更改了 C3 的时间,但您正在插入新的提交,更改提交消息、重新排序提交、将提交压缩在一起等)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

这是重要的地方,请注意提交哈希是不同的。这是预期的行为,因为您已经更改了有关它们的某些内容(任何内容)。没关系,但是:

尝试推送会显示一个错误(并提示您应该运行git pull)。

$ git push origin master
To git@bitbucket.org:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

如果我们运行git pull,我们会看到这个日志:

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

或者,以另一种方式显示:

现在我们在本地有重复的提交。如果我们要运行git push,我们会将它们发送到服务器。

为了避免进入这个阶段,我们可以运行 git push --force(我们改为运行 git pull)。这会毫无问题地将带有新哈希的提交发送到服务器。为了在这个阶段解决这个问题,我们可以重置回到我们运行 git pull 之前:

查看 reflog (git reflog) 以了解提交哈希是什么我们运行 git pull

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

在上面我们看到ba7688a 是我们在运行git pull 之前的提交。有了这个提交哈希,我们可以重置回那个(git reset --hard ba7688a),然后运行git push --force

我们完成了。

但是等等,我继续基于重复提交的工作

如果您不知何故没有注意到提交被重复并继续在重复提交之上工作,那么您真的把自己弄得一团糟。混乱的大小与您在重复项之上的提交数量成正比。

这是什么样子的:

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

或者,以另一种方式显示:

在这种情况下,我们希望删除重复的提交,但保留基于它们的提交——我们希望保留 C6 到 C10。与大多数事情一样,有很多方法可以解决这个问题:

要么:

  • 在最后一次重复提交时创建一个新分支1cherry-pick 每次提交(包括 C6 到 C10)到该新分支上,并将该新分支视为规范。
  • 运行git rebase --interactive $commit,其中$commit 是两个重复提交2之前 的提交。在这里,我们可以彻底删除重复的行。

1无论您选择ba7688a2a2e220 中的哪一个都可以。

2 在示例中为85f59ab

TL;DR

advice.pushNonFastForward 设置为false

git config --global advice.pushNonFastForward false

【讨论】:

  • 只要意识到省略号隐藏了“--rebase”选项(又名“-r”),就可以遵循“git pull...”的建议。 ;-)
  • 我建议现在使用git push--force-with-lease,因为它是一个更好的默认值
  • 要么是这个答案,要么是时间机器。谢谢!
  • 非常简洁的解释......我偶然发现了一个类似的问题,在我反复尝试 rebase 后重复了我的代码 5-6 次......只是为了确保代码与 master 是最新的...但是每次它将新提交推送到我的分支时,也会复制我的代码。如果我是唯一在我的分支上工作的开发人员,你能告诉我强制推送(带有租赁选项)是否安全?还是将 master 合并到我的而不是 rebase 是更好的方法?
  • 谢谢!这对我帮助很大。
【解决方案2】:

你不应该在这里使用变基,一个简单的合并就足够了。您链接的 Pro Git 书基本上解释了这种确切情况。内部工作方式可能略有不同,但我是这样想象的:

  • C5C6暂时退出dev
  • C7 应用于 dev
  • C5C6C7 之上回放,创建新的差异并因此产生新的提交

因此,在您的 dev 分支中,C5C6 实际上不再存在:它们现在是 C5'C6'。当你推送到origin/dev 时,git 将C5'C6' 视为新提交并将它们附加到历史记录的末尾。实际上,如果您查看origin/dev 中的C5C5' 之间的差异,您会注意到虽然内容相同,但行号可能不同——这使得提交的哈希值不同。

我将重申 Pro Git 规则:永远不要对除本地存储库之外的任何地方存在的提交进行变基。请改用合并。

【讨论】:

  • 我有同样的问题,我现在如何修复我的远程分支历史,除了删除分支并使用樱桃采摘重新创建它之外,还有其他选择吗??
  • @xdsy:看看thisthis
  • 您说“C5 和 C6 暂时退出开发... C7 应用于开发”。如果是这种情况,那么在 origin/dev 上的提交顺序中,为什么 C5 和 C6 会出现在 C7 之前?
  • @KJ50:因为 C5 和 C6 已经推送到 origin/dev。当dev 被rebase 时,它​​的历史被修改(C5/C6 暂时移除并在C7 之后重新应用)。除非您知道自己在做什么,否则修改推送回购的历史通常是一个非常糟糕的主意™。在这个简单的例子中,问题可以通过在 rebase 之后从dev 强制推送到origin/dev 并通知其他在origin/dev 工作的人他们可能会有糟糕的一天来解决。再一次,更好的答案是“不要那样做...改用合并”
  • 需要注意的一点:C5和C5'的hash肯定是不同的,但不是因为行号不同,而是以下两个事实,其中任何一个都足以区分: 1)我们所说的散列是提交后整个源树的散列,而不是增量差异的散列,因此C5'包含来自C7的任何内容,而C5没有,并且2)C5'的父级与 C5 不同,此信息也包含在影响哈希结果的提交树的根节点中。
【解决方案3】:

我认为您在描述步骤时跳过了一个重要细节。更具体地说,您的最后一步,git push on dev,实际上会给您一个错误,因为您通常无法推送非快进更改。

所以你在最后一次推送之前做了git pull,这导致了以 C6 和 C6' 作为父级的合并提交,这就是为什么两者都将保留在日志中。更漂亮的日志格式可能更明显地表明它们是重复提交的合并分支。

或者你创建了一个git pull --rebase(或者没有明确的--rebase,如果你的配置暗示了它),它将原来的 C5 和 C6 拉回到你的本地开发中(并进一步将以下的重新调整为新的哈希,C7' C5'' C6'')。

解决此问题的一种方法可能是git push -f 在出现错误时强制推送并从原点擦除 C5 C6,但如果其他人在您擦除它们之前也将它们拉出,那么您将面临更多的麻烦......基本上每个拥有C5 C6的人都需要采取特殊步骤来摆脱它们。这就是为什么他们说你永远不应该对已经发布的任何内容进行变基。不过,如果说“发布”是在一个小团队中,它仍然是可行的。

【讨论】:

  • git pull 的省略至关重要。您对git push -f 的推荐虽然很危险,但可能正是读者想要的。
  • 确实如此。当我写这个问题时,我实际上做了git push --force,只是为了看看Git 会做什么。从那时起,我学到了很多关于 Git 的知识,现在rebase 是我正常工作流程的一部分。但是,我使用git push --force-with-lease 是为了避免覆盖其他人的工作。
  • 使用--force-with-lease 是一个很好的默认值,我也会在我的回答下留下评论
【解决方案4】:

我发现在我的情况下,这个问题是 Git 配置问题的结果。 (涉及拉取和合并)

问题描述:

症状: rebase 后在子分支上重复提交,这意味着在 rebase 期间和之后进行了多次合并。

工作流程: 以下是我正在执行的工作流程的步骤:

  • 处理“Features-branch”(“Develop-branch”的子级)
  • 在“Features-branch”上提交和推送更改
  • 签出“Develop-branch”(功能的母分支)并使用它。
  • 在“开发分支”上提交并推送更改
  • 签出“Features-branch”并从存储库中提取更改(以防其他人已提交工作)
  • 将“Features-branch”重新定位到“Develop-branch”
  • “Feature-branch”变化的推动力

作为此工作流程的后果,自上次 rebase 以来“Feature-branch”的所有提交都重复... :-(

问题是由于在 rebase 之前拉取子分支的更改。 Git 默认拉取配置是“合并”。这是更改在子分支上执行的提交的索引。

解决方法:在 Git 配置文件中,配置 pull 工作在 rebase 模式:

...
[pull]
    rebase = preserve
...

希望能帮到你 JN Grx

【讨论】:

    【解决方案5】:

    您可能从与当前不同的远程分支中提取。例如,当您的分支正在开发跟踪开发时,您可能已经从 Master 中撤出。如果从未跟踪的分支中拉出,Git 将尽职尽责地拉入重复的提交。

    如果发生这种情况,您可以执行以下操作:

    git reset --hard HEAD~n
    

    在哪里n == <number of duplicate commits that shouldn't be there.>

    然后确保你从正确的分支中拉取然后运行:

    git pull upstream <correct remote branch> --rebase
    

    使用--rebase 拉取将确保您不会添加可能混淆提交历史的无关提交。

    Here is a bit of hand holding for git rebase.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-17
      • 1970-01-01
      • 2018-12-13
      • 2017-03-25
      • 2020-03-01
      • 2014-10-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多