我想从一个分支中插入几个提交到另一个分支的历史记录中,以便:
A - B - F - G - H - I - J (branch working)
\
C - D - E (old branch)
...变为
A - B - C - D - E - F - G - H - I - J (continue with branch working)
到目前为止我尝试过的任何事情导致多次冲突阻止我继续,但只要后续提交的状态相同,我就不关心它们。提交F的问题是“不知道”如何成为提交E的孩子?
答案 0 :(得分:4)
您实际上无法 ,因为这将涉及更改某些现有提交。在这种情况下,现有提交F
将B
的哈希ID存储为其父级。不能改变现有的提交:提交完全是只读的(实际上,所有Git的内部对象都是只读的)。
我假设你想假装F
将E
的哈希ID存储为其父代,而不更改与任何现有提交相关联的快照。你可以做这个,你可以做很多类似的事情。不过,在我们看一下之前,让我们看一下git rebase
。
这里使用git rebase
的问题在于,复制提交的rebase函数(到目前为止一切都很好),每个副本都像 1 使用git cherry-pick
(这是事情开始出错的地方)。为了挑选一个提交,Git将:
比较提交的快照与提交的父级快照,例如J
vs I
或F
vs { {1}}。您可以自行运行B
,或者更简单地通过git diff <hash1> <hash2>
运行。
将此处显示的差异与父项的差异(上面的git show <hash2>
)合并到当前或<hash1>
提交,并使用合并结果进行新的提交。
将原始提交的消息复制到新提交。
新提交一如既往地进入当前分支,使分支提交更长,并更改HEAD
提交以命名新提交。
合并步骤,加上Git将快照(提交)转换为变更集(差异)的事实,意味着与原始结果相比,最终挑选的结果可能是一个非常不同的快照。这一切都取决于启动流程时HEAD
与<hash1>
之间的差异。
HEAD
命令基本上可以自动完成一次性提交一系列提交的过程。在所有复制结束时,git rebase
强制分支标签指向最终复制的提交。这样,您就可以将git rebase
复制到父F
的新F'
,然后将E
复制到父G
的新G'
等等:
F'
1 有时A--B--F--G--H--I--J <-- (original)
\
C--D--E <-- (target of rebase)
\
F'-G'-H'-I'-J' <-- branch
字面上运行git rebase
,有时候它使用的方法通常会产生相同的结果,但在某些奇怪的角落情况下,并不是git cherry-pick
。吨。
现在,假设不是所有这些,我们创建一个Git对象,Git可以&#34;放在一边&#34;无论什么时候使用F
。这个替换为F
的内容如下:
A--B--F--G--H--I--J <-- branch
\
C--D--E <-- some_name
\
Frepl <-- refs/replace/<hash>
我们将F
的提交消息复制到Frepl
的提交消息,并复制大多数其他字段,包括提交树对象的内部Git哈希ID。但是,不是指向提交B
,而是提交Frepl
指向提交E
。
现在我们只需要Git来#34;转过眼睛&#34;从F
到Frepl
每次出于任何原因即将使用提交F
。还有一种方法可以做到这一点:我们为Frepl
提供一个特殊名称refs/replace/big-ugly-hash-id
,其中 big-ugly-hash-id
是提交{{1}的实际哈希ID }。
进行替换的Git命令是F
。旁听是自动的:Git总是这样做,除非我们运行git replace
。这一切都是在没有更改任何现有 Git对象的情况下完成的,所以它只是添加到存储库中。
替换对象的最大缺点是git --no-replace-objects
默认情况下不会复制替换对象(它不会获取它们,也不会获取它们的名称)。这意味着克隆在视图中没有替换,并且永远不会忽略它。您可以明确地将替换添加到fetch refspecs以获取它们,但这有点痛苦。 git clone
操作默认情况下不会转移它们。
最大的好处是,它们不要求每个人都停止使用原始提交,而是支持新的和改进的提交。但是,如果您可以让每个人都切换,您可以使用git push
复制许多提交并移动分支标签。或者,您最初可以使用git rebase
来创建替换对象,然后在没有过滤器的情况下运行git replace
,但要告诉它过滤发生替换的分支。
git filter-branch
做的是复制每次提交(在分支或分支上告知要过滤)。它 - 至少在逻辑上;有很多优化 - 提取每个提交,从最旧的提交到最新的提交,到一个临时目录,按某种顺序应用每个过滤器,然后使用过滤器所做的任何事情进行新的提交。如果新提交与原始提交一点一点地相同,则两个提交实际上只是一个提交,否则副本是一个新的不同的提交。每个新副本的默认父级是在父级的早期副本中进行的提交(尽管有一个git filter-branch
可以让您更改它!)。它完成了所有这一切,发生了替换后备噱头, 2 所以当Git复制--parent-filter
时,它实际上复制F
,然后跟随Frepl
回到Frepl
。如果我们将此过滤器命名为E
,则结果是Git会复制branch
,然后是A
,然后是B
,然后是C
,然后是{{1然后是D
,然后是E
,然后是Frepl
,然后是G
,然后是H
,给出了:
I
请注意,J
指向提交A--B--F--G--H--I--J <-- refs/original/refs/heads/branch
\
C--D--E <-- some_name
\
Frepl <-- refs/replace/<hash>
\
G'-H'-I'-J' <-- branch
,其父级为branch
但其树(快照)与J'
的树(快照)相同。将I'
点提交回J
,返回I'
,返回H'
(我们运行G'
时复制的副本:在过滤期间保持不变)。这指向Frepl
(这是它自己未更改的副本),它指向git replace
,依此类推回E
。
然后,如果我们抛弃所有D
名称(例如A
),那么最后的效果就是&#34;水泥到位&#34;任何替换提交。然后,我们可以删除refs/original/
的{{1}}名称,看起来好像我们在存储库中只有提交refs/original/refs/heads/branch
。许多提交的哈希ID 已更改,因此此存储库不再与原始存储库兼容;但是如果我们从名称refs/replace/
开始,我们只会在所有正确的位置看到闪亮的新复制提交。
2 当然,如果您运行Frepl
,则会禁用替换旁视。可能没有任何理由这样做。