在功能分支合并之前回滚 git 主分支,做一些工作并再次合并功能分支

  • 本文关键字:分支 功能 合并 工作 git git
  • 更新时间 :
  • 英文 :


main分支包括来自功能分支feature-A的提交。我想在合并功能分支代码更改之前main分支恢复到提交,并部署此代码进行测试。假设在测试期间报告了多个错误,我将提交推送到main以修复错误。当主分支没有任何更多错误时,我想将功能分支feature-A的代码更改重新合并回main

注意:main受到保护,因此需要拉取请求才能应用新更改。

例:

(main) 1 - 2 - 3 - 4 - 5

提交 3、4、5 包括feature-A

现在回滚主,所以它变成:

(main) 1 - 2

将错误修复作为提交 6 和 7 的一部分应用

(main) 1 - 2 - 6 - 7

现在feature-A合并回最新的main状态

final state - (main) 1 - 2 - 6 - 7 - 3 - 4 - 5

我尝试过的: 我首先创建了主分支的备份,并将其命名为main-backup

来自这篇文章: 想要将我的主提交更改为较旧的提交,我该怎么做?我尝试了下面的方法,它在feature-A之前给了我一个新的分支,可以合并到 main 中。

If you want to avoid force pushing, here's how to revert your repo to an older commit and preserve all intervening work:
git checkout 307a5cd        # check out the commit that you want to reset to 
git checkout -b fixy        # create a branch named fixy to do the work
git merge -s ours master    # merge master's history without changing any files
git checkout master         # switch back to master
git merge fixy              # and merge in the fixed branch
git push                    # done, no need to force push!
Done! Replace 307a5cd with whatever commit you want in your repo.
(I know the first two lines can be combined, but I think that makes it less clear what's going on)
Here it is graphically:
c1 -- c2 -- c3 -- c4 -- c2' -- c5 ...
              /
'------------'
You effectively remove c3 and c4 and set your project back to c2. However, c3 and c4 are still available in your project's history if you ever want to see them again.

所以我创建了 PR 并合并。现在mainfeature-A之前提交。

现在,如果我创建一个 PR 以main-backup合并到main它应该向我显示与feature-A相关的所有代码提交/更改,但 GitHub 说没有代码更改。

所以我的理解不在这里。 最好的(也可能是最安全的)方法是什么?

在我深入研究答案之前,我需要解决这部分问题,因为我必须绘制一些图表,并且我喜欢绘制正确和明确的图表:

例:

(main) 1 - 2 - 3 - 4 - 5

在这里,您绘制了一个总共包含五个提交的存储库,所有这些提交都在一个名为main的分支上。 我对这张特定的图纸有几个问题。 一个很小,但以后可能会引起混淆。 另一个也是次要的,但很重要。

首先,虽然 Git 提交有编号的,但它们没有简单的顺序计数数字。 它们的实际数字是巨大的(目前高达2160-1,将来甚至更大),并且看起来是随机的。 因此,我认为最好使用字母代码进行提交:在这里我会使用AE,或者FJ,或者类似的东西。

第二个问题——很重要,但仍然很小——是像main这样的分支名称不是你应该贴在提交前面的标签。 这是一个标签,你应该粘贴到一个提交上,在这种情况下,提交#5,或者 - 正如我所说的 - 提交E

main
|
v
A <-B <-C <-D <-E

请注意每个提交如何向后指向上一个提交。

对于更紧凑的表示,我倾向于使用这个:

A--B--C--D--E   <-- main

在堆栈溢出上。 这会掉落内部箭头(这不是很好,但正如我们将看到的,几乎无法忍受)。

像这样绘制它们的原因是分支名称确实只指向一个提交。 这是了解我们可以如何处理您现有的仓库及其提交的第一个关键项目。 而且,像这样的提交链中的每个提交确实指向上一个提交。

更准确地说,正如我们前面提到的,提交是有编号的。 实际数字(以十六进制表示)看起来奇怪且随机,例如f6b272b0c674c2d5022e90c3dec868af4ea26522。 它们对人类来说太难打扰了。 这就是为什么我们让计算机使用分支名称记住链中的最后一个,例如main.

每个提交包含两件事:

  • 提交具有每个文件的完整快照。 存储在每个提交中的文件以特殊的、仅限 Git 的、压缩的和重复的格式存储。 这是您的计算机通常无法读取的内容;只有Git可以读取这些文件。 因此,您实际上并不使用这些文件:它们仅用于历史记录目的。 它们就像一个永久存档,就像每个文件的zip或tar存档一样。

  • 并且,每个提交都包含一些元数据或有关提交本身的信息。 例如,这包括提交人员的姓名、他们的电子邮件地址以及日期和时间戳。 但它也包括早期提交的原始哈希 ID。 因此,提交E,无论其哈希 ID 是什么,实际上都包含早期提交D的哈希 ID(无论该哈希 ID 是什么)。

提交向后指向其父级的事实允许 Git 在像main这样的名称中仅存储一个提交哈希 ID。 当main指向E时,这就足够了,因为E指向D。 Git 可以通过Emain中找到D。 由于D指向后指向C,Git 可以从main中找到C,通过返回两跳......由于C指向后指向B,Git 也可以在这里找到B,这找到了A(然后我们用完了提交,git log在这里停止)。

注意:main受到保护,因此需要拉取请求才能应用新更改。

我们现在需要另一个关于 Git 的事实。 我在上面提到过,Git 不存储更改,而是存储称为提交的快照,Git通过从分支名称开始,然后根据需要向后工作来查找提交。 您需要知道的最后一件事是:任何提交都无法更改。

但是:如果 Git 只存储快照,并且无法更改任何提交 - 并且两者都是正确的 - 那么我们如何将任何更改放入 Git 存储库?完整而正确的答案很长,所以我会花一些额外的时间写下这个并缩短它:

  • 我们使用分支名称提取某个分支上的最后一次提交。
  • 然后,我们使用提取的提交一段时间,并最终进行新的提交:新快照。

也就是说,给定:

A--B--C--D--E   <-- main

我们运行git checkout maingit switch main提取提交E,然后我们做我们的工作,然后我们运行git commit来创建一个新的提交F

A--B--C--D--E   <-- main

F

为了查找提交F,我们需要一个名称(通常是分支名称),因此当我们使用 GitHub 和受保护的分支时,我们会在此过程中创建一个新的分支名称,以便通过新名称找到提交F

A--B--C--D--E   <-- main

F   <-- feature

然后我们将我们自己的 Git 存储库的新分支推送到 GitHub,发出"拉取请求",并使用特定于 GitHub 的拉取请求机制最终将FF(F')的某个副本合并GitHub 上。 同样,有很多细节,通常我会在这里讨论所有这些细节,但我花时间让自己退缩。 如果我们把每件事都做得恰到好处,我们最终会得到:

A--B--C--D--E--F   <-- main, feature

我们现在可以删除名称feature. 请注意,提交F根本没有更改。 它仍然指向提交E改变的是名称本身main现在指向F.

这为我们提供了一组关于 Git 的简单规则:

  • 我们总是可以添加更多提交。
  • 我们可以移动分支名称。 使用 GitHub "受保护的分支",GitHub 存储库中的名称(但不是其他克隆中的名称!)受到保护,不会被移动。 只允许一些仅限 GitHub 的特殊情况。您可能希望覆盖它(例如,通过暂时取消保护main),但请参阅下文。
  • 我们无法更改任何现有提交。
  • 我们也不能真正删除提交。
  • 但是人们会发现使用名称的提交,我们可以移动这些名称

进入您的第一个问题

现在回滚主,所以它变成:

(main) 1 - 2

这将是:

A--B   <-- main

C--D--E

未更改任何提交。 我们只是利用我们的能力来移动一个名字。 在这种情况下,我们将名称main移动,使其指向B而不是E

这立即遇到了两个问题:

  1. 您将 GitHub 上的main设置为受保护。 所以你不允许自己自己移动它。
  2. 即使我们解决了问题 #1,提交C-D-E会发生什么?

虽然提交C-D-E会保留(一段时间不确定),但我们只能通过使用名称然后向后工作来查找提交。 我们需要一个名称来查找提交E

还有一个次要问题,尽管它的重要性取决于你和你的同事/朋友/同事如何使用Git 和 GitHub。 具体来说,任何克隆GitHub存储库的人现在可能都认为分支main也应该在提交E结束。

Git 喜欢在分支的末尾添加新的提交,正如我们上面看到的。 它不太愿意在分支的末尾"丢失"提交。 Git 知道,如果你向分支添加一个提交——通过选取一个全新的提交,比如E之后的F,或者E之后进行一个全新的提交,或者只是接受一些你已经提交的提交,比如F,在E之后,并将其添加到main——如果你只是添加一个新提交,所有旧的提交仍然存在,并且很容易以 Git 通常的向后方式找到。 要以添加提交的方式移动分支名称,Git 通常说OK!做!(GitHub 的分支保护,你在 GitHub 端的 Git 存储库副本中设置的,让它说

但是如果你要求 Git 向移动一个分支名称,这样它就会失去分支末尾的提交,Git 通常会吓坏并说不!那会丢失一些提交!(当然,它们仍然在那里,但你必须知道丑陋的随机哈希ID才能找到它们。

您可以绕过这些:

  1. 要阻止main受到保护,请删除保护:要么给自己全部权力(main只得到部分保护),要么完全取消保护,以便每个人都有充分的权力。 然后使用git push --force更新它,以便 Git 自己的正常答案变成正常,但在抗议下

  2. 保持提交C-D-E,只需创建一个为您记住E的新名称

因此,一旦您这样做,您将拥有:

A--B   <-- main

C--D--E   <-- saved

例如。 在 GitHub 上修复保护后(和/或根据需要重复,直到下面的git push --force正常工作)运行:

git switch main          # switch to main
git status               # make sure that this worked and all is clean and good
git branch saved         # make new branch name to remember E
git reset --hard HEAD~3  # force `main` back three steps
git push -f origin main  # force GitHub to drop 3 commits too

你是否git push origin saved取决于你:这让你也为GitHub副本上的三个提交命名。 (分支名称是每个 Git 的本地名称。

将错误修复作为提交 6 和 7 的一部分应用

(main) 1 - 2 - 6 - 7

让我们正确地绘制这个

A--B--F--G   <-- main

C--D--E   <-- saved
您可以通过在main

上创建两个新提交FG来达到此状态。 由于这些提交只是添加到main,您现在可以将它们直接推送到 GitHub(您可以直接推送到 GitHub:main对您不受保护)。完成此步骤后,您可以根据需要在 GitHub 上重新保护main

现在解决您的主要(无双关语)问题

现在合并feature-A回到最新的main状态

final state - (main) 1 - 2 - 6 - 7 - 3 - 4 - 5

哇,等一下:feature-A从哪里来的?

我知道你之前提到过。 但是 Git 存储库中的分支名称仅在查找一个提交方面很重要。 该提交是分支名称指向的提交。 那么这一切在哪里feature-A呢? 我们从来没有把它画进来

在你尝试真正解决这个问题之前——因此在使用我到目前为止提到的任何内容之前——你需要正确地feature-A画进去。 在这里,您需要小心,因为当您使用 GitHub 保护的分支功能和那里的合并按钮时,您可能会得到惊喜。

特别是,绿色的合并按钮上有一个下拉箭头。 使用箭头会使按钮更改为以下三个选项之一:

  • MERGE的意思就是:进行完全合并。 GitHub 将包含一个合并提交(见下文)。

  • 变基和合并意味着将一些原始提交复制到新的和改进的提交中。 这可能是您之前所做的,在这种情况下,三个saved-branch 提交与feature-A提交是不同的提交

  • SQUASH 和 MERGE意味着获取整个提交链,并将它们转换为单个普通提交并将其添加到当前分支的末尾。 在这种情况下,main不会有来自某个feature-A分支的三个提交,也不会有一个合并提交。 相反,它将有一个普通提交。 这根本不适合C-D-E

所以我们一直在用虚假的图片工作。 这里只有你有仓库,所以只有你能画出真实的画面。 没有结论,我们就无法得出任何可靠的结论。 但是,如果我们在这里绘制的图片足够接近,则整个重置和重建方法将允许您继续。 您现在可以简单地将savedmain合并:

git switch main    # if needed
git merge saved

这使用 Git 的合并机制以通常的方式组合工作。

让我们重新绘制一下输入:

F-----G   <-- main (HEAD)
/
A--B

C--D--E   <-- saved

这张图和以前一样,但我F-G移到了一条单独的线上,并G滑了一点。 我还在括号中添加了特殊名称HEAD,作为"附加到"main,以便我们知道我们在运行git merge时"在哪个分支上"。

合并的工作方式是找到最佳共享提交(两个分支上的提交),作为 Git 称之为合并基础提交的东西。 在这里,main(提交G)和saved(提交E)之间的最佳共享提交显然是提交B。 至少,在更新的图纸中应该很清楚。

因此,在自行定位提交B后,Git 现在将运行两个git diff命令,以查看三个快照中的不同之处:

  • git diff --find-renameshash-of-B hash-of-G:这找出了自B年以来"我们"在main上发生了哪些变化。 也就是说,无论从这个差异中得出什么,都是我们在提交FG中所做的任何更改的总和。

  • git diff --find-renameshash-of-B hash-of-E:这找出了自B年以来"他们"(我们,真的)在saved上发生了什么变化。

Git 现在可以这两组更改结合起来。 这是一个常规的、全面的合并,Git 试图自己做那种。 如果我们和他们更改了不同的文件,或者相同文件的不同(非重叠)行,Git 将能够合并更改。 然后,Git 可以将组合更改应用于在提交B中找到的快照。 这:

  • 保持我们的变化,但也
  • 添加其更改。

然后,结果已准备好放入新快照中。 下一个字母是H,但让我们称之为提交M,并将其绘制为:

A--B--F--G---M   <-- main (HEAD)
       /
C--D--E   <-- saved

提交M的唯一特别之处在于,它不是像普通提交那样指向G,而是指向两个提交 - 两个父,用Git的术语来说 -GE

GitHub可以进行此提交M如果 Git 可以进行此提交M。 为此,您只需将saved推送到 GitHub 并像往常一样发出拉取请求。 请注意,这里不涉及feature-A分支:我们只是在main上进行了两次提交,在回滚main之后,然后使用了git merge。 这里唯一特别的是回滚部分,它需要撤消分支保护并使用git push --force. 提交M完成后,我们可以安全地删除名称saved因为可以通过移动到提交M(但是我们发现M),然后回到第二个父级来找到提交E

这也可能会拥有原始 GitHub 存储库克隆的任何其他人带来小麻烦。 以下是他们在Git 存储库中可能拥有的内容

A--B--C--D--E   <-- main, origin/main

H--I   <-- my-feature (HEAD)

如果他们现在从 GitHub 获取新提交,他们的存储库将变为:

A--B--F--G---M   <-- origin/main
       /
C--D--E   <-- main

H--I   <-- my-feature (HEAD)

也就是说,他们的main曾经与他们的origin/main完全齐平,说提交E。 这是他们的main所以还没有改变。 如果他们现在重新同步他们的main- 这将很好,因为M"领先于"E- 他们现在将拥有:

A--B--F--G---M   <-- main (HEAD), origin/main
       /
C--D--E

H--I   <-- my-feature

这里没有任何真正的问题,但他们可能希望他们的my-feature"基于"origin/mainmain。 也就是说,他们现在可能想要得到这个:

H'-I'  <-- my-feature (HEAD)
/
A--B--F--G---M   <-- main (HEAD), origin/main
       /
C--D--E

H--I   [abandoned]

为此,他们将希望使用git rebase.他们需要了解这种变基的工作原理,以防出现任何问题。

结论

您可能通过不正确地绘制存储库中的实际内容而引导我们所有人走上花园小径,但如果没有,您的具体要求非常简单。 除非并且直到你在GitHub上取消保护main,至少对你自己来说,否则你不能这样做,因为GitHub工具将在这里保护你免受自己的伤害。不过,Git完全愿意这样做。

这可能给别人带来的小问题是否是你应该担心的事情,只有你和他们可以回答。

了解 Git 在查看目标时使用的机制非常重要。 还有其他方法可以实现不涉及git push --force的各种目标,但是如果没有完整准确的图纸和对整个团队的访问权限,很难提供特定的建议。

相关内容

最新更新