Git提交在重新绑定之后被复制到同一分支中

我了解Pro Git中提供的有关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的历史logging,我注意到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. 我怎样才能避免呢? 这样做明智吗?

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

  • C5C6暂时从dev拉出来
  • C7应用于dev
  • C5C6C7之上播放,创build新的差异,从而创build新的提交

所以,在你的dev分支中, C5C6实际上已经不存在了:现在是C5'C6' 。 当你推到origin/dev ,git将C5'C6'视为新的提交,并将它们粘贴到历史logging的末尾。 实际上,如果你看一下origin/dev C5C5'之间的区别,你会注意到虽然内容是相同的,但是行号可能是不同的 – 这使得提交的哈希值不同。

我将重申Pro Git规则: 永远不要重新提交曾经存在于本地存储库以外任何地方的提交 。 改用合并。

简短的回答

你省略了你运行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 (eg hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. 

尽pipeGit试图提供帮助,但它的'git pull'build议很可能不是你想要做的

如果你是:

  • 单独使用“function分支”或“开发者分支”,那么你可以运行git push --force来更新远程分区后的提交( 按照user4405677的答案 )。
  • 与多个开发人员同时在一个分支上工作,那么你可能不应该首先使用git rebase 。 要更新dev master从变化,你应该,而不是运行git rebase master dev ,运行git merge masterdev ( 根据贾斯汀的答案 )。

稍微长一点的解释

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

如果您对提交进行重新sorting,您将更改提交哈希值; 重新绑定(当它做了什么)将改变提交散列。 因此,运行git rebase master dev (其中devmaster不同步)的结果将创build具有与dev上相同的内容的提交(并因此散列),但在其之前插入master提交。

你可以用多种方式结束这样的情况。 我可以想到两种方法:

  • 你可以在master上提交你想要的dev工作
  • 你可以在dev上提交已经被推送到远程的提交,然后进行更改(重新提交提交消息,重新sorting提交,压缩提交等)

让我们更好地了解发生了什么 – 这里是一个例子:

你有一个仓库:

 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 (eg 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 

或者,换个angular度来看:

显示合并提交的图表日志

现在我们在本地有重复的提交。 如果我们要运行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

我们完成了。

但是,等等,我继续从重复的提交中find工作

如果你没有注意到提交重复,并继续在重复的提交上工作,你真的为自己弄得一团糟。 乱七八糟的大小与你在重复项上提交的次数成正比。

这看起来像什么:

 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日志

或者,换个angular度来看:

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

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

没有特别的顺序:

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

1无论你select哪一个, ba7688a2a2e220可以正常工作。

2在这个例子中,它将是85f59ab

TL; DR

advice.pushNonFastForward设置为false

 git config --global advice.pushNonFastForward false 

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

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

或者你做了一个git pull --rebase (或者没有显式的--rebase如果它被你的configuration隐含的话),它把原来的C5和C6拉回你的本地开发中(并且进一步将其重新转换为新的哈希,C7,C5“,C6”)。

解决这个问题的一个办法就是用git push -f来强制推送错误,并从原点擦除C5 C6,但是如果有其他人在你擦除它们之前也拉了它们,那么你将会花费很多更麻烦…基本上每个有C5 C6的人都需要采取特别的措施来摆脱它们。 这就是为什么他们说你永远不应该改变已经发表的任何东西。 但是,如果说“发布”是在一个小团队中,那还是可行的。

我发现在我的情况下,这个问题是Gitconfiguration问题的后果。 (涉及拉和合并)

问题描述:

Sympthoms:在rebase之后重复在子分支上,意味着在rebase期间和之后的大量合并。

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

  • 工作在“function分支”(“开发分支”的孩子)
  • 提交并推送“function分支”
  • 签出“开发分支”(function的母亲分支)并使用它。
  • 承诺并推动“开发分支”
  • 签出“function – 分支”并从存储库中提取更改(如果有其他人提交了工作)
  • Rebase“function – 分支”到“开发分支”
  • “特色分支”的推动力

作为这个工作stream程的重要组成部分,重复所有提交的“特征分支”

这个问题是由于分支机构变革之前的变化拉动的。 Git默认的拉取configuration是“合并”。 这正在改变对子分支执行的提交索引。

解决scheme:在Gitconfiguration文件中,configurationpull工作在rebase模式:

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

希望能帮助JN Grx