我的问题与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
答案 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的提交,您根本不清楚要做什么。)