【问题标题】:How to preserve tags on git filter-branch for prune-empty or subdirectory filter如何在 git filter-branch 上为 prune-empty 或子目录过滤器保留标签
【发布时间】:2018-01-19 19:25:39
【问题描述】:

git filter-branch --tag-name-filter cat … 使用--prune-empty 和/或--subdirectory-filter=… 重写历史记录时,您会遇到这样的情况,即删除标记的提交。 到目前为止这是合理的,并且按设计工作。

问题/目标

我现在要归档的是:在附近的重写提交中保留标签

示例:

A -> B(tag: foo) -> C -> D -> E开始

(其中 E 比 D 比 C 新……)

运行git filter-branch 我得到了任何一个

  • get A' -> B'(tag: foo)' -> E (^ 好案例)

  • 或者:A' -> D' -> E'(^坏情况)

我想要得到的是:A'(tag: foo)' -> D' -> E' 因为A' 代表B 中标记的内容

一些研究: 我绊倒的第一件事是Git: Is there a way to figure out where a commit was cherry-pick'ed from? 中的git cherry,但这似乎对发现树之间的差异没有太大帮助。

相反,我已经找到了一个有用的例子 --commit-filter https://stackoverflow.com/a/14783391/529977 来写重写对象的日志

一些想法: 考虑到--commit-filter“映射文件”,理论上我可以

  1. 过滤所有未在重写树中设置的标签
    • 找不到如何过滤树中的信息
  2. 迭代有疑问的标签列表
  3. 通过git log --oneline -1 ${tag}读取原始提交点
  4. 在原始树的历史记录中查找任何已知被重写的较新提交
    • 前向查找也很困难
    • 或者从任何重写的提交中查找历史记录以查找标记
  5. 将标签移动到新树中的第一个匹配项
    • 已知问题:如何保存所有信息,我不想重新标记经典方式
  6. 跳过标签,如果只有在另一个标签之后重写的提交
    • 如何确定有问题的提交是否有标签

我的其他想法是:

  • 通过比较原始树中的git log -1 --format="%an%ae%at%cn%ce%ct%s" | sha1sum 来查找任何“相似”提交,然后遍历历史记录到下一个已知标记,但这与上面的想法很接近

听起来仍然很难,即使我没有解决这些步骤的好主意......任何其他想法或已知解决方案(?!)欢迎!

【问题讨论】:

  • 您好,赏金的宽限期刚刚开始,想领取。我只是想提醒您,以防第一条通知/电子邮件丢失。
  • @timakro 抱歉,但是:您指的是哪个宽限期?我不记得设置赏金..了吗?!
  • 别人设置了赏金,但现在用完了^^。不过没关系,不是你的错。
  • 啊,在编辑历史中...我看到@pistache 对 ;o 感兴趣

标签: git-filter-branch git-tag


【解决方案1】:

我发现使用git_commit_non_empty_tree 不可靠。另一种相对简单的方法是将标签重新应用于树哈希的第一次出现。这不是存在退出的“正确”答案,但实际上可能是可取的,具体取决于您的用例。

for tag in $(git tag)
do
  t=$(git rev-parse $tag^{tree})
  r=$(git log --format='%T %H' | grep "^$t" | tail -n 1 | sed -e 's/.* //')
  git tag -f $tag $r
done

git log 显然可以被缓存。这需要在没有--prune-emptyfilter-branch 之后完成,然后运行

git filter-branch --prune-empty --tag-name-filter cat -- --all

删除空提交。这仅适用于轻量级标签,但如果您使用过滤,您可能希望先将带注释的标签转换为轻量级标签,然后在最后重新应用它们。

