I love Git. I was very much a TFS purist up until the last year or so, but after using Git for a while now, I’m hooked. Whilst TFS certainly isn’t bad, I love the flexibility and the fact I can muck about to my hearts content in a DVCS without affecting anyone (unless I choose to). That doesn’t mean I love everything though, one of my biggest headaches recently has been tags.
Tags in Git are simply textual markers against specific commits, very much like branches (although tags don’t move). Generally they’re used to mark points in your commit history (i.e. versions etc), but they can – being just text – be used to mark anything really.
So what’s the problem? Well, the issue isn’t with tags themselves, it’s more with the fact they’re very, very annoying to kill. I purposefully say kill and not delete, because deleting a tag is actually very simple, it’s making sure that tag stays deleted/dead that’s the problem. Once a tag has been pushed to a remote, it is automatically retrieved during any subsequent pulls, at this point the tag exists in the user’s local repository until they choose to delete it. But it also means that said user can also then re-push a single tag – or even worse, all their local tags – to a remote when doing a push.
To illustrate this, consider the following example:
- Bob creates the tag ‘foo’ against a commit and pushes this to a remote
- Fred pulls from the remote to update his local copy of a branch he’s working on with Jane
- Jane does the same as Fred, meaning both Fred and Jane now have Bob’s tag ‘foo’ in their local repos
- Bob realises that ‘foo’ is a terrible name for a tag and wants to delete it, so he deletes the tag locally and pushes the delete to the remote
- Fred has written some more code he wants to share with Jane, so – using his handy Atlassian SourceTree app – he pushes his changes. But; Fred doesn’t 100% understand what he’s doing, so he checks the “Push all tags” checkbox when doing his push, thus re-pushing the tag which Bob just deleted!
- Bob does a further pull later that day, and lo and behold, his poorly named tag ‘foo’ has reappeared in his local repository!
- Jane pulls again and also pulls down the once deleted tag, much to Bob’s annoyance
Alright, so the answer here is probably to give Fred some guidance about how to use Git, but it does illustrate the real life problem I had recently, the only difference was that in my situation there were lots more developers involved.
So how do you solve this? Well, the Git documentation states that – by default – tags are not pushed unless you specify otherwise (using the “tags” switch). So actually, Git does a pretty good job of preventing people from automatically sharing all their tags, but tools like SourceTree, which provide a seductive “Push all tags” checkbox when pushing – are clearly too tempting for developers to pass up:
Another option – and probably a better one than just relying on your developers to not push their tags – is to implement a pre-receive server side hook to check whether tags are being pushed and reject the push it the user doesn’t have permission. Unfortunately, if you’re using a hosted Git repository like GitHub or Bitbucket, you’re out of luck as they don’t currently provide the ability for you to edit hooks and unfortunately don’t allow you to apply permissions to tags. This is a real shame as the functionality is already there to control access to branches.
Of course, the final option is just to accept that tags are not really meant to be deleted after they’re pushed, which is all well and good if you and your developers understand this from the start, not so good if not!