Merge branch 'ab/fetch-prune'
authorJunio C Hamano <gitster@pobox.com>
Tue, 6 Mar 2018 22:54:01 +0000 (14:54 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 6 Mar 2018 22:54:01 +0000 (14:54 -0800)
Clarify how configured fetch refspecs interact with the "--prune"
option of "git fetch", and also add a handy short-hand for getting
rid of stale tags that are locally held.

* ab/fetch-prune:
fetch: make the --prune-tags work with <url>
fetch: add a --prune-tags option and fetch.pruneTags config
fetch tests: add scaffolding for the new fetch.pruneTags
git-fetch & config doc: link to the new PRUNING section
git remote doc: correct dangerous lies about what prune does
git fetch doc: add a new section to explain the ins & outs of pruning
fetch tests: fetch <url> <spec> as well as fetch [<remote>]
fetch tests: expand case/esac for later change
fetch tests: double quote a variable for interpolation
fetch tests: test --prune and refspec interaction
fetch tests: add a tag to be deleted to the pruning tests
fetch tests: re-arrange arguments for future readability
fetch tests: refactor in preparation for testing tag pruning
remote: add a macro for "refs/tags/*:refs/tags/*"
fetch: stop accessing "remote" variable indirectly
fetch: trivially refactor assignment to ref_nr
fetch: don't redundantly NULL something calloc() gave us

1  2 
Documentation/config.txt
builtin/fetch.c
contrib/completion/git-completion.bash
t/t5510-fetch.sh
diff --combined Documentation/config.txt
index f57e9cf10ca485ae0147ee1d01639c94ce97fb56,e254bfd5311a80e23a8f96bd652d007682c438b2..bbd66f5b9806b1315f8d79525f7b1fda48b434ee
@@@ -1398,7 -1398,16 +1398,16 @@@ fetch.unpackLimit:
  
  fetch.prune::
        If true, fetch will automatically behave as if the `--prune`
-       option was given on the command line.  See also `remote.<name>.prune`.
+       option was given on the command line.  See also `remote.<name>.prune`
+       and the PRUNING section of linkgit:git-fetch[1].
+ fetch.pruneTags::
+       If true, fetch will automatically behave as if the
+       `refs/tags/*:refs/tags/*` refspec was provided when pruning,
+       if not set already. This allows for setting both this option
+       and `fetch.prune` to maintain a 1=1 mapping to upstream
+       refs. See also `remote.<name>.pruneTags` and the PRUNING
+       section of linkgit:git-fetch[1].
  
  fetch.output::
        Control how ref update status is printed. Valid values are
@@@ -2945,6 -2954,15 +2954,15 @@@ remote.<name>.prune:
        remote (as if the `--prune` option was given on the command line).
        Overrides `fetch.prune` settings, if any.
  
+ remote.<name>.pruneTags::
+       When set to true, fetching from this remote by default will also
+       remove any local tags that no longer exist on the remote if pruning
+       is activated in general via `remote.<name>.prune`, `fetch.prune` or
+       `--prune`. Overrides `fetch.pruneTags` settings, if any.
+ +
+ See also `remote.<name>.prune` and the PRUNING section of
+ linkgit:git-fetch[1].
  remotes.<group>::
        The list of remotes which are fetched by "git remote update
        <group>".  See linkgit:git-remote[1].
@@@ -3343,10 -3361,6 +3361,10 @@@ uploadpack.packObjectsHook:
        was run. I.e., `upload-pack` will feed input intended for
        `pack-objects` to the hook, and expects a completed packfile on
        stdout.
 +
 +uploadpack.allowFilter::
 +      If this option is set, `upload-pack` will advertise partial
 +      clone and partial fetch object filtering.
  +
  Note that this configuration variable is ignored if it is seen in the
  repository-level config (this is a safety measure against fetching from
diff --combined builtin/fetch.c
index 8ee998ea2ee8f736a49afe285d31b0be478cf15b,c96f17a9a3c39d9f66439b7ca12e649c46a20a76..d32d94692c7eae8372c4a8abac45f172e6693247
@@@ -19,7 -19,6 +19,7 @@@
  #include "argv-array.h"
  #include "utf8.h"
  #include "packfile.h"
 +#include "list-objects-filter-options.h"
  
  static const char * const builtin_fetch_usage[] = {
        N_("git fetch [<options>] [<repository> [<refspec>...]]"),
@@@ -39,6 -38,10 +39,10 @@@ static int fetch_prune_config = -1; /* 
  static int prune = -1; /* unspecified */
  #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
  
+ static int fetch_prune_tags_config = -1; /* unspecified */
+ static int prune_tags = -1; /* unspecified */
+ #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
  static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
  static int progress = -1;
  static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@@ -57,7 -60,6 +61,7 @@@ static int recurse_submodules_default 
  static int shown_url = 0;
  static int refmap_alloc, refmap_nr;
  static const char **refmap_array;
 +static struct list_objects_filter_options filter_options;
  
  static int git_fetch_config(const char *k, const char *v, void *cb)
  {
                return 0;
        }
  
+       if (!strcmp(k, "fetch.prunetags")) {
+               fetch_prune_tags_config = git_config_bool(k, v);
+               return 0;
+       }
        if (!strcmp(k, "submodule.recurse")) {
                int r = git_config_bool(k, v) ?
                        RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
@@@ -128,6 -135,8 +137,8 @@@ static struct option builtin_fetch_opti
                    N_("number of submodules fetched in parallel")),
        OPT_BOOL('p', "prune", &prune,
                 N_("prune remote-tracking branches no longer on remote")),
+       OPT_BOOL('P', "prune-tags", &prune_tags,
+                N_("prune local tags no longer on remote and clobber changed tags")),
        { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
                    PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
                        TRANSPORT_FAMILY_IPV4),
        OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
                        TRANSPORT_FAMILY_IPV6),
 +      OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
        OPT_END()
  };
  
@@@ -1048,11 -1056,6 +1059,11 @@@ static struct transport *prepare_transp
                set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes");
        if (update_shallow)
                set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
 +      if (filter_options.choice) {
 +              set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
 +                         filter_options.filter_spec);
 +              set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
 +      }
        return transport;
  }
  
