Subject: Re: [PATCH v2 12/12] fetch: add a --fetch-prune option and fetch.pruneTags config

From: Ævar Arnfjörð Bjarmason <[email-masked]>
Date: Tue, 23 Jan 2018 00:48:02 +0100

On Mon, Jan 22 2018, Junio C. Hamano jotted:

> Ævar Arnfjörð Bjarmason  <[email-masked]> writes:
>
>> Add a --fetch-prune option to git-fetch, along with fetch.pruneTags
>> config option. This allows for doing any of:
>>
>>     git fetch -p -P
>>     git fetch --prune --prune-tags
>>     git fetch -p -P origin
>>     git fetch --prune --prune-tags origin
>>
>> Or simply:
>>
>>     git config fetch.prune true &&
>>     git config fetch.pruneTags true &&
>>     git fetch
>>
>> Instead of the much more verbose:
>>
>>     git fetch --prune origin 'refs/tags/*:refs/tags/*' '+refs/heads/*:refs/remotes/origin/*'
>>
>> Before this feature it was painful to support the use-case of pulling
>> from a repo which is having both its branches *and* tags deleted
>> regularly, and wanting our local references to reflect upstream.
>>
>> At work we create deployment tags in the repo for each rollout, and
>> there's *lots* of those, so they're archived within weeks for
>> performance reasons.
>>
>> Without this change it's hard to centrally configure such repos in
>> /etc/gitconfig (on servers that are only used for working with
>> them). You need to set fetch.prune=true globally, and then for each
>> repo:
>>
>>     git -C {} config --replace-all remote.origin.fetch "refs/tags/*:refs/tags/*" "^refs/tags/*:refs/tags/*$"
>
> I think the last one is supposed to be a regular expression on
> existing values.  Shouldn't the asterisks be quoted?
>
> Otherwise, it would appears as if "refs/tags:refs/tags///" are
> replaced with "refs/tags/*:refs/tags/*", but it certainly is not
> what you are doing.

Yes, well spotted. I copied this from an escaped version and forgot to
update that escaping. Will fix.

> I also wonder why the existing one does not expect a leading '+',
> which I think is what we place by default when you clone.

I didn't raise this because I didn't want to get side-tracked with yet
another thing, but it appears to me that a + prefix in tags refspecs
does absolutely nothing. Consider on a fresh git.git clobbering the new
v2.16.1 tag locally:

    $ git tag -a -m"fake" -f v2.16.1 v2.16.0
    Updated tag 'v2.16.1' (was eb5fcb24f)

This should clobber it, and does:

    $ git fetch --prune --dry-run origin '+refs/tags/*:refs/tags/*'
    From github.com:git/git
     t [tag update]          v2.16.1    -> v2.16.1

But so will this without +, which seems like a bug to me:

    $ git fetch --prune --dry-run origin 'refs/tags/*:refs/tags/*'
    From github.com:git/git
     t [tag update]          v2.16.1    -> v2.16.1

But maybe I'm missing something.

>> +-P::
>> +--prune-tags::
>> +	.... This option is
>> +	merely a shorthand for providing the explicit tag refspec
>> +	along with `--prune`, see the discussion about that in its
>> +	documentation.
>
> So would "git fetch -P origin" be like "git fetch --prune --tags
> origin"?

No, as I understand it --tags just has the effect of considering remote
tags that aren't tags of the branches you're fetching, so e.g. on a
fresh git.git:

    $ git tag -a -m"fake" -f v2.16.2 v2.16.0
    $ git fetch --prune --tags --dry-run
    $

I.e. it does nothing. Whereas this will prune the new fake tag:

    $ git fetch --prune origin 'refs/tags/*:refs/tags/*'
    From github.com:git/git
     - [deleted]             (none)     -> v2.16.2

And so does the --prune-tags option:

    $ ~/g/git/git-fetch --prune --prune-tags origin
    From github.com:git/git
     - [deleted]             (none)     -> v2.16.2

>>  +
>>  See the PRUNING section below for more details.
>>
>> diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
>> index 18fac0da2e..5682ed4ae1 100644
>> --- a/Documentation/git-fetch.txt
>> +++ b/Documentation/git-fetch.txt
>> @@ -148,6 +148,30 @@ So be careful when using this with a refspec like
>>  `refs/tags/*:refs/tags/*`, or any other refspec which might map
>>  references from multiple remotes to the same local namespace.
>>
>> +Since keeping up-to-date with both branches and tags on the remote is
>> +a common use-case the `--prune-tags` option can be supplied along with
>> +`--prune` to prune local tags that don't exist on the remote. Tag
>> +pruning can also be enabled with `fetch.pruneTags` or
>> +`remote.<name>.pruneTags` in the config. See linkgit:git-config[1].
>> +
>> +The `--prune-tags` option is equivalent to having
>> +`refs/tags/*:refs/tags/*` configured in the refspecs for the
>> +remote. This can lead to some seemingly strange interactions:
>> +
>> +------------------------------------------------
>> +# These both fetch tags
>> +$ git fetch --no-tags origin 'refs/tags/*:refs/tags/*'
>> +$ git fetch --no-tags --prune-tags origin
>> +------------------------------------------------
>
> This description is too confusing.  First you say "having
> ... configured in the refspecs for the remote", but configured
> refspecs for the remote (I presume that you are missing 'fetch' from
> that description and are talking about the "remote.$name.fetch"
> configuration variable) are overridden when you give explicit
> refspecs from the command line, so the above two are not even
> equivalent.  The first one gives a refspec explicitly from the
> command line, so other configured refspecs like
>
>     [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/*
>
> should be ignored, while the second one, if --prune-tags tells Git
> to behave as if
>
>     [remote "origin"] fetch = refs/tags/*:refs/tags/*
>
> also exists in the config, would not cause other ones for the same
> remote from getting ignored.  So...

Indeed, I'll reword this. FWIW I meant something closer to "declared in
the refspecs of the remote" not "configured ... remote".