Git 的特殊之处在于分支名称 不会影响您的提交历史 中的内容,除了它们允许您和其他任何人,这非常重要—找到提交。秘密在于,在 Git 中,一次提交可能会在许多个分支上同时。
不过,重要的是,一旦提交,就不能更改。它可以复制到(新的,略有不同的)提交,但不能更改。
此外,虽然任何分支名称可以被强制指向任何提交,但分支名称运动有一个“正常方向”:通常,它们只是提前。正如我们稍后会看到的那样,推进意味着他们过去指向的任何提交仍然“在”那个分支上。因此,一旦您将提交提交给其他人(其他 Git)并要求他们将这些提交称为 branch-D,就很难让 other Git 收回它。因此:
我还没有将任何东西推送到远程存储库。有没有办法做到这一点?
是的,并且可能有一个非常简单的方法:“还没有推送任何东西”意味着你是唯一拥有这些提交的人,所以无论你找到它们,那就是每个人如何找到他们,因为你是“每个人”。 :-) 无需让其他人更改任何内容。
只要它们已经按照您想要的方式进行布局,您只需重新排列查找这些提交的方式即可。
让我们用“Git Way”画出你现有的一系列提交:
... <--A <--B <--C <--D <-- branch-D
在 Git 中,每个提交都由其哈希 ID 唯一标识。这些东西又大又丑,而且显然是随机的(尽管它们实际上是完全确定的),所以我们几乎从不使用它们,或者使用像 d1c9d3a 这样的缩写形式。取而代之的是,让我们将最后一次提交称为D。
在提交D 内部,还有另一个哈希ID,标识D 的父提交。假设它是c033bae,但让我们称之为“提交C”。所以我们说D 指向 C。
同样,C 指向 B,A,又指向 ...就目前而言:
...--o--o <-- master
\
A--B--C--D <-- branch-D
这是一种更紧凑的绘制方式。提交总是,必然,指向向后,所以我们并不真正需要内部箭头——我们知道它们总是向后移动。但是,分支名称,例如master 和branch-D ...好吧,我们可以在任何地方说明这些观点。我们要做的是让它们指向一个分支的“tip commit”。
Git 通过从分支名称指向的那个开始查找提交:D,对于branch-D,或者在第一行的最后一个o 对于master。然后它查看当前提交的父级,以向后移动。然后它查看父母的父母,依此类推。因此两个o 提交同时在master 和 branch-D 上:我们可以从master 开始找到它们,或者我们可以从branch-D 开始并向后工作来找到它们四个步骤。
这意味着我们想要的图片看起来可能是这样的:
...--o--o <-- master
\
A <-- branch-D
\
B--C--D <-- branch-C
这里,master 上的提交也在两个分支上,提交A,现在是branch-D 的尖端,仍然在branch-C 上。这不再是branch-C 的提示了。
另一方面,也许我们想要的图片是这样的:
?--?--? <-- branch-C
/
...--o--o <-- master
\
A <-- branch-D
\
B--C--D <-- ???
也就是说,我们需要回答一个问题。名称 branch-C 将指向某个特定的提交。当我们后退三步时,我们应该到达提交A 吗?或者我们应该到达master 的最后一次提交?
如果第一张图是对的,答案很简单:取新名字branch-C,指向commit D;然后强制现有名称branch-D 移回提交A。为此:
git branch branch-C branch-D # copy the hash ID from branch-D to new branch-C
然后,根据现在签出的分支,选择:
git reset --hard <hash-of-A> # move current branch; re-set index and work-tree
或:
git branch -f branch-D <hash-of-A> # force branch-D to point to A
请注意,在使用git reset --hard 之前,最好确保您没有任何要保存的修改。虽然 commits 几乎是永久性的(您可以将它们取回,通常至少 30 天,即使您将它们从所有分支名称中踢掉),git reset --hard clobbers 的索引和工作树是不是。
如果你想要第二张图片——如果你想让提交 B 的父级不是提交 A——你必须复制提交 B 到一个新的、不同的提交,即“像B,但是......”。原始B 和副本之间的差异将包括更改的父哈希 ID。
对于,您将需要使用git cherry-pick 或等效项(例如git rebase,这基本上是一个复制大量提交的整体樱桃挑选操作)。为此,您可以:
git checkout -b branch-C master
给予:
...--o--o <-- branch-C (HEAD), master
\
A--B--C--D <-- branch-D
然后运行三个git cherry-pick命令复制B、C和D;或更简单——这使用<commit>~<number> 表示法:
git cherry-pick branch-D~3..branch-D
一次复制所有三个。这会产生:
B'-C'-D' <-- branch-C (HEAD)
/
...--o--o <-- master
\
A--B--C--D <-- branch-D
此时,强制branch-D 指向提交A 是安全的:
git branch -f branch-D branch-D~3
您也可以像前面的示例一样通过哈希 ID 来做到这一点:
git branch -f branch-D <hash-of-A>
我们对branch-D~3 所做的只是告诉 Git:倒数三个父步骤,一次一个父步骤。所以我们从D开始,倒数到C,再倒数到B,再倒数到A。