我了解 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 中的解释,所以我想知道两件事:
- 为什么 Git 会重复这些提交变基时?是否有特殊原因要这样做,而不是在
C7
之后应用 C5
和 C6
?
- 我怎样才能避免这种情况?这样做明智吗?
I understand the scenario presented in Pro Git about The Perils of Rebasing. The author basically tells you how to avoid duplicated commits:
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.
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
:
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/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 master
to make a quick fix:
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 dev
I rebase the changes to include the quick fix in my actual development:
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/dev
now contains two identical commits C5'
and C6'
which are different to Git. Now if I push the changes to origin/dev
this is the result:
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:
- Why does Git duplicate these commits while rebasing? Is there a particular reason to do that instead of just applying
C5
and C6
after C7
?
- How can I avoid that? Would it be wise to do it?
发布评论
评论(5)
简短回答
您忽略了运行 git push 的事实,出现以下错误,然后继续运行 git pull :
尽管 Git 试图提供帮助,但它的“git pull”建议很可能不是您想要做的。
如果您:
git push --force
使用 post-rebase 更新远程提交(根据 user4405677 的回答)。git rebase
。要使用master
的更改来更新dev
,您应该运行git merge mastergit rebase master dev
> 在dev
上(根据 Justin 的回答)。稍微长一点的解释
Git 中的每个提交哈希都基于许多因素,其中之一是它之前的提交的哈希。
如果您重新排序提交,您将更改提交哈希值;变基(当它执行某些操作时)将更改提交哈希值。这样,运行
git rebase master dev
的结果(其中dev
与master
不同步)将创建新 提交(并因此散列)与dev
上的内容相同,但在master
上插入了它们之前的提交。您可能会以多种方式陷入这种情况。我能想到的两种方法:
master
上进行提交,您希望将其作为您的dev
工作的基础dev
上进行提交已经被推送到远程,然后您可以继续更改(重写提交消息、重新排序提交、压缩提交等)。让我们更好地理解发生了什么 - 这是一个示例:
您有一个存储库:
然后您可以继续更改提交。
(在这里,您必须相信我的话:在 Git 中,有多种方法可以更改提交。在本例中,我更改了
C3
的时间,但您要插入新的提交,更改提交消息,重新排序提交,将提交压缩在一起等)这就是它所在的位置重要的是要注意提交哈希值不同。这是预期的行为,因为您已经更改了有关它们的某些内容(任何内容)。这没关系,但是:
尝试推送将显示出现错误(并提示您应该运行 git pull )。
如果我们运行
git pull
,我们会看到以下日志:或者,以另一种方式显示:
现在我们在本地有重复的提交。如果我们要运行 git Push,我们会将它们发送到服务器。
为了避免进入这个阶段,我们可以运行
git push --force
(我们改为运行git pull
)。这将毫无问题地将我们的带有新哈希值的提交发送到服务器。要解决此阶段的问题,我们可以重置回运行git pull
之前的状态:查看 reflog (
git reflog
) 以查看提交哈希值在我们运行git pull
之前。上面我们看到 ba7688a 是我们在运行 git pull 之前所处的提交。有了提交哈希,我们就可以重置回该值(
git reset --hard ba7688a
),然后运行git push --force
。我们就完成了。
但是等等,我继续基于重复的提交进行工作
如果您不知何故没有注意到提交是重复的并继续在重复的提交之上继续工作,那么您真的把自己搞得一团糟。混乱的大小与重复项之上的提交数量成正比。
它看起来像什么:
或者,以另一种方式显示:
在这种情况下,我们想要删除重复提交,但保留基于它们的提交 - 我们想要保留 C6 到 C10。与大多数事情一样,有多种方法可以解决此问题:
要么:
cherry-pick
每次提交(C6 到 C10)到该新分支,并将该新分支视为规范分支。git rebase --interactive $commit
,其中$commit
是重复提交2之前的提交>。在这里我们可以彻底删除重复的行。1 无论您选择两者中的哪一个,
ba7688a
或2a2e220
都可以正常工作。2 在本例中,它将是
85f59ab
。TL;DR
将
advice.pushNonFastForward
设置为假:
Short answer
You omitted the fact that you ran
git push
, got the following error, and then proceeded to rungit pull
:Despite Git trying to be helpful, its 'git pull' advice is most likely not what you want to do.
If you are:
git push --force
to update the remote with your post-rebase commits (as per user4405677's answer).git rebase
in the first place. To updatedev
with changes frommaster
, you should, instead of runninggit rebase master dev
, rungit merge master
whilst ondev
(as per Justin's answer).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.
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
, wheredev
is out of sync withmaster
, will create new commits (and thus hashes) with the same content as those ondev
but with the commits onmaster
inserted before them.You can end up in a situation like this in multiple ways. Two ways I can think of:
master
that you want to base yourdev
work ondev
that have already been pushed to a remote, which you then proceed to change (reword commit messages, reorder commits, squash commits, etc.)Let's better understand what happened—here is an example:
You have a repository:
You then proceed to change commits.
(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.)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:
Trying to push will show you an error (and hint that you should run
git pull
).If we run
git pull
, we see this log:Or, shown another way:
And now we have duplicate commits locally. If we were to run
git push
we would send them up to the server.To avoid getting to this stage, we could have run
git push --force
(where we instead rangit 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 rangit pull
:Look at the reflog (
git reflog
) to see what the commit hash was before we rangit pull
.Above we see that
ba7688a
was the commit we were at before runninggit pull
. With that commit hash in hand we can reset back to that (git reset --hard ba7688a
) and then rungit 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:
Or, shown another way:
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:
Either:
cherry-pick
each commit (C6 through C10 inclusive) onto that new branch, and treat that new branch as canonical.git rebase --interactive $commit
, where$commit
is the commit prior to both the duplicated commits2. Here we can outright delete the lines for the duplicates.1 It doesn't matter which of the two you choose, either
ba7688a
or2a2e220
work fine.2 In the example it would be
85f59ab
.TL;DR
Set
advice.pushNonFastForward
tofalse
:您不应该在这里使用变基,简单的合并就足够了。您链接的 Pro Git 书基本上解释了这种确切的情况。内部工作原理可能略有不同,但我是这样想象的:
C5
和C6
暂时从dev
C7< /code> 应用于
dev
C5
和C6
在C7
之上播放,创建新的差异并因此新的提交所以,在你的
dev
中分支,C5
和C6
实际上不再存在:它们现在是C5'
和C6'
。当您推送到origin/dev
时,git 将C5'
和C6'
视为新提交,并将它们添加到历史记录的末尾。事实上,如果您在origin/dev
中查看C5
和C5'
之间的差异,您会发现虽然内容是相同的,行号可能不同——这使得提交的哈希值不同。我将重申 Pro Git 规则:永远不要对除本地存储库之外任何地方都存在的提交进行变基。请改用合并。
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:
C5
andC6
are temporarily pulled out ofdev
C7
is applied todev
C5
andC6
are played back on top ofC7
, creating new diffs and therefore new commitsSo, in your
dev
branch,C5
andC6
effectively no longer exist: they are nowC5'
andC6'
. When you push toorigin/dev
, git seesC5'
andC6'
as new commits and tacks them on to the end of the history. Indeed, if you look at the differences betweenC5
andC5'
inorigin/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.I'll restate the Pro Git rule: never rebase commits that have ever existed anywhere but your local repository. Use merge instead.
我认为您在描述步骤时跳过了一个重要细节。更具体地说,您的最后一步,
git push
on dev,实际上会给您一个错误,因为您通常无法推送非快进更改。因此,您在上次推送之前执行了 git pull ,这导致了以 C6 和 C6' 作为父级的合并提交,这就是为什么两者都将保留在日志中列出的原因。更漂亮的日志格式可能会让它们更明显地显示它们是重复提交的合并分支。
或者你做了一个 git pull --rebase (或者没有显式的
--rebase
,如果它是由你的配置暗示的),它将原始的 C5 和 C6 拉回你的本地开发(并进一步将以下内容重新重新设置为新的哈希值,C7' C5'' C6'')。解决这个问题的一种方法可能是 git push -f 在出现错误时强制推送并从原点擦除 C5 C6,但如果其他人在你擦除它们之前也拉了它们,那么你'将会遇到更多麻烦...基本上每个拥有 C5 C6 的人都需要执行特殊步骤来摆脱它们。这正是为什么他们说你永远不应该对已经发布的任何内容进行 rebase。不过,如果所说的“发布”是在一个小团队内进行的话,这仍然是可行的。
I think you skipped an important detail when describing your steps. More specifically, your last step,
git push
on dev, would have actually given you an error, as you can not normally push non-fastforward changes.So you did
git pull
before 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.Or you made a
git pull --rebase
(or without explicit--rebase
if 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'').One way out of this could have been
git push -f
to 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 配置问题的结果。 (涉及拉取和合并)
问题描述:
症状: rebase 后在子分支上重复提交,这意味着在 rebase 期间和之后进行了多次合并。
工作流程:
以下是我正在执行的工作流程的步骤:
作为此工作流程的后果,自上次变基以来“功能分支”的所有提交都重复... :-(
问题是由于之前子分支的更改引起的rebase。 Git 默认的 pull 配置是“merge”。这是更改在子分支上执行的提交的索引。
解决方案:在 Git 配置文件中,将 pull 配置为在 rebase 模式下工作:
希望它能有所帮助。
JN格克斯
I found out that in my case, this issue the consequence of a Git configuration problem. (Involving pull and merge)
Description of the problem:
Sympthoms: Commits duplicated on child branch after rebase, implying numerous merges during and after rebase.
Workflow:
Here are steps of the workflow I was performing:
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.
The solution: in Git configuration file, configure pull to work in rebase mode:
Hope it can help
JN Grx
您可能从与当前分支不同的远程分支中拉取。例如,当您的分支正在开发跟踪开发时,您可能已经从 Master 中拉取了。如果从非跟踪分支拉取重复提交,Git 将尽职尽责地拉取重复提交。
如果发生这种情况,您可以执行以下操作:
where
n == <不应该存在的重复提交数。>
然后确保您从正确的分支拉取,然后运行:
使用
--rebase
进行拉取将确保您不会添加无关的提交,这可能会混淆提交历史记录。这里是 git rebase 的一些指导。
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.
If this happens, you can do the following:
where
n == <number of duplicate commits that shouldn't be there.>
Then make sure you are pulling from the correct branch and then run:
Pulling with
--rebase
will ensure you aren't adding extraneous commits which could muddy up the commit history.Here is a bit of hand holding for git rebase.