当我在原始分支中时,GIT副本提交到另一个分支



要将提交从一个分支移动到另一个分支,我需要在目标分支上

问题是,当我在原始分支时,是否可以复制提交

TL;DR

考虑git worktree。跳到最后看看如何操作,但从中间读一读,看看为什么

Long

要将提交从一个分支移动到另一个分支,我需要在目标分支上。

事实并非如此。这种说法源于对Git提交和分支名称工作方式的误解。但这是一个可以理解的错误,因为Git提交执行的方式非常令人困惑,最终用户的目标通常不是移动提交,而是将提交的效果复制到新的提交中。

这些术语都有点笨拙:

问题是,当我在原始分支中时,是否可以复制提交。

是的,但这里有一个重大问题,具体取决于复制提交的意思。您的标记提到了git cherry-pick,它将提交转换为一组更改,然后将同一组更改应用于其他提交。这是";复制";一个承诺,很可能就是你的意思,也是当";在原始分支中";(这个短语也有点笨拙:你从来没有在分支中真正,尽管你可以在分支上)。

以下是发生的事情:

  • 在Git中,提交是真实存在的,具有永久性——嗯,大多是永久性——你可以从一个Git存储库转移到另一个存储库。每个提交都有编号,带有一个唯一但看起来随机的哈希ID。每个提交都保存每个文件的完整快照,以及一些元数据:例如,关于谁提交、何时提交以及为什么提交的信息(他们的日志消息)。

  • 每个提交都在其元数据中存储一些早期提交的哈希ID。大多数提交都只存储一个这样的原始哈希ID,我们称之为提交的父级

  • 分支名称在Git中是临时的和短暂的。他们来来去去随你。从某种意义上说,它们不是真正的:它们的行为很像黄色的小便签。

你只需在一次提交中粘贴任意多的便签,就可以写一个不同的"分支机构名称";在每个音符上。该提交现在是所有分支上的最后一个提交。剥去提交的便笺,将其粘贴在一些其他的提交上,现在您新选择的(现有的)提交是该分支上的最后一次提交。

你选择这些便签中的一个作为你的";当前分支";将不同颜色的便签粘贴到黄色便签上,上面有分支机构的名称。(例如,让我们用绿色表示HEAD。)

每当您使用git commit进行新的提交时,或者通过结束樱桃选择或合并或其他方式,Git都会将该新提交放置在当前分支的顶端。它通过:

  • 查找哪个分支名称上粘贴了HEAD(附加到它)
  • newcommit的new哈希ID(此新commit唯一)填充到适当的分支名称中,或者,在我们的类比中,从旧的分支顶端剥离适当的黄色便笺,并将其粘贴到新commit上

新提交的父级提交是旧的分支提示提交。也就是说,假设我们从开始

... <-F <-G <-H   <-- somebranch (HEAD)

名称CCD_ 6——我们的";黄色便签";上面写有somebranch——表示此提交链的最后提交是提交H

提交H在其元数据中有早期提交G的原始哈希ID。因此Git可以使用提交H的内容来定位提交G

提交G当然也是一个提交,所以它有元数据,包括一些更早提交F的原始哈希ID。因此,Git可以使用G来查找F。找到F后,Git将能够在历史上再跳一步,依此类推,从现在开始,在所有历史中向下跳。

Git无法以这种方式向前移动。Git只能后退。要找到提交H——链中最后一个提交——Git需要保存H的哈希ID。那个东西是我们的分店名称。

但现在我们做出了新的承诺。这个新提交需要一个父级:Git将其父级设置为当前提交的哈希IDH。因此,新提交I指向现有提交H:

... <-F <-G <-H <-I

完成后,Git现在将I的哈希ID写入名称somebranch,更新黄色便签:

... <-F <-G <-H <-I   <-- somebranch (HEAD)

HEAD的绿色便签仍然附着在同一张旧的黄色便签上;只是标记为somebranch的黄色便笺上的数字现在是提交I的实际原始哈希ID。

这就是为什么我们可以将提交从一个分支移动到另一个分支

假设我们有以下提交:

I--J   <-- dev
/
...--F--G--H   <-- master

如果我们只是让Git向前滑动名称master——这是Git自己无法做到的;我们必须给它命名为dev,这样它才能找到提交J——我们得到:

...--F--G--H--I--J   <-- dev, master

委员会IJ现在都在两个分支上,尽管以前它们只在dev分支上。现在,如果我们愿意的话,我们可以完全删除dev名称:我们有它是为了找到提交J,而名称master找到了提交H。如果删除dev,则所有提交现在都只在master上。

事实上,提交本身根本没有移动。移动的是分支名称,然后我们删除了一个。分支名称其实并不重要,只有一个重要的例外:它们让我们找到最后一个提交。如果我们根本没有名字,我们就根本找不到提交。还有其他类型的名称——例如标签名称和远程跟踪名称等等——但我们需要一些名称来表示";最后一个";以任何顺序提交,这样我们就可以很容易地命名该提交。

