如何将分支还原为提交并将前向提交提交到新分支?

How do I revert a branch to a commit and put the forward commits to a new branch?

我有一个 git 看起来像这样的远程和本地。

A---B---C---D master

现在我希望我的 masterB 提交时恢复,但我也不想丢失我的 CD 提交。

有没有办法让我的 git 在远程和本地看起来都像这样?

      C---D new branch
     /
A---B master

我知道回溯到之前的点是不可能的,也许像这样的方法也可以解决问题:

                          C---D new branch
                         /
A---B---C---D---revertToB master

请多多指教

可以得到这个:

     C--D   <-- new-branch
    /
A--B   <-- master

但要做到这一点,您必须强制 所有 Git 具有名为 master 分支的存储库指向现有提交 D收回他们的 master 以指向现有的提交 B。这可能很困难,因为 Git "likes" 到 add 提交,而不是 "remove" 他们。

要在本地获取此信息,假设您有:

A--B--C--D   <-- master (HEAD)

运行:

git branch new-branch master
git reset --hard HEAD~2

您现在拥有:

     C--D   <-- new-branch
    /
A--B   <-- master (HEAD)

本地。不幸的是,位于 origin 的另一个 Git 存储库仍然有 master 指向提交 D。从其他 Git 存储库制作的任何克隆也有 他们的 master 指向提交 D。您必须让所有这些存储库执行相同类型的撤回。如果只有一个仓库,你有权使用git push --force收回它,你现在可以:

git push origin new-branch   # to create new-branch on origin
git push -f origin master    # to force them to retract their master

(您可能需要将 -u 添加到第一个 git push。)


为了获得您正在寻找的更安全的东西——向 master 添加一个新的提交,将其重置为提交时的状态 B——你可以使用:

git branch temp master

设置要读取的内容:

A--B--C--D   <-- master (HEAD), temp

然后你可以 运行 git revert -n HEAD~2..HEAD; git commit, 或 git read-tree -u HEAD~2; git commit 进行新提交 E 其快照与 B:

A--B--C--D   <-- temp
          \
           E   <-- master (HEAD)

现有提交 C-D 卡在原处。如果您希望 new-branch 成为 master 的后代,您现在必须创建它。请注意,如果您同意 new-branch 指向现有提交 D,您可以只使用名称 new-branch 而不是 temp,您将获得想要的内容。但是如果你需要一个新的提交——我们称之为 F——在 E 之后与提交 D 的内容相匹配,你现在可以创建新分支:

git checkout -b new-branch master

给予:

A--B--C--D   <-- temp
          \
           E   <-- master, new-branch (HEAD)

现在您可以:

  • 还原还原提交 E,或
  • git read-tree 提交 D 并提交,或者
  • git cherry-pick 提交 CD

要执行第一个,运行 git revert mastergit revert HEAD(两者都足够)并且您有:

A--B--C--D   <-- temp
          \
           E   <-- master
            \
             F   <-- new-branch (HEAD)

要执行第二个操作,运行 git read-tree -u temp; git commit 进行新提交 F(您需要输入新的提交消息)。

做第三个,运行git cherry-pick temp~2..temp;这将产生:

A--B--C--D   <-- temp
          \
           E   <-- master
            \
             C'-D'  <-- new-branch (HEAD)

其中名称 C'D' 表示它们分别是 CD 的精心挑选的副本。 (当我们还原为 E 时,我们结合了两个撤消步骤,使用 git revert -n,因此 E 不完全是 D C,而是同时撤消两者。)

现在删除名称 temp 是安全的,因为我们不再需要它来方便地查找 CD。 (我们一直不需要它,因为无论如何我们都可以找到它们,但是为提交 D 命名很方便,这样提交 CBtemp~2temp~1 分别。)

现在可以安全地(并且不需要 git push --force)到 git push origin master new-branch


或者,您可以不创建 temp,而是让名称 new-branch 始终指向提交 D。也就是说,从:

开始
A--B--C--D   <-- master (HEAD)

你可以这样做:

git branch new-branch master

给予:

A--B--C--D   <-- master (HEAD), new-branch

然后你可以 "undo" C-D 像以前一样在 master 上提交,例如,生成:

A--B--C--D   <-- new-branch
          \
           E   <-- master (HEAD)

下一步,您可以 运行:

git checkout new-branch; git merge -s ours master

使 Git 创建一个新的 merge 提交 F 其快照与现有提交的快照相匹配 D:

A--B--C--D---F   <-- new-branch
          \ /
           E   <-- master (HEAD)

提交 F 现在和以前一样 领先于 master,但是当以直线向后读取时,跨第一父链接,它永远不会visits commit E: Git 从 F 走回 D 再到 C 等等。同样,这是 提供的 git loggit rev-listF 返回时使用 --first-parent。默认情况下,从 F 返回的遍历访问 both 提交 E and D (并且自默认是按照提交者日期顺序,你会看到 E,我们晚于 D,然后看到 D).