git filter-branch导致断裂的历史记录:如何摆脱旧提交?

7
场景如下:
我有一个大的CVS代码库,想将其转换为14个不同的git代码库。cvs2git部分处理得很好,并导致了一个名为repo.git的大型代码库。
对于14个git代码库中的每一个,我克隆主代码库,并运行以下命令:
git filter-branch -d /tmp/rep --tag-name-filter cat --prune-empty --subdirectory-filter "sub/directory" -- --all

不过,在执行这个命令之前,我需要先对一些git存储库执行另一个git filter-branch命令,因为我需要重写提交记录以将一个文件从一个目录移动到另一个目录。我使用的选项是--tree-filter。下面是执行的命令行示例:

script_tree_filter="if test -f rep/to/my/file && test -d another/rep ; then echo Moving my file ; mv rep/to/my/file another/rep; fi"
git filter-branch -d /tmp/rep --tag-name-filter cat --prune-empty --tree-filter '$script_tree_filter' -- --all

在整个过程结束后(约14500个提交:需要约1小时!),我会清理引用并使用git gc命令:
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git reflog expire --expire=now --all
git gc --prune=now

最终,我得到了一个1.2Go的存储库 (这显然还是太大了),通过查看提交记录,我可以看到许多旧的提交记录仍然存在。它们涉及文件和目录,在执行--subdirectory-filter命令后不应该再存在于这里。
在提交记录历史中,如gitk --all所示,存在不良提交和好的提交之间的不连续性: discontinuity seen in gitk 我非常确定这些提交记录之所以仍然存在,是因为其中一些标签。如果是这种情况,是否有可能删除那些标签而不会移除好的提交上的标签?
如果标签不是原因,有什么想法吗?
更多信息,通过subdirectory-filter获得的git存储库中refs目录的内容为空:
$ ls -R refs/
refs/:
heads  original  tags

refs/heads:

refs/original:
refs

refs/original/refs:
heads  tags

refs/original/refs/heads:

refs/original/refs/tags:

refs/tags:

我发现在 git 仓库中,分支和标签都列在文件 packed-refs 中:

d0c675d8f198ce08bb68f368b6ca83b5fea70a2b refs/tags/v03-rev-04
95c3f91a4e92e9bd11573ff4bb8ed4b61448d8f7 refs/tags/v03-rev-05

该文件中列出了817个标签和219个分支。


1
git gc 将标签引用打包到 .git/packed-refs 中,因此出现了空目录。但是我不确定为什么标签会指向旧的提交,因为每个 filter-branch 操作都使用了 --tag-name-filter - torek
1
你按照这篇文章中的4个命令清理了吗?https://dev59.com/XnE85IYBdhLWcg3wZymt#7966852 - CharlesB
除了reset hard,我已经按照我的问题中提到的三个命令执行了(由于我有裸库,rm -rf .git/refs/original/的写法不同)。我没有使用gc的--aggressive选项,但我无法尝试(我认为这不会改变任何东西)。 - Frodon
所以你问题中的 ls 日志不是最新的,对吧?里面有 refs/original 的东西。另外,请在你的评论中使用 @user ,否则我们不会收到通知。 - CharlesB
好的;在packed-refs文件中是否有refs/original的内容?如果有,请将其从文件中删除,然后重新运行git-gc。 - CharlesB
显示剩余7条评论
2个回答

5

我通过改变使用cvs2git的方式成功解决了我的问题:不是将整个CVS库转换,然后使用subdirectory-filter命令,而是转换每个我想要的子模块。在我这种情况下,这意味着启动18个不同的cvs2git命令:

之前的做法

cvs2git --blobfile=blob --dump=dump /path/to/cvs/base
# Module 1
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter "path/to/module1" -- --all
# Module 2
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter "path/to/module2" -- --all

现在

# Module 1
cvs2git --blobfile=blob_module1 --dump=dump_module1 /path/to/cvs/base/path/to/module1
# Module 2
cvs2git --blobfile=blob_module2 --dump=dump_module2 /path/to/cvs/base/path/to/module2

每个存储库现在都有完美的历史记录。 为什么之前的方法没有起作用?我的猜测是cvs2git 混淆了所有子模块(其中一些在其历史记录中更改了其目录名称)。 @Michael @CharlesB,感谢你们抽出时间回答并帮助我。

2
很高兴你解决了这个问题,我很好奇为什么你要从cvs本身创建一个大型repo - 很棒你分享了解决方案 - 干杯。我也+1了。 - Michael

2
我敢打赌你正在遭受这种情况:
CVS和Git分支/标签模型之间的差异:CVS允许从多个源分支的任意组合中创建分支或标签。它甚至允许将从未同时存在的文件修订添加到单个分支/标签中。另一方面,Git仅允许作为一个单元的历史记录中某个时刻存在的完整源代码树被分支或打上标签。此外,git修订的血统对该修订的内容产生影响。这种差异意味着在Git存储库中无法百分之百忠实地表示任意CVS历史记录。cvs2git使用以下解决方法:
- cvs2git尝试从单个源创建分支,但如果无法确定如何创建,则使用来自多个源的“合并”创建分支。在病态情况下,分支的合并源数量可以是任意大的。结果的历史记录意味着每当向分支添加任何文件时,整个源分支都会合并到目标分支中,这显然是不正确的。(另一种选择是省略合并,这将丢弃某些内容从一个分支移动到另一个分支的信息。) - 如果cvs2git无法确定是否可以从单个修订中创建CVS标记,则创建名为TAG.FIXUP的标记修复分支,然后对该分支进行标记。 (这是一个必要的解决方法,因为git仅允许标记现有修订。)TAG.FIXUP分支创建为包含在标记中的文件修订的所有分支之间的合并,这涉及与分支相同的权衡描述。 TAG.FIXUP分支在转换结束时被清除,但(由于git快速导入文件格式的技术限制)不会被删除。有些情况下,可以从单个修订中创建标记,但cvs2git没有意识到,并创建了一个多余的标记修复分支。可以通过在生成的git存储库中运行contrib/git-move-refs.py脚本来删除多余的标记修复分支。
没有检查CVS分支和标记名称是否是合法的git名称。可能还应该检查其他git约束条件。请参阅cvs2git 你是要展示新目录下的refs目录还是转换后的大型仓库的refs目录?在筛选和拆分大型仓库之前,你可以删除单个大型导出仓库中的标签。
你可以通过删除目录中的文件来删除大型仓库中的标签 - 它只是一个指向SHA的引用。

“refs”目录是新目录之一(在子目录过滤器之后)。删除所有标签不是一个选项:我想保留与我保留的目录相关的标签。 - Frodon
我并没有说要删除所有标签,只是删除那些不重要的标签 = 你说有一些不必要的标签会导致问题。 - Michael
诀窍在于我不知道如何区分“好”的标签和“坏”的标签。我目前正在研究如何删除带有标签的空提交。 - Frodon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接