如果我跑
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错误。有没有人对为什么这不起作用有任何想法?
答案 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
这对你没有任何好处。我们马上就会明白为什么。
其余两个参数origin
和refs/tags/...
分别是存储库和 refspec 参数。
存储库名称origin
提供了URL,以便您的Git知道使用ssh来调用github.com:myname/myrepo
(user@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 fetch
和git 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
仅在您提供一些目标引用时才有用 - :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
但这会产生副作用,即其他标签也会被提取。