@@@ -1220,6 -1223,8 +1231,8 @@@ static void add_options_to_argv(struct 
                argv_array_push(argv, "--dry-run");
        if (prune != -1)
                argv_array_push(argv, prune ? "--prune" : "--no-prune");
+       if (prune_tags != -1)
+               argv_array_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags");
        if (update_head_ok)
                argv_array_push(argv, "--update-head-ok");
        if (force)
@@@ -1273,62 -1278,15 +1286,65 @@@ static int fetch_multiple(struct string
        return result;
  }
  
- static int fetch_one(struct remote *remote, int argc, const char **argv)
 +/*
 + * Fetching from the promisor remote should use the given filter-spec
 + * or inherit the default filter-spec from the config.
 + */
 +static inline void fetch_one_setup_partial(struct remote *remote)
 +{
 +      /*
 +       * Explicit --no-filter argument overrides everything, regardless
 +       * of any prior partial clones and fetches.
 +       */
 +      if (filter_options.no_filter)
 +              return;
 +
 +      /*
 +       * If no prior partial clone/fetch and the current fetch DID NOT
 +       * request a partial-fetch, do a normal fetch.
 +       */
 +      if (!repository_format_partial_clone && !filter_options.choice)
 +              return;
 +
 +      /*
 +       * If this is the FIRST partial-fetch request, we enable partial
 +       * on this repo and remember the given filter-spec as the default
 +       * for subsequent fetches to this remote.
 +       */
 +      if (!repository_format_partial_clone && filter_options.choice) {
 +              partial_clone_register(remote->name, &filter_options);
 +              return;
 +      }
 +
 +      /*
 +       * We are currently limited to only ONE promisor remote and only
 +       * allow partial-fetches from the promisor remote.
 +       */
 +      if (strcmp(remote->name, repository_format_partial_clone)) {
 +              if (filter_options.choice)
 +                      die(_("--filter can only be used with the remote configured in core.partialClone"));
 +              return;
 +      }
 +
 +      /*
 +       * Do a partial-fetch from the promisor remote using either the
 +       * explicitly given filter-spec or inherit the filter-spec from
 +       * the config.
 +       */
 +      if (!filter_options.choice)
 +              partial_clone_get_default_filter_spec(&filter_options);
 +      return;
 +}
 +
+ static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok)
  {
        static const char **refs = NULL;
        struct refspec *refspec;
        int ref_nr = 0;
+       int j = 0;
        int exit_code;
+       int maybe_prune_tags;
+       int remote_via_config = remote_is_configured(remote, 0);
  
        if (!remote)
                die(_("No remote repository specified.  Please, specify either a URL or a\n"
  
        if (prune < 0) {
                /* no command line request */
-               if (0 <= gtransport->remote->prune)
-                       prune = gtransport->remote->prune;
+               if (0 <= remote->prune)
+                       prune = remote->prune;
                else if (0 <= fetch_prune_config)
                        prune = fetch_prune_config;
                else
                        prune = PRUNE_BY_DEFAULT;
        }
  
+       if (prune_tags < 0) {
+               /* no command line request */
+               if (0 <= remote->prune_tags)
+                       prune_tags = remote->prune_tags;
+               else if (0 <= fetch_prune_tags_config)
+                       prune_tags = fetch_prune_tags_config;
+               else
+                       prune_tags = PRUNE_TAGS_BY_DEFAULT;
+       }
+       maybe_prune_tags = prune_tags_ok && prune_tags;
+       if (maybe_prune_tags && remote_via_config)
+               add_prune_tags_to_fetch_refspec(remote);
+       if (argc > 0 || (maybe_prune_tags && !remote_via_config)) {
+               size_t nr_alloc = st_add3(argc, maybe_prune_tags, 1);
+               refs = xcalloc(nr_alloc, sizeof(const char *));
+               if (maybe_prune_tags) {
+                       refs[j++] = xstrdup("refs/tags/*:refs/tags/*");
+                       ref_nr++;
+               }
+       }
        if (argc > 0) {
-               int j = 0;
                int i;
-               refs = xcalloc(st_add(argc, 1), sizeof(const char *));
                for (i = 0; i < argc; i++) {
                        if (!strcmp(argv[i], "tag")) {
                                i++;
                                                    argv[i], argv[i]);
                        } else
                                refs[j++] = argv[i];
+                       ref_nr++;
                }
-               refs[j] = NULL;
-               ref_nr = j;
        }
  
        sigchain_push_common(unlock_pack_on_signal);
@@@ -1378,14 -1356,12 +1414,15 @@@ int cmd_fetch(int argc, const char **ar
  {
        int i;
        struct string_list list = STRING_LIST_INIT_DUP;
 -      struct remote *remote;
 +      struct remote *remote = NULL;
        int result = 0;
++      int prune_tags_ok = 1;
        struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
  
        packet_trace_identity("fetch");
  
 +      fetch_if_missing = 0;
 +
        /* Record the command line for the reflog */
        strbuf_addstr(&default_rla, "fetch");
        for (i = 1; i < argc; i++)
        if (depth || deepen_since || deepen_not.nr)
                deepen = 1;
  
 +      if (filter_options.choice && !repository_format_partial_clone)
 +              die("--filter can only be used when extensions.partialClone is set");
 +
        if (all) {
                if (argc == 1)
                        die(_("fetch --all does not take a repository argument"));
                else if (argc > 1)
                        die(_("fetch --all does not make sense with refspecs"));
                (void) for_each_remote(get_one_remote_for_fetch, &list);
 -              result = fetch_multiple(&list);
        } else if (argc == 0) {
                /* No arguments -- use default remote */
                remote = remote_get(NULL);
 -              result = fetch_one(remote, argc, argv, 1);
        } else if (multiple) {
                /* All arguments are assumed to be remotes or groups */
                for (i = 0; i < argc; i++)
                        if (!add_remote_or_group(argv[i], &list))
                                die(_("No such remote or remote group: %s"), argv[i]);
 -              result = fetch_multiple(&list);
        } else {
                /* Single remote or group */
                (void) add_remote_or_group(argv[0], &list);
                        /* More than one remote */
                        if (argc > 1)
                                die(_("Fetching a group and specifying refspecs does not make sense"));
 -                      result = fetch_multiple(&list);
                } else {
                        /* Zero or one remotes */
                        remote = remote_get(argv[0]);
 -                      result = fetch_one(remote, argc-1, argv+1, argc == 1);
++                      prune_tags_ok = (argc == 1);
 +                      argc--;
 +                      argv++;
                }
        }
  
-               result = fetch_one(remote, argc, argv);
 +      if (remote) {
 +              if (filter_options.choice || repository_format_partial_clone)
 +                      fetch_one_setup_partial(remote);
++              result = fetch_one(remote, argc, argv, prune_tags_ok);
 +      } else {
 +              if (filter_options.choice)
 +                      die(_("--filter can only be used with the remote configured in core.partialClone"));
 +              /* TODO should this also die if we have a previous partial-clone? */
 +              result = fetch_multiple(&list);
 +      }
 +
        if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
                struct argv_array options = ARGV_ARRAY_INIT;
  
index c7d5c7af29a8e9595375a535af59549b01da1b49,4ecd0d4d7a5d54925e78e86cc031eacd685c7470..4c86ab66e23ee02e15f15173bc7e613c20347f36
@@@ -594,7 -594,7 +594,7 @@@ __git_is_configured_remote (
  
  __git_list_merge_strategies ()
  {
 -      git merge -s help 2>&1 |
 +      LANG=C LC_ALL=C git merge -s help 2>&1 |
        sed -n -e '/[Aa]vailable strategies are: /,/^$/{
                s/\.$//
                s/.*://
@@@ -1077,7 -1077,7 +1077,7 @@@ _git_am (
  {
        __git_find_repo_path
        if [ -d "$__git_repo_path"/rebase-apply ]; then
 -              __gitcomp "--skip --continue --resolved --abort"
 +              __gitcomp "--skip --continue --resolved --abort --quit"
                return
        fi
        case "$cur" in
@@@ -1468,7 -1468,7 +1468,7 @@@ __git_fetch_recurse_submodules="yes on-
  __git_fetch_options="
        --quiet --verbose --append --upload-pack --force --keep --depth=
        --tags --no-tags --all --prune --dry-run --recurse-submodules=
-       --unshallow --update-shallow
+       --unshallow --update-shallow --prune-tags
  "
  
  _git_fetch ()
diff --combined t/t5510-fetch.sh
index 3debc87d4aefb2d0d5c77881a9485c06a2908858,dce237130286c448359ab34f9a7b5cf0c3d8d50e..da9ac0055721237f177d3d475e56ddb38b25eff1
@@@ -222,9 -222,12 +222,9 @@@ test_expect_success 'fetch uses remote 
        (
                cd descriptive &&
                git fetch o 2>actual &&
 -              grep " -> refs/crazyheads/descriptive-branch$" actual |
 -              test_i18ngrep "new branch" &&
 -              grep " -> descriptive-tag$" actual |
 -              test_i18ngrep "new tag" &&
 -              grep " -> crazy$" actual |
 -              test_i18ngrep "new ref"
 +              test_i18ngrep "new branch.* -> refs/crazyheads/descriptive-branch$" actual &&
 +              test_i18ngrep "new tag.* -> descriptive-tag$" actual &&
 +              test_i18ngrep "new ref.* -> crazy$" actual
        ) &&
        git checkout master
  '
@@@ -540,82 -543,232 +540,232 @@@ test_expect_success "should be able to 
  set_config_tristate () {
        # var=$1 val=$2
        case "$2" in
-       unset)  test_unconfig "$1" ;;
-       *)      git config "$1" "$2" ;;
+       unset)
+               test_unconfig "$1"
+               ;;
+       *)
+               git config "$1" "$2"
+               key=$(echo $1 | sed -e 's/^remote\.origin/fetch/')
+               git_fetch_c="$git_fetch_c -c $key=$2"
+               ;;
        esac
  }
  
  test_configured_prune () {
-       fetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4
+       test_configured_prune_type "$@" "name"
+       test_configured_prune_type "$@" "link"
+ }
  
-       test_expect_success "prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4" '
+ test_configured_prune_type () {
+       fetch_prune=$1
+       remote_origin_prune=$2
+       fetch_prune_tags=$3
+       remote_origin_prune_tags=$4
+       expected_branch=$5
+       expected_tag=$6
+       cmdline=$7
+       mode=$8
+       if test -z "$cmdline_setup"
+       then
+               test_expect_success 'setup cmdline_setup variable for subsequent test' '
+                       remote_url="file://$(git -C one config remote.origin.url)" &&
+                       remote_fetch="$(git -C one config remote.origin.fetch)" &&
+                       cmdline_setup="\"$remote_url\" \"$remote_fetch\""
+               '
+       fi
+       if test "$mode" = 'link'
+       then
+               new_cmdline=""
+               if test "$cmdline" = ""
+               then
+                       new_cmdline=$cmdline_setup
+               else
+                       new_cmdline=$(printf "%s" "$cmdline" | perl -pe 's[origin(?!/)]["'"$remote_url"'"]g')
+               fi
+               if test "$fetch_prune_tags" = 'true' ||
+                  test "$remote_origin_prune_tags" = 'true'
+               then
+                       if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
+                       then
+                               new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
+                       fi
+               fi
+               cmdline="$new_cmdline"
+       fi
+       test_expect_success "$mode prune fetch.prune=$1 remote.origin.prune=$2 fetch.pruneTags=$3 remote.origin.pruneTags=$4${7:+ $7}; branch:$5 tag:$6" '
                # make sure a newbranch is there in . and also in one
                git branch -f newbranch &&
+               git tag -f newtag &&
                (
                        cd one &&
                        test_unconfig fetch.prune &&
+                       test_unconfig fetch.pruneTags &&
                        test_unconfig remote.origin.prune &&
-                       git fetch &&
-                       git rev-parse --verify refs/remotes/origin/newbranch
+                       test_unconfig remote.origin.pruneTags &&
+                       git fetch '"$cmdline_setup"' &&
+                       git rev-parse --verify refs/remotes/origin/newbranch &&
+                       git rev-parse --verify refs/tags/newtag
                ) &&
  
                # now remove it
                git branch -d newbranch &&
+               git tag -d newtag &&
  
                # then test
                (
                        cd one &&
+                       git_fetch_c="" &&
                        set_config_tristate fetch.prune $fetch_prune &&
+                       set_config_tristate fetch.pruneTags $fetch_prune_tags &&
                        set_config_tristate remote.origin.prune $remote_origin_prune &&
-                       git fetch $cmdline &&
-                       case "$expected" in
+                       set_config_tristate remote.origin.pruneTags $remote_origin_prune_tags &&
+                       if test "$mode" != "link"
+                       then
+                               git_fetch_c=""
+                       fi &&
+                       git$git_fetch_c fetch '"$cmdline"' &&
+                       case "$expected_branch" in
                        pruned)
                                test_must_fail git rev-parse --verify refs/remotes/origin/newbranch
                                ;;
                        kept)
                                git rev-parse --verify refs/remotes/origin/newbranch
                                ;;
+                       esac &&
+                       case "$expected_tag" in
+                       pruned)
+                               test_must_fail git rev-parse --verify refs/tags/newtag
+                               ;;
+                       kept)
+                               git rev-parse --verify refs/tags/newtag
+                               ;;
                        esac
                )
        '
  }
  
