能有人回答我关于 Git 中 rebase 的观察吗?

huangapple go评论48阅读模式
英文:

Can anybody answer my observation with Git in rebase?

问题

我看到了一些行为(在我看来不太友好开发者)与git rebase有关。我会简要解释一下步骤:

步骤1. 我有一个master分支,其中包含提交m1m2

步骤2:创建了一个名为'feature_branch'的特性分支,并添加了提交f1f2。与此同时,其他开发者添加了提交m3

步骤3:我使用命令git rebase masterfeature_branchmaster分支合并,以使特性分支现在按顺序包含提交m1,m2,m3,f1,f2(我解决了一些合并冲突。从f1和f2的角度来看,这是完全可以的)。在推送之前,Git要求我进行拉取。我进行了拉取并在将更改推送到特性分支之前再次解决了一些合并冲突。

步骤4:现在,一位开发者在主分支上进行了提交m4

步骤5:再次将feature_branchmaster进行合并。

在第5步中,我观察到特性分支按正确顺序获取了m1、m2、m3、m4,并开始逐个应用f1和f2。到目前为止,一切都还好。

但就在Git逐个应用f1f2之前(作为rebase的一部分),本地工作区文件已经包含了f1+f2的更改(这很令人困惑)。在应用f1时,它与f1+f2更改中的文件发生了合并冲突。我解决了冲突并继续进行rebase。在应用f2时,它再次发生了冲突。我理解在播放提交f1和f2时,我应该得到与第3步时已经遇到的相同冲突(假设在m4中文件没有与f1和f2相同的更改)。但为什么会出现奇怪的冲突呢?

对于这个长问题,我表示歉意。

谢谢,
Prasanth

英文:

I saw some behaviour (not developer friendly in my opinion) with git rebase. I will explain steps briefly:

Step 1. I have a master branch with commits m1 and m2.

Step2: Created a feature branch 'feature_branch' and added commit f1, f2. Mean time some other developer added commit m3.

Step 3: I have rebased feature_branch with master (using command git rebase master)so that feature branch have now commits in the order m1, m2, m3, f1, f2 (I fixed some merge conflicts. This perfectly OK in view of f1 and f2). While pushing git asked me to do pull. I did pull and fixed some merge conflicts again before pushing changes to feature branch.

Step4: Now a commit of m4 is made on master by some developer

Step 5: Rebase feature_branch again with master.

In step 5, I have observed that feature branch get m1, m2, m3, m4 in correct order and start applying f1 and f2 one by one on top of that. UP TO THIS I AM PEREFECTLY OK.

But just before git apply applying f1 and f2 one by one (as part of rebase), the local workspace folder files has already f1+f2 changes (This is confusing). While applying f1 it get merge conflicts with changes in files due f1+f2. I fixed the conflicts and continue rebasing. While f2 is being applied it gets conflicts again. My understanding while playing the commits f1 and f2, i am supposed to get same conflicts which i had got already during step 3(consider in m4 there is no changes in files which f1 and f2 have). But getting weird conflicts. Why is this happening?

Apology for a big question.

Thanks,
Prasanth

答案1

得分: 1

首先,当你运行git rebase时,你在你的特性分支上所做的提交会被Git暂时保存起来,就像一个补丁一样。你正在重新基于的分支被检出,这种情况下是主分支。这就是为什么你看到你的本地工作区已经包含了来自主分支的更改。

然后,你在feature_branch上所做的更改会逐个重新应用到这个分支上。这就是为什么你需要再次解决冲突的原因 - Git 实际上正在尝试重新创建最初完成的提交,所以如果这些提交在那个时候与主分支冲突,你将不得不再次解决这些冲突。

你在步骤3看到不同冲突的原因可能是因为rebase的基础已经发生了变化。在步骤3中,你正在重新基于m3,但在步骤5中,你正在重新基于m4。即使m4没有触及与f1和f2相同的文件,它也可能已经改变了f1和f2被应用的上下文,导致不同的冲突。

