我有一个项目存在于两个 SVN 存储库中。第二个 SVN 存储库是通过从旧 SVN 存储库的签出中添加存储库而简单地创建的,而没有剥离 SCM 信息。文件的内容字节相同,但没有关联的 SCM 元数据。
我采用了新的 SVN 存储库并通过 git-svn 将其移植到 Git 存储库中。现在我想导入旧存储库并以某种方式让它链接新存储库,这样我就可以看到两者的历史记录。有没有一种简单的方法可以在不将两个存储库手动拼接在一起的情况下做到这一点?
另请参阅:如何在我在 github.com 上分叉的项目之上重新播放本地 Git 存储库的提交?问题(和我的回答),虽然情况略有不同,我认为。
你至少有三种可能性:
使用嫁接连接两个历史,但不要重写历史。这意味着您(以及任何拥有相同移植物的人)将拥有完整的历史记录,而其他用户将拥有较小的存储库。如果有人已经开始在具有较短历史的转换存储库之上工作,这也避免了重写历史的问题。
使用嫁接连接两个历史,并使用“git log”或“gitk”(或其他 Git 历史浏览器/查看器)检查它是否正确,然后使用git filter-branch重写历史;然后你可以删除移植文件。这意味着从重写的存储库中克隆(获取)的每个人都将获得完整的、加入的历史记录。但是,如果有人已经基于转换后的短历史存储库工作(但这种情况可能不适用于您),那么重写历史是一个很大的问题。
使用git replace加入两个历史。refs/replace/
这将允许人们通过选择获取(然后他们得到完整的历史)或不(然后他们得到简短的历史)来选择他们是否想要完整的历史,或者只是当前的历史。不幸的是,这需要当前使用尚未发布的 Git 版本,使用开发(“主”)版本,或 1.6.5 的候选发布版本之一。该refs/replace/
层次结构计划用于即将发布的 Git 版本 1.6.5。
下面是所有这些方法的分步说明:移植(本地)、使用移植重写历史记录和 refs/replace/。
在所有情况下,我假设您在单个存储库中同时拥有当前和历史存储库历史记录(您可以使用git remote add从另一个存储库添加历史记录)。我还假设短历史存储库中的(其中一个)分支名为“master”,并且您要附加当前历史记录的历史存储库的分支(提交)称为“history”。您将不得不替换您自己的分支名称(或提交 ID)。
首先,您必须在要附加到完整历史记录的短历史记录中找到提交的(SHA-1 标识符)。这将是短暂历史中的第一次提交,即根提交(没有任何父母的提交)。
有两种方法可以找到它。如果您确定没有任何其他根提交,则可以使用以下方法按拓扑顺序查找最后一个(最底部)提交:
$ git rev-list --topo-order master | tail -n 1
(wheretail -n 1
用于获取输出的最后一行;如果没有,则无需使用。)
如果有多个根提交的可能性,您可以使用以下单行查找所有无父提交:
$ git rev-list --parents master | grep -v ' '
(其中grep -v ' '
,即单引号之间的空格,用于过滤掉所有具有任何父项的提交)。然后,如果有多个提交,您必须检查(使用例如“ git show <commit>
”)这些提交,并选择一个要附加到较早历史记录的提交。
我们将此提交称为 TAIL。您可以使用以下方法将其保存在 shell 变量中(假设更简单的方法适合您):
$ TAIL=$(git rev-list --topo-order master | tail -n 1)
在下面的描述中,我$TAIL
的意思是你必须替换当前(短)历史中最底层提交的 SHA-1 ......或者允许 shell 为你做替换。
这部分很简单。我们必须将提交的符号名称转换为 SHA-1 标识符。我们可以使用“git rev-parse”来做到这一点:
$ git rev-parse --verify history^0
(如果 'history' 是标签,则使用 'history^0' 代替 'history';我们需要提交的 SHA-1,而不是标签对象的 SHA-1)。类似地,就像查找要附加的提交一样,让我们将此提交 ID 命名为 TOP。您可以使用以下命令将其保存在 shell 变量中:
$ TOP=$(git rev-parse --verify history^0)
位于中的移植文件.git/info/grafts
(如果不存在,则需要创建此文件,如果要使用此机制)用于替换提交的父信息。它是基于行的格式,其中每一行包含我们要修改的提交的 SHA-1,后跟零个或多个空格分隔的提交列表,我们希望给定提交作为父提交;git rev-list --parents <revision>
与 " " 输出的格式相同。
我们希望没有任何父母的 $TAIL 提交将 $TOP 作为其单亲。所以在info/grafts
文件中应该有一行包含 $TAIL 提交的 SHA-1,由 $TOP 提交的 SHA-1 以空格分隔。您可以为此使用以下单行代码(另请参阅git filter-branch文档中的示例):
$ echo "$TAIL $TOP" >> .git/info/grafts
现在您应该使用“git log”、“git log --graph”、“gitk”或其他历史浏览器检查您是否正确加入了历史。
请注意,这将改变历史!
为了使移植文件中记录的历史永久化,使用“git filter-branch”重写您需要的分支就足够了。如果只有一个分支需要重写('master'),它可以很简单:
$ git filter-branch $TOP..master
(这将只处理最小的提交集)。如果有更多受加入历史影响的分支,您可以简单地使用
$ git filter-branch --all
现在您可以删除移植文件。检查一切是否如您所愿,并删除备份refs/original/
(有关详细信息,请参阅“git filter-branch”文档)。
这是移植文件的替代方法。它的优点是它是可转移的,所以如果你发布了简短的历史并且不能重写它(因为其他人的工作基于简短的历史),那么使用 refs/replace/ 可能是一个很好的解决方案......好吧,至少当 Git 版本 1.6.5 发布时。
refs/replace/ 机制与移植文件的操作方式不同:您替换对象而不是修改父对象的信息。所以首先你必须创建一个提交对象,它具有与 $TAIL 相同的属性,但有 $TOP 作为父对象。
我们可以用
$ git cat-file commit $TAIL > TAIL_COMMIT
(临时文件的名称只是一个例子)。
现在您需要编辑“TAIL_COMMIT”文件(看起来像这样):
树 2b5bfdf7798569e0b59b16eb9602d5fa572d6038 作者 Joe R Hacker 1112911993 -0700 提交者 Joe R Hacker 1112911993 -0700 移动到新存储库后,“项目”的初始修订
现在您需要添加 $TOP 作为父级,方法是在“tree”标头和“author”标头之间放置一条带有“parent $TOP”(其中 $TOP 必须扩展为 SHA-1 id!)的行。编辑 'TAIL_COMMIT' 后,它应该如下所示:
树 2b5bfdf7798569e0b59b16eb9602d5fa572d6038 父 0f6592e3c2f2fe01f7b717618e570ad8dff0bbb1 作者 Joe R Hacker 1112911993 -0700 提交者 Joe R Hacker 1112911993 -0700 移动到新存储库后,“项目”的初始修订
如果需要,您可以编辑提交消息。
现在您需要使用git hash-object在存储库中创建一个新的提交。您需要保存此命令的结果,即新提交对象的 SHA-1,例如:
$ NEW_TAIL=$(git hash-object -t commit -w TAIL_COMMIT)
(此处的 ' -w
' 选项用于将对象实际写入存储库)。
最后使用git replace将 $TAIL 替换为 $NEW_TAIL:
$ git replace $TAIL $NEW_TAIL
现在还需要检查什么(使用“git log”或其他一些历史查看器)历史是否正确。
现在,任何想要拥有完整历史记录的人都需要添加 ' +refs/replace/*:refs/replace/*
' 作为 pull refspecs 之一。
最后一点: 我没有检查过这个解决方案,所以你的里程可能会有所不同。
首先,创建一个嫁接点来附加这两个历史。然后在存储库上运行git filter-branch以使更改永久化。注意,这将更改移植下游所有提交的提交 ID。