从特定提交开始创建新分支

Sto*_*ace 1 git version-control branch commit git-commit

我想从某个提交开始创建一个新分支。但是,我希望之后提交的所有提交也成为该分支的一部分。

让我们假设这是branch-D所有提交。

> branch-D
commit-A ---- commit-B ---- commit-C ---- commit-D
Run Code Online (Sandbox Code Playgroud)

我想删除所有内容并commit-B创建一个新的branch,让我们命名它branch-C,然后只commit-A继续branch-D

> branch-D
commit-A

> branch-C
commit-B ---- commit-C ---- commit-D
Run Code Online (Sandbox Code Playgroud)

我还没有将任何内容推送到远程存储库。有没有办法做到这一点?

tor*_*rek 5

Git 的奇特之处在于分支名称不会影响您的提交历史记录中的内容,除了它们可以让您\xe2\x80\x94 和其他任何人(这非常重要)\xe2\x80\x94找到提交。秘密在于,在 Git 中,提交可能会同时在许多分支上进行。

\n\n

但重要的是,任何提交一旦做出就无法更改。它可以复制到(新的,略有不同的)提交,但不能更改。

\n\n

此外,虽然任何分支名称都可以被迫指向任何提交,但分支名称移动有一个“正常方向”:通常,它们只会前进。正如我们稍后将看到的,前进意味着无论他们过去指向什么提交,仍然位于该分支“上”。因此,一旦你将你的提交交给其他人\xe2\x80\x94一些其他 Git\xe2\x80\x94 并要求他们调用这些提交branch-D,就很难让其他Git 撤回它。因此:

\n\n
\n

我还没有将任何内容推送到远程存储库。有没有办法做到这一点?

\n
\n\n

是的,可能有一种非常简单的方法:“尚未推送任何内容”意味着您是唯一拥有这些提交的人,因此无论您如何找到它们,这就是每个人找到它们的方式,因为你是“每个人”。:-) 没有必要让其他人改变任何事情。

\n\n

只要它们已经按照您想要的方式布局,您只需重新安排查找这些提交的方式即可。

\n\n

让我们用“Git Way”绘制现有的一系列提交:

\n\n
... <--A <--B <--C <--D   <-- branch-D\n
Run Code Online (Sandbox Code Playgroud)\n\n

在 Git 中,每个提交都由其哈希 ID 唯一标识。这些东西又大又丑,而且显然是随机的(尽管它们实际上是完全确定性的),所以我们几乎从不使用它们,或者使用像d1c9d3a. 相反,我们只调用最后一次提交D

\n\n

在 commit 内部D,有另一个哈希 ID,用于标识D其父提交。假设它是c033bae,但我们称之为“提交C”。所以我们说D 指向 C

\n\n

类似地,C指向B,指向A,指向 ... 好吧,也许master\xe2\x80\x94 的提示提交你没有说,但现在让我们假设:

\n\n
...--o--o   <-- master\n         \\\n          A--B--C--D   <-- branch-D\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是一种更紧凑的绘制方式。提交总是必然向后指向,所以我们并不真正需要内部箭头\xe2\x80\x94,我们知道它们总是向后指向。然而,分支名称,例如master“和branch-D”……好吧,我们可以在任何地方指出这些点。我们想要做的是让它们指向分支的“提示提交”。

\n\n

Git 通过从分支名称指向的提交开始查找提交:D、 forbranch-Do第一行的最后一个 for master。然后它查看当前提交的父级,以向后移动。然后它查看父级的父级,依此类推。因此,这两个o提交都在master branch-D上:我们可以通过从 开始找到它们master,或者我们可以通过从 开始branch-D并向后移动四个步骤来找到它们。

\n\n

这意味着我们想要的图片可能看起来像这样:

\n\n
...--o--o   <-- master\n         \\\n          A   <-- branch-D\n           \\\n            B--C--D   <-- branch-C\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,打开的提交master也在两个分支上,并且A现在是 的尖端的commit 也branch-D仍然处于打开状态branch-C。这已经不再是小窍门branch-C了。

\n\n
\n\n

另一方面,也许我们想要的图片是这样的:

\n\n
          ?--?--?   <-- branch-C\n         /\n...--o--o   <-- master\n         \\\n          A   <-- branch-D\n           \\\n            B--C--D   <-- ???\n
Run Code Online (Sandbox Code Playgroud)\n\n

也就是说,我们需要回答一个问题。该名称branch-C将指向某个特定的提交。当我们退后三步时,我们应该到达 commit 吗A?或者我们应该到达最后一次提交master

\n\n
\n\n

如果第一张图片是正确的,答案很简单:创建新名称 ,branch-C指向 commit D;然后强制现有名称branch-D移回 commit A。去做这个:

\n\n
git branch branch-C branch-D  # copy the hash ID from branch-D to new branch-C\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,根据现在签出的分支,执行以下任一操作:

\n\n
git reset --hard <hash-of-A> # move current branch; re-set index and work-tree\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者:

\n\n
git branch -f branch-D <hash-of-A> # force branch-D to point to A\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,在使用之前git reset --hard,最好确保您没有任何要保存的修改。虽然提交几乎是永久性的(您可以将它们取回,通常至少 30 天,即使您将它们从所有分支名称中删除),但索引和工作树并不是git reset --hard破坏者。

\n\n

如果您想要第二张图片,尽管 \xe2\x80\x94 如果您希望提交的父级B不是提交A\xe2\x80\x94,则必须将提交复制B到一个新的、不同的提交,那就是“喜欢B,但是……”。B原始文件和副本之间的差异将包括更改的父哈希 ID。

\n\n

为此您将需要使用git cherry-pick或等效项(例如git rebase,这基本上是复制大量提交的集体挑选操作)。为此,您可以:

\n\n
git checkout -b branch-C master\n
Run Code Online (Sandbox Code Playgroud)\n\n

给予:

\n\n
...--o--o   <-- branch-C (HEAD), master\n         \\\n          A--B--C--D   <-- branch-D\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后运行三个git cherry-pick命令来复制BC、 和D;或更简单的\xe2\x80\x94这使用<commit>~<number>符号:

\n\n
 git cherry-pick branch-D~3..branch-D\n
Run Code Online (Sandbox Code Playgroud)\n\n

一次复制所有三个。这会产生:

\n\n
          B\'-C\'-D\'   <-- branch-C (HEAD)\n         /\n...--o--o   <-- master\n         \\\n          A--B--C--D   <-- branch-D\n
Run Code Online (Sandbox Code Playgroud)\n\n

branch-D此时,强制指向 commit是安全的A

\n\n
git branch -f branch-D branch-D~3\n
Run Code Online (Sandbox Code Playgroud)\n\n

您也可以通过哈希 ID 来完成此操作,如前面的示例所示:

\n\n
git branch -f branch-D <hash-of-A>\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们所做的branch-D~3就是告诉 Git:倒数三个父步骤,一次一个父步骤。所以我们从 开始D,倒数一步到C,再倒数一步到B,倒数第三步到A

\n