There is what I consider to be a bug in Git tag fetching, and you may have tickled that a bit at some point. See Why is git fetch not fetching any tags? for details. However, the git fetch
syntax you are using actually explicitly inhibits fetching tags by default.
The bottom line, though, is that this CircleCI script is buggy. It may be interacting with an additional Git bug, and Mark Adelsberger's suggestion of setting the tag option to --tags
may help as long as you haven't run into the Git bug, but the CircleCI script is still wrong.
Analysis
Let's take this apart here:
git fetch --force origin "refs/tags/release-2017-12-22T15_28_47-05_00"
The --force
here is doing you no good whatsoever. We'll see why in a moment.
The remaining two arguments, origin
, and refs/tags/...
, are the repository and refspec arguments, respectively.
The repository name origin
provides the URL, so that your Git knows to use ssh to call up github.com:myname/myrepo
(the user@host:path/to/repo
syntax is a special Git-only spelling for the equivalent, but more-standard, ssh://user@host/path/to/repo
URL). This repository name origin
would also provide a default set of refspecs, if you gave none on the command line; but you are giving some on the command line, so the default refspecs are less important.
The last argument—your refspec—is where things go wrong. A refspec in general consists of two parts separated by a colon, which Git refers to as src
and dst
. You can prefix the pair with a plus sign +
to set a force flag on that one specific refspec, or use --force
to set the force flag on all refspecs. (You can list more than one refspec on the command line—every argument after the repository
is a refspec, so you could run git fetch origin srcref1:dstref1 srcref2:dstref2
, for instance.)
You did not use a colon :
in your refspec (nor a leading +
but you did use --force
). The meaning here is different for git fetch
and for git push
—I mention this only because both commands take refspecs, but they do different things with colon-free refspecs. For git fetch
, if the :dst
part of the refspec is missing, that tells Git to throw away the name after fetching the appropriate underlying Git objects.
(When the name being discarded like this is a branch name that appears in the default refspecs provided by the specified repository
argument, Git doesn't throw it away after all, which is why the default refspecs are still somewhat relevant—but this isn't a branch name, it's a tag name.)
Every hash that git fetch
fetches, git fetch
writes to the old Git-1.5-and-earlier compatibility file, .git/FETCH_HEAD
, that programs like git pull
still use as well. So even though git fetch
is throwing the name away, it saves the hash ID (and some auxiliary data as well) in FETCH_HEAD
. This is why you see, as a result, the line:
* tag release-2017-12-22T15_28_47-05_00 -> FETCH_HEAD
This line is git fetch
's way of telling you: I found a tag. I copied the object to which the tag points. Then, as you instructed, I threw away the tag name, and just wrote the hash ID to the file FETCH_HEAD
. So we're all good, right?
If you didn't want git fetch
to throw the name away, you should have supplied a dst
part in your refspec:
git fetch origin refs/tags/release-2017-12-22T15_28_47-05_00:refs/tags/release-2017-12-22T15_28_47-05_00
for instance. (For tag names, it's normal to use the exact same name on both sides of the colon.) This tells Git that, having fetched a tag with the name release-2017-12-22T15_28_47-05_00
from the remote repository, it should write a tag named release-2017-12-22T15_28_47-05_00
into the local repository, pointing to same object (same Git hash ID).
This is where the force flag comes into effect. If that tag already exists on the local system, --force
tells Git to overwrite it, rather than producing an error. If the tag doesn't exist, --force
has no effect (and of course if the tag already exists with the correct value, re-writing it with the same value has no effect as well). So --force
is only useful if you supply some destination reference—a :dst
part—in your command-line refspecs.
(If you were fetching branch names, Git would apply the normal branch name update rules, which allow the write as long as the operation is a "fast-forward", but not if it's not. Here --force
still means "always allow the write", but a branch update is allowed even without --force
as long as it's a fast-forward. A tag update is not allowed without --force
, except for a bug in Git versions 1.8.1 and earlier, which apply the branch rules by mistake.)
The fix is clear enough: the script should have the git fetch
line changed to read:
git fetch origin "+refs/tags/${CIRCLE_TAG}:refs/tags/${CIRCLE_TAG}"
so that Git is forced to create or update the tag name in the local repository. (Note, I used the shorter/simpler +
-means-force
option here, which is not required, it's just the style I like.) Or, alternatively, the script could use the git fetch
that writes no local name, as it does now, then fish the correct hash ID out of the FETCH_HEAD
file, a la git pull
. But that's a bigger change to the script, and means that there is no permanent name for the target commit, which probably has additional drawbacks.
You could give all this analysis to the CircleCI folks, who might argue that the Git bug itself should be fixed too (which it probably should), but given that there are buggy Gits all over the world, and that the meaning of a refspec without a local name is pretty well defined, it would be simpler and more reliable to change the script to repeat the tag on both sides of the refspec.