然后,从那里,我们可以让git log或其他一些Git操作反向工作。这发现了一些早期的承诺。如果之前的承诺看起来特别重要(或令人心酸或兴奋或其他什么),我们可以直接在它上面加上一个名字,这样就很容易找到它。原始哈希ID是Git真正找到它的方式,但名称给了我们人类我们可以处理的东西:看起来随机的哈希ID太难了。

樱桃采摘

正如我前面提到的,git cherry-pick所做的是";复制";承诺。现在,提交包含一个完整的快照,而不是一组更改。但是提交也包含一个父散列ID。提交也有一个快照。假设我们有一些提交链:

...--G--H   <-- main

I--J--K   <-- branch

我们刚刚修复了提交K中的一些可怕错误,并希望立即将相同的更改直接导入main。嗯,K有一个快照,但它的快照是从J的快照构建的,并对其进行了一些更改

但是Git可以很容易地向我们展示K中的J中的有什么不同。对于哈希ID为K的提交,git showhashgit log -p会向我们显示这一点。(事实上,我们可以运行git show branch,因为Git会将名称branch转换为我们的哈希ID。)Git只需将两个快照(一个用于J,另一个用于K)提取到内存中的临时区域中,然后将两者进行比较。对于每个相同的文件,它什么也没说,对于每个不同的文件,我们可以看到发生了什么变化。

我们可以接受这组更改——从JK的差异——并让Git将这组更改应用于任何其他提交。如果我们首先使用git checkoutgit switchHEAD连接到somebranch0,那么我们就有了这个:

...--G--H   <-- main (HEAD)

I--J--K   <-- branch

那么当前提交,我们已经将其快照Git提取到工作树中,是来自提交H的快照。我们现在可以运行:

git cherry-pick branch

让Git比较JK——Git从名称branch和提交K中的元数据中找到两个哈希ID——并将这些相同的更改应用于我们当前的提交H

从技术上讲,这个应用程序实际上使用了Git的merge代码。这种合并可能会有合并冲突,如果发生冲突,Git将需要同时写入Git自己的暂存区和我们的工作树。在这种情况下,Git将在之后因合并冲突错误而停止,清理混乱并完成流程将成为我们的工作。但如果一切顺利,Git将能够自己进行合并,然后像往常一样进行普通的提交。这个普通的提交将以H作为其父级,并将获得一个新的、唯一的哈希ID。我们可以称之为L,但由于其效果将与K的效果相同,并且其commit消息来自K的元数据中的提交消息,我倾向于将其称为K':

...--G--H--K'  <-- main (HEAD)

I--J--K   <-- branch

请注意,所有这些都要求我们在启动时将main签出为当前分支,并将提交H签出为当前提交。完成后,提交K'当前提交,名称main选择提交K'

使用git worktree

问题是,当我在原始分支中时,是否可以复制提交。

假设您完全掌握了我刚才绘制的起始情况,但您使用的是branch,并且在制作了K之后,您已经开始研究将如何很快提交L

...--G--H   <-- main

I--J--K   <-- branch (HEAD)

要切换到main,您需要将当前工作提交到某个地方——可能使用git stash(进行提交),也可能只是提前一点使用git commit(我在git worktree存在之前使用的方法)——然后您可以使用git switch main进入main并开始挑选。

不过,为了避免到目前为止破坏您的工作,您可以使用git worktree add创建一个新的工作树来避免所有这些。这个新的工作树带有自己的新HEAD和索引/暂存区。然后,新的工作树将从某个现有或新的分支名称填充,就像由git checkoutgit switch填充一样(作为git worktree选项的一部分,您可以选择是创建新分支名称,还是使用某个现有分支名称)。

因此,假设您处于现有工作树的顶层,您可能会运行:

git worktree add ../project.main main

它将创建../project.main,输入新的空目录,创建一个将新目录连接到现有存储库的.git文件,然后从main分支填充新的工作树。这个新的工作树是main分支上的。然后,您可以创建一个新窗口,或者只使用现有窗口:

pushd ../project.main

例如(假设bash或类似情况)。此添加的工作树中的git status将显示您在分支main上,而默认工作树的git status将显示您位于分支branch上。

您在添加的工作树中所做的提交将进入(共享)存储库,并更新您在添加工作树中的分支名称。当你完成添加的工作树时,你可以简单地将其完全删除:

popd
rm -rf ../project.main

然后运行git worktree prune,让Git知道添加的工作树不见了:

git worktree prune

(当前的Git版本有git worktree remove,可以在一步中删除和修剪添加的工作树,但早期的Git版缺乏这一额外功能,需要两步删除和修剪。请注意,虽然git worktree在Git 2.5中是新的,但它有一个直到Git 2.15才修复的严重错误:如果您使用的是Git版本>=2.5但<2.15,并使用git worktree add,请在两周后,你就可以避免被这种虫子咬了。)

相关内容

  • 没有找到相关文章

最新更新