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

eli*_*lon 108 git branch rebase

我理解Pro Git中关于风险的方案dev.作者基本上告诉你如何避免重复提交:

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

我将告诉你我的具体情况,因为我认为它不完全适合Pro Git场景,我仍然最终得到重复的提交.

假设我与当地同行有两个远程分支机构:

origin/master    origin/dev
|                |
master           dev
Run Code Online (Sandbox Code Playgroud)

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

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

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4
Run Code Online (Sandbox Code Playgroud)

经过几次提交后,我将更改推送到master:

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
Run Code Online (Sandbox Code Playgroud)

我必须回去dev快速解决问题:

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
Run Code Online (Sandbox Code Playgroud)

然后回到origin/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
Run Code Online (Sandbox Code Playgroud)

如果我用GitX/gitk显示提交历史记录,我注意到C5'现在包含两个相同的提交C6',origin/dev这些提交与Git不同.现在,如果我将更改推送C5到此结果是:

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'
Run Code Online (Sandbox Code Playgroud)

也许我不完全理解Pro Git中的解释,所以我想知道两件事:

  1. 为什么Git在重新定位时复制这些提交?有没有特别的理由这样做,而不仅仅是申请C6C7之后dev
  2. 我怎么能避免这种情况?这样做是明智的吗?

Why*_*rrh 92

简短的回答

你省略了你运行的事实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.
Run Code Online (Sandbox Code Playgroud)

尽管Git试图提供帮助,但它的'git pull'建议很可能不是你想做的.

如果你是:

  • 单独使用"功能分支"或"开发人员分支" ,您可以运行git push --force以使用post-rebase提交更新远程(根据user4405677的回答).
  • 同时在多个开发人员的分支上工作,那么你可能不应该首先使用git rebase它.要dev根据更改进行更新master,您应该(而不是git rebase master dev运行)git merge master在运行时运行dev(根据Justin的回答).

一个稍长的解释

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

如果你重新提交提交,你将改变提交哈希; 变基(当它做某事时)将改变提交哈希.有了这个,运行的结果git rebase master dev,在哪里dev不同步master,将创建新的提交(因此哈希)与内容相同的内容,devmaster在它们之前插入提交.

你可以通过多种方式最终处于这种情况.我能想到的两种方式:

  • 您可能会根据master自己想要的dev工作进行提交
  • 您可能已经提交到dev已经被推送到远程的提交,然后您继续更改(重新提交提交消息,重新提交提交,压缩提交等)

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

你有一个存储库:

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

存储库中的初始线性提交集

然后,您继续更改提交.

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing
Run Code Online (Sandbox Code Playgroud)

(这是你必须接受我的话:有很多方法可以改变Git中的提交.在这个例子中我改变了时间C3,但是你要插入新的提交,更改提交消息,重新排序提交,挤压一起提交等)

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

与新哈希相同的提交

这是重要的是要注意提交哈希是不同的.这是预期的行为,因为你已经改变了关于它们的东西(任何东西).这没关系,但是:

显示主节点与遥控器不同步的图形日志

尝试推送会显示错误(并提示您应该运行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.
Run Code Online (Sandbox Code Playgroud)

如果我们运行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
Run Code Online (Sandbox Code Playgroud)

或者,显示另一种方式:

显示合并提交的图形日志

现在我们在本地有重复的提交.如果我们要运行,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
Run Code Online (Sandbox Code Playgroud)

上面我们看到这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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • 我建议现在使用`git push`的[**-force-with-lease` **](https://robots.thoughtbot.com/git-push-force-with-lease),因为它是更好的默认 (3认同)
  • 碉堡了!这是我在 SO 上遇到过的最有用的答案之一!我只会建议一个编辑,因为我一开始很困惑:“要么:”项目符号列表。我一开始并没有意识到这是一个非此即彼的事情,所以我最初认为我需要执行第一个项目符号项目,然后执行第二个项目。 (3认同)
  • 这可能是答案,也可能是时间机器。谢谢! (2认同)

Jus*_*ᚄᚒᚔ 80

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

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

所以,在你的dev分支,C5C6有效地不复存在:他们现在C5'C6'.当你推动origin/dev,git看到C5'C6'作为新的提交并将它们固定到历史的最后.事实上,如果你看看之间的差异C5,并C5'origin/dev,你会发现,虽然内容相同,行号可能是不同的-这使得散列提交不同.

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

  • 您说“ C5和C6暂时从开发人员中撤出。C7应用于开发人员”。如果是这种情况,那么为什么在Origin / dev上的提交顺序中C5和C6出现在C7之前? (2认同)
  • 有一点需要注意:C5和C5'的散列肯定是不同的,但不是因为行号不同,而是因为以下两个事实,其中任何一个都足以区别:1)我们所讨论的哈希提交后整个源树的哈希值,而不是delta差值的哈希值,因此C5'包含来自C7的任何内容,而C5不包含,2)C5'的父级与C5不同,并且此信息也包含在影响哈希结果的提交树的根节点中. (2认同)

小智 12

我想你在描述你的步骤时跳过了一个重要的细节.更具体地说,您git push在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` 的推荐虽然危险,但可能正是读者想要的。 (2认同)