添加原始哈希以在git rebase上提交(使用新根)

时间:2016-11-16 10:54:01

标签: git version-control rebase git-rebase git-filter-branch

我有一个以前用SVN管理的代码库,但现在使用git进行管理。当代码迁移到git时,历史记录丢失了。

我已设法recover the SVN-history,现在正尝试git-rebase最近提交的提交。

我有两个分支git-commits,其中包含自迁移到git以来的提交,以及包含旧历史记录的svn-commits。每个分支包含超过3000个提交。

我发现以下命令在旧版本之上构建新历史记录(尽管有一些手动合并冲突处理):

git rebase git-commits --root --onto svn-commits --preserve-merges

几个提交引用了提交哈希,我知道在完成rebase时这些会改变。因此,这些信息不会永远丢失,我想将每个提交的原始提交哈希添加到新重定位的提交消息中。

这意味着像这样的原始提交:

commit aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Author: Boaty McBoatface <boaty@example.com>
AuthorDate: Wed Jul 27 00:00:00 1938 +0000
Commit: Boaty McBoatface <boaty@example.com>
CommitDate: Wed Jul 27 00:00:00 1938 +0000

Reticulate splines

The splines had been derezzed, and needed to be reticulated.

会变成类似

的东西
commit bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Author: Boaty McBoatface <boaty@example.com>
AuthorDate: Wed Jul 27 00:00:00 1938 +0000
Commit:     Meshy <meshy@example.com>
CommitDate: Wed Nov 16 10:23:31 2016 +0000

Reticulate splines

The splines had been derezzed, and needed to be reticulated.

Original hash: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

这可能吗?也许是git-filter-branch

2 个答案:

答案 0 :(得分:1)

首先,注意:确保你真的想要这样做,因为git replace(下面简要提到)可以用来以保留ID的方式拼接历史。当然,它也有其自身的缺点;搜索使用过它的人的报告。

是的,您可以使用git filter-branch

执行此操作

但是,您可能希望组合&#34;在新的转化时重新提交新提交&#34;步骤使用&#34; ...然后编辑所有新提交以包含其旧ID&#34;步骤,因为rebase通过复制提交工作,而filter-branch工作通过... 复制提交。 : - )

执行此类操作的所有Git命令必须复制,因为每个提交的哈希ID是提交内容的函数。如果新提交以任何方式与原始提交不同,则会获得一个新的不同ID。

git rebasegit filter-branch之间的区别在于复制了哪些提交以及如何执行复制。

Rebase,在没有--preserve-merges的情况下完成,通过选择非合并提交列表,将每个这样的提交转换为变更集(通过减法,或多或少:子项减去父项=从父项到子项的delta) ,然后将此delta添加到--onto点或添加到提交的提交。

