如何在git-filter-branch中为标签禁用“重映射到祖先”?

时间:2016-11-07 16:09:23

标签: git git-filter-branch

我的问题与that question非常相似,而that answer对我来说非常有效。

唯一的问题是标签:我在结果仓库中收到了很多不需要的标签。

这是我的命令:

git filter-branch --tag-name-filter cat --prune-empty --index-filter "git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- path/to/dir1 path/to/dir2" -- --all

使用选项-- --all保留所有标签;指向跳过提交的标记被移动到最近的祖先提交 如果没有选项-- --all,所有标记都将丢失(当然,如果未在命令行中明确列出)。

我希望标记指向被跳过的提交被自动排除,而不是移动到最近的祖先提交。
应保留所有其他标签 我怎么能这样做?

P.S。
我想避免在运行git filter-branch之前手动删除不需要的标签 回购中有数千个标签。

更新
感谢@torek确认没有直截了当的方式 我通过运行删除所有不需要的标签的Lua脚本解决了我的问题。

local list_of_dirs = "path/to/dir1 path/to/dir2" -- separated by space

local useful = {}
for line in io.popen(
   "git log --full-history --decorate=full --format=%D -- "..list_of_dirs
):lines() do
   for tag in line:gmatch"tag: refs/tags/([^,]+)" do
      -- Limitation: your tag names should not contain comma
      useful[tag] = true
   end
end
for tag in io.popen"git tag":lines() do
   if not useful[tag] then
      os.execute('git tag -d "'..tag..'"')
   end
end

1 个答案:

答案 0 :(得分:1)

很遗憾没有办法让git filter-branch自动执行此操作。有关修改代码以使其成为可能的一个想法,请参见下文(远远低于:-))(这可能比下一部分更容易,更可靠。)

幸运的是, 是一种自动发现重新映射到其原始提交的标记的方法,而标记重新映射到其他一些(因此是祖先)提交。不幸的是,我从来没有真正这样做,所以以下基本上只是理论,而不是练习。

第一步是建立自己的地图。对于每个标记,您将需要标识最终标记的对象:

git for-each-ref --format '%(refname)' refs/tags |
    while read name; do echo $name $(git rev-parse $name^{}); done

(这样的两步法,而不是使用%(object),似乎需要将标签映射到最终对象,以防标签指向另一个标签;如果你没有那个)。上面的输出是名称到对象的映射。您将需要一张与"之前相对应的地图"状态所以在过滤之前运行它(或者在"未过滤的"副本;见下文)。

您可能希望将自己限制为最终指向提交的标记(请参阅下面有关修改filter-branch的替代方法。)

完成filter-branch后,使用相同的命令获取新地图。 (将两个命令'输出重定向到临时文件。)

如果您愿意,可以在过滤后通过提供标记名称过滤器,将旧标记名称映射到唯一的,可区分的新标记名称,来执行此操作一次。例如,如果所有现有标记都符合模式vnumber.number,则可以使标记过滤器生成以w开头的标记。这样可以很容易地在后期过滤的回购中告诉哪个标签是哪个。最终,你必须重新命名所有标签。

或者,既然您应该过滤原始仓库的副本,那么您可以在原始仓库中运行for-each-ref以获取" old"映射并再次在" new"的过滤回购中映射。或者,检查refs/original/refs/tags/名称空间以查找原始标记(我不确定filter-branch是否保存原始标记,保存原始分支名称的方式)。

你剩下的任务很难:现在我们必须弄清楚 new 目标对象"是否是"原始目标对象(过滤后),或者是通过重映射到祖先找到的一些祖先。这是我们理论上的地方,因为你的过滤器分支过滤器正在做什么。我们如何判断commit 89abcde"是否是" " 1234567"的过滤结果,或者我们是否只是跳过了那次提交?当然,这取决于您的过滤器

因为filter-branch将所有原始提交保留在存储库的副本旁边,并且原始分支名称存储在refs/original/refs/...中,我们可以看到所有原始提交。这意味着我们可以运行两个映射并比较提交,或重新运行过滤器,以进行此类发现。

如果您的过滤器始终保持tree完整,我们可以使用git cat-file -p <commit-id> | headergrep tree来提取树ID。如果旧提交和新提交的树ID匹配,我们保留该提交,因此我们希望保留标记;如果没有,我们希望丢弃标签。 (请注意,您必须编写headergrep:它只是第一个空行的内容的grep,它将提交标头与提交消息分开。)

如果您的过滤器始终保留 tree的所有内容,我们可能会提取所有内容,除了 tree和{{1行。这是一个更简单的,因为旧的提交读取:

parent

可能看起来与使用完全相同的消息的新的但重新映射的提交相同,并且由同一作者和提交者在相同的秒内进行,以便时间戳匹配(如果某些提交是由自动软件制作,每秒进行多次提交)。但是,通常,复制的提交的内容将匹配(在丢弃树和父行之后),而重新映射的提交的内容将赢得。因此,我们可以使用相当于tree ... parent ... author A U Thor <thor@example.com> 1471018671 -0700 committer A U Thor <thor@example.com> 1471018671 -0700 terriblecommitmessage 的哈希值来比较文本或比较原始文本(您必须再次写一下:它是上面我们的理论headergrep -v的直接变体,除了{ {1}}我们必须复制空白行和提交消息,以及除排除的标题行之外的所有内容,并将输出发送到临时文件和headergrep,或者通过-v发送输出:我们可以只是假装这些cmp输出行是blob并获取它们唯一的SHA-1哈希ID,然后比较它们。

当然,如果你的过滤器做了一些非常容易识别的东西,比如跳过与某个特定作者的提交(如在一个文档示例中),那么很容易分辨哪些提交被跳过并因此导致重新映射到-ancestor。

一旦我们知道哪些提交被保留,哪些被重新映射,我们就知道要保留(保留)或丢弃(重新映射)哪些标记。现在它只是删除所有&#34;丢弃&#34;标签

另一种可能性是复制git hash-object脚本:

headergrep -v

请注意,标记名称过滤器在之后运行,remap_to_ancestor代码处理分支名称,这些分支名称指向被丢弃的提交,因此重新映射(创建filter-branch)。如果你将它移动到之前那么你就可以轻松地告诉你跳过了哪些提交。实际上,如果标记的目标提交不在映射中,或者标记的目标不是提交,则重新映射该标记的代码根本不执行任何操作。 (在这种情况下,您可能希望将其删除。对于指向树或blob的提交,您根本不清楚要做什么。)