gpt4 book ai didi

git - 如何在 git 中 "rebase tags"?

转载 作者:行者123 更新时间:2023-12-02 14:56:04 28 4
gpt4 key购买 nike

假设我有以下简单的 git 存储库:一个分支,一些分支一个接一个提交,其中一些在提交每个分支后被标记(带有带注释的标签),然后有一天我决定要更改第一个提交(顺便说一句,没有标记,如果这有什么改变的话)。所以我跑 git rebase --interactive --root并为初始提交标记“编辑”,更改其中的内容和 git rebase --continue .现在我的存储库中的所有提交都已重新创建,因此它们的 sha1 已更改。但是,我创建的标签完全没有变化,仍然指向之前提交的 sha1。

是否有一种自动方法可以将标签更新为 rebase 时创建的相应提交?

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

最佳答案

从某种意义上说,为时已晚(但等一下,有好消息)。 filter-branch代码能够调整标签,因为它在过滤过程中保留了 old-sha1 到 new-sha1 的映射。

事实上,两者都是filter-branchrebase使用相同的基本思想,即复制每个提交,通过扩展原始内容,进行任何所需的更改,然后从结果中进行新的提交。这意味着在每个复制步骤中,将 对写入文件是微不足道的,然后一旦完成,您就可以通过从 old-sha1 中查找 new-sha1 来修复引用.完成所有引用后,您将提交新编号并删除映射。

map 现在已经消失了,因此“从某种意义上说,为时已晚”。

幸运的是,现在还为时不晚。 :-) 你的 rebase 是可重复的,或者至少,它的关键部分可能是。此外,如果您的 rebase 足够简单,您可能根本不需要重复它。

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

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

(哇,一个飞碟!)。我们做了一个 git rebase --root在它的(部分)上,复制(部分或全部)提交(保留 merge 与否)以获得一些新图 G':
    o--o--o--o   <-- branch-tip
/
/ o--o
/ / \
o--o--o---o--o
\ /
o--o--o--o

我只绘制了这个共享的原始根节点(现在它是一艘带有起重机的帆船,而不是飞碟)。共享可能更多,也可能更少。一些旧节点可能已经完全没有被引用,因此被垃圾收集(可能不是:引用日志应该让所有原始节点至少存活 30 天)。但无论如何,我们仍然有标签指向 G' 的某个“旧 G 部分”,这些引用保证这些节点及其所有父节点仍在新 G' 中。

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

如果你重复它,这次你想保留映射,特别是如果结果图 G'' 没有完全重叠 G',因为你现在需要的不是 map 本身,而是这张 map 的投影,从 G 到 G'。

我们简单地给原始 G 中的每个节点一个唯一的相对地址(例如,“从尖端,找到父提交 #2;从那个提交,找到父提交 #1;从那个提交……”),然后找到相应的G'' 中的相对地址。这使我们能够重建 map 的关键部分。

根据原始 rebase 的简单性,我们也许可以直接跳到这个阶段。例如,如果我们确定整个图是在没有展平的情况下复制的(因此我们有两个独立的飞碟),那么标签 T 的相对地址。在 G 中是我们想要在 G' 中的相对地址,现在使用该相对地址来创建一个指向复制提交的新标签是微不足道的。

基于新信息的大更新

使用原始图完全线性的附加信息,并且我们复制了每个提交,我们可以使用一个非常简单的策略。我们仍然需要重建 map ,但现在很容易,因为每个旧提交都有一个新提交,它与原始图的任一端都有一些线性距离(这很容易表示为单个数字)(我会使用距离尖端)。

也就是说,旧图看起来像这样,只有一个分支:
A <- B <- C ... <- Z   <-- master

标签只是指向提交之一(通过带注释的标签对象),例如,可能是标签 foo指向一个带注释的标记对象,该对象指向提交 W .然后我们注意到 W是来自 Z 的四次提交.

新图看起来完全一样,只是每个提交都被替换成了它的副本。让我们称这些 A' , B' ,依此类推,通过 Z' . (单个)分支指向最尖端的提交,即 Z' .我们要调整原始标签 foo这样我们就有一个新的带注释的标签对象指向 W' .

我们需要原始最尖端提交的 SHA-1 ID。这应该很容易在(单个)分支的引用日志中找到,并且可能只是 master@{1} (尽管这取决于您从那时起调整分支的次数;如果您在 rebase 后添加了新的提交,我们也需要考虑这些)。它也可能在特殊引用文献 ORIG_HEAD 中, 其中 git rebase如果您决定不喜欢 rebase 结果,请留下。

让我们假设 master@{1}是正确的 ID,并且没有这样的新提交。然后:
orig_master=$(git rev-parse master@{1})

将此 ID 保存在 $orig_master 中.

如果我们想构建完整的 map ,可以这样做:
$ 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

(这是未经测试的,意在将这两个文件粘贴在一起——这在两个列表中是 Python zip 的 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_tag shell 函数。不过,首先让我们编写一个 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))
}

WARNING 情况永远不会发生,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 mktag , 设置 shell 变量 new_sha1 .

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

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

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

关于git - 如何在 git 中 "rebase tags"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33534411/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com