避免一遍又一遍解决相同冲突的一种方法是使用git rerere("重用记录的解决方案"),它允许Git记住你如何解决冲突,以便下次再次遇到相同的冲突时,Git可以自动解决它们。

要启用rerere,你可以使用以下命令:

git config --global rerere.enabled true

下次你重新基于并解决冲突时,Git将记住这些解决方案,并在你再次重新基于时应用它们。

英文:

Firstly, when you run a git rebase, the commits that you've made on your feature branch are temporarily saved away by Git, like a patch. The branch you're rebasing onto is checked out, in this case, the master branch. This is why you're seeing your local workspace already containing the changes from the master branch.

Then, the changes that you made on your feature_branch are reapplied one by one onto this branch. This is why you're having to solve conflicts again -- Git is essentially trying to recreate the commits as they were originally done, so if those commits had conflicts with the master branch at that point, you'll have to solve those conflicts again.

The reason you're seeing different conflicts than you did in step 3 could be due to the fact that the base of the rebase has moved. In step 3, you were rebasing onto m3, but in step 5, you're rebasing onto m4. Even if m4 didn't touch the same files as f1 and f2, it could have changed the context in which f1 and f2 are applied, leading to different conflicts.

One way to avoid having to solve the same conflicts over and over again is to use git rerere ("reuse recorded resolution"), which allows Git to remember how you've resolved a conflict so that the next time it sees the same conflict, Git can automatically resolve it for you.

To enable rerere, you can use the following command:

git config --global rerere.enabled true

Next time you rebase and resolve conflicts, Git will remember these resolutions and apply them when you rebase again.

答案2

得分: 1

我相当肯定你遗漏了一个步骤:

步骤2.5:你推送了 feature_branch

那时候 feature 看起来像:

m1-m2-f1-f2

在第3步中,在你将 feature_branch 重新基于 master 之后,feature_branch 看起来有点像:

m1-m2-m3-f1'f2'

(请注意 f1'f2' 中的 "prime",它们用于表示它们类似于 f1f2 但略有不同,例如每个都有一个新的提交 ID,而且在这种情况下还有一个新的父提交 ID、提交者日期等等。)

现在,当你尝试推送时,你可能看到了这样的消息:

Your branch and 'origin/feature_branch' have diverged, and have 3 and 2 different commits each, respectively.

这是因为 origin/feature_branch 有提交 f1f2,而你的本地分支不再有这些提交,因为现在它们是 f1'f2',它们有不同的提交 ID。当 Git 检测到这一点时,它总是告诉你先拉取然后推送,但在你的情况下,你不想拉取,因为那会将你的分支的旧版本合并回你当前的更好的版本。在这里拉取会导致你拥有重复版本的提交。相反,你想要在有意重写你的分支后强制推送,比如使用 amendrebase

修复方法:

为了防止下次发生这种情况,如Phillipe的评论中所提到的,在重新基于你的分支后,如果你之前已经推送过它,不要像 Git 告诉你的那样拉取,而是应该使用 git push --force-with-lease

现在你可以使用 git reflog 找到 f2' 或甚至 f2'',然后使用 git reset --hard <commit-id-you-want>。然后你可以强制推送你的 feature_branch 来删除与旧版本的这些提交的合并。

解释:

你没有这样做的事实解释了你所见的一切。由于在 m3 之上重播 f1f2 时发生冲突,解决冲突后你的新状态只与 f1'f2' 兼容。当你拉取时,这又合并了 f1f2 的原始版本,这与新状态不兼容,所以你又遇到了冲突。在解决冲突并完成合并后,你的分支可能看起来像这样:

m1-m2-m3-f1'-f2'-MC
     \---f1--f2-/

当你在第5步中再次基于 master 时,现在有5个提交不在 master 上,并且默认情况下在重播时合并提交会被删除,所以只有4个提交会被重播。假设提交 m4 没有与 f1'f2' 产生冲突,因此将它们重播为 "1 of 4" 和 "2 of 4" 是完全没有问题的,此时你的临时分支看起来像这样:

