57

I had hoped this would work:

git checkout remote/tag_name

but it doesn't. This does:

git checkout tags/tag_name

but I'm doing something weird where I have a lot of remotes, and I'm worried about what happens if two remotes have the same tag. Is there a way to specify the remote when checking out the tag?

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Narfanator
  • 5,595
  • 3
  • 39
  • 71

4 Answers4

99

Executive summary: what you want to achieve is possible, but first you must invent remote tags.

You do this with a series of refspecs, one for each remote. The rest of this is about what these are, how they work, and so on.


Your question asks about checking out a "remote tag", but Git does not have remote tags, and this:

but I'm doing something weird where I have a lot of remotes, and I'm worried about what happens if two remotes have the same tag. Is there a way to specify the remote when checking out the tag?

reveals (I think) the source of your confusion.

Let's back up for a moment and just talk about what Git has in a general sense, which are "references". To help cement the idea, specific forms of references include your local branch names (master, devel, feature, and so on), "remote branch names" like origin/master and stuff_from_bobs_computer/master, and tag names. Things like Git's "stash" also use references, and even HEAD is a reference, though it's a very special one, and usually a "symbolic" reference. The point here is that Git has lots of forms of references, and they all really work the same way in the end: a reference name resolves, in the end, to one of those big SHA-1 values, 676699a0e0cdfd97521f3524c763222f1c30a094 or whatever.

Most references—the exceptions are things like HEAD, ORIG_HEAD, MERGE_HEAD, and a few others along those lines—are actually spelled with names that start with refs/. These are kept in a sort of directory- or folder-like structure,1 with sub-directories: refs/tags/ contains your tags,2 refs/heads/ contains all your branches, and refs/remotes/ contains all your remote branches.

The remote branches are further subdivided by the name of the remote: refs/remotes/origin/ contains all the origin remote-branches, while refs/remotes/stuff_from_bobs_computer/ contains all the stuff_from_bobs_computer remote-branches. If you have a lot of remotes, you have a lot of sub-directories inside refs/remotes/.

I just mentioned that your tags are all in refs/tags/. What about the remotes' tags, all the tags on all the various remotes? Well, again, git doesn't have "remote tags". Git does have "remote branches", but those are in fact all local. They are stored in your repository, under the refs/remotes/ heading.

When your Git contacts a "remote"—usually through git fetch remote, but also for push (and the initial clone step, for that matter), your Git asks the remote Git3 the question: "What local branches do you have? What are their SHA-1 values?" This is, in fact, how fetch works: as a simplified description, the process of fetching consists of asking the remote Git "hey, whaddaya got?" and it gives you a set of names and SHA-1s. Your Git then checks to see if it has the same SHA-1s. If so, the conversation is done; if not, your Git then says "ok, I need whatever's in the commit(s) for these SHA-1s", which actually turns out to be another bunch of SHA-1s, and your Git and theirs talk it over to figure out which files and such you need as well, all identified by SHA-1s. Your Git brings over those objects, and stuffs the new SHA-1s into your refs/remotes/, under the name of the remote and then under their local branch names.

If you ask for tags with your fetch, your Git does a bit more.4 Instead of just asking their Git about their branches, your Git also asks theirs about their tags as well. Again, their Git just gives you a list of names and SHA-1s. Your Git then brings over any underlying objects needed, and then—here's the key to the whole problem—it writes their tag names into your refs/tags/.

So, what happens when you go over to remote origin and ask it for tags, and it says "I have refs/tags/pinky and refs/tags/brain", is that this creates, for you, local tags pinky and brain, also named refs/tags/pinky and refs/tags/brain in your reference name-space.

Now you go over to Bob's computer (the remote named stuff_from_bobs_computer above) and ask it for tags. He's into neurology, rather than Warner Brothers and Sister, and his tags are refs/tags/spinal_cord and refs/tags/brain, and the second one is probably not related to the one on origin. Uh oh!

Exactly what happens here gets a little bit complicated,5 but in short, this is just a bad situation and you should probably avoid it if possible. There are two easy (well...) ways to avoid it. One, with obvious drawback, is: just don't get their tags. Then you won't have any tag conflicts. The other is: keep all their tags separated from each other (and maybe from yours as well). It turns out that the second one is not really that difficult. You just have to "invent" remote tags.

Let's take a quick side look at how Git actually implements "remote branches", and how fetch --tags works. They both use the same basic mechanism, what git calls "refspecs".

In its simplest form a refspec just looks like two ref names with a colon between them: refs/heads/master:refs/heads/master, for instance. In fact, you can even leave out the refs/heads/ and Git will put it in for you,6 and sometimes you can leave out the colon and the repeated name as well. This is the kind of thing you use with git push: git push origin branch means to push to origin, using your refs/heads/branch, and call it refs/heads/branch when it arrives on "their" Git as well.

