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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9264314/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 12:58:40  来源:igfitidea点击:

Git commits are duplicated in the same branch after doing a rebase

gitbranchrebase

提问by elitalon

I understand the scenario presented in Pro Git about The Perils of Rebasing. The author basically tells you how to avoid duplicated commits:

我了解 Pro Git 中关于The Perils of Rebasing 的场景。作者基本上告诉你如何避免重复提交:

Do not rebase commits that you have pushed to a public repository.

不要将您推送到公共存储库的提交变基。

I am going to tell you my particular situation because I think it does not exactly fit the Pro Git scenario and I still end up with duplicated commits.

我将告诉你我的特殊情况,因为我认为它并不完全适合 Pro Git 场景,而且我仍然以重复提交告终。

Let's say I have two remote branches with their local counterparts:

假设我有两个远程分支与本地分支:

origin/master    origin/dev
|                |
master           dev

All four branches contains the same commits and I am going to start development in dev:

所有四个分支都包含相同的提交,我将在以下位置开始开发dev

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

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

After a couple of commits I push the changes to origin/dev:

几次提交后,我将更改推送到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

I have to go back to masterto make a quick fix:

我必须回去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

And back to devI rebase the changes to include the quick fix in my actual development:

回到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

If I display the history of commits with GitX/gitk I notice that origin/devnow contains two identical commits C5'and C6'which are different to Git. Now if I push the changes to origin/devthis is the result:

如果我使用 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'

Maybe I don't fully understand the explanation in Pro Git, so I would like to know two things:

可能我没有完全理解Pro Git中的解释,所以我想知道两件事:

  1. Why does Git duplicate these commits while rebasing? Is there a particular reason to do that instead of just applying C5and C6after C7?
  2. How can I avoid that? Would it be wise to do it?
  1. 为什么 Git 在变基时会重复这些提交?是否有特别的理由这样做而不仅仅是申请C5C6之后C7
  2. 我怎样才能避免这种情况?这样做是否明智?

采纳答案by Justin ??????

You should not be using rebase here, a simple merge will suffice. The Pro Git book that you linked basically explains this exact situation. The inner workings might be slightly different, but here's how I visualize it:

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

  • C5and C6are temporarily pulled out of dev
  • C7is applied to dev
  • C5and C6are played back on top of C7, creating new diffs and therefore new commits
  • C5并被C6暂时拉出dev
  • C7应用于 dev
  • C5C6在 之上回放C7,创建新的差异,从而创建新的提交

So, in your devbranch, C5and C6effectively no longer exist: they are now C5'and C6'. When you push to origin/dev, git sees C5'and C6'as new commits and tacks them on to the end of the history. Indeed, if you look at the differences between C5and C5'in origin/dev, you'll notice that though the content is the same, the line numbers are probably different -- which makes the hash of the commit different.

所以,在你的dev分支,C5C6有效地不复存在:他们现在C5'C6'。当你 push 到 时origin/dev,git 会看到C5'C6'作为新的提交并将它们添加到历史的末尾。事实上,如果您查看C5C5'in之间的差异origin/dev,您会注意到虽然内容相同,但行号可能不同——这使得提交的哈希不同。

I'll restate the Pro Git rule: never rebase commits that have ever existed anywhere but your local repository. Use merge instead.

我将重申 Pro Git 规则:永远不要对除了本地存储库之外的任何地方都存在过的提交进行变基。改用合并。

回答by Whymarrh

Short answer

简答

You omitted the fact that you ran git push, got the following error, and then proceeded to run git pull:

你忽略了你运行的事实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.

Despite Git trying to be helpful, its 'git pull' advice is most likely not what you want to do.

尽管 Git 试图提供帮助,但它的“git pull”建议很可能不是您想要做的

If you are:

