Subject: Re: [BUG] git remote prune removes local tags, depending on fetch config

From: Ævar Arnfjörð Bjarmason <[email-masked]>
Date: Tue, 16 Jan 2018 12:14:16 +0100

On Tue, Jan 16 2018, Michael Giuffrida jotted:

> Just to confirm, you're talking about the behavior of removing *all*
> tags that aren't found on the remote? (Not just tags that used to be
> on some remote, but have since been removed from that remote.) So,
> with your proposed workflow, you would never create your own tags
> locally, without pushing them to the remote before running `git fetch`
> -- otherwise they would simply be deleted.
>
> It doesn't seem like a useful feature -- you wouldn't expect `git
> fetch --prune` to remove your local branches that you were developing
> on, right?

Yes, I realize this isn't everyone's cup of tea, but it's very useful in
our in-house use-case for git.

We have repos that have tags pushed to them on every deployment, thus
the cumulative set of tags is in the hundreds of thousands.

There's a pruning script that prunes both branches and tags from the
server (and archives them to an archive repo that has every ref ever).

Then we need to push these deletions back to client checkouts. For
branches that's easy, just set fetch.prune=true. For tags I need to
chase down checkouts and do:

    git -C {} config --replace-all remote.origin.fetch "refs/tags/*:refs/tags/*" "^refs/tags/*:refs/tags/*$"

It would be easier to just set fetch.{prune,pruneTags}=true in
/etc/gitconfig, this is on e.g. shared development staging boxes where
you might have a manually cloned checkout in your ~.

Of course it'll wipe away any manually created tag you have, *ideally*
we'd have something like the refs/remotes/ namespace for tags, but that
ship has sailed.

But meanwhile tag pruning is an OK compromise, 99% of our users will
never even think to create a tag (and if they created one manually they
couldn't push it), the advanced users that would know what sort of repo
they're editing and can out-out of the behavior by editing their local
config.

> On Mon, Jan 15, 2018 at 4:38 PM, Ævar Arnfjörð Bjarmason
> <[email-masked]> wrote:
>>
>> On Mon, Jan 15 2018, Michael Giuffrida jotted:
>>
>>> `git remote prune <name>` should "delete all stale remote-tracking
>>> branches under <name>". I was surprised to discover, after some
>>> troubleshooting, that it also deletes *all* local tags that don't
>>> exist on the remote, if the following refspec is included in the
>>> remote's fetch config:
>>>
>>>     +refs/tags/*:refs/tags/*
>>>
>>> So, if `remote.origin.fetch` is configured to fetch all tags from the
>>> remote, any tags I create locally will be deleted when running `git
>>> remote prune origin`. This is not intuitive [1], nor is is it
>>> explained in the docs [2]. Is this behavior obvious to someone with a
>>> better understanding of Git internals?
>>>
>>> I did find a better way to automatically fetch tags (using tagopt
>>> instead of adding the fetch refspec). However, the refspec doesn't
>>> seem "wrong" in itself; in particular, `git fetch --tags` used to be
>>> considered equivalent to specifying the refspec
>>> "refs/tags/*:refs/tags/*" -- implying that this is a sensible refspec
>>> [3]. So I wouldn't expect it to "break" the behavior of another
>>> command.
>>>
>>> [1] https://stackoverflow.com/q/34687657/1327867
>>> [2] https://git-scm.com/docs/git-remote.html#git-remote-empruneem
>>> [3] https://github.com/git/git/commit/c5a84e92a2fe9e8748e32341c344d7a6c0f52a50
>>
>> These docs are really confusing, but it is working as intended, and
>> really should be re-documented.
>>
>> The `git remote prune` subcommand just ends up piggy-backing on
>> git-fetch, whose behavior is explained here:
>> https://git-scm.com/docs/git-fetch.html#git-fetch---prune
>>
>> It's worked this way since at least v1.8.5.6, maybe at some distant
>> point in the past it only did this for branches when invoked via
>> git-remote as the documentation says.
>>
>> RELATED:
>>
>> I've actually had the reverse problem with this. I want some way to turn
>> this behavior on without explicitly hacking the refspec, so I can do it
>> globally in /etc/gitconfig or in ~/.gitconfig without screwing with the
>> config of each checkout on certain machines.
>>
>> You can set fetch.prune=true, but that only prunes the branches, you
>> need to inject remote.origin.fetch into each checkout, unless I've
>> missed some way of doing this.
>>
>> I wanted to add fetch.pruneTags that would make it as if you had
>> refs/tags/*:refs/tags/* in the fetch spec, but I haven't hacked that up
>> yet, if anyone can see any inherent issue with that plan I'd like to
>> know about it.