如何在git中“重新设置标签”?

用户名

假设我有以下简单的git仓库:一个分支,一些分支一个接一个地提交,在提交了每个分支之后,其中几个已经被标记(带有注释标签),然后有一天我决定更改第一个提交(顺便说一句,如果有任何更改,则不会被标记)。因此,我运行git rebase --interactive --root并仅将“ commit”标记为初始提交,并在其中进行了更改git rebase --continue现在,我的存储库中的所有提交都已重新创建,因此它们的sha1已更改。但是,我创建的标记完全不变,仍然指向先前提交的sha1。

有没有一种自动的方法可以将标签更新为在重新基准化时创建的相应提交?

有人建议使用,git filter-branch --tag-name-filter cat -- --tags但是首先警告我我的每个标签均未更改,然后说我的每个标签均已更改为自己(相同的标签名称和相同的提交哈希)。而且,git show --tags说标签仍然指向旧提交。

星期二

从某种意义上说,为时已晚(但请稍等,有个好消息)。filter-branch代码能够调整标签,因为它在过滤期间保留了从旧sha1到新sha1的映射。

事实上,无论是filter-branchrebase使用相同的基本的想法,这是每次提交被复制,通过扩大原有的内容,进行任何所需的更改,然后又做了新的提交结果出来。这意味着在每个复制步骤中,将<old-sha1,new-sha1>对写入文件很简单,然后一旦完成,便可以通过从其old-sha1中查找new-sha1来修复引用。 。完成所有引用后,您将致力于新的编号并删除映射。

该地图现在已经消失,因此“从某种意义上讲,为时已晚”。

幸运的是,还不算太晚。:-)您的基础是可重复的,或者至少是它的关键部分。而且,如果您的变基很简单,则可能根本不需要重复。

让我们看一下“重复”思想。我们有一些任意形状的原始图形G:

     o--o
    /    \
o--o--o---o--o   <-- branch-tip
 \          /
  o--o--o--o

(哇,飞碟!)。我们对其进行了git rebase --root(部分)处理,复制了(部分或全部)提交(是否保留合并)以获取一些新图形G':

    o--o--o--o   <-- branch-tip
   /
  /  o--o
 /  /    \
o--o--o---o--o
 \          /
  o--o--o--o

我已经画出了只共享原始根节点(现在是带有起重​​机的帆船,而不是飞碟)的共享。共享可能更多,或更少。一些旧节点可能已变得完全未被引用,因此被垃圾回收了(可能不是:reflogs应该使所有原始节点至少存活30天)。但是无论如何,我们仍然具有指向G'的“旧G部分”的标记,并且这些引用确保这些节点及其所有父代仍在新G'中。

