如果我跑
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标记提取中存在一个错误,你可能在某些时候有点痒痒。有关详细信息,请参阅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脚本仍然是错误的。
让我们把它分开:
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-only拼写,用于等效但更标准的ssh://user@host/path/to/repo
URL)。如果你在命令行中没有提供,那么这个存储库名称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
只有在你的命令行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两侧的标记会更简单,更可靠。
一个可能的问题是,如果您正在获取指向本地历史记录中尚未提交的提交的标记。在这种情况下,提交最终将无法从任何本地分支到达,我认为在这种情况下,fetch默认情况下不会复制标记。
如果是这种情况,那么你可以通过将--tags
选项传递给fetch
来使其工作;但由于您无法控制脚本,因此可能需要更改repo的配置
git config remote.origin.tagOpt --tags
但是,这也会产生其他标签的副作用。