535

We use tags in git as part of our deployment process. From time to time, we want to clean up these tags by removing them from our remote repository.

This is pretty straightforward. One user deletes the local tag and the remote tag in one set of commands. We have a little shell script that combines both steps.

The 2nd (3rd, 4th,...) user now has local tags that are no longer reflected on the remote.

I am looking for a command similar to git remote prune origin which cleans up locally tracking branches for which the remote branch has been deleted.

Alternatively, a simple command to list remote tags could be used to compare to the local tags returned via git tag -l.

Stevoisiak
  • 16,510
  • 19
  • 94
  • 173
kEND
  • 6,149
  • 3
  • 16
  • 13
  • 3
    I proposed a new feature in git to support pruning stale tags: http://thread.gmane.org/gmane.comp.version-control.git/168833 – Adam Monsen Mar 11 '11 at 15:44
  • 3
    Note: with Git 2.17 (Q2 2018), a simple `git config fetch.pruneTags true` will make your `git fetch` do what you want! See [my answer to this other question](https://stackoverflow.com/a/49215190/6309). – VonC Mar 10 '18 at 23:48
  • 4
    Reposting a comment from one of the answers below: At least with git 2.18.0 one can also use this syntax: git fetch --prune --prune-tags origin – zutnop Aug 21 '18 at 12:36
  • thanks @zutnop for your comment. I would have almost missed the correct answer for today's versions of git. – gelonida Feb 17 '21 at 12:50

16 Answers16

1186

This is great question, I'd been wondering the same thing.

I didn't want to write a script so sought a different solution. The key is discovering that you can delete a tag locally, then use git fetch to "get it back" from the remote server. If the tag doesn't exist on the remote, then it will remain deleted.

Thus you need to type two lines in order:

git tag -l | xargs git tag -d
git fetch --tags

These:

  1. Delete all tags from the local repo. FWIW, xargs places each tag output by "tag -l" onto the command line for "tag -d". Without this, git won't delete anything because it doesn't read stdin (silly git).

  2. Fetch all active tags from the remote repo.

This even works a treat on Windows.

Torsten Bronger
  • 6,770
  • 7
  • 28
  • 39
Richard W
  • 11,923
  • 2
  • 13
  • 3
  • 65
    This must be my favourite git answer on StackOverflow. It combines knowledge, simplicity and trickery an explains everything. Great – tymtam Nov 30 '11 at 08:12
  • 27
    as noted in a separate answer, this deletes ALL local tags, and ones not in the remote repo obviously won't be re-created – second Aug 06 '12 at 11:03
  • 2
    Does the first command require an `xargs` extension or something? I get this when executing that command in windows cmd: `'xargs' is not recognized as an internal or external command, operable program or batch file.` – Web_Designer Jun 02 '14 at 18:39
  • 17
    FWIW this should be completely unnecessary. There should be a `git tag prune origin` command. – void.pointer Jun 05 '14 at 14:41
  • 2
    @Web_Designer: I assume you are working with Windows here. You need to use the real git-bash or have the git installation directory with the xargs executable in your path. – fmuecke Jul 02 '14 at 09:16
  • 9
    This might not work for everybody. You should do git fetch --tags to be on the safe side. – Adam Kurkiewicz Jul 13 '15 at 17:12
  • Brilliant! The accepted answer did not work for me on mac but this one did. Pity I have run out of votes but I will sure come back tomorrow to pay my gratitude. – NSNoob Nov 18 '15 at 14:40
  • how come this is not marked as the correct answer? It did exactly what we were trying to do: remove local tags that are no longer on the remote repository. – R.D. Apr 01 '16 at 18:16
  • 8
    I had to go `git tag -l | %{git tag -d $_}` to get this working in PowerShell. Not sure about anyone else. – Alain Dec 20 '16 at 20:27
  • For pure batch: `for /F %%i in ('call git tag -l') do git tag -d %%i` – Munchkin Feb 14 '17 at 22:37
  • One word of warning on this-- we have a very slow pipe to our remote repo, and this caused a huge data download on the `git fetch --tags`: took over 30 minutes, and I think redownloaded the entire repo. Be advised, YMMV. – Robert N Mar 17 '17 at 00:38
  • git for-each-ref --format='delete %(refname)' refs/tags | git update-ref --stdin – G. Sylvie Davies Aug 05 '17 at 22:49
  • Chad Juliano's answer (but with double quotes added) really is the best answer. It's a single command, no pipes, no bash, no xargs, pure git. And here it is: git fetch --prune origin "+refs/tags/*:refs/tags/*" – G. Sylvie Davies Aug 05 '17 at 23:11
  • or `git tag -d $(git tag -l)` – kyb Aug 17 '19 at 11:04
  • 1
    For powershell, you can also use: git tag -l | ForEach-Object {git tag -d $_} – Pascal Aug 21 '19 at 18:22
  • The answer is about tags, not about *branches* what are actually originally asked in the question. – Jarek C Nov 11 '19 at 20:24
  • How do I favourite something on StackOverflow? You deserve a medal of honour mate. – Brandon Stillitano Jan 01 '20 at 23:24
261

From Git v1.7.8 to v1.8.5.6, you can use this:

git fetch <remote> --prune --tags

Update

This doesn't work on newer versions of git (starting with v1.9.0) because of commit e66ef7ae6f31f2. I don't really want to delete it though since it did work for some people.

As suggested by "Chad Juliano", with all Git version since v1.7.8, you can use the following command:

git fetch --prune <remote> +refs/tags/*:refs/tags/*

You may need to enclose the tags part with quotes (on Windows for example) to avoid wildcard expansion:

git fetch --prune <remote> "+refs/tags/*:refs/tags/*"
G. Sylvie Davies
  • 3,849
  • 2
  • 16
  • 28
loganfsmyth
  • 135,356
  • 25
  • 296
  • 231
  • 1
    This is explicitly documented as not doing what you say it would in git documentation v1.9.4. It would seem like a very good interface to do it though.. Can you clarify "recent version" – Félix Cantournet Jul 25 '14 at 07:27
  • @FélixCantournet Huh, could you send me a link to the docs you are talking about? I can't seem to find what you are referring to. – loganfsmyth Jul 25 '14 at 15:30
  • 2
    I refer to the documentation that is packaged with Git for Windows 1.9.4-preview20140611 (and i suspect all previous versions). I access said documentation with "git fetch --help" [quote]Tags are not subject to pruning if they are fetched only because of the default tag auto-following or due to a --tags option.[/quote] – Félix Cantournet Jul 29 '14 at 08:17
  • https://github.com/git/git/blob/master/Documentation/fetch-options.txt Look at --prune. It appears to be still documented as I say in version 2.1rc0. (at least if the github.com/Git repository is the "official" one, which I suppose it is. – Félix Cantournet Jul 29 '14 at 08:24
  • It doesn't do what the OP needs (tested with git v2.1.1). – ocroquette Oct 20 '14 at 09:07
  • 2
    `git fetch --prune +refs/tags/*:refs/tags/*` didn't work in ZSH however it works in BASH – Alex Aug 12 '15 at 10:35
  • 1
    On Windows you also don't need `'` quotes, so `git fetch --prune origin +refs/tags/*:refs/tags/*` works fine. – Mariusz Pawelski Apr 25 '16 at 10:41
  • 3
    @Alex That's just because zsh expands `*` but if you single quote that it should be fine. – NSF Jun 20 '16 at 04:30
  • On Windows, this worked for me with double quotes but NOT with single quotes. So, this worked: `git fetch --prune origin "+refs/tags/*:refs/tags/*"` – twasbrillig Dec 19 '16 at 17:04
  • git --prune --tags stopped working in precisely version v1.9.0 because of commit e66ef7ae6f31. The Chad Juliano approach works as far back as v1.7.8, but no farther back (I tested v1.7.7 --- no luck!). – G. Sylvie Davies Aug 06 '17 at 02:52
  • This seems like the most git way to do it from all answers. This would deserve a shortcut! – v01pe Jul 21 '18 at 13:32
  • 9
    @v01pe - there is now a git shortcut --prune-tags available since git 2.17.0 described in the documentation under the PRUNING section: https://git-scm.com/docs/git-fetch/2.17.0 From the document: The --prune-tags option is equivalent to having refs/tags/*:refs/tags/* declared in the refspecs of the remote. Equivalents: `git fetch origin --prune --prune-tags` OR `git fetch origin --prune 'refs/tags/*:refs/tags/*'` OR `git fetch --prune --prune-tags` OR `git fetch --prune 'refs/tags/*:refs/tags/*'` – mkisaacs Sep 20 '18 at 20:21
  • 5
    `git fetch origin --prune --prune-tags` prune both remote tracking branches and tags. checked in git 2.18 version. – Number945 Sep 27 '18 at 04:02
176

If you only want those tags which exist on the remote, simply delete all your local tags:

$ git tag -d $(git tag)

And then fetch all the remote tags:

$ git fetch --tags
newmangt
  • 2,031
  • 1
  • 11
  • 8
  • 1
    flawless, I was having problem with the xargs one it doesn't find some tags – Marcio Toshio Jun 26 '15 at 12:23
  • 3
    @ocroquette, I'm not sure how it's nicer than `xargs`. If you have more tags than `ARG_MAX`, or similar limitations, this won't work. Unlikely, but possible, and that's why `xargs` is great. – Paul Draper Aug 25 '15 at 15:08
  • 2
    "nice" is a subjective thing, everyone will make his/her own opinion. About ARG_MAX, that's true. However, on the systems I use, ARG_MAX is much higher than the number of tags I have in any repository, so I don't mind the limitation, just as I don't mind about it when I write "ls *.jpg". – ocroquette Aug 26 '15 at 16:02
  • 2
    cleanest solution – mitsest May 04 '18 at 16:51
  • 3
    `git config --global alias.prune-tags '!git tag -d $(git tag) && git fetch --tags'` Obligatory alias command. Enjoy. :-) – Karl Wilbur Aug 22 '18 at 21:33
  • 1
    Thank you, there are so many convoluted answers to a simple question – Shardj Oct 17 '18 at 08:15
  • 1
    upvoted both as these worked for me on my multiple repos having more than 150 tags !! – whoami - fakeFaceTrueSoul Apr 16 '19 at 21:01
128

Looks like recentish versions of Git (I'm on git v2.20) allow one to simply say

git fetch --prune --prune-tags

Much cleaner!

https://git-scm.com/docs/git-fetch#_pruning

You can also configure git to always prune tags when fetching:

git config fetch.pruneTags true

If you only want to prune tags when fetching from a specific remote, you can use the remote.<remote>.pruneTags option. For example, to always prune tags when fetching from origin but not other remotes,

git config remote.origin.pruneTags true
Nicholas Carey
  • 60,260
  • 12
  • 84
  • 126
84

All versions of Git since v1.7.8 understand git fetch with a refspec, whereas since v1.9.0 the --tags option overrides the --prune option. For a general purpose solution, try this:

$ git --version
git version 2.1.3

$ git fetch --prune origin "+refs/tags/*:refs/tags/*"
From ssh://xxx
 x [deleted]         (none)     -> rel_test

For further reading on how the "--tags" with "--prune" behavior changed in Git v1.9.0, see: https://github.com/git/git/commit/e66ef7ae6f31f246dead62f574cc2acb75fd001c

G. Sylvie Davies
  • 3,849
  • 2
  • 16
  • 28
Chad Juliano
  • 1,117
  • 9
  • 3
  • 7
    This should be the top answer. It's a single git command, with no bash, no pipes, and no xargs. – G. Sylvie Davies Aug 05 '17 at 23:13
  • 1
    Replaced `origin` with `upstream` and git corrected my local tags based on the upstream; next `git push origin :` updated my GitHub fork, removing the deleted tag. – leanne Sep 22 '17 at 21:17
  • 3
    At least with git 2.18.0 one can also use this syntax: `git fetch --prune --prune-tags origin` – Martin Aug 07 '18 at 14:28
  • 3
    Starting with git 2.17.0 - the --prune-tags option was included and described in the PRUNING section in detail with equivalent commands in the following document: https://git-scm.com/docs/git-fetch/2.17.0 `git fetch origin --prune --prune-tags` OR `git fetch origin --prune 'refs/tags/*:refs/tags/*' ` OR `git fetch --prune --prune-tags ` OR `git fetch --prune 'refs/tags/*:refs/tags/*' ` – mkisaacs Sep 20 '18 at 20:14
83

Good question. :) I don't have a complete answer...

That said, you can get a list of remote tags via git ls-remote. To list the tags in the repository referenced by origin, you'd run:

git ls-remote --tags origin

That returns a list of hashes and friendly tag names, like:

94bf6de8315d9a7b22385e86e1f5add9183bcb3c        refs/tags/v0.1.3
cc047da6604bdd9a0e5ecbba3375ba6f09eed09d        refs/tags/v0.1.4
...
2f2e45bedf67dedb8d1dc0d02612345ee5c893f2        refs/tags/v0.5.4

You could certainly put together a bash script to compare the tags generated by this list with the tags you have locally. Take a look at git show-ref --tags, which generates the tag names in the same form as git ls-remote).


As an aside, git show-ref has an option that does the opposite of what you'd like. The following command would list all the tags on the remote branch that you don't have locally:

git ls-remote --tags origin | git show-ref --tags --exclude-existing
Mike West
  • 4,903
  • 22
  • 23
  • Thanks Mike. I'll roll my own bash script using each list for comparison. – kEND Dec 04 '09 at 16:33
  • 12
    Richard W's answer does this much more elegantly, in case you are not interested in a complicated script. – Kyle Heironimus Feb 11 '13 at 19:28
  • 1
    The side note about tags not present locally can be expanded to check more remotes: `git remote | xargs -L 1 git ls-remote --tags | git show-ref --tags --exclude-existing` – Palec Jun 23 '14 at 14:26
  • 1
    git supports --prune-tags. Uncertain what version this was introduced. https://git-scm.com/docs/git-fetch#git-fetch---prune-tags – John Kloian Oct 01 '18 at 19:29
11

Git natively supports cleanup of local tags

git fetch --tags --prune-tags

This command pulls in the latest tags and removes all deleted tags

David Ongaro
  • 2,899
  • 1
  • 19
  • 34
Nirav Shah
  • 355
  • 2
  • 6
  • It seems it should be "--prune" instead of "--prune-tags", otherwise that's what I needed, thanks. – AnyDev Aug 17 '18 at 07:39
  • I have getting issue in source tree failed to push some refs to... : It works for me :) Thanks Alot – Abhishek Thapliyal Aug 22 '18 at 07:21
  • @AnyDev: `--prune-tags` is correct. The man page says about `--prune`: `Tags are not subject to pruning if they are fetched only because of the default tag auto-following or due to a --tags option.` – David Ongaro May 13 '21 at 13:26
11

I know I'm late to the party, but now there's a quick answer to this:

git fetch --prune --prune-tags # or just git fetch -p -P

Yes, it's now an option to fetch.

If you don't want to fetch, and just prune:

git remote prune origin
joker
  • 2,626
  • 2
  • 30
  • 32
6

this is a good method:

git tag -l | xargs git tag -d && git fetch -t

Source: demisx.GitHub.io

kb1000
  • 318
  • 1
  • 10
imjoseangel
  • 2,493
  • 2
  • 16
  • 27
6

In new git version(like v2.26.2)

-P, --prune-tags Before fetching, remove any local tags that no longer exist on the remote if --prune is enabled. This option should be used more carefully, unlike --prune it will remove any local references (local tags) that have been created. This option is a shorthand for providing the explicit tag refspec along with --prune, see the discussion about that in its documentation.

So you would need run:

git fetch origin --prune --prune-tags
Ela Dute
  • 346
  • 6
  • 8
4

Show the difference between local and remote tags:

diff <(git tag | sort) <( git ls-remote --tags origin | cut -f2 | grep -v '\^' | sed 's#refs/tags/##' | sort)
  • git tag gives the list of local tags
  • git ls-remote --tags gives the list of full paths to remote tags
  • cut -f2 | grep -v '\^' | sed 's#refs/tags/##' parses out just the tag name from list of remote tag paths
  • Finally we sort each of the two lists and diff them

The lines starting with "< " are your local tags that are no longer in the remote repo. If they are few, you can remove them manually one by one, if they are many, you do more grep-ing and piping to automate it.

dotstaraj
  • 49
  • 1
  • 2
  • 2
    Please consider adding some explanation to your code. This would definitively improve the quality of your answer. – honk Oct 15 '14 at 19:10
  • The complete command to delete all remote tags not present locally would then be: `diff " | cut -c3- | xargs -I{} git push origin :refs/tags/{}` – Daniel Gehriger Sep 14 '18 at 11:43
  • If you need to make such a diff and displaying the commit hash at the same time: `diff – piroux Mar 15 '19 at 09:38
  • This comparison was exactly what I was looking for, thank you. The only thing I'm confused about is that it also outputs a couple of lines which don't start with an arrow ` – Kay Jul 14 '19 at 11:47
3

Just added a git sync-local-tags command to pivotal_git_scripts Gem fork on GitHub:

https://github.com/kigster/git_scripts

Install the gem, then run "git sync-local-tags" in your repository to delete the local tags that do not exist on the remote.

Alternatively you can just install this script below and call it "git-sync-local-tags":


#!/usr/bin/env ruby

# Delete tags from the local Git repository, which are not found on 
# a remote origin
#
# Usage: git sync-local-tags [-n]
#        if -n is passed, just print the tag to be deleted, but do not 
#        actually delete it.
#
# Author: Konstantin Gredeskoul (http://tektastic.com)
#
#######################################################################

class TagSynchronizer
  def self.local_tags
    `git show-ref --tags | awk '{print $2}'`.split(/\n/)
  end

  def self.remote_tags
    `git ls-remote --tags origin | awk '{print $2}'`.split(/\n/)
  end

  def self.orphaned_tags
    self.local_tags - self.remote_tags
  end

  def self.remove_unused_tags(print_only = false)
    self.orphaned_tags.each do |ref|
      tag = ref.gsub /refs\/tags\//, ''
      puts "deleting local tag #{tag}"
      `git tag -d #{tag}` unless print_only
    end
  end
end

unless File.exists?(".git")
  puts "This doesn't look like a git repository."
  exit 1
end

print_only = ARGV.include?("-n")
TagSynchronizer.remove_unused_tags(print_only)
2

The same answer as @Richard W but for Windows (PowerShell)

git tag | foreach-object -process { git tag -d $_ }
git fetch -t
Danon
  • 1,793
  • 14
  • 28
2

Updated @2021/05

enter image description here

Pass $REPO parameter to custom script.

The content of sync_git_tags.sh

#!/bin/sh

# cd to $REPO directory
cd $1
pwd

# sync remote tags
git tag -l | xargs git tag -d && git fetch -t

Old

ps: updated @2021/05, git fetch --prune --prune-tags origin not working in my MacOS.

I add the command to SourceTree as a Custom Action on my MacOS.
Setting Custom Actions by Sourcetree -> Preferences... -> Custom Actions


`Script to run` have to be the `git` path.

I use git fetch --prune --prune-tags origin to sync tags from remote to local.

enter image description here enter image description here

AechoLiu
  • 15,710
  • 9
  • 85
  • 113
1

How about this - drop all local tags and then re-fetch? Considering your repo might contain submodules:

git submodule foreach --recursive  'git tag | xargs git tag -d'
(alternatively, "for i in `find .git  -type d -name '*tags*'`; do rm -f $i/*;  done")
git fetch -t
git submodule foreach --recursive git fetch -t
tetzu0
  • 21
  • 2
1

TortoiseGit can compare tags now.

Left log is on remote, right is at local.

enter image description here

Using the Compare tags feature of Sync dialog:

enter image description here

Also see TortoiseGit issue 2973

Yue Lin Ho
  • 2,479
  • 23
  • 32