执行rebase后,Git提交在同一分支中重复

问题描述 投票:99回答:4

我理解Pro Git中关于the risks of git rebase的情景。作者基本上告诉你如何避免重复提交:

不要将已推送到公共存储库的提交重新绑定。

我将告诉你我的具体情况,因为我认为它不完全适合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在重新定位时复制这些提交?有没有特别的理由这样做而不是仅仅在C5之后应用C6C7
  2. 我怎么能避免这种情况?这样做是明智的吗?
git branch rebase
4个回答
73
投票

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

  • C5C6暂时退出dev
  • C7适用于dev
  • C5C6C7上播放,创造新的差异,因此新的提交

因此,在你的dev分支中,C5C6实际上不再存在:它们现在是C5'C6'。当你推向origin/dev时,git将C5'C6'视为新的提交并将其解决到历史的最后。实际上,如果你看看C5C5'origin/dev之间的差异,你会注意到尽管内容是相同的,但行号可能不同 - 这使得提交的哈希值不同。

我将重述Pro Git规则:永远不要重新提交除了本地存储库以外的任何地方的提交。请改用合并。


77
投票

Short answer

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

To [email protected]:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to '[email protected]: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以使用post-rebase提交更新远程(as per user4405677's answer)。
  • 同时在多个开发人员的分支上工作,那么你可能不应该首先使用git rebase。要使用dev的更改更新master,您应该在git rebase master devgit merge master)上运行dev,而不是运行as per Justin's answer

A slightly longer explanation

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 [email protected]:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '[email protected]: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之前重置为:

在我们运行git reflog之前,查看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

我们已经完成了。

But wait, I continued to base work off of the duplicated commits

如果你以某种方式没有注意到提交是重复的并且继续在重复提交的基础上继续工作,那么你真的为自己弄得一团糟。混乱的大小与重复顶部的提交数量成正比。

这是什么样的:

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。与大多数事情一样,有很多方法可以解决这个问题:

或者:

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

1无论你选择哪一个都无关紧要,无论是ba7688a还是2a2e220都能正常工作。

2在示例中,它将是85f59ab

TL;DR

advice.pushNonFastForward设为false

git config --global advice.pushNonFastForward false

12
投票

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

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

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

出于这种情况的一种方法可能是git push -f在发出错误时强行推动并从原点擦拭C5 C6,但是如果其他人在你擦拭它们之前也让它们被拉动,那么你会遇到更多麻烦。 ..基本上每个拥有C5 C6的人都需要采取特殊措施来摆脱它们。这正是为什么他们说你永远不应该重新发布任何已发布的内容。如果说“发布”是在一个小团队中,它仍然可行。


1
投票

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

问题描述:

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

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

  • 工作在“功能分支”(“发展分支”的孩子)
  • “功能 - 分支”上的提交和推送更改
  • 结帐“开发分支”(功能的母亲分支)并使用它。
  • 提交并推动“开发分支”的变更
  • 签出“功能分支”并从存储库中提取更改(如果其他人已提交工作)
  • 将“功能 - 分支”重新引入“开发分支”
  • 推动“功能分支”的变化

作为这个工作流程的后果,重复所有提交的“功能分支”,因为以前的rebase ...... :-(

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

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

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

希望它可以帮助IN Grx

© www.soinside.com 2019 - 2024. All rights reserved.