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

时间:2017-12-22 20:45:59

标签: git circleci

如果我跑

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错误。有没有人对为什么这不起作用有任何想法?

2 个答案:

答案 0 :(得分:2)

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

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

分析

让我们把它分开:

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拼写,但更标准的ssh://user@host/path/to/repo网址)。如果在命令行中没有指定,则此存储库名称origin也将提供一组默认的refspec;但是你在命令行上给出一些,所以默认的refspecs不太重要。

最后一个参数 - 你的refspec-是出错的地方。 refspec 一般由冒号分隔的两部分组成,Git称之为 src dst 。您可以在加号前加+前缀,在该特定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_HEADgit 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仅在您提供一些目标引用时才有用 - :dst部分在您的命令行refspecs中。

(如果您要获取分支名称,Git将应用正常的分支名称更新规则,只要操作是"快进"就允许写入,但如果它是"不是。--force仍然意味着"始终允许写",但只要它是快进的,即使没有--force也允许分支更新。没有--force的情况下不允许更新,除了Git版本1.8.1及更早版本中的错误,它会错误地应用分支规则。)

修复程序足够清晰:脚本应将git fetch行更改为:

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

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

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

答案 1 :(得分:0)

如果您正在获取指向尚未包含在本地历史记录中的提交的标记,则可能存在一个问题。在这种情况下,提交最终无法通过任何本地分支进行访问,在这种情况下,我不认为fetch会默认复制标记。

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

git config remote.origin.tagOpt --tags

但这会产生副作用,即其他标签也会被提取。