Under the sink: Removing remote git tags
Tags: git
Published: 2017-10-10 14:02:28

---
title: "Under the sink: Removing remote git tags"
published: true
tags: git
canonical_url: https://medium.com/@poosanth/under-the-sink-removing-remote-git-tags-3d5644d67ea0
---

TLDR; git push [remote-name]&nbsp;:refs/tags/[tag-name]

Job well doneâ€Š–â€Šmove on. Butt wait, if you’re like me, you’re probably wondering: “wait what, what the hell is going on here?” So go ahead and picture a plumbers butt crack â€˜cuz we goin’ to take a peek under the git&nbsp;sink.

![](https://cdn-images-1.medium.com/max/330/1*0o9GZUzXiNnI4poEvxvy8g.png)<figcaption><a href="https://xkcd.com/1597/"><em>xkcd</em></a></figcaption>

Maybe not popular opinion, but one of my favorite parts about git is its commands. I think they’re pretty great, it’s one of the main reasons I us the terminal. However, I don’t stray too far away from the common git commands, which for me are the following:

git [push]|[pull]|[checkout]|[rebase]|[diff]|[log]

All (relatively) easy to understand and use. These are referred to as [porcelain commands](https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain)â€Š–â€Šthey hide away the plumbing. The plumbing being all the parts tucked away that make git work and do the things behind the scenes we take for grantedâ€Š–â€Šsorta like the plumbing in your&nbsp;home.

So, when I needed to remove a tag that I had already pushed up to my remote, a task I’d done several times in the past,â€Š–â€ŠI’d like to say I just remembered how to do it or figured it out on my own. I didn’t. I google it, in fact, I even remember googling it several times before. It’s just something about that command that didn’t sink in with me, probably because I never took the time to figure out _why_ it worked. So, lets dive&nbsp;in.

> git push [remote-name]&nbsp;:refs/tags/[tag-name]

Looks complex; lets break this down (lc;lbtd).

- git
- push
- [remote-name] e.g&nbsp;_origin_

Okay.. so far so&nbsp;good…

- :refs/tags/[tag-name] e.g&nbsp;:refs/tags/1.10.1

Whatâ€Š–â€Š_the_â€Š–â€Šf%\*#K? Okay I think I’ve identified where I need to&nbsp;focus.

> :refs/tags/[tag-name]

Lets break this down&nbsp;further.

- :
- refs/tags/[tag-name]

Being slightly familiar with the internal file structure of the&nbsp;.git folder, which is in every repo, I can tell the second part looks like a path to the.git/refs folder. I also know the refs folder is where all the _git references_ are stored. After reviewing the man pages for push (git help push), focusing on the options push accepts, I found out the colon is part of the _refspec_ format. Meaning the entire part,&nbsp;:refs/tag/[tag-name], is a _refspec_. Before we discuss _refspecs_, lets go over what _git references_ are.

#### Git references

As mentioned earlier, git references are stored in the /.git/refs folder. Here you will find several sub-folders, which git uses to group the references. But what are&nbsp;they?

```
$_: ls ./.git/refs/
heads remotes tags
```

References in git, or “refs” as its commonly referred to in the documentation, refer to raw SHA-1 values. Branch names, **tags** , they are all just human friendly aliases for the full 40-character SHA-1 checksum (+header) hash. I won’t be going over what a content-addressable filesystem is or how that works, but know, git is one, and these hashes are how git is able to _reference_ our data within it. Think of it as a key-value store, the ref being the&nbsp;key.

Git creates and updates these aliases all the time for us while we use it. Imagine having to type out the entire 40-character hash when you wanted to checkout or pull a branch? If you cat out a ref you will see&nbsp;this:

```
$_: cat ./.git/refs/heads/master 
8d519a1066b4d05e010e0a6435401f0ea6fdca71
```

These folders also exist on the remote server. In fact if you ever want to backup your git repo, like really lo-fi, copy the.git&nbsp;folder.

#### The Refspec

A _refspec_ is a format to specify the source and destination of, wait for it, _references!_ Git automatically sets the refspec for each remote you add. If you are curious about what your refspecs are set to, take a look at this file&nbsp;./.git/config in your&nbsp;repo.

```
$_: cat ./.git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = git@github.com:vprasanth/dotfiles.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
```
> Format: +\<src\>:\<dst\>

It’s important to understand that depending on if you’re pushing or pulling, the source and destination flip between local and remote. When you are pushing, the src is _local_ and destination is _remote_, vice-versa if you are&nbsp;pulling.

Another important thing to know is, git expands the reference to its fully qualified name, it does this by using the destination set in the config for a remote (see how branch remote is set to “origin” in the above snippet). In fact git always makes this expansion. Ever try to push local branch that didn’t exist on your remote?&nbsp;:)

```
$_: git log -n 1 --oneline origin/master 
8d519a1 Remove vundle configs and set tab to 2
$_: git log -n 1 --oneline remotes/origin/master
8d519a1 Remove vundle configs and set tab to 2
$_: git log -n 1 --oneline refs/remotes/origin/master
8d519a1 Remove vundle configs and set tab to 2
```

Yeah.

Oh and one more thing about the refspecs, pushing an empty \<src\> tells git to set the \<dst\> on the remote to nothing, which **_deletes_&nbsp;** it.

#### So… git push origin&nbsp;:refs/tags/1.10.1

All the magic is in that refspec. Since pushing normally expands the refspec for you, which tries to match whatever is appropriate to what your current HEADis set to, here we are manually setting it to say: **_push nothing to the tag reference 1.10.1 at origin please._** Which we now know deletes the tag on the&nbsp;remote.

Yeah I was definitely taking things for granted by not looking in to _refspecs_. I’m glad I did. The funny thing is, understanding whats going on here also explains some other errors that didn’t make sense to be before when I was trying to push or pull things. Nexy time I hit those, maybe I’ll write something up. I hope this also helps&nbsp;you.

#### Further reading

- [Git Internals chapter on git-scm](https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain). Covers everything I mention and then some! Overall great resource.

#### Note

I didn’t cover what the + means in the _refspec_ format. Its optional first of all, and tells git to update the reference even if it isn’t a _fast-forward_. I don’t want to go in to what that means, perhaps a another post for another&nbsp;day.