显式提取标记后,git标记似乎不可用

问题描述 投票:0回答:2

如果我跑

git fetch --force origin "refs/tags/release-2017-12-22T15_28_47-05_00"

它输出

From github.com:myname/myrepo
 * tag               release-2017-12-22T15_28_47-05_00 -> FETCH_HEAD

但是如果我做git tag -l并且如果我试着检查它,我就不会看到分支

git checkout -q "release-2017-12-22T15_28_47-05_00"

然后我收到一个关于它没有找到的错误:

error: pathspec 'release-2017-12-22T15_28_47-05_00' did not match any file(s) known to git.

如果我改为执行它确实有用

git fetch --all

哪个输出

From github.com:myname/myrepo
 * [new tag]         release-2017-12-22T15_28_47-05_00 -> release-2017-12-22T15_28_47-05_00

并使标签可用。不幸的是,我在CircleCI脚本中遇到这个错误,我没有任何控制权,所以我不能只使用第二种方法。他们正在跑步

git fetch --force origin "refs/tags/${CIRCLE_TAG}"
git reset --hard "$CIRCLE_SHA1"
git checkout -q "$CIRCLE_TAG"

看起来它会工作,但它会遇到pathspec错误。有没有人对为什么这不起作用有任何想法?

git circleci
2个回答
2
投票

我认为Git标记提取中存在一个错误,你可能在某些时候有点痒痒。有关详细信息,请参阅Why is git fetch not fetching any tags?。但是,您使用的git fetch语法实际上默认禁止提取标记。

但最重要的是,这个CircleCI脚本是错误的。它可能与另外的Git bug进行交互,只要你没有遇到Git bug,Mark Adelsberger's suggestion of setting the tag option to --tags可能会有所帮助,但是CircleCI脚本仍然是错误的。

Analysis

让我们把它分开:

git fetch --force origin "refs/tags/release-2017-12-22T15_28_47-05_00"

这里的--force对你没有任何好处。我们马上就会明白为什么。

剩下的两个参数originrefs/tags/...分别是存储库和refspec参数。

存储库名称origin提供了URL,因此您的Git知道使用ssh来调用github.com:myname/myrepouser@host:path/to/repo语法是一种特殊的Git-only拼写,用于等效但更标准的ssh://user@host/path/to/repo URL)。如果你在命令行中没有提供,那么这个存储库名称origin也会提供一组默认的refspec;但是你在命令行上给出一些,所以默认的refspecs不太重要。

最后一个参数 - 你的refspec-是出错的地方。 refspec一般由两个由冒号分隔的部分组成,Git称为srcdst。你可以在加号前加上+加号,在一个特定的refspec上设置一个强制标志,或者使用--force在所有refspec上设置强制标志。 (你可以在命令行列出多个refspec-在repository是refspec之后的每个参数,所以你可以运行git fetch origin srcref1:dstref1 srcref2:dstref2,例如。)

你没有在你的refspec中使用冒号:(也没有使用+,但你确实使用了--force)。这里的含义与git fetchgit push不同 - 我之所以提到这一点,只是因为这两个命令都采用了refspecs,但它们使用无冒号的refspecs做了不同的事情。对于git fetch,如果缺少refspec的:dst部分,则告诉Git在获取适当的底层Git对象后丢弃该名称。

(当像这样被丢弃的名称是一个分支名称,它出现在由指定的repository参数提供的默认refspecs中时,Git不会抛弃它,这就是为什么默认的refspecs仍然有些相关 - 但这不是'分支名称,它是标记名称。)

git fetch提取的每个哈希值,git fetch写入旧的Git-1.5及更早版本的兼容性文件.git/FETCH_HEAD,像git pull这样的程序仍然使用。所以即使git fetch抛弃了这个名字,它也会在FETCH_HEAD中保存哈希ID(以及一些辅助数据)。这就是为什么你看到这条线的原因:

 * tag               release-2017-12-22T15_28_47-05_00 -> FETCH_HEAD

这行是git fetch告诉你的方式:我发现了一个标签。我复制了标签指向的对象。然后,按照您的指示,我扔掉了标签名称,并将哈希ID写入文件FETCH_HEAD。所以我们都很好,对吧?

如果你不想让git fetch扔掉你的名字,你应该在你的refspec中提供了一个dst部分:

git fetch origin refs/tags/release-2017-12-22T15_28_47-05_00:refs/tags/release-2017-12-22T15_28_47-05_00

例如。 (对于标记名称,在冒号的两边使用完全相同的名称是正常的。)这告诉Git,从远程存储库中获取了名为release-2017-12-22T15_28_47-05_00的标记后,它应该将名为release-2017-12-22T15_28_47-05_00的标记写入本地存储库,指向同一个对象(相同的Git哈希ID)。

这是强制标志生效的地方。如果该标记已存在于本地系统上,--force会告诉Git覆盖它,而不是产生错误。如果标签不存在,--force没有效果(当然,如果标签已经存在且具有正确的值,则使用相同的值重写它也没有效果)。所以--force只有在你的命令行refspecs中提供一些目标引用 - 一个:dst部分时才有用。

(如果你正在获取分支名称,Git将应用正常的分支名称更新规则,只要操作是“快进”,它就允许写入,但如果不是,则不允许写入。这里--force仍然意味着“总是允许写入“,但是即使没有--force也允许分支更新,只要它是快进。没有--force就不允许更新标签,除了Git版本1.8.1及更早版本中的错误,它们错误地应用了分支规则。 )

修复很清楚:脚本应该将git fetch行更改为:

git fetch origin "+refs/tags/${CIRCLE_TAG}:refs/tags/${CIRCLE_TAG}"

这样Git就被迫在本地存储库中创建或更新标签名称。 (注意,我在这里使用了更短/更简单的+-means-force选项,这不是必需的,它只是我喜欢的样式。)或者,或者,脚本可以使用不写本地名称的git fetch,就像现在一样,然后从FETCH_HEAD文件中获取正确的哈希ID,la git pull。但这对脚本来说是一个更大的改变,并且意味着目标提交没有永久名称,这可能还有其他缺点。

你可以把所有这些分析都交给CircleCI的人,他们可能会认为Git bug本身也应该修复(它可能应该这样),但考虑到全世界都有bug的Gits,以及没有refspec的含义一个本地名称定义得很好,更改脚本以重复refspec两侧的标记会更简单,更可靠。


0
投票

一个可能的问题是,如果您正在获取指向本地历史记录中尚未提交的提交的标记。在这种情况下,提交最终将无法从任何本地分支到达,我认为在这种情况下,fetch默认情况下不会复制标记。

如果是这种情况,那么你可以通过将--tags选项传递给fetch来使其工作;但由于您无法控制脚本,因此可能需要更改repo的配置

git config remote.origin.tagOpt --tags

但是,这也会产生其他标签的副作用。

热门问题
推荐问题
最新问题