移动创建分支的提交

Moving what commit a branch was created on

好的,这就是交易。我从提交 (2f6ea37) 创建了一个分支 (master),然后从 master 创建了另一个分支 (develop)。在开发中进行了更改。问题是,master 实际上应该是根据之前的提交 (df7992e) 而不是 (2f6ea37) 创建的。

如何在不丢失致力于开发的新内容的情况下更改创建 master 的位置?

现在是这样的:

branch1 --> <2f6ea37> --> <df7992e>
                                   \ 
                                    master
                                          \
                                           develop --> <new_commit>

应该是这样的:

branch1 --> <2f6ea37> --> <df7992e>
                     \ 
                      master
                            \
                             develop --> <new_commit>

TL;DR

你可能只需要三个 git branch -f 命令来做你想做的事:

git branch -f master 2f6ea37
git branch -f branch1 df7992e
git branch -f develop 2f6ea37

但是要使用 git branch -f 执行此操作,您必须在除这三个中的任何一个之外的某个分支名称上,因此您可能需要一个 git reset --hard 或其他命令。如果 develop 已经有额外的提交,你有一个更大的问题。请参阅下面的详细说明。

首先(这有点小,但确实很重要),图中的箭头是错误的,因为它们是向前的。 :-) Git 做所有事情都是倒退的。因此,像 df7992e 这样的提交将 向后 指向其前身 2f6ea37.

masterdevelop 这样的分支名称只包含一个原始哈希 ID,即它们直接指向特定的提交。所以我会把它画成:

2f6ea37   <-- branch1
    \
  df7992e   <-- master
      \
       C <-D <-E   <-- develop

为了避免弄乱不可读的哈希 ID,我会调用 2f6ea37 Adf7992e B:

A   <-- branch1
 \
  B   <-- master
   \
    C--D--E   <-- develop

现在,关于 Git 中的分支名称的事情是,您可以 always 将新的哈希 ID 填充到 any分店名称。该分支现在立即指向您提供的哈希 ID。

随着时间的推移,预期 分支名称的移动方式是为了积累新的提交:

A--H   <-- branch1
 \
  B--F--G   <-- master
   \
    C--D--E--I   <-- develop

例如以"expected"方式移动了所有三个分支名称:branch1现在用散列H标识提交,它指向回散列[=33=的提交],branch1 较早确定。所以没关系。类似的论点适用于其他两个。

您显然希望名称 master 标识提交 A,名称 branch1 标识提交 B。 (我们将在下一节中讨论 develop!)您可以通过将正确的哈希 ID 强行插入每个名称来实现此部分:

A   <-- master
 \
  B   <-- branch1
   \
    C--D--E   <-- develop

但是如果我们将此与刚才的状态进行比较,master 这个名字已经朝着 "wrong" 的方向移动了。它曾经命名为 commit B,现在命名为 commit A。名称 branch1 已向 "right" 方向移动(向前,与内部向后箭头相反)。

像这样向后移动分支名称会混淆 other Git 存储库(and/or 他们的用户)if他们之前已经看到 your master 识别提交 B。请记住,所有 Git 都共享原始哈希 ID,因此如果任何其他 Git 提交了 B,他们的一些名字可能会记住它。他们可能有他们的 origin/master 指向他们共享提交的副本 B,因此,他们可能有他们自己的 master 也指向 B

如果没有其他人见过这个,那你就没事了。只需使用 git branch -f and/or git reset --hard 将正确的哈希 ID 填充到正确的分支名称中即可。

潜在的大刺在这里

最后一期是分支名称develop。在这里,我将其绘制为指向提交 EE 指向 D,后者指向 C,后者指向 B

我相信,目前——根据你绘制初始图表的方式——你真正拥有的看起来更像这样:

A   <-- branch1
 \
  B   <-- master, develop

也就是说,提交 C-D-E 根本不存在。如果是这样,我们只需要移动 develop 也直接指向 A,移动 develop "backwards" 的注意事项与移动 master 相同同理:

A   <-- master, develop
 \
  B   <-- branch1

