在执行 rebase 之后,Git 提交在同一个分支中被复制

我理解 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. 我怎么才能避免呢? 这样做明智吗?
95857 次浏览

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

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

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

我将重述 Pro Git 规则: 除了您的本地存储库之外,永远不要使用以前任何地方都存在的 rebase 提交。改为使用 merge。

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

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

或者你创建了一个 git pull --rebase(或者没有显式的 --rebase,如果你的配置暗示了这一点) ,它将原来的 C5和 C6拉回到你的本地开发中(并且进一步将下面的重新基于新的散列,C7’C5“ C6”)。

解决这个问题的一个办法可能是 git push -f在发生错误时强行推动并将 C5 C6从原始位置擦除,但是如果其他人在你擦除它们之前也把它们擦除了,你就会陷入更多的麻烦中... ... 基本上每个拥有 C5 C6的人都需要采取特殊的步骤来处理它们。这就是为什么他们说你永远不应该重新定义已经发表的东西。但是,如果说“发布”是在一个小团队中进行的,那么它仍然是可行的。

长话短说

您忽略了运行 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,用后基提交(根据用户4405677的回答)更新远程。
  • 同时使用多个开发人员处理一个分支,然后首先使用 你可能不应该使用 git rebase。要使用从 master改变的内容来更新 dev,您应该在 dev(根据贾斯汀的回答)上运行 git merge master而不是运行 git rebase master dev

一个稍微长一点的解释

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

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

你可以通过多种方式来结束这种情况,我认为有两种方式:

  • 您可能已经提交了您想要基于 dev工作的 master
  • 您可以对已经推送到远程的 dev进行提交,然后进行更改(重写提交消息、重新排序提交、压缩提交等)

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

你有一个仓库:

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

Initial set of linear commits in a repository

然后进行更改提交。

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

The same commits with new hashes

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

A graph log showing that master is out-of-sync with the remote

尝试推送将显示一个错误(并提示您应该运行 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

或者,换一种说法:

A graph log showing a merge commit

现在我们在本地有了重复提交。如果我们运行 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 log showing linear commits atop duplicated commits

或者,换一种说法:

A log graph showing linear commits atop duplicated commits

在这个场景中,我们希望删除重复的提交,但是保留基于它们的提交ーー我们希望保留 C6到 C10。与大多数事情一样,有很多方法可以做到这一点:

或者:

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

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

2 在这个例子中是 85f59ab

DR

advice.pushNonFastForward设为 false:

git config --global advice.pushNonFastForward false

我发现在我的案例中,这个问题是 Git 配置问题的结果

问题描述:

症状: rebase 后在子分支上重复提交,这意味着在 rebase 期间和之后会有许多合并。

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

  • “特色支线”工作(“发展支线”之子)
  • 承担和推动“特色分公司”的变革
  • 检查“开发分支”(特性的母分支)并使用它。
  • 提交并推动“开发分支”的变更
  • 签出“ Features-Branch”并从存储库中提取更改(以防其他人已经提交了工作)
  • 将“特色分支”重构为“开发分支”
  • “特色支流”变革的推动力

作为这个工作流程的结果,重复了自前面的 rebase... : :-(以来所有“ Feature-Branch”的提交

这个问题是由于在重新基础之前子分支的更改所引起的。Git 默认的 pull 配置是“ merge”。这将更改在子分支上执行的提交的索引。

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

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

希望能帮上忙 JN Grx

您可能从一个远程分支拉不同于您的当前。例如,当您的分支正在开发跟踪开发时,您可能已经从 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将确保您不会添加可能会混淆提交历史的无关提交。

下面是一些关于 git rebase 的内容。