r123454321
r123454321

Reputation: 3403

Fix git tagging?

I did the following just now:

// make change
// git add .
// git commit -m "2.0.0"
// git tag -a v2.0.0 <sha-of-above-commit>
// git push --tags

However, I forgot to push the actual commit, and now my tag isn't showing up (presumably because I didn't actually push the commit that I tagged.)

I then re-cloned the repo and ran git tag, and saw my tag there.

However, my commit wasn't listed under git log. So I remade my change, and ran git push. This ran successfully. However, git push --tags failed, as it said the tag already existed on the remote.

Any solution to this? I tried to delete the remote v2.0.0 tag, but got a (hook declined) error.

Upvotes: 2

Views: 1992

Answers (1)

torek
torek

Reputation: 489898

TL;DR: git push origin master

ElpieKay's comment has the correct answer: your git push pushed the commit and the tag, but no branch in the other (remote) Git repository contains the commit. That is, in terms of your own Git commit graph, you have—here I'm going to guess that the branch in question is master— this:

...--o     <-- origin/master
      \
       o   <-- master, tag:v2.0.0

You then sent, to the Git on origin, the commit itself (as required in order to send the tag v2.0.0), along with a request for it to create a tag named v2.0.0 pointing to the new commit—which it did, giving it the graph:

...--o     <-- master
      \
       o   <-- tag:v2.0.0

Anyone who now clones the repository from the other Git, including yourself, will see the tag in git tag output, but will not see the commit on branch master, because it's not on branch master. The only link to the new commit is the tag.

The solution is simple: do a git push to have your Git ask their (origin's) Git to set their branch master to point to the new commit. For instance, if the commit's hash ID is 1234567, this would do the trick:

git push origin 1234567:master

even from your new clone. Or, since the tag v2.0.0 resolves to the correct commit hash:1

git push origin v2.0.0^{commit}:master

The simplest version of this simple fix, though, is to do the push operation from your original development repository, where your master branch points to the same commit as your v2.0.0 tag. In this case you don't need to specify both halves of the refspec argument:

git push origin master

(this does, of course, assume that master on origin has not grown new commits since this point).


1I say "resolves to" and then introduce this funky v2.0.0:^{commit} syntax. What's that all about?

Since v2.0.0 is an annotated tag (made with git tag -a), the name itself actually points to a tag object in the repository. It's the tag object that points to the commit. When I drew the commit graph, I drew the commit graph, not the commit-and-tag graph. :-) So I left this little bit out.

When we go to git push, though, it's probably wise to make sure we ask their Git to set their branch master to point directly to the commit, not to a tag. Branch names are not allowed to point to tags. Perhaps the request will work—perhaps Git will know "branch names are not allowed to point to tags" and will do the appropriate magic to convert the tag object to its target commit-ID—but perhaps not. We can avoid the question entirely by telling our Git to turn any tag name into its target commit.

That is what this funky syntax does. As described in the gitrevisions documentation, name:^{type} tells Git first to resolve the name, then to "peel" that name as needed to find the Git object of the given type.

When you give Git a branch name, the name is guaranteed to resolve to a commit object, because branch names must point to commit objects. Tag names, on the other hand, may point to any Git object at all—commit, tag, tree, or blob. Normally they point only to tag objects or commit objects, but Git won't stop you from making one that points to a blob or tree. I've done this for various experimental purposes in my own repositories. So for a tag, you can use ^{commit} to force it to resolve to a commit. If the name points to an annotated tag object, this follows that tag to its commit. If the name already points to a commit, it does nothing. If the name somehow points to a tree or blob, it produces an error.

You can also use :^{tree} and :^{blob} to verify that a name points to a tree or blob object. Of course if the name is a branch name, it doesn't, but the name part can include a path as well: for instance, master:Documentation names a tree object in branch master in a Git repository for Git itself, and if you were writing a script and needed to be really sure you were getting the hash ID of a tree, you could write:

treehash=$(git rev-parse master:Documentation^{tree}) || die ...

in your script.

Upvotes: 2

Related Questions