- test_configured_prune unset unset ""          kept
- test_configured_prune unset unset "--no-prune"        kept
- test_configured_prune unset unset "--prune"   pruned
- test_configured_prune false unset ""          kept
- test_configured_prune false unset "--no-prune"        kept
- test_configured_prune false unset "--prune"   pruned
- test_configured_prune true  unset ""          pruned
- test_configured_prune true  unset "--prune"   pruned
- test_configured_prune true  unset "--no-prune"        kept
- test_configured_prune unset false ""          kept
- test_configured_prune unset false "--no-prune"        kept
- test_configured_prune unset false "--prune"   pruned
- test_configured_prune false false ""          kept
- test_configured_prune false false "--no-prune"        kept
- test_configured_prune false false "--prune"   pruned
- test_configured_prune true  false ""          kept
- test_configured_prune true  false "--prune"   pruned
- test_configured_prune true  false "--no-prune"        kept
- test_configured_prune unset true  ""          pruned
- test_configured_prune unset true  "--no-prune"        kept
- test_configured_prune unset true  "--prune"   pruned
- test_configured_prune false true  ""          pruned
- test_configured_prune false true  "--no-prune"        kept
- test_configured_prune false true  "--prune"   pruned
- test_configured_prune true  true  ""          pruned
- test_configured_prune true  true  "--prune"   pruned
- test_configured_prune true  true  "--no-prune"        kept
+ # $1 config: fetch.prune
+ # $2 config: remote.<name>.prune
+ # $3 config: fetch.pruneTags
+ # $4 config: remote.<name>.pruneTags
+ # $5 expect: branch to be pruned?
+ # $6 expect: tag to be pruned?
+ # $7 git-fetch $cmdline:
+ #
+ #                     $1    $2    $3    $4    $5     $6     $7
+ test_configured_prune unset unset unset unset kept   kept   ""
+ test_configured_prune unset unset unset unset kept   kept   "--no-prune"
+ test_configured_prune unset unset unset unset pruned kept   "--prune"
+ test_configured_prune unset unset unset unset kept   pruned \
+       "--prune origin refs/tags/*:refs/tags/*"
+ test_configured_prune unset unset unset unset pruned pruned \
+       "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+ test_configured_prune false unset unset unset kept   kept   ""
+ test_configured_prune false unset unset unset kept   kept   "--no-prune"
+ test_configured_prune false unset unset unset pruned kept   "--prune"
+ test_configured_prune true  unset unset unset pruned kept   ""
+ test_configured_prune true  unset unset unset pruned kept   "--prune"
+ test_configured_prune true  unset unset unset kept   kept   "--no-prune"
+ test_configured_prune unset false unset unset kept   kept   ""
+ test_configured_prune unset false unset unset kept   kept   "--no-prune"
+ test_configured_prune unset false unset unset pruned kept   "--prune"
+ test_configured_prune false false unset unset kept   kept   ""
+ test_configured_prune false false unset unset kept   kept   "--no-prune"
+ test_configured_prune false false unset unset pruned kept   "--prune"
+ test_configured_prune false false unset unset kept   pruned \
+       "--prune origin refs/tags/*:refs/tags/*"
+ test_configured_prune false false unset unset pruned pruned \
+       "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+ test_configured_prune true  false unset unset kept   kept   ""
+ test_configured_prune true  false unset unset pruned kept   "--prune"
+ test_configured_prune true  false unset unset kept   kept   "--no-prune"
+ test_configured_prune unset true  unset unset pruned kept   ""
+ test_configured_prune unset true  unset unset kept   kept   "--no-prune"
+ test_configured_prune unset true  unset unset pruned kept   "--prune"
+ test_configured_prune false true  unset unset pruned kept   ""
+ test_configured_prune false true  unset unset kept   kept   "--no-prune"
+ test_configured_prune false true  unset unset pruned kept   "--prune"
+ test_configured_prune true  true  unset unset pruned kept   ""
+ test_configured_prune true  true  unset unset pruned kept   "--prune"
+ test_configured_prune true  true  unset unset kept   kept   "--no-prune"
+ test_configured_prune true  true  unset unset kept   pruned \
+       "--prune origin refs/tags/*:refs/tags/*"
+ test_configured_prune true  true  unset unset pruned pruned \
+       "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+ # --prune-tags on its own does nothing, needs --prune as well, same
+ # for for fetch.pruneTags without fetch.prune
+ test_configured_prune unset unset unset unset kept kept     "--prune-tags"
+ test_configured_prune unset unset true unset  kept kept     ""
+ test_configured_prune unset unset unset true  kept kept     ""
+ # These will prune the tags
+ test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
+ test_configured_prune true  unset true  unset pruned pruned ""
+ test_configured_prune unset true  unset true  pruned pruned ""
+ # remote.<name>.pruneTags overrides fetch.pruneTags, just like
+ # remote.<name>.prune overrides fetch.prune if set.
+ test_configured_prune true  unset true unset pruned pruned  ""
+ test_configured_prune false true  false true  pruned pruned ""
+ test_configured_prune true  false true  false kept   kept   ""
+ # When --prune-tags is supplied it's ignored if an explicit refspec is
+ # given, same for the configuration options.
+ test_configured_prune unset unset unset unset pruned kept \
+       "--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
+ test_configured_prune unset unset true  unset pruned kept \
+       "--prune origin +refs/heads/*:refs/remotes/origin/*"
+ test_configured_prune unset unset unset true pruned  kept \
+       "--prune origin +refs/heads/*:refs/remotes/origin/*"
+ # Pruning that also takes place if a file:// url replaces a named
+ # remote. However, because there's no implicit
+ # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
+ # command-line negates --prune-tags, the branches will not be pruned.
+ test_configured_prune_type unset unset unset unset kept   kept   "origin --prune-tags" "name"
+ test_configured_prune_type unset unset unset unset kept   kept   "origin --prune-tags" "link"
+ test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
+ test_configured_prune_type unset unset unset unset kept   pruned "origin --prune --prune-tags" "link"
+ test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
+ test_configured_prune_type unset unset unset unset kept   pruned "--prune --prune-tags origin" "link"
+ test_configured_prune_type unset unset true  unset pruned pruned "--prune origin" "name"
+ test_configured_prune_type unset unset true  unset kept   pruned "--prune origin" "link"
+ test_configured_prune_type unset unset unset true  pruned pruned "--prune origin" "name"
+ test_configured_prune_type unset unset unset true  kept   pruned "--prune origin" "link"
+ test_configured_prune_type true  unset true  unset pruned pruned "origin" "name"
+ test_configured_prune_type true  unset true  unset kept   pruned "origin" "link"
+ test_configured_prune_type unset  true true  unset pruned pruned "origin" "name"
+ test_configured_prune_type unset  true true  unset kept   pruned "origin" "link"
+ test_configured_prune_type unset  true unset true  pruned pruned "origin" "name"
+ test_configured_prune_type unset  true unset true  kept   pruned "origin" "link"
+ # When all remote.origin.fetch settings are deleted a --prune
+ # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
+ # tags, but not tracking branches, will be deleted.
+ test_expect_success 'remove remote.origin.fetch "one"' '
+       (
+               cd one &&
+               git config --unset-all remote.origin.fetch
+       )
+ '
+ test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "name"
+ test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
  
  test_expect_success 'all boundary commits are excluded' '
        test_commit base &&