重新设置基准后,Git提交在同一分支中重复


130

我了解Pro Git中提出的有关“变基的危险”的方案。作者基本上告诉您如何避免重复的提交:

不要对已推送到公共存储库的提交进行重新基准化。

我要告诉您我的特殊情况,因为我认为它不完全适合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在重新设基时会重复这些提交?是否有这样做的特殊原因,而不仅仅是应用C5C6C7
  2. 我该如何避免呢?这样做明智吗?

Answers:


86

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

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

所以,在你的dev分支,C5C6有效地不复存在:他们现在C5'C6'。当您按时origin/dev,git会看到C5'C6'作为新的提交,并将它们附加到历史记录的末尾。确实,如果您看一下C5和之间的差异C5'origin/dev,你会发现,虽然内容相同,行号可能是不同的-这使得哈希的承诺不同。

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


我有一个相同的问题,如何立即修复我的远程分支历史记录,除了删除分支并通过选择樱桃重新创建它之外,还有其他选择吗?
Wazery


2
您说“ C5和C6暂时从开发人员中撤出。C7应用于开发人员”。如果是这种情况,那么为什么在Origin / dev上的提交顺序中C5和C6出现在C7之前?
2014年

@ KJ50:因为C5和C6已经被推到origin/dev。重新设置时dev,其历史记录将被修改(C5 / C6临时删除并在C7之后重新应用)。除非您知道自己在做什么,否则修改推送回购的历史记录通常是一个Really Bad Idea™。在这个简单的情况下,这个问题可以通过做力推得到解决dev,以origin/dev底垫后,并通知其他人的工作过origin/dev,他们很可能即将有一个糟糕的一天。更好的答案还是:“不要那样...使用合并代替”
Justin

3
需要注意的一件事:C5和C5'的哈希当然不同,但这并不是因为行号不同,而是针对以下两个事实,其中任何一个都足以满足要求:1)我们正在谈论的哈希是提交后整个源树的哈希,而不是增量差的哈希,因此C5'包含来自C7的所有内容,而C5不包含,以及2)C5'的父代不同于C5,并且此信息也包含在影响哈希结果的提交树的根节点中。
Ozgur Murat

113

简短答案

您忽略了您已运行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以使用还原后的提交来更新远程服务器(根据user4405677的回答)。
  • 同时与多个开发人员一起工作在一个分支上,那么您可能一开始就不应使用git rebase。要通过更新dev来自的更改master,您应该git rebase master dev在的git merge master同时运行(而不是运行)dev根据Justin的回答)。

稍长的解释

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

如果您对提交进行重新排序,则会更改提交哈希值;重定基础(执行某些操作时)将更改提交哈希。这样,run的结果与git rebase master dev哪里dev不同步master,将创建新的提交(并由此产生哈希值),其内容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

Git日志显示重复提交之上的线性提交

或者,显示另一种方式:

在重复提交之上显示线性提交的日志图

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

要么:

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

1无论选择哪个,ba7688a还是2a2e220工作正常,都没有关系。

2在示例中为85f59ab

TL; DR

设置advice.pushNonFastForwardfalse

git config --global advice.pushNonFastForward false

1
只要您意识到省略号隐藏了“ --rebase”选项(也称为“ -r”),就可以遵循“ git pull ...”建议。;-)
G.西尔维·戴维斯

4
我建议您使用git push--force-with-lease今天”,因为它是更好的默认设置
Whymarrh

4
这可能是答案,也可能是时间机器。谢谢!
ZeMoon

非常简洁的解释...我偶然发现了一个类似的问题,在我反复尝试重新设置基准后,重复了5-6次代码...只是为了确保代码与master保持最新...但是每次推送时new提交到我的分支,也复制了我的代码。如果我是唯一在分支机构工作的开发人员,能否请您告诉我强制推送(带有租赁选项)在这里是否安全?还是将主人合并到我的矿山中而不是重新基地是更好的方法?
Dhruv Singhal

12

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

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

或者,您改为制作了一个git pull --rebase(或--rebase如果未明确显示,则未明确显示),从而将原始C5和C6拉回到了本地开发人员中(并进一步将以下内容重新基于新的哈希,即C7'C5''C6' ')。

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


1
的遗漏git pull至关重要。您的推荐git push -f虽然很危险,但可能是读者正在寻找的。
Whymarrh 2015年

确实。回到我写我实际上做过的问题时git push --force,只是想看看Git会做什么。从那时起,我学到了很多有关Git的信息,如今,这rebase已成为我正常工作流程的一部分。但是,我这样做是git push --force-with-lease为了避免覆盖其他人的工作。
elitalon

使用--force-with-lease是一个很好的默认值,我也会在回答下留下评论
Whymarrh '18

2

我发现,就我而言,此问题是Git配置问题的结果。(涉及拉并合并)

问题描述:

Sympthoms:重新设置基准之后,在子分支上提交重复的操作,这意味着在重新基准设置期间和之后的大量合并。

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

  • 在“功能分支”(“开发分支”的子代)上工作
  • 在“功能分支”上提交并推送更改
  • 检出“ Develop-branch”(功能的母公司)并使用它。
  • 在“开发分支”上提交并推动更改
  • 检出“功能分支”并从存储库中提取更改(以防其他人提交了工作)
  • 将“功能分支”重新设置为“开发分支”
  • 变化对“功能分支”的推动力

作为此工作流程的结果,自上次重新设置以来,重复了所有“功能分支”的提交... :-(

该问题是由于在重新设置基础之前撤消了子分支的更改。Git的默认拉取配置为“合并”。这正在更改在子分支上执行的提交的索引。

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

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

希望它可以帮助JN Grx


1

您可能已从不同于当前分支的远程分支中撤出。例如,当您的分支发展跟踪发展时,您可能已退出Master。如果从非跟踪分支中提取,Git将尽职地提取重复的提交。

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

git reset --hard HEAD~n

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

然后确保您从正确的分支中拉出,然后运行:

git pull upstream <correct remote branch> --rebase

使用with --rebase可以确保您不会添加不必要的提交,而这会混淆提交历史。

这是git rebase的一些帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.