如果您现在 git checkout develop 并进行新的提交 C,新的 C 将指向 A:

  C   <-- develop (HEAD)
 /
A   <-- master
 \
  B   <-- branch1

这里的附件(HEAD)表示您此时签出的分支是develop.

但是 如果您已经有现有的提交怎么办 C-D-E 也就是说,如果您当前的图表是:

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

这里的问题是现有提交 C 已经指向现有提交 B任何提交都不能更改 一旦提交。所以你根本不能改变 C。在这种情况下,您需要做的是 copy C,因此也 DE 到新的和改进的提交。它们的新之处和改进之处在于新的 C——我们称它为 C'——指向 A,并使用 A 作为其源库。同样,新的 D' 指向 C' 而新的 E' 指向 D',给我们这个:

  C'-D'-E'   <-- develop (HEAD)
 /
A   <-- branch1
 \
  B   <-- master
   \
    C--D--E   [abandoned]

这也以意想不到的方式改变了名称 develop:现在我们不仅备份了 B,我们先备份了,放弃了,E,然后 D 然后 C 也是。 (现有提交 B 仍然可以通过某些分支名称找到,因此不会被放弃。)

实现这一切的命令是git rebase。将 HEAD 牢固地附加到 develop,您将 运行:

git rebase --onto 2f6ea37 df7992e

使用原始哈希 ID 表示 排除提交 B 和更早版本,从当前提交向后复制所有提交,新提交在提交 2f6ea37 之后。然后移动名称 develop 以指向最后一个这样复制的提交。 排除意味着要复制的提交集恰好是正确的集 - C-D-E - 所以我们得到我们上面画的。

现在是使用两个 git branch -f 命令交换其他两个分支名称的时候了,而我们仍在开发中。或者,我们可以在开始 git rebase.

之前交换它们

如果您没有要复制的提交 C-D-E,但在 develop 上怎么办?

假设现在的实际画面可以这样画:

A   <-- branch1
 \
  B   <-- master, develop (HEAD)

您可以使用 git branch -f 交换两个名称 branch1master,而无需以任何方式更改任何其他提交。但现在你还剩下:

A   <-- master
 \
  B   <-- branch1, develop (HEAD)

使用git branch -f develop会报错:

fatal: Cannot force update the current branch.

但是你还需要移动develop。有很多方法可以做到:

  • Using git rebasegit rebase --onto master branch1:这从复制中排除了从 B 开始的提交,即所有提交;在提交 A 之后复制零提交;然后将您当前的分支名称移动到指向提交 A.

  • 使用git resetgit reset --hard master:这会丢弃索引和工作树中所有正在进行的工作,重新设置它们以匹配提交A相反,并将您当前的分支名称 develop 移动到指向提交 A。这样做的好处是它快速而简单,并且可以做你想做的事。缺点是它会清除您忘记保存在某处的任何正在进行的工作。 rebase 命令会警告你。

  • 使用 git checkoutgit checkout master; git branch -f develop master; git checkout develop。这与 rebase 和 reset 命令一样有效,但需要输入三个单独的命令。在某些情况下,它确实能够在您的索引和工作树中携带未提交的工作(有关详细信息,请参阅 Checkout another branch when there are uncommitted changes on the current branch)。

结论

分支名称可以很容易地移动。要移动你所站的树枝,要么离开它(git checkout 其他东西),要么使用 git reset --hard 或其他偷偷摸摸的方法。请记住,git reset --hard 会破坏未提交的工作。

您不能修改任何现有的提交。如果你想对分支名称进行的调整可以在没有这个的情况下完成,你可以简单地调整这些分支名称。其他任何人——例如,您提供提交的其他 Git 存储库——可能会被 "unusual" 分支名称移动弄糊涂,因此请确保其他 Git 存储库及其用户已做好准备为此。

如果您需要复制一些提交,git rebase 将完成这项工作。这也可能会以其他 Git 跟踪您的分支名称(和您的提交)的人可能需要准备的方式调整您的分支名称。