这个可能吗?从其他分支挑选到主分支的特定提交。例如:假设我有两个分支:
-> master : <commit1>, <commit2>, <commit3>
-> test: <c1>, <c2>, <c3>, <c4>
现在我想从 test
分支中挑选一个提交(例如:<c3>
),并将其应用到主分支的一个 <commit2>
上。
编辑:我知道可以通过 rebase
命令来完成,但是否只能通过 cherry-pick
命令来完成?
这个可能吗?从其他分支挑选到主分支的特定提交。例如:假设我有两个分支:
-> master : <commit1>, <commit2>, <commit3>
-> test: <c1>, <c2>, <c3>, <c4>
现在我想从 test
分支中挑选一个提交(例如:<c3>
),并将其应用到主分支的一个 <commit2>
上。
编辑:我知道可以通过 rebase
命令来完成,但是否只能通过 cherry-pick
命令来完成?
你对 Git 的工作原理有一个基本错误。
这是可能的吗?从其他分支 cherry picking 到特定的 master 分支提交中。
不可以:Git 不能 更改 任何 提交。您可以添加新的提交,并可以复制现有提交以制作一个新的提交,即添加一个新的提交,该提交是现有提交的副本(其中,副本中至少有一件事情与原提交不同)。后者就是 git cherry-pick
所做的。
您还可以更改任何分支 名称 对您记忆的特定提交哈希 ID。
比如,我有两个分支:
-> master : <commit1>, <commit2>, <commit3>
-> test: <c1>, <c2>, <c3>, <c4>
这不是绘制分支的好方法。下面是更好的方法:...--D--E--F--G <-- master
\
H--I--J--K <-- test
这里的提交E
对应于您的<commit1>
,我只是使用单个字母来缩短它们的长度。同样,F
就像您的<commit2>
,J
就像您的<c3>
。 我还包括了另一个早期的提交D
,从该提交扩展出两个分支,以表明在E
和H
之前可能存在更早的提交。
请注意,每个提交都记住其父提交: G
的父提交是F
,因此我们说G
“指向”F
。F
指向E
,E
指向D
,依此类推。同样,提交K
指向J
,J
指向I
,I
指向H
。请注意,仅在分支test
上的H
也指向D
。
(提交D
特别有趣:它位于两个分支上。这也是Git称为两个分支的合并基础的原因。但这不是我们在这里关心的问题; 这只是一个有趣的侧面说明,在Git中,提交可以同时位于多个分支上。)
在本示例中,分支名称为master
和test
,每个名称都指向一个特定的提交。这里的名称master
指向提交G
,而名称test
指向提交K
。这就是我们如何确定哪些提交在哪些分支上:我们像Git一样,从分支名称开始查找分支尖端提交。从这些尖端提交开始,我们(或者Git)向后工作:从任何一个提交开始,到其父提交,然后到该提交的父提交,以此类推。这个过程只有在我们跟着父提交感到疲倦(例如退出git log
),或被告知停止,或遇到根提交时才会停止,根提交是没有父提交的提交。(您在存储库中创建的第一个提交是根提交,因为没有早期的提交可用作父提交。)(可能会创建额外的根提交,但在这里我们不会看到它们。)
现在我想从测试分支中挑选一个提交记录(例如:<c3>
),并将其“复制”到主分支中的<commit2>
位置。你可以使用cherry-pick来实现这一点,但是我加粗和斜体的部分(“到一个提交记录”)是无意义的。cherry-pick意味着复制一个提交记录,这个副本是一个新的提交记录,通常会添加到分支上。
既然你的<c3>
就是我的J
,那么让我们看看如果你使用以下命令会发生什么:
git checkout master
git cherry-pick test~1
第一步让你“进入”分支master
,也就是说,你现在添加的新提交将被添加到master
中,通过将名称master
指向它们。
第二步通过从test
的末端向后走一步来确定提交J
。名称test
标识提交K
:一个分支名称“意味着”该分支的末端提交。后缀~1
表示“后退一级”,因此我们从提交K
移动到提交J
。那就是我们要求Git复制的提交。
暂时忽略进行此复制的确切机制,只看结果。 Git创建了一个“类似于”J
的新提交;让我们称这个新提交为J'
。原始J
和新的副本J'
之间存在几个差异,目前最重要的差异是复制品的父节点是master
的现有末端提交,即提交G
。因此,J'
指向G
。
完成复制并永久存储新的J'
后,Git会更新master
以指向我们刚刚创建的新提交:
...--D--E--F--G--J' <-- master
\
H--I--J--K <-- test
请注意,没有现有的提交受到影响。这是因为更改任何现有提交是完全不可能的。
我知道可以通过
rebase
命令来完成...
无法通过rebase命令实现,这真的很重要。变基所做的不是修改提交,而是复制——可能会复制很多次。
让我们考虑这样一种情况,你在master
上有一个D-E-F-G
序列,并且想要复制J
并在F
之后但G
之前插入它。也就是说,你想要得到这个结果,即使这是不可能的:
...--D--E--F--J'-G <-- master
\
H--I--J--K <-- test
之所以不可能是因为G
无法更改,而且G
已经指回了F
。但如果我们这样做呢:
原因在于G
无法更改,并且G
已经指回到F
。但如果我们做了这个:
J'-G' <-- new-master
/
...--D--E--F--G <-- old-master
\
H--I--J--K <-- test
换句话说,假设我们将 J
复制到新的 new-master
分支上的 J'
,并将 J'
放在 F
之后,然后将 G
复制到 G'
,并将 G'
放在 J'
之后,这样会怎样呢? 我们可以使用 git cherry-pick
来完成此操作,我们只需挑选两个提交,先选择 J
,然后再选择 G
,复制到一个新分支上:
git checkout -b new-master master~1 # make the new-master branch at F
git cherry-pick test~1 # copy J to J'
git cherry-pick master # copy G to G'
假设我们完成了这样的操作,我们 擦除 旧的 master
名称,只需将新名称设置为 master
,像这样:
And, suppose once we have done that, we erase the old master
name entirely and just call the new one master
, like this:
J'-G' <-- master
/
...--D--E--F--G [abandoned]
\
H--I--J--K <-- test
现在,如果我们停止绘制已废弃的原始 G
,并直接画出master
,它看起来像是我们在F
和G
之间插入了J'
...但实际上我们没有:我们将G
复制到了G'
。
git rebase
命令本质上是一个自动化的、高级的“挑选很多提交”命令,紧接着在末尾进行一些分支名称移动操作。
这只有在某些情况下才会关系到,但当它确实关系到时,就非常重要。
假设我们从以下内容开始,这与之前非常相似,只是还有另一个分支:
...--D--E--F--G <-- master
\ \
\ L <-- develop
\
H--I--J--K <-- test
现在我们将 J
复制到 J'
,放在 F
之后,然后将 G
复制到新的提交 G'
中,使用 J'
作为 G'
的父提交,并使名称 master
记住新提交 G'
的哈希 ID。 (这与我们之前执行的 rebase-with-copy 相同;我们只是有了这个额外的 develop
分支。)
J'-G' <-- master
/
...--D--E--F--G
\ \
\ L <-- develop
\
H--I--J--K <-- test
现在让我们重新绘制这个布局,使其更加清晰:
...--D--E--F--J'-G' <-- master
\ \
\ G--L <-- develop
\
H--I--J--K <-- test
尽管我们让 Git“忘记”了位于主分支上的原始提交 G,转而使用具有 J' 作为其父对象的全新G’,但是原始的 G 仍然存在于 develop 分支上。实际上,现在看起来好像 G 一直都在 develop 分支上,并且我们可能是从那里将其合并到 master 分支中去的。是的,您可以使用git cherry-pick <test分支中的提交>
来应用更改到master
分支。
要将测试分支中的提交在非最新的主分支上挑选出来,您可以使用以下方法:
git checkout <a commit on master you want to cherry pick>
git cherry-pick <a commit from test branch>
git rebase --onto HEAD <the commit you used in step1> master
git checkout my-branch
,那么可能不需要使用 git rebase --onto HEAD
。实际上,如果你执行 git rebase --onto HEAD my-branch
,它什么也不会做。 - Nawaz您将重新编写存储库历史记录,这不是推荐的做法。
如果需要将主分支推送到远程存储库,则必须强制推送主分支。如果其他人已经提交了主分支,他们的主分支将因此失效。
git rebase
的做法——以避免需要一个临时分支名称,但你仍然需要一个临时分支。 - torekrebase
与cherry-pick
结合使用。GIT_SEQUENCE_EDITOR='sed -i s/^pick/edit/' git rebase -i <commit2>
git cherry-pick -n <c3>
git commit --amend -C HEAD
git rebase --continue
首先运行rebase以选择要编辑的主分支提交。然后应用来自其他提交的更改,修改主分支上的提交,并完成rebase。当然,这实际上并不会更改提交,但它将创建一个新的提交,其中包含来自<commit2>
和<c3>
的更改。
使用GIT_SEQUENCE_EDITOR
只是避免在开始rebase时手动选择单个提交进行编辑。