m1-m2-m3-m4-f1''-f2''
# 在重播 f1 时冲突

这解释了为什么你已经看到了来自 f1f2 的更改,但仍然试图重播原始的 f1f2,因此又发生了冲突,因为上次解决冲突的合并提交在重播期间不会被重播。如果你仍然处于这种状态,你实际上可以选择跳过这些提交,因为你不再需要它们,或者只是停在当前状态:

git rebase --quit

现在你的分支应该看起来像这样:

m1-m2-m3-m4-f1''-f2''

如果你处于分离状态,你可以重新指向这个提交:

git switch -C feature_branch # 重新创建分支并指向 f2''
# 然后
git push --force-with-lease
英文:

I'm pretty sure you left out a step:

Step 2.5: You pushed feature_branch.

At that moment feature looked like:

m1-m2-f1-f2

In step 3, after you rebased feature_branch onto master, feature_branch looked something like:

m1-m2-m3-f1&#39;-f2&#39;

(Note the "prime" here in f1&#39; and f2&#39;, which is used to signify that they are like f1 and f2 but slightly different, e.g. each has a new commit ID, and in this case also a new parent commit ID, and committer datetime, etc.)

Now when you tried to push, you probably saw a message like:

> Your branch and 'origin/feature_branch' have diverged, and have 3 and 2 different commits each, respectively.

This is because origin/feature_branch has commits f1 and f2 and your local branch no longer has those commits, since now they are f1&#39; and f2&#39; which are different commit IDs. When Git detects this, it always tells you to pull and then push, but in your case, you don't want to pull because that will merge the old version of your branch back into your current, better, version. By pulling here you end up with duplicate versions of your commits. Instead you want to force push after purposefully rewriting your branch, such as with amend, or rebase.

The Fix:

To prevent this next time, as mentioned in Phillipe's comment, after rebasing your branch, if you've already pushed it before, don't pull like Git tells you to, but instead you should use git push --force-with-lease.

For now you can use git reflog to either find f2&#39; or even f2&#39;&#39; and then git reset --hard &lt;commit-id-you-want&gt;. Then you can force push your feature_branch to remove the merge with the old version of those commits.

The Explanation:

The fact that you didn't do that explains everything you witnessed. Since you had conflicts replaying f1 and f2 on top of m3, after resolving them your new state is compatible with f1&#39; and f2&#39; only. When you pulled, that merged in the original versions of f1 and f2 again which aren't compatible with the new state, so you had conflicts again. After resolving them and completing the merge, your branch probably looked something like this:

m1-m2-m3-f1&#39;-f2&#39;-MC
     \---f1--f2-/

When you rebased onto master a second time in step 5, you now have 5 commits that aren't on master, and, when you rebase the merge commits fall out by default, so only the 4 commits will be replayed. (Had you added the option -i to the rebase you could have witnessed it.) Presumably commit m4 didn't introduce any conflicts with f1&#39; and f2&#39;, so replaying them as "1 of 4" and "2 of 4" went just fine, and at that moment your temporary branch looked like this:

m1-m2-m3-m4-f1&#39;&#39;-f2&#39;&#39;
# conflict on replaying f1

This explains why you already saw the changes from f1 and f2 there, but it was still stuck on trying to replay the original f1 and f2; thus it had conflicts again because the merge commit that resolved the conflicts last time isn't being replayed during the rebase. If you're still in that state you can actually elect to skip those commits as you no longer need them, or just quit where you are:

git rebase --quit

Now your branch should look like:

m1-m2-m3-m4-f1&#39;&#39;-f2&#39;&#39;

If you are detached you could just repoint your branch to this commit:

git switch -C feature_branch # recreate branch pointing to f2&#39;&#39;
# then
git push --force-with-lease

huangapple
  • 本文由 发表于 2023年6月19日 03:26:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76502212.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定