TLDR; git push [remote-name] :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 sink. 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 — 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 home. porcelain commands 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 it worked. So, lets dive in. why git push [remote-name] :refs/tags/[tag-name] Looks complex; lets break this down (lc;lbtd). git push [remote-name] e.g origin Okay.. so far so good… :refs/tags/[tag-name] e.g :refs/tags/1.10.1 What — — f%*#K? Okay I think I’ve identified where I need to focus. the :refs/tags/[tag-name] Lets break this down further. : refs/tags/[tag-name] Being slightly familiar with the internal file structure of the folder, which is in every repo, I can tell the second part looks like a path to the folder. I also know the folder is where all the are stored. After reviewing the man pages for ( ), focusing on the options accepts, I found out the colon is part of the format. Meaning the entire part, , is a . Before we discuss , lets go over what are. .git .git/refs refs git references push git help push push refspec :refs/tag/[tag-name] refspec refspecs git references Git references As mentioned earlier, git references are stored in the folder. Here you will find several sub-folders, which git uses to group the references. But what are they? /.git/refs $_: 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, , 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 our data within it. Think of it as a key-value store, the ref being the key. tags reference 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 out a ref you will see this: cat $_: cat ./.git/refs/heads/master8d519a1066b4d05e010e0a6435401f0ea6fdca71 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 folder. .git The Refspec A is a format to specify the source and destination of, wait for it, 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 in your repo. refspec references! ./.git/config $_: cat ./.git/config[core]repositoryformatversion = 0filemode = truebare = falselogallrefupdates = true[remote "origin"]url = :vprasanth/dotfiles.gitfetch = +refs/heads/*:refs/remotes/origin/*[branch "master"]remote = originmerge = refs/heads/master git@github.com 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 and destination is , vice-versa if you are pulling. local remote 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? :) $_: git log -n 1 --oneline origin/master8d519a1 Remove vundle configs and set tab to 2$_: git log -n 1 --oneline remotes/origin/master8d519a1 Remove vundle configs and set tab to 2$_: git log -n 1 --oneline refs/remotes/origin/master8d519a1 Remove vundle configs and set tab to 2 Yeah. Oh and one more thing about the refspecs, pushing an empty tells to set the on the remote to nothing, which it. <src> git <dst> deletes So… git push origin :refs/tags/1.10.1 All the magic is in that . Since pushing normally expands the refspec for you, which tries to match whatever is appropriate to what your current is set to, here we are manually setting it to say: Which we now know deletes the tag on the remote. refspec HEAD push nothing to the tag reference 1.10.1 at origin please. Yeah I was definitely taking things for granted by not looking in to . 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 you. refspecs Further reading . Covers everything I mention and then some! Overall great resource. Git Internals chapter on git-scm Note I didn’t cover what the means in the format. Its optional first of all, and tells git to update the reference even if it isn’t a . I don’t want to go in to what that means, perhaps a another post for another day. + refspec fast-forward