将整个git分支重新引导到孤立分支上,同时保持提交树完好无损

时间:2017-11-22 16:50:01

标签: git git-rebase

我有一个repo,其中有两个分支mastermaster-old,它们是作为孤儿分支创建的。

现在我想将整个master重新绑定到master-old,但每个提交的树应保持不变,即master和{{1}上每个提交的工作副本在rebase之前和之后应该看起来完全一样。

master-old

我尝试使用Current state ------------- A - B - C - D <--- master E - F - G - H <--- master-old Desired state ------------- E'- F'- G'- H'- A'- B'- C'- D' <--- master 完成此操作。问题是,在git rebase --onto master-old --root的初始提交和master的整个提交历史记录中,创建了大量相同的文件,因此我需要解决大量冲突。 / p>

有没有办法以保持每个提交的树完整的方式重写历史记录?

1 个答案:

答案 0 :(得分:4)

鉴于您希望保留与原始A--B--C--D系列提交相关联的,您根本不想 rebase 。 Rebasing意味着将提交转换为差异(变更集),然后将这些变更集一次一个地应用于某个现有起点 - 但您要做的就是将附加到A的树复制到您的父提交为A'的新提交H,然后将附加到B的树复制到父提交为B'的新提交A',依此类推。

这是git filter-branch运作良好的地方。当你跑:

git filter-branch <filter-list> <branch-name>

Git发现每个提交都可以从给定的<branch-name>到达,然后复制每个提交。无论如何,通过按原样提取整个提交,运行<filter-list>中的每个过滤器,然后使用生成的树和消息进行新提交,完成复制。它以与Git正常顺序相反的方式运行复制过程,即&#34;转发历史记录&#34;而不是向后。

如果新提交(可能已更改 - 可能不是树,可能已更改 - 可能 - 不是父,可能已更改 - 可能不是消息等)是100%与原始提交相同的bit-bit-bit,新提交的哈希ID不变。在这种情况下,默认的&#34;新的父母&#34;对于 next 提交与原始父提交相同。否则默认&#34;新父母&#34;下一次提交是我们刚刚提出的。

(实际上,因为提交图可以分散并再次合并,因为你可以跳过提交或添加新提交,所以filter-branch真正做的是将旧提交哈希的映射新的提交哈希。每次复制时,它都会输入一对:&lt; old-hash,new-hash&gt;到这个映射中。对于一个简单的线性链,你可以把它想象成只记住最近的提交& #39;新的哈希ID。)

现在,您遇到的问题是您希望更改一个特定提交的父哈希ID,即根提交。有专门针对它的过滤器--parent-filter。还有两种方法可以做到这一点,但让我们首先描述--parent-filter。这来自the git filter-branch documentation

  

- parent-filter&lt; command&gt;

     
    这是用于重写提交的父列表的过滤器。它会    在stdin上接收父字符串并输出新的父字符串    stdout上的字符串。父字符串采用的格式为    git-commit-tree(1):对于初始提交为空,&#34; -p parent&#34;为一个    正常提交和&#34; -p parent1 -p parent2 -p parent3 ...&#34;为一个    合并提交。

因此,您可以测试stdin是否为空,如果是,则输出-p <hash-of-H>。结果将是:

E--F--G--H--A'-B'-C'-D'   <-- master

(不是你要求的,但可能更好)。

(要复制E-F-G-H链,您必须将master-old作为正参考传递,并且因为任何逐位相同的提交必须具有相同的哈希ID作为原始版本,您必须至少进行一次更改以提交E,例如将提交者tiemstamp更改为一秒。)

另外两种方法是值得一提的。一种是使用--commit-filter:这是实际进行新提交的命令。你可以在这里做任何事情,包括完全省略一些提交;但是所有其他过滤器的原因是为了使事情更容易,所以在这种情况下根本就没有理由使用提交过滤器。

使用git replace

最后,有the git replace commandgit replace做的是创建保留在存储库中的新对象,由refs/replace/名称空间中的特殊名称引用。每当Git通过其哈希ID查看某个对象时,Git通常首先检查是否存在refs/replace/<hash-id>。如果是这样,Git会查看该引用指向的对象。

这意味着您可以构造一个非常类似于提交A的新Git对象,但略有不同。稍有不同的是,新的提交对象中存储了一个父哈希ID。父哈希ID是提交H的哈希ID。 (请注意,它与A具有相同的。)

现在您已经拥有了这个新对象 - 我们将其称为A' - 您将其粘贴到存储库中并使refs/replace/<big-ugly-hash>指向它:

A--B--C--D   <-- master

E--F--G--H   <-- master-old
          \
           A'   <-- refs/replace/deadcabf001...

(基于A的实际哈希值,可能不是deadcabf001...,所以请在此处使用正确的ID。

git log转到查看从提交D开始的历史记录时,它会查看提交D,然后获取D&# 39;父ID C,查看提交C,获取B的ID,然后继续提交B,获取A& #39;的ID和......哇,嘿,这个人有refs/replace/!我们毕竟不要看A!我们来看看A'!它会将A'显示为B的父级,然后转到A'的父级并向您显示H,然后{{1等等。

当您使用G时,您不必复制任何其他提交。 您拥有的是新的&#的提交历史记录34;更好&#34;承诺取代旧的&#34;不那么好&#34;一个,但两者实际上是共存的。 Git在这些条件下使用替换:

  1. 当然,它必须拥有替换对象;
  2. 必须要查看带有一些哈希哈希的对象,但在引用中找到git replace;和
  3. 它必须以正常方式运行,而不是refs/replace/hash
  4. 如果您愿意,要求3可让您查看原始(未放置的)历史记录。第2项表示默认情况下,在git --no-replace-objects上,您获得替换。你必须明确地要求它们(这并不难,但也没有任何简单易用的前端)。

    使用带有替换的filter-branch

    由于上面的第2项,您可能希望进行替换,确保所有内容都按照您喜欢的方式运行,然后运行git clone。由于您没有运行git filter-branch,Git会看到替换提交git --no-replace-objects filter-branch而不是原始提交A'。因此,它会复制A而不是A'。你不会需要A。当它复制--parent-filterE时,新副本将与原始文件一点一点地相同,因此这些副本将保持不变。最终结果与使用正确的父过滤器运行H相同。