【讨论】:

    【解决方案2】:
    Deleted:           *    *         *                   *    *         *
    Tags:              R    S    T    U                        V         W
    Commits: A -> B -> C -> D -> E -> F -> G -> H -> I -> J -> K -> L -> M -> N
    

    预期输出:

    Tags:         R    T              V    W
    Commits: A -> B -> E -> G -> H -> I -> L -> N
    

    我们将使用--prune-empty 对此进行测试,因此我们正在为应该删除的提交创建空提交。让我们设置测试存储库。

    git init
    
    touch n && git add n && git commit -m "N"
    git commit --allow-empty -m "M"
    touch l && git add l && git commit -m "L"
    git commit --allow-empty -m "K"
    git commit --allow-empty -m "J"
    touch i && git add i && git commit -m "I"
    touch h && git add h && git commit -m "H"
    touch g && git add g && git commit -m "G"
    git commit --allow-empty -m "F"
    touch e && git add e && git commit -m "E"
    git commit --allow-empty -m "D"
    git commit --allow-empty -m "C"
    touch b && git add b && git commit -m "B"
    touch a && git add a && git commit -m "A"
    
    git tag W $(git log --pretty=oneline --grep=M | cut -d " " -f1)
    git tag V $(git log --pretty=oneline --grep=K | cut -d " " -f1)
    git tag U $(git log --pretty=oneline --grep=F | cut -d " " -f1)
    git tag T $(git log --pretty=oneline --grep=E | cut -d " " -f1)
    git tag S $(git log --pretty=oneline --grep=D | cut -d " " -f1)
    git tag R $(git log --pretty=oneline --grep=C | cut -d " " -f1)
    

    首先,我们将创建一个包含所有标签名称和它们指向的提交哈希的文件。

    for i in $(git tag); do echo $i; git log -1 --pretty=oneline $i | cut -d " " -f1; done > ../tags
    

    当运行git filter-branch 时,提交哈希会改变。为了跟踪这些更改,我们创建了一个文件,其中包含从旧提交哈希到新提交哈希的映射。这样做的技巧显示在here

    --subdirectory-filter=... 命令将如下所示:

    git filter-branch --subdirectory-filter=... --commit-filter 'echo -n "${GIT_COMMIT}," >>/tmp/commap; git commit-tree "$@" | tee -a /tmp/commap'
    

    由于--prune-empty 选项与--commit-filter 冲突,我们需要更改一些内容。 --prune-empty 的文档在这里有所帮助:

    一些过滤器会生成空提交,使树保持不变。此选项指示 git-filter-branch 如果它们恰好有一个或零个未修剪的父级,则删除此类提交;因此,合并提交将保持不变。此选项不能与--commit-filter 一起使用,但可以通过在提交过滤器中使用提供的git_commit_non_empty_tree 函数来实现相同的效果。

    所以我们将用于这个测试的--prune-empty 命令看起来像这样。在运行命令之前,请确保 /tmp/commap 不存在或为空。

    git filter-branch --commit-filter 'echo -n "${GIT_COMMIT}," >>/tmp/commap; git_commit_non_empty_tree "$@" | tee -a /tmp/commap'
    mv /tmp/commap ../commap
    

    现在我们运行git filter-branch 并收集了处理标签所需的所有信息。我们将不得不删除标签,我们将不得不更改提交标签指向。我们很幸运,git 将标签指向的提交哈希存储在 .git/refs/tags/TAGNAME 中。

    现在剩下的就是编写一个脚本来自动更正标签。这是我用 Python 写的。

    def delete(tagname):
        print('git tag -d {}'.format(tagname))
    
    def move(tagname, tagref):
        print('echo "{}" > .git/refs/tags/{}'.format(tagref, tagname))
    
    tags = {}
    with open('tags') as tagsfile:
        for i, line in enumerate(tagsfile):
            if i%2 == 0:
                tagname = line[:-1]
            else:
                # if there are multiple tags on one commit
                # we discard all but one
                tagref = line[:-1]
                if tagref in tags:
                    delete(tags[tagref])
                tags[tagref] = tagname
    
    commap = []
    with open('commap') as commapfile:
        for line in commapfile:
            old, new = line[:-1].split(',')
            commap.append((old, new))
    
    lastnew = None
    takentag = None
    for old, new in commap:
        if old in tags:
            if takentag:
                delete(takentag)
            takentag = tags[old]
        if new != lastnew:
            # commit was not deleted
            if takentag:
                move(takentag, new)
                takentag = None
        lastnew = new
    

    脚本输出调整标签所需的命令。在我们的示例中,这是输出:

    echo "0593fe3aa7a50d41602697f51f800d34b9887ba3" > .git/refs/tags/W
    echo "93e65edf18ec8e33e5cc048e87f8a9c5270dd095" > .git/refs/tags/V
    git tag -d U
    echo "41d9e45de069df2c8f2fdf9ba1d2a8b3801e49b2" > .git/refs/tags/T
    git tag -d S
    echo "a0c4c919f841295cfdb536fcf8f7d50227e8f062" > .git/refs/tags/R
    

    将命令粘贴到控制台后,git 存储库看起来像预期的那样:

    $ git log
    8945e933c1d8841ffee9e0bca1af1fce84c2977d A
    a0c4c919f841295cfdb536fcf8f7d50227e8f062 B
    41d9e45de069df2c8f2fdf9ba1d2a8b3801e49b2 E
    6af1365157d705bff79e8c024df544fcd24371bb G
    108ddf9f5f0a8c8d1e17042422fdffeb147361f2 H
    93e65edf18ec8e33e5cc048e87f8a9c5270dd095 I
    0593fe3aa7a50d41602697f51f800d34b9887ba3 L
    5200d5046bc92f4dbe2aee4d24637655f2af5d62 N
    $ git tag
    R
    T
    V
    W
    $ git log -1 --pretty=oneline R
    a0c4c919f841295cfdb536fcf8f7d50227e8f062 B
    $ git log -1 --pretty=oneline T
    41d9e45de069df2c8f2fdf9ba1d2a8b3801e49b2 E
    $ git log -1 --pretty=oneline V
    93e65edf18ec8e33e5cc048e87f8a9c5270dd095 I
    $ git log -1 --pretty=oneline W
    0593fe3aa7a50d41602697f51f800d34b9887ba3 L
    

    【讨论】:

    • 有趣的是:您的发现git_commit_non_empty_tree--tag-name-filter cat 一起自然地导致移动标签和正确处理。谢谢我什至怀疑我用 A -> B 写了一个误导性的例子,其中 B 对我来说比 A 新,但对你来说是相反的。原生解决方案会导致 E 被标记为 R、S、T 的移动——在我看来这是正确的!
    • 我想使用 --commit-filtergit_commit_non_empty_tree 将您的分析器更改/剥离到相关部分,因为我认为您的回答会因误导 A 而与历史顺序问题所需的解决方案相反-> 例子中的B命令?或者在我接受它作为答案之前,您可能会自己更改它?
    • @childno͡.de 请编辑我的答案,删除所有不必要的内容来解决您的问题。我会接受你的修改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-29
    • 2023-03-14
    • 2011-08-06
    相关资源
    最近更新 更多