使用--preserve-merges时,rebase 仍然选择非合并提交列表。然后,在有合并提交的地方,rebase 重新执行合并(这就是你必须重新解决合并冲突的原因)。它必须重新合并,因为新的基础可能会导致不同的合并,并且因为合并不能变成单个变更集(&#34;孩子 - 父级&#34;给你一个delta,但至少有两个父母因此至少有两个增量,在一般情况下我们不能同时保留这两个增量。)

Filter-branch使用完全不同的方法。无论是否合并,都要选择要过滤的提交。 (实际选择是通过运行git rev-list来完成的,这是&#34; plumbing&#34;相当于git log。)这个完整的提交ID列表放在一堆:排序的,拓扑的-order pile存储在普通文件中,因此父提交始终在其子项之前处理。

然后,对于列表中的每个ID:

  • 将原始提交la git checkout提取到没有基础Git仓库的临时树中。

  • 应用树过滤器修改树。 (此修改在包含临时树的临时目录中运行。当他们尝试访问像../../fixed-version这样的文件时,该部分会让很多人在第一个树过滤器中运行。相对路径因为临时而失败树根本不在存储库中。)

  • 重建一组表示新树的Git tree-and-blob对象,即新的提交快照。

  • 将提交邮件过滤器应用于邮件。

  • 将提交环境过滤器应用于剩余的提交元数据(作者和提交者)。

  • 使用新邮件和新树进行新提交。或者,如果您提供提交过滤器,请使用它来进行或不进行提交;此时,您还可以使用父过滤器修改新提交的父级。

  • 最后,记录配对:&#34;旧提交&lt; oldhash&gt;成为新提交&lt; newhash&gt;。&#34; (如果您使用提交过滤器跳过提交,则旧哈希映射到其对应的新祖先,即您没有跳过的父级。)此配对是 地图

由于提取+树过滤器+重建部分,此过程非常慢。因此,如果您使用树过滤器,git filter-branch会跳过此部分:它无论如何都会让原始树回来。无论如何,为了让您修改新提交的内容,filter-branch还允许您指定索引过滤器(提交总是在索引中工作,因此提取+修改+重建只更新index;如果我们可以更新到位,那就快得多了。但是 - 这里的关键点 - 出于您的目的,您根本不需要对每棵树做任何事情。您只需要修改父级!这将允许您保留原始合并及其源树,而无需重新合并。

请注意,--commit-filter说明讨论了 map 便利功能(shell功能)。这个&#34;地图&#34;函数使用我上面提到的地图。默认设置是自动映射到新复制的提交的新父级。

最后,在复制所有提交之后 - 如果你提供--tag-name-filter,还要复制带注释的标签并映射副本(所以如果你有注释标签,你想要这里有--tag-name-filter cat) - filter-branch命令重写一些引用,即分支和标记名称。仍将指向原始提交(和带注释的标记对象)的原始引用将转储到refs/original/名称空间中。 (除非您使用--force,否则在进程开始时必须为空。)重写的引用指向新副本。重写使用相同的映射技术,因此如果有跳过的提交,名称现在指向保留的祖先提交。

(&#34;一些&#34;引用?等等,哪些引用?答案在文档中,但它有点神秘:它谈论正参考。参数传递给git rev-list,以便您可以过滤特定的提交范围,例如branch~30..branchbranch ^otherbranch。&#34;肯定&#34;参考是那些主动选择提交的人,而'#34;否定&#34;引用是限制提交的人,所以branch ^otherbranch我们有一个正参考,branch和一个负数, not-otherbranch part。所以这只重写refs/heads/branch而不是refs/heads/otherbranch。)

这是很多的措辞,但......怎么样?

解释以上所有内容的原因是要指出移植过程的简单性,使用git filter-branch时,然后展示如何访问地图。

首先,我们只需显式替换一个父ID。具体来说,我们希望git-commits root commit 的父级成为svn-commits的现有提示提交:

$ git rev-parse svn-commits
9999999999999...

(那是所需的新父母),并且:

$ git rev-list --max-parents=0 git-commits
11111111111111...

(那是根提交 - 运气好的只有一个,否则,现在是什么?)。

所以,我们想要一个父过滤器,上面写着:&#34;如果这是提交1111111 ...那么echo 9999999 ...,否则只需回显参数&#34;。默认父参数在stdin上,作为一系列-p <id> s,ID已经映射。当然,现有的root有 no 父项,因此stdin将没有我们想要在此更改的一个提交的内容。因此:

--parent-filter 'if [ $GIT_COMMIT = 11111... ]; then
  echo -p 999999...; else cat; fi'

filter-branch的这一部分将完成我们的重新育儿。请注意,与git rebase不同,所有树都保持完整。我们从不在这里将快照转换为增量,我们只是按原样使用。 这意味着无需重新解决合并冲突。

(附注:您实际上可以使用名称svn-commits代替此处的硬编码99999...。您可以使用名称代替硬编码11111...作为好吧,但是我们不会拥有一个名字。而且,每次查找名称都会为过滤添加一点点延迟。对于一个重新育儿到svn-commits ,这是一个小小的延迟;但是,为了测试这是否是旧的根,这将是一个小的延迟时间3000次提交。)

(第二方注意:您也可以通过&#34;移植物&#34;或更现代的版本git replace进行重新定位。如果您在运行{{1}时有效移植或替换},移植或替换成为永久,因为Git只是按照指示复制提交,指令也在替换之后。)

仍然存在过滤提交消息的问题,添加:

filter-branch

如上所示,原始哈希位于Original hash: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ,因此我们只需要:

$GIT_COMMIT

如果我们想要花哨,我们甚至可以使用 map 便利功能:

--msg-filter 'cat; echo; echo "Original hash: $GIT_COMMIT"'

或类似的东西,但没有充分的理由去打扰... 除非你想要真的看上去,看看你是否可以检测提交消息中的旧哈希ID并将其重写到位。我不确定这是否是一个好主意,并且不会尝试为它提供一些shell脚本,但请注意,这些过滤器的所有 1 都是&# 34; eval&#34; -ed作为shell片段。您可以从这些eval-ed片段调用其他shell脚本,只需记住所有过滤都在临时目录中进行。

对引用--msg-filter 'cat; echo; echo "new commit $(map $GIT_COMMIT) \ filtered to reparent original commit $GIT_COMMIT"' 运行过滤。完成过滤后,git-commits将指向上次复制的提交,refs/heads/git-commits将指向原始链(以上示例中以refs/original/refs/heads/git-commits为根的链)。

1 好吧,差不多全部。正如文档所述,&#34;除了提交过滤器之外,出于技术原因&#34;。

摘要

我们需要两个过滤器,11111...(或有效的移植或替换)和--parent-filter。父过滤器说&#34;用我们移植到&#34;的地方的尖端替换移植副本的根,这样就完成了我们的rebase-without-changing-snapshots。消息过滤器显示&#34;此新提交将替换我们在过滤时从扩展变量--msg-filter&#34;扩展的ID的提交。

答案 1 :(得分:0)

答案可能取决于您要重新设置的提交数量。
如果您正在进行变基的分支包含您可以手动编辑的相当少的提交,则以下提示可能会起作用:
https://help.github.com/articles/changing-a-commit-message/
一般来说,交互式rebase应该对你有帮助,不一定你应该去分支过滤我希望。

r, reword = use commit, but edit the commit message

使用交互式rebase尝试通过在提交消息中插入原始哈希来重新编写每个提交。

对于更多的提交,在3000左右的情况下让我们尝试使用filter-branch:

git filter-branch --msg-filter 'cat && echo "Original hash $GIT_COMMIT"' HEAD~3000..HEAD

它将为您所在分支的过去3000次提交中的每一次提交重新生成的提交msg生成新提交。新的提交消息将具有与此类似的格式(请注意底部的提交哈希):

commit 08ac9b84d820ec7b70fa53075adc06f0a8185ab4
Author:
Date:   Mon Nov 14 13:14:30 2016 +0100

 Adds javadoc

Auto inserted text: ....
Change-Id: ...dbf9497387a3c271ae0349822cb4b8...
Original hash 9d01f3e5b39b15c9dbe923916b6c25019b5b9796

之后,您可以安全地进行改造。应该保留旧的提交哈希。

BR 马切伊