For fetch, though, doing remote branches, you get a refspec that looks like this:

+refs/heads/*:refs/remotes/origin/*

The + at the front means "force", and the *s do the obvious thing. Your Git talks to theirs and gets a list of refs. Those that match refs/heads/*, yours brings over (along with their repository objects as needed)—but then it sticks them in your repo under names staring with refs/remotes/origin/, and now you have all the "remote branches" from origin.7

When you run git fetch --tags, your git adds +refs/tags/*:refs/tags/* to the refspecs it uses.8 That brings their tags over and puts them in your local tags. So all you have to do is give fetch a refspec that looks like:

+refs/tags/*:refs/rtags/origin/*

and suddenly you'll have a whole new name-space of "remote tags" under refs/rtags/ (for origin only, in this case). It's safe to use the + force-flag here since you are just updating your copy of their tags: if they've force-moved (or deleted and re-created) a tag, you force-move your copy. You may also want or even need --no-tags behavior, which you can get by specifying --no-tags on the command line, or, well, see the next paragraph.

The only remaining handy item to know is that git fetch gets its default refspecs, for any given remote, from the Git config file.9 If you examine your Git config file, you'll see a fetch = line under each remote, using the +refs/heads/*:refs/remotes/remote-name/* string. You can have as many fetch = lines as you like per remote, so you can add one to bring over their tags, but put them in your newly-(re)invented "remote tags" name-space. You may also want to make --no-tags the default for this remote by setting tagOpt = --no-tags in this same section. See this comment by user200783 for details.

As with all Git commands that resolve a name to a raw SHA-1, you can then git checkout by full ref-name to get into "detached HEAD" mode on the corresponding SHA-1:

git checkout refs/rtag/stuff_from_bobs_computer/spinal_cord

Because Git does not have the idea of "remote tags" built in, you have to spell out the long form (see gitrevisions for details).


1In fact, it's a real directory, in .git/refs. However, there's also a "packed" form for refs, that wind up in .git/packed-refs. The packed form is meant to save time and effort with refs that don't change often (or at all, as is common with tags). There is also an ongoing effort to rewrite the "back end" storage system for references, so at some point a lot of this may change. This change is needed for Windows and Mac systems. Git believes that branch and tag names are case-sensitive: that you can have branch polish for your shoeshine material, and Polish for your sausages. The packed versions are case-sensitive, so this works; but the stored-in-files versions sometimes aren't, so it doesn't!

2I'm glossing over the difference between lightweight and annotated tags here. Annotated tags are actual objects in the repository, while lightweight tags are labels in the refs/tags/ space. However, in general, each annotated tag has one corresponding lightweight tag, so for this particular usage, they work out the same.

3It's almost always another Git repo, although there are now adapters for Git to Mercurial, svn, and so on. They have their own tricks for pretending to be Git repos. Also, this description is not meant to be definitive: the actual sequence of operations is coded for transfer efficiency, rather than for making-sense-to-humans.

4I've glossed over a bit of special weirdness about plain fetch and clone here, i.e., the versions without --tags. The versions with --tags are easy to explain: they bring over all tags using the refspecs I've described here—and, at least in Git 2.10 and 2.11, --tags also does forced-updates, as if the + force flag were set. But unless you explicitly call for --no-tags, a plain fetch (and clone) brings over some tags. The sneaky thing it does is to look for tags that correspond to objects that are coming in due to the fetch, and it adds those (without forcing updates) to your (global) tags name-space. Without --tags your Git won't overwrite your own existing tags; with --tags, your Git will overwrite your own existing tags, at least in Git 2.10, per actual experiments performed in early 2017.

5Older versions of Git applied "branch" rules to tags during push (but not necessarily fetch), allowing a tag update if it was a fast-forward, and otherwise requiring the force flag. Newer version of git push just require the force-tag. The fetch refspec from --tags does not have the force flag set, yet acts as though it does. I have not experimented with push with --tags. There's one more special git fetch weirdness about --tags vs --no-tags vs explicit refspecs, having to do with how --prune works. The documentation says that --prune applies to any explicit command-line refs/tags/ refspecs, but not to the implicit --tags refspec. I have not experimented to verify this, either.

6For your Git to fill in refs/heads/ or refs/tags/ for you, your Git has to be able to figure out which one you meant. There are some cases where it does, and some where it doesn't. If your Git fails to figure it out, you'll get an error message, and can try again with it filled in—but in scripts you should always fill it in explicitly, to get more-predictable behavior. If you are just running git push to push an existing branch, you can almost always let your Git figure it out.

7Leaving out the colon and the second name does not work so well for git fetch: it tells your Git not to update your own references at all! This seems senseless, but actually can be useful, because git fetch always writes the special file FETCH_HEAD. You can fish the Git object IDs (SHA-1s) out of the special file and see what got fetched. This is mostly a holdover from very early versions of Git, before remote-tracking branches were invented.

8The refspec that git fetch --tags and git push --tags uses is pre-compiled internally, in Git version 2.10, and handled by some special case code. The pre-compiled form does not have the + flag set; yet experimentation shows that fetched tags are force-updated in Git 2.10/2.11. I recall experimenting years ago with Git 1.x, and finding that these --tags-fetched tags were not force-updated, so I think this has changed, but that may be just faulty memory. In any case, if you are (re)inventing remote tags, you most likely do not want to use an explicit --tags.

9In fact, this is how mirrors work. For instance, with fetch = +*:* you get a pure fetch mirror. The fetch process can see all refs. You can see them yourself with git ls-remote. It's also how --single-branch works: if you use --single-branch during cloning, your Git config file will list only the one single branch in the fetch line. To convert from single-branch to all-branch, simply edit the line to contain the usual glob-pattern entry.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Awesome, and thanks! (Answer will be accepted when I can put this into practice and see it working - but excellent explanation!) – Narfanator Mar 03 '14 at 20:51
  • 3
    git fetch remote --tags +refs/tags/*:refs/rtags/remote/* – Narfanator Mar 05 '14 at 02:29
  • 3
    git checkout refs/rtag/remote/tag_name – Narfanator Mar 05 '14 at 02:30
  • 1
    You shouldn't need the `--tags` with an explicit refspec. And, you can add the refspecs to the `fetch =` lines for each remote, so that you don't have to supply it on the command line, and can just `git remote update --prune`, e.g. But it will work as shown. – torek Mar 05 '14 at 02:33
  • Thanks for pointing out the redundancy; specifying the fetch on the command line works better with my automation setup, but yeah, if I was doing this for manual use I'd set it in the file. – Narfanator Mar 10 '14 at 20:18
  • You say "When you run git fetch --tags, your git adds refs/heads/tags:refs/heads/tags to the refspecs it uses." Did you mean "git adds refs/tags:refs/tags" instead? – Tim Schaub Dec 18 '14 at 19:21
  • @TimSchaub: oh, yes, although it's `refs/tags/*` (with the actual asterisk). Will fix. – torek Dec 18 '14 at 19:24
  • 6
    I know i am not suppose to use the comments for this sort of thing and never do but I am making an exception. That was a wonderful answer, i learned more about git overall then any doc or blob post. Seriously thanks for putting in the time to write this @torek. – j_walker_dev Jan 17 '15 at 21:23
  • 1
    I appreciate this answer but it clearly shows weakness of GIT. Whatever simple task one would like to achieve with it, it is always like needing to be GIT Ph.D. A tool should minimize an effort not increase it. Even though I'm using GIT I'm so sad it became the most popular CVS. http://stevebennett.me/2012/02/24/10-things-i-hate-about-git/ – topr Feb 16 '15 at 18:17
  • @topr Er, what? The basic version control tasks in git are simple and don't require detailed knowledge, at all. Maybe you were taught it poorly? How would one achieve this in SVN? – Narfanator Apr 21 '15 at 18:44
  • @Narfanator: Yea, until you come across the 1st obstacle or even earlier. Nothing is basic with GIT and the worst is user interface (command line). It's inconsistency and clutter is terrible. It seems like somebody's revenge on developers. And by the way, I don't need to be learnt, I comprehend things myself. Now I'm pretty skilful and comfortable with GIT but it have taken me many painful hours of despair to get here. Not because I'm stupid or something. GIT is just unnecessary complex, that's it. SVN yes, much simpler but that's history. How about Mercurial though? – topr Apr 23 '15 at 14:57
  • Awesome explanantion. We all owe you a beer. – DeejUK Jun 16 '15 at 09:04
  • @torek - "You shouldn't need the `--tags` with an explicit refspec." - not only is `--tags` not needed in this situation (`git fetch remote --tags +refs/tags/*:refs/rtags/remote/*`), but it seems that `--no-tags` _is_ needed. Without `--no-tags`, the contents of "their" `refs/tags/` will end up in both "our" `refs/tags/` and `refs/rtags/remote/`. – user200783 Jan 22 '17 at 21:24
  • @user200783: I was just experimenting with `--tags` and `--no-tags` in Git 2.10 recently. I'm not sure if the behavior has changed, but now, even though `--tags` internally does not have the force bit set, it (now?) forcibly updates `refs/tags/*`. At some point I may have to build some older Git versions for testing, but yes, you probably want `--no-tags` here. – torek Jan 22 '17 at 21:33
  • @user200783: OK, I've updated the answer somewhat. I still haven't tried out the neither-`--tags`-nor-`--no-tags` behavior, though. Given the complicated special-case code in the Git source, I can believe it will act squirrelly by default. Besides this, you can also set tag update behavior per remote using yet another config entry, and this is probably best way to work around any fetch bugs with the default implied tag fetch. – torek Jan 22 '17 at 22:23
  • Thanks for the reply. I'm also interested to know: Would there be any disadvantage to putting the remote tags into `refs/tags//` instead of introducing a separate `rtags`? This would allow them to be accessed as `/` (i.e. without the `refs/rtags/` prefix), just as remote branches can be referred to as `/`. – user200783 Jan 25 '17 at 06:41
  • @user200783: well, `refs/tags/R/T` (where R and T are the remote and tag name respectively) is not obviously-guaranteed-unique: it requires that your "regular" tags never use `R/` as a prefix. Since tags use shared name spaces, you can only rely on self-discipline if you do this with *all* your remotes. In other words, it *can* work, it's just not automatic like it is with remote-tracking branch names. – torek Jan 25 '17 at 14:39
  • Awesome answer! Do you have any idea why git didn't use "remote tags" in the first place? It seems to me that most people would expect tags to work just like (mostly) immutable branches. I don't see any advantages with the approach they used: there is a risk of conflict, it is hard to diff tags between local/remotes, syncing the tags of a remote to another one without pushing tags created locally is very hard (while it is easy with branches), etc. – Luc Touraille Nov 17 '22 at 14:28
  • 1
    @LucTouraille: you'd have to ask Linus, but generally the idea appears to be that all Git clones of some repository *R* should have the *same* (annotated) tags, making tags as universal as OIDs. This is clearly not possible in general (in the presence of malice or accidents) so if you want a truly-universal name you're stuck with OIDs. – torek Nov 20 '22 at 10:21
86

1 - Fetch the tag from the remote with:

git fetch origin --tags 

Or, to checkout a tag from a different remote use:

git fetch your_remote --tags

2 Check out the tag by running

git checkout tags/<tag_name>

More here: Download a specific tag with Git

Community
  • 1
  • 1
Russell Fair
  • 981
  • 6
  • 6
  • thx this did help me when I want the tags from `origin` remote I need todo `git fetch origin --tags` when I want the tags from `upstream` remote I need todo `git fetch upstream --tags` before do e.g.: `git checkout 1.0.0` – Alexander Schranz Feb 23 '16 at 12:50
  • 1
    What will happen if there are two tags with the same name, one in remote1 and one in remote2? – Benyamin Jafari Jan 10 '21 at 07:45
9

In my case when a new tag was added to the remote repository [I'm using Stash], the new tag wasn't available in the result of git tag -l.
But I was able to view the newly added tag using git ls-remote --tags.
I had to run the following command to get all the latest tags to my local repository:
git pull --tags Running git tag -l now displayed the newly added tags as well.

In order to checkout a tag, use:
git checkout <tag_name>

Note: It is only normal to run git status and find a message like this:
HEAD detached at tag_name

hipsandy
  • 982
  • 8
  • 7
0

There are some questions on my mind:

  • Why should different remotes have different code (in the same tree)?
  • Why does the remote code affect you checking out tags?

The thing is the following:

When you check out a tag using git checkout tags/fancytag it will look in your current repository (on your machine) for the fitting tag.

If you want to checkout a tag from a specific remote you have to fetch it (the tree of the specific remote) first and then check it out.

Langusten Gustel
  • 10,917
  • 9
  • 46
  • 59
  • I am abusing git for the purposes of a build server, which is why I may have remotes from entirely different code trees (although that's unlikely - more likely they're each forks of the central repo). So, I guess the real question is, what happens if I have two remotes with the same tags, and I fetch from one then the other? – Narfanator Mar 01 '14 at 01:44
  • @jan1337z You can just add new remote with address of totally different repo. Moreover you can even merge their branches! – František Hartman Mar 01 '14 at 18:02
  • @frant.hartm yes sure - sorry you are (possibly - don't know) right but the current tree stays the same (tags, commits) and the code is just merged? so the answer to tag question still correct, doesn't it? – Langusten Gustel Mar 01 '14 at 19:22
  • BTW if you *do* fetch from a lot of unrelated repos (so that you get many independent commit-graphs in one repo), you can get "repository bloat", which eventually leads to bad performance. Git needs some tweaking for what happens with a large number of packs, and packs that contain very large (multi-gigabyte) objects. This does not seem to be high priority for git coders though, for some reason :-) – torek Mar 01 '14 at 21:52
  • I've built it broad because it's simpler what way, but the actual expected use case is a bunch of forks of the same codebase. – Narfanator Mar 03 '14 at 20:54