如果你是:

  • Working on a "feature branch" or "developer branch" alone, then you can run git push --forceto update the remote with your post-rebase commits (as per user4405677's answer).
  • Working on a branch with multiple developers at the same time, then you probably should not be using git rebasein the first place. To update devwith changes from master, you should, instead of running git rebase master dev, run git merge masterwhilst on dev(as per Justin's answer).
  • 单独在“功能分支”或“开发人员分支”上工作,然后您可以运行git push --force以使用您的 post-rebase 提交更新远程(根据 user4405677 的回答)。
  • 同时与多个开发人员在一个分支上工作,那么您可能一开始就不应该使用git rebase。要更新dev与变化master,你应该,而不是运行git rebase master dev,运行git merge master而上dev按照贾斯汀的答案)。

A slightly longer explanation

稍微长一点的解释

Each commit hash in Git is based on a number of factors, one of which is the hash of the commit that comes before it.

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

If you reorder commits you will change commit hashes; rebasing (when it does something) will change commit hashes. With that, the result of running git rebase master dev, where devis out of sync with master, will create newcommits (and thus hashes) with the same content as those on devbut with the commits on masterinserted before them.

如果您重新排序提交,您将更改提交哈希;变基(当它做某事时)会改变提交哈希。就这样,运行的结果git rebase master dev,在这里dev是同步进行master,将创造新的提交(因此哈希)具有相同的内容,是关于dev但在提交master之前插入。

You can end up in a situation like this in multiple ways. Two ways I can think of:

您可能会以多种方式最终陷入这种情况。我能想到的两种方法:

  • You could have commits on masterthat you want to base your devwork on
  • You could have commits on devthat have already been pushed to a remote, which you then proceed to change (reword commit messages, reorder commits, squash commits, etc.)
  • 你可以提交master你想要基于你的dev工作的承诺
  • 您可以将dev已经推送到远程的提交提交到远程,然后您继续更改(重写提交消息,重新排序提交,压缩提交等)

Let's better understand what happened—here is an example:

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

You have a repository:

您有一个存储库:

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

Initial set of linear commits in a repository

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

You then proceed to change commits.

然后您继续更改提交。

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(This is where you'll have to take my word for it: there are a number of ways to change commits in Git. In this example I changed the time of C3, but you be inserting new commits, changing commit messages, reordering commits, squashing commits together, etc.)

(这就是你必须相信我的话:在 Git 中有很多方法可以更改提交。在这个示例中,我更改了 的时间C3,但是您要插入新的提交、更改提交消息、重新排序提交,将提交压缩在一起等)

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

The same commits with new hashes

使用新散列进行相同的提交

This is where it is important to notice that the commit hashes are different. This is expected behaviour since you have changed something (anything) about them. This is okay, BUT:

这是重要的地方,注意提交哈希是不同的。这是预期的行为,因为您已经更改了有关它们的某些(任何)内容。这没关系,但是:

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

显示 master 与 remote 不同步的图形日志

Trying to push will show you an error (and hint that you should run git pull).

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

If we run git pull, we see this log:

如果我们运行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

Or, shown another way:

或者,以另一种方式显示:

A graph log showing a merge commit

显示合并提交的图形日志

And now we have duplicate commits locally. If we were to run git pushwe would send them up to the server.

现在我们在本地有重复的提交。如果我们要运行,git push我们会将它们发送到服务器。

To avoid getting to this stage, we could have run git push --force(where we instead ran git pull). This would have sent our commits with the new hashes to the server without issue. To fix the issue at this stage, we can reset back to before we ran git pull:

为了避免进入这个阶段,我们可以运行git push --force(我们改为运行的地方git pull)。这会将我们的提交和新的哈希值毫无问题地发送到服务器。为了在这个阶段解决这个问题,我们可以在运行之前重新设置git pull

Look at the reflog (git reflog) to see what the commit hash was beforewe ran 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

Above we see that ba7688awas the commit we were at before running git pull. With that commit hash in hand we can reset back to that (git reset --hard ba7688a) and then run git push --force.

上面我们看到这ba7688a是我们在运行之前的提交git pull。有了这个提交哈希,我们可以重置回那个 ( git reset --hard ba7688a) 然后运行git push --force.

And we're done.

我们已经完成了。

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

但是等等,我继续基于重复提交的工作

If you somehow didn't notice that the commits were duplicated and proceeded to continue working atop of duplicate commits, you've really made a mess for yourself. The size of the mess is proportional to the number of commits you have atop of the duplicates.

如果您以某种方式没有注意到提交被重复并继续在重复提交之上工作,那么您真的把自己弄得一团糟。混乱的大小与您在重复项之上的提交数量成正比。

What this looks like:

这看起来像什么:

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

Git log showing linear commits atop duplicated commits

Or, shown another way:

或者,以另一种方式显示:

A log graph showing linear commits atop duplicated commits

A log graph showing linear commits atop duplicated commits

In this scenario we want to remove the duplicate commits, but keep the commits that we have based on them—we want to keep C6 through C10. As with most things, there are a number of ways to go about this:

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

Either:

任何一个:

  • Create a new branch at the last duplicated commit1, cherry-pickeach commit (C6 through C10 inclusive) onto that new branch, and treat that new branch as canonical.
  • Run git rebase --interactive $commit, where $commitis the commit priorto both the duplicated commits2. Here we can outright delete the lines for the duplicates.
  • 在最后一次重复提交1处创建一个新分支,cherry-pick每次提交(包括 C6 到 C10)到该新分支上,并将该新分支视为规范。
  • 运行git rebase --interactive $commit,其中两个重复$commit提交之前的提交2。在这里,我们可以彻底删除重复的行。

1It doesn't matter which of the two you choose, either ba7688aor 2a2e220work fine.

1这不要紧,你选择哪两个,要么ba7688a还是2a2e220做工精细。

2In the example it would be 85f59ab.

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

TL;DR

TL; 博士

Set advice.pushNonFastForwardto false:

设置advice.pushNonFastForwardfalse

git config --global advice.pushNonFastForward false

回答by user4405677

I think you skipped an important detail when describing your steps. More specifically, your last step, git pushon dev, would have actually given you an error, as you can not normally push non-fastforward changes.

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

So you did git pullbefore the last push, which resulted in a merge commit with C6 and C6' as parents, which is why both will remain listed in log. A prettier log format might have made it more obvious they are merged branches of duplicated commits.

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

Or you made a git pull --rebase(or without explicit --rebaseif it is implied by your config) instead, which pulled the original C5 and C6 back in your local dev (and further re-rebased the following ones to new hashes, C7' C5'' C6'').

或者你做了一个git pull --rebase(或者--rebase如果你的配置暗示它没有明确),它将原始的 C5 和 C6 拉回到你的本地开发中(并进一步将以下的重新基于新的哈希值,C7' C5'' C6' ')。

One way out of this could have been git push -fto force the push when it gave the error and wipe C5 C6 from origin, but if anyone else also had them pulled before you wiped them, you'd be in for a whole lot more trouble... basically everyone that has C5 C6 would need to do special steps to get rid of them. Which is exactly why they say you should never rebase anything that's already published. It's still doable if said "publishing" is within a small team, though.

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

回答by JN Gerbaux

I found out that in my case, this issue the consequence of a Git configuration problem. (Involving pull and merge)

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

Description of the problem:

问题描述:

Sympthoms:Commits duplicated on child branch after rebase, implying numerous merges during and after rebase.

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

Workflow:Here are steps of the workflow I was performing:

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

  • Work on the "Features-branch" (child of "Develop-branch")
  • Commit and Push changes on "Features-branch"
  • Checkout "Develop-branch" (Mother branch of Features) and work with it.
  • Commit and push changes on "Develop-branch"
  • Checkout "Features-branch" and pull changes from repository (In case someone else has commited work)
  • Rebase "Features-branch" onto "Develop-branch"
  • Push force of changes on "Feature-branch"
  • 在“Features-branch”(“Develop-branch”的子代)上工作
  • 在“Features-branch”上提交和推送更改
  • 签出“Develop-branch”(功能的母分支)并使用它。
  • 在“Develop-branch”上提交并推送更改
  • 签出“Features-branch”并从存储库中提取更改(以防其他人提交了工作)
  • 将“Features-branch”变基为“Develop-branch”
  • 推动“Feature-branch”变化的力量

As conséquences of this workflow, duplication of all commits of "Feature-branch" since previous rebase... :-(

作为此工作流程的结果,自上次变基以来,“功能分支”的所有提交都会重复...... :-(

The issue was due to the pull of changes of child branch before rebase.Git default pull configuration is "merge". This is changing indexes of commits performed on the child branch.

问题是由于在 rebase 之前拉动了子分支的更改。Git 默认拉取配置是“合并”。这是更改在子分支上执行的提交的索引。

The solution: in Git configuration file, configure pull to work in rebase mode:

解决办法:在Git配置文件中,配置pull以rebase模式工作:

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

Hope it can help JN Grx

希望它可以帮助 JN Grx

回答by ScottyBlades

You may have pulled from a remote branch different from your current. For example you may have pulled from Master when your branch is develop tracking develop. Git will dutifully pull in duplicate commits if pulled from a non-tracked branch.

您可能从与当前分支不同的远程分支中拉取。例如,当您的分支正在开发跟踪开发时,您可能已经从 Master 拉取。如果从非跟踪分支中提取,Git 将尽职尽责地提取重复提交。

If this happens, you can do the following:

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

git reset --hard HEAD~n

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

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

Then make sure you are pulling from the correct branch and then run:

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

git pull upstream <correct remote branch> --rebase

Pulling with --rebasewill ensure you aren't adding extraneous commits which could muddy up the commit history.

拉取--rebase将确保您不会添加可能混淆提交历史的无关提交。

Here is a bit of hand holding for git rebase.

这是 git rebase 的一些手把手。