因此,如果我们知道原始的重新设置是如何完成的,则可以在G'的子图G'上重复它,它是G的重要组成部分。这是多么困难或容易,以及使用了什么命令,取决于所有原始G是否都在G'中,rebase命令是什么,多少G'覆盖原始G以及更多(因为git rev-list,这是我们获取节点列表的关键,可能无法区分“原始的was-in-G”节点和“ new to G'”节点)。但这可能是可以做到的:这只是编程的一个小问题。

如果您确实重复了一次,这一次您将要保留映射,特别是如果生成的图形G''不完全重叠G'时,因为现在所需的不是地图本身,而是该地图投影,从G变成G'。

我们只需给原始G中的每个节点一个唯一的相对地址即可(例如,“从头开始,找到父提交#2;从该提交,找到父提交#1;从那个提交...”),然后找到对应的G中的相对地址。这使我们能够重建地图的关键部分。

根据原始基准库的简单性,我们也许可以直接跳到该阶段。例如,如果我们确定整个图被复制而没有展平(因此我们有两个独立的飞碟),那么TG中标签的相对地址就是我们在G'中想要的相对地址,现在使用起来很简单该相对地址,以创建指向复制的提交的新标签。

基于新信息的重大更新

使用额外的信息,即原始图形是完全线性的,并且我们已经复制了每个提交,我们可以使用一个非常简单的策略。我们仍然需要重建地图,但是现在很容易,因为每个旧提交都恰好有一个新提交,它与原始图的两端之间有一定的线性距离(很容易用一个数字表示)(我会使用距尖端的距离)。

也就是说,旧图看起来像这样,只有一个分支:

A <- B <- C ... <- Z   <-- master

标签只是指向一个提交(通过带注释的标签对象),例如,也许标签foo指向一个指向commit的带注释的标签对象W然后,我们注意到W从返回了四次提交Z

新图看起来完全一样,只是每次提交都已被其副本替换。让我们称这些为A'B'等等Z'(单个)分支指向最尖端的提交,即Z'我们将要调整原始标签,foo以便有一个指向的新的带注释的标签对象W'

我们需要原始最尖端提交的SHA-1 ID。这应该很容易在(单个)分支的reflog中找到,并且可能很简单master@{1}(尽管这取决于自那时以来对分支进行了调整的次数;并且如果在重新定基后添加了新的提交,我们需要同时考虑到这些)。它也很可能在特殊的ref中ORIG_HEADgit rebase如果您认为自己不喜欢重新设置基准结果,它会留下。

假设这master@{1}是正确的ID,并且没有这样的新提交。然后:

orig_master=$(git rev-parse master@{1})

会将这个ID保存在中$orig_master

如果我们要构建完整的地图,可以这样做:

$ git rev-list $orig_master > /tmp/orig_list
$ git rev-list master > /tmp/new_list
$ wc -l /tmp/orig_list /tmp/new_list

(两个文件的输出应该是相同的;如果不是,那么这里的一些假设就出了问题;与此同时,我在$下面也将省略shell前缀,因为其余部分实际上应该放入脚本中,即使是一次使用,以防出现错别字并需要调整)

exec 3 < /tmp/orig_list 4 < /tmp/new_list
while read orig_id; do
    read new_id <& 4; echo $orig_id $new_id;
done <& 3 > /tmp/mapping

(这是未经测试的,旨在将两个文件粘贴到一起(zip在两个列表上都类似于Python的shell版本)以获取映射)。但是我们实际上并不需要映射,我们所需要的只是那些“到尖​​端的距离”计数,因此我要假装我们没有在这里打扰。

现在我们需要遍历所有标签:

# We don't want a pipe here because it's
# not clear what happens if we update an existing
# tag while `git for-each-ref` is still running.
git for-each-ref refs/tags > /tmp/all-tags

# it's also probably a good idea to copy these
# into a refs/original/refs/tags name space, a la
# git filter-branch.
while read sha1 objtype tagname; do
    git update-ref -m backup refs/original/$tagname $sha1
done < /tmp/all-tags

# now replace the old tags with new ones.
# it's easy to handle lightweight tags too.
while read sha1 objtype tagname; do
    case $objtype in
    tag) adj_anno_tag $sha1 $tagname;;
    commit) adj_lightweight_tag $sha1 $tagname;;
    *) echo "error: shouldn't have objtype=$objtype";;
    esac
done < /tmp/all-tags

我们仍然需要编写两个adj_anno_tagadj_lightweight_tagshell函数。不过,首先,让我们编写一个shell函数,该函数在给定旧ID的情况下生成新ID,即查找映射。如果我们使用真实的映射文件,则将grep或awk作为第一个条目,然后打印第二个。但是,使用肮脏的单一文件方法,我们想要的是匹配ID行号,可以通过grep -n以下方法获得

map_sha1() {
    local grep_result line

    grep_result=$(grep -n $1 /tmp/orig_list) || {
        echo "WARNING: ID $1 is not mapped" 1>&2
        echo $1
        return 1
    }
    # annoyingly, grep produces "4:matched-text"
    # on a match.  strip off the part we don't want.
    line=${grep_result%%:*}
    # now just get git to spit out the ID of the (line - 1)'th
    # commit before the tip of the current master.  the "minus
    # one" part is because line 1 represents master~0, line 2
    # is master~1, and so on.
    git rev-parse master~$((line - 1))
}

警告情况永远不会发生,并且rev-parse永远不会失败,但是我们可能应该检查此shell函数的返回状态。

轻量级标签更新器现在非常简单:

adj_lightweight_tag() {
    local old_sha1=$1 new_sha1 tag=$2

    new_sha1=$(map_sha1 $old_sha1) || return
    git update-ref -m remap $tag $new_sha1 $old_sha1
}

更新带注释的标签比较困难,但是我们可以从中窃取代码git filter-branch我在这里不引用所有内容。相反,我只给您一点:

$ vim $(git --exec-path)/git-filter-branch

和这些指令:搜索的第二次出现git for-each-ref,并注意通过git cat-file管道sed传递到的结果,该结果将传递给git mktagshell变量new_sha1

这就是我们需要复制标签对象的过程。新副本必须指向在旧标记指向的提交上使用$(map_sha1)找到的对象。我们可以发现,犯同样的方式filter-branch做,使用git rev-parse $old_sha1^{commit}

(顺便说一句,编写此答案并查看filter-branch脚本,我发现filter-branch中存在一个错误,我们将其导入到rebase标记修复程序代码中:如果现有带注释的标记指向到另一个标签,我们不会修复。我们只修复轻量级标签和直接指向提交的标签。)

请注意,上面的示例代码均未经过实际测试,并且将其转换为更通用的脚本(例如,可以在进行任何rebase之后运行,或者更好地将其合并到交互式rebase本身中)需要大量的代码。额外的工作。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章