Merge branch 'ms/fetch-prune-configuration'
authorJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2013 21:27:11 +0000 (14:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2013 21:27:11 +0000 (14:27 -0700)
Allow fetch.prune and remote.*.prune configuration variables to be set,
and "git fetch" to behave as if "--prune" is given.

"git fetch" that honors remote.*.prune is fine, but I wonder if we
should somehow make "git push" aware of it as well. Perhaps
remote.*.prune should not be just a boolean, but a 4-way "none",
"push", "fetch", "both"?

* ms/fetch-prune-configuration:
fetch: make --prune configurable

1  2 
Documentation/config.txt
builtin/fetch.c
remote.c
t/t5510-fetch.sh
diff --combined Documentation/config.txt
index ec57a15ac5da284f35ced4441e2315fec9b05e2d,e4ce7c46f9dbeaa3be7f869d52bb5011df954f74..4a51e6a68dadf0d51c1951c645ee5d361f4ec1d8
@@@ -199,9 -199,6 +199,9 @@@ advice.*:
        amWorkDir::
                Advice that shows the location of the patch file when
                linkgit:git-am[1] fails to apply it.
 +      rmHints::
 +              In case of failure in the output of linkgit:git-rm[1],
 +              show directions on how to proceed from the current state.
  --
  
  core.fileMode::
@@@ -213,6 -210,17 +213,6 @@@ The default is true, except linkgit:git
  will probe and set core.fileMode false if appropriate when the
  repository is created.
  
 -core.ignoreCygwinFSTricks::
 -      This option is only used by Cygwin implementation of Git. If false,
 -      the Cygwin stat() and lstat() functions are used. This may be useful
 -      if your repository consists of a few separate directories joined in
 -      one hierarchy using Cygwin mount. If true, Git uses native Win32 API
 -      whenever it is possible and falls back to Cygwin functions only to
 -      handle symbol links. The native mode is more than twice faster than
 -      normal Cygwin l/stat() functions. True by default, unless core.filemode
 -      is true, in which case ignoreCygwinFSTricks is ignored as Cygwin's
 -      POSIX emulation is required to support core.filemode.
 -
  core.ignorecase::
        If true, this option enables various workarounds to enable
        Git to work better on filesystems that are not case sensitive,
@@@ -868,17 -876,16 +868,17 @@@ The values of these variables may be sp
  
  color.interactive::
        When set to `always`, always use colors for interactive prompts
 -      and displays (such as those used by "git-add --interactive").
 -      When false (or `never`), never.  When set to `true` or `auto`, use
 -      colors only when the output is to the terminal. Defaults to false.
 +      and displays (such as those used by "git-add --interactive" and
 +      "git-clean --interactive"). When false (or `never`), never.
 +      When set to `true` or `auto`, use colors only when the output is
 +      to the terminal. Defaults to false.
  
  color.interactive.<slot>::
 -      Use customized color for 'git add --interactive'
 -      output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
 -      four distinct types of normal output from interactive
 -      commands.  The values of these variables may be specified as
 -      in color.branch.<slot>.
 +      Use customized color for 'git add --interactive' and 'git clean
 +      --interactive' output. `<slot>` may be `prompt`, `header`, `help`
 +      or `error`, for four distinct types of normal output from
 +      interactive commands.  The values of these variables may be
 +      specified as in color.branch.<slot>.
  
  color.pager::
        A boolean to enable/disable colored output when the pager is in
@@@ -912,21 -919,17 +912,21 @@@ color.ui:
        as `color.diff` and `color.grep` that control the use of color
        per command family. Its scope will expand as more commands learn
        configuration to set a default for the `--color` option.  Set it
 -      to `always` if you want all output not intended for machine
 -      consumption to use color, to `true` or `auto` if you want such
 -      output to use color when written to the terminal, or to `false` or
 -      `never` if you prefer Git commands not to use color unless enabled
 -      explicitly with some other configuration or the `--color` option.
 +      to `false` or `never` if you prefer Git commands not to use
 +      color unless enabled explicitly with some other configuration
 +      or the `--color` option. Set it to `always` if you want all
 +      output not intended for machine consumption to use color, to
 +      `true` or `auto` (this is the default since Git 1.8.4) if you
 +      want such output to use color when written to the terminal.
  
  column.ui::
        Specify whether supported commands should output in columns.
        This variable consists of a list of tokens separated by spaces
        or commas:
  +
 +These options control when the feature should be enabled
 +(defaults to 'never'):
 ++
  --
  `always`;;
        always show in columns
        never show in columns
  `auto`;;
        show in columns if the output is to the terminal
 +--
 ++
 +These options control layout (defaults to 'column').  Setting any
 +of these implies 'always' if none of 'always', 'never', or 'auto' are
 +specified.
 ++
 +--
  `column`;;
 -      fill columns before rows (default)
 +      fill columns before rows
  `row`;;
        fill rows before columns
  `plain`;;
        show in one column
 +--
 ++
 +Finally, these options can be combined with a layout option (defaults
 +to 'nodense'):
 ++
 +--
  `dense`;;
        make unequal size columns to utilize more space
  `nodense`;;
        make equal size columns
  --
 -+
 -This option defaults to 'never'.
  
  column.branch::
        Specify whether to output branch listing in `git branch` in columns.
        See `column.ui` for details.
  
 +column.clean::
 +      Specify the layout when list items in `git clean -i`, which always
 +      shows files and directories in columns. See `column.ui` for details.
 +
  column.status::
        Specify whether to output untracked files in `git status` in columns.
        See `column.ui` for details.
@@@ -1061,6 -1049,10 +1061,10 @@@ fetch.unpackLimit:
        especially on slow filesystems.  If not set, the value of
        `transfer.unpackLimit` is used instead.
  
+ fetch.prune::
+       If true, fetch will automatically behave as if the `--prune`
+       option was given on the command line.  See also `remote.<name>.prune`.
  format.attach::
        Enable multipart/mixed attachments as the default for
        'format-patch'.  The value can also be a double quoted string
@@@ -1838,59 -1830,39 +1842,59 @@@ pull.twohead:
        The default merge strategy to use when pulling a single branch.
  
  push.default::
 -      Defines the action `git push` should take if no refspec is given
 -      on the command line, no refspec is configured in the remote, and
 -      no refspec is implied by any of the options given on the command
 -      line. Possible values are:
 +      Defines the action `git push` should take if no refspec is
 +      explicitly given.  Different values are well-suited for
 +      specific workflows; for instance, in a purely central workflow
 +      (i.e. the fetch source is equal to the push destination),
 +      `upstream` is probably what you want.  Possible values are:
  +
  --
 -* `nothing` - do not push anything.
 -* `matching` - push all branches having the same name in both ends.
 -  This is for those who prepare all the branches into a publishable
 -  shape and then push them out with a single command.  It is not
 -  appropriate for pushing into a repository shared by multiple users,
 -  since locally stalled branches will attempt a non-fast forward push
 -  if other users updated the branch.
 -  +
 -  This is currently the default, but Git 2.0 will change the default
 -  to `simple`.
 -* `upstream` - push the current branch to its upstream branch
 -  (`tracking` is a deprecated synonym for this).
 -  With this, `git push` will update the same remote ref as the one which
 -  is merged by `git pull`, making `push` and `pull` symmetrical.
 -  See "branch.<name>.merge" for how to configure the upstream branch.
 -* `simple` - like `upstream`, but refuses to push if the upstream
 -  branch's name is different from the local one. This is the safest
 -  option and is well-suited for beginners. It will become the default
 -  in Git 2.0.
 -* `current` - push the current branch to a branch of the same name.
 ---
 +
 +* `nothing` - do not push anything (error out) unless a refspec is
 +  explicitly given. This is primarily meant for people who want to
 +  avoid mistakes by always being explicit.
 +
 +* `current` - push the current branch to update a branch with the same
 +  name on the receiving end.  Works in both central and non-central
 +  workflows.
 +
 +* `upstream` - push the current branch back to the branch whose
 +  changes are usually integrated into the current branch (which is
 +  called `@{upstream}`).  This mode only makes sense if you are
 +  pushing to the same repository you would normally pull from
 +  (i.e. central workflow).
 +
 +* `simple` - in centralized workflow, work like `upstream` with an
 +  added safety to refuse to push if the upstream branch's name is
 +  different from the local one.
 ++
 +When pushing to a remote that is different from the remote you normally
 +pull from, work as `current`.  This is the safest option and is suited
 +for beginners.
 ++
 +This mode will become the default in Git 2.0.
 +
 +* `matching` - push all branches having the same name on both ends.
 +  This makes the repository you are pushing to remember the set of
 +  branches that will be pushed out (e.g. if you always push 'maint'
 +  and 'master' there and no other branches, the repository you push
 +  to will have these two branches, and your local 'maint' and
 +  'master' will be pushed there).
 ++
 +To use this mode effectively, you have to make sure _all_ the
 +branches you would push out are ready to be pushed out before
 +running 'git push', as the whole point of this mode is to allow you
 +to push all of the branches in one go.  If you usually finish work
 +on only one branch and push out the result, while other branches are
 +unfinished, this mode is not for you.  Also this mode is not
 +suitable for pushing into a shared central repository, as other
 +people may add new branches there, or update the tip of existing
 +branches outside your control.
  +
 -The `simple`, `current` and `upstream` modes are for those who want to
 -push out a single branch after finishing work, even when the other
 -branches are not yet ready to be pushed out. If you are working with
 -other people to push into the same shared repository, you would want
 -to use one of these.
 +This is currently the default, but Git 2.0 will change the default
 +to `simple`.
 +
 +--
  
  rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
  rebase.autosquash::
        If set to true enable '--autosquash' option by default.
  
 +rebase.autostash::
 +      When set to true, automatically create a temporary stash
 +      before the operation begins, and apply it after the operation
 +      ends.  This means that you can run rebase on a dirty worktree.
 +      However, use with care: the final stash application after a
 +      successful rebase might result in non-trivial conflicts.
 +      Defaults to false.
 +
  receive.autogc::
        By default, git-receive-pack will run "git-gc --auto" after
        receiving data from git-push and updating refs.  You can stop
@@@ -2024,6 -1988,12 +2028,12 @@@ remote.<name>.vcs:
        Setting this to a value <vcs> will cause Git to interact with
        the remote with the git-remote-<vcs> helper.
  
+ remote.<name>.prune::
+       When set to true, fetching from this remote by default will also
+       remove any remote-tracking branches which no longer exist on the
+       remote (as if the `--prune` option was give on the command line).
+       Overrides `fetch.prune` settings, if any.
  remotes.<group>::
        The list of remotes which are fetched by "git remote update
        <group>".  See linkgit:git-remote[1].
@@@ -2062,10 -2032,6 +2072,10 @@@ sendemail.smtpencryption:
  sendemail.smtpssl::
        Deprecated alias for 'sendemail.smtpencryption = ssl'.
  
 +sendemail.smtpsslcertpath::
 +      Path to ca-certificates (either a directory or a single file).
 +      Set it to an empty string to disable certificate verification.
 +
  sendemail.<identity>.*::
        Identity-specific versions of the 'sendemail.*' parameters
        found below, taking precedence over those when the this
@@@ -2110,14 -2076,6 +2120,14 @@@ status.relativePaths:
        relative to the repository root (this was the default for Git
        prior to v1.5.4).
  
 +status.short::
 +      Set to true to enable --short by default in linkgit:git-status[1].
 +      The option --no-short takes precedence over this variable.
 +
 +status.branch::
 +      Set to true to enable --branch by default in linkgit:git-status[1].
 +      The option --no-branch takes precedence over this variable.
 +
  status.showUntrackedFiles::
        By default, linkgit:git-status[1] and linkgit:git-commit[1] show
        files which are not currently tracked by Git. Directories which
diff --combined builtin/fetch.c
index 99afed03d4c95c02bc7cff8c568feb7e162439ae,08ab9480223dca2dc8999b6b9755058ffb0254bb..593653955201995227c062d3878aafc09b424940
@@@ -30,7 -30,11 +30,11 @@@ enum 
        TAGS_SET = 2
  };
  
- static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+ static int fetch_prune_config = -1; /* unspecified */
+ static int prune = -1; /* unspecified */
+ #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
+ static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
  static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  static int tags = TAGS_DEFAULT, unshallow;
  static const char *depth;
@@@ -54,17 -58,26 +58,26 @@@ static int option_parse_recurse_submodu
        return 0;
  }
  
+ static int git_fetch_config(const char *k, const char *v, void *cb)
+ {
+       if (!strcmp(k, "fetch.prune")) {
+               fetch_prune_config = git_config_bool(k, v);
+               return 0;
+       }
+       return 0;
+ }
  static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
 -      OPT_BOOLEAN(0, "all", &all,
 -                  N_("fetch from all remotes")),
 -      OPT_BOOLEAN('a', "append", &append,
 -                  N_("append to .git/FETCH_HEAD instead of overwriting")),
 +      OPT_BOOL(0, "all", &all,
 +               N_("fetch from all remotes")),
 +      OPT_BOOL('a', "append", &append,
 +               N_("append to .git/FETCH_HEAD instead of overwriting")),
        OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
                   N_("path to upload pack on remote end")),
        OPT__FORCE(&force, N_("force overwrite of local branch")),
 -      OPT_BOOLEAN('m', "multiple", &multiple,
 -                  N_("fetch from multiple remotes")),
 +      OPT_BOOL('m', "multiple", &multiple,
 +               N_("fetch from multiple remotes")),
        OPT_SET_INT('t', "tags", &tags,
                    N_("fetch all tags and associated objects"), TAGS_SET),
        OPT_SET_INT('n', NULL, &tags,
        { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
                    PARSE_OPT_OPTARG, option_parse_recurse_submodules },
 -      OPT_BOOLEAN(0, "dry-run", &dry_run,
 -                  N_("dry run")),
 -      OPT_BOOLEAN('k', "keep", &keep, N_("keep downloaded pack")),
 -      OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
 +      OPT_BOOL(0, "dry-run", &dry_run,
 +               N_("dry run")),
 +      OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
 +      OPT_BOOL('u', "update-head-ok", &update_head_ok,
                    N_("allow updating of HEAD ref")),
        OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
        OPT_STRING(0, "depth", &depth, N_("depth"),
@@@ -119,7 -132,7 +132,7 @@@ static void add_merge_config(struct re
  
                for (rm = *head; rm; rm = rm->next) {
                        if (branch_merge_matches(branch, i, rm->name)) {
 -                              rm->merge = 1;
 +                              rm->fetch_head_status = FETCH_HEAD_MERGE;
                                break;
                        }
                }
                refspec.src = branch->merge[i]->src;
                get_fetch_map(remote_refs, &refspec, tail, 1);
                for (rm = *old_tail; rm; rm = rm->next)
 -                      rm->merge = 1;
 +                      rm->fetch_head_status = FETCH_HEAD_MERGE;
        }
  }
  
@@@ -160,8 -173,6 +173,8 @@@ static struct ref *get_ref_map(struct t
        const struct ref *remote_refs = transport_get_remote_refs(transport);
  
        if (ref_count || tags == TAGS_SET) {
 +              struct ref **old_tail;
 +
                for (i = 0; i < ref_count; i++) {
                        get_fetch_map(remote_refs, &refs[i], &tail, 0);
                        if (refs[i].dst && refs[i].dst[0])
                }
                /* Merge everything on the command line, but not --tags */
                for (rm = ref_map; rm; rm = rm->next)
 -                      rm->merge = 1;
 +                      rm->fetch_head_status = FETCH_HEAD_MERGE;
                if (tags == TAGS_SET)
                        get_fetch_map(remote_refs, tag_refspec, &tail, 0);
 +
 +              /*
 +               * For any refs that we happen to be fetching via command-line
 +               * arguments, take the opportunity to update their configured
 +               * counterparts. However, we do not want to mention these
 +               * entries in FETCH_HEAD at all, as they would simply be
 +               * duplicates of existing entries.
 +               */
 +              old_tail = tail;
 +              for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
 +                      get_fetch_map(ref_map, &transport->remote->fetch[i],
 +                                    &tail, 1);
 +              for (rm = *old_tail; rm; rm = rm->next)
 +                      rm->fetch_head_status = FETCH_HEAD_IGNORE;
        } else {
                /* Use the defaults */
                struct remote *remote = transport->remote;
                                        *autotags = 1;
                                if (!i && !has_merge && ref_map &&
                                    !remote->fetch[0].pattern)
 -                                      ref_map->merge = 1;
 +                                      ref_map->fetch_head_status = FETCH_HEAD_MERGE;
                        }
                        /*
                         * if the remote we're fetching from is the same
                        ref_map = get_remote_ref(remote_refs, "HEAD");
                        if (!ref_map)
                                die(_("Couldn't find remote ref HEAD"));
 -                      ref_map->merge = 1;
 +                      ref_map->fetch_head_status = FETCH_HEAD_MERGE;
                        tail = &ref_map->next;
                }
        }
@@@ -405,7 -402,7 +418,7 @@@ static int store_updated_refs(const cha
        const char *what, *kind;
        struct ref *rm;
        char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
 -      int want_merge;
 +      int want_status;
  
        fp = fopen(filename, "a");
        if (!fp)
        }
  
        /*
 -       * The first pass writes objects to be merged and then the
 -       * second pass writes the rest, in order to allow using
 -       * FETCH_HEAD as a refname to refer to the ref to be merged.
 +       * We do a pass for each fetch_head_status type in their enum order, so
 +       * merged entries are written before not-for-merge. That lets readers
 +       * use FETCH_HEAD as a refname to refer to the ref to be merged.
         */
 -      for (want_merge = 1; 0 <= want_merge; want_merge--) {
 +      for (want_status = FETCH_HEAD_MERGE;
 +           want_status <= FETCH_HEAD_IGNORE;
 +           want_status++) {
                for (rm = ref_map; rm; rm = rm->next) {
                        struct ref *ref = NULL;
 +                      const char *merge_status_marker = "";
  
                        commit = lookup_commit_reference_gently(rm->old_sha1, 1);
                        if (!commit)
 -                              rm->merge = 0;
 +                              rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
  
 -                      if (rm->merge != want_merge)
 +                      if (rm->fetch_head_status != want_status)
                                continue;
  
                        if (rm->peer_ref) {
                                        strbuf_addf(&note, "%s ", kind);
                                strbuf_addf(&note, "'%s' of ", what);
                        }
 -                      fprintf(fp, "%s\t%s\t%s",
 -                              sha1_to_hex(rm->old_sha1),
 -                              rm->merge ? "" : "not-for-merge",
 -                              note.buf);
 -                      for (i = 0; i < url_len; ++i)
 -                              if ('\n' == url[i])
 -                                      fputs("\\n", fp);
 -                              else
 -                                      fputc(url[i], fp);
 -                      fputc('\n', fp);
 +                      switch (rm->fetch_head_status) {
 +                      case FETCH_HEAD_NOT_FOR_MERGE:
 +                              merge_status_marker = "not-for-merge";
 +                              /* fall-through */
 +                      case FETCH_HEAD_MERGE:
 +                              fprintf(fp, "%s\t%s\t%s",
 +                                      sha1_to_hex(rm->old_sha1),
 +                                      merge_status_marker,
 +                                      note.buf);
 +                              for (i = 0; i < url_len; ++i)
 +                                      if ('\n' == url[i])
 +                                              fputs("\\n", fp);
 +                                      else
 +                                              fputc(url[i], fp);
 +                              fputc('\n', fp);
 +                              break;
 +                      default:
 +                              /* do not write anything to FETCH_HEAD */
 +                              break;
 +                      }
  
                        strbuf_reset(&note);
                        if (ref) {
@@@ -600,8 -584,7 +613,8 @@@ static int add_existing(const char *ref
  {
        struct string_list *list = (struct string_list *)cbdata;
        struct string_list_item *item = string_list_insert(list, refname);
 -      item->util = (void *)sha1;
 +      item->util = xmalloc(20);
 +      hashcpy(item->util, sha1);
        return 0;
  }
  
@@@ -620,7 -603,7 +633,7 @@@ static void find_non_local_tags(struct 
                        struct ref **head,
                        struct ref ***tail)
  {
 -      struct string_list existing_refs = STRING_LIST_INIT_NODUP;
 +      struct string_list existing_refs = STRING_LIST_INIT_DUP;
        struct string_list remote_refs = STRING_LIST_INIT_NODUP;
        const struct ref *ref;
        struct string_list_item *item = NULL;
                item = string_list_insert(&remote_refs, ref->name);
                item->util = (void *)ref->old_sha1;
        }
 -      string_list_clear(&existing_refs, 0);
 +      string_list_clear(&existing_refs, 1);
  
        /*
         * We may have a final lightweight tag that needs to be
@@@ -723,11 -706,11 +736,11 @@@ static int truncate_fetch_head(void
  static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
  {
 -      struct string_list existing_refs = STRING_LIST_INIT_NODUP;
 -      struct string_list_item *peer_item = NULL;
 +      struct string_list existing_refs = STRING_LIST_INIT_DUP;
        struct ref *ref_map;
        struct ref *rm;
        int autotags = (transport->remote->fetch_tags == 1);
 +      int retcode = 0;
  
        for_each_ref(add_existing, &existing_refs);
  
  
        /* if not appending, truncate FETCH_HEAD */
        if (!append && !dry_run) {
 -              int errcode = truncate_fetch_head();
 -              if (errcode)
 -                      return errcode;
 +              retcode = truncate_fetch_head();
 +              if (retcode)
 +                      goto cleanup;
        }
  
        ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
  
        for (rm = ref_map; rm; rm = rm->next) {
                if (rm->peer_ref) {
 -                      peer_item = string_list_lookup(&existing_refs,
 -                                                     rm->peer_ref->name);
 +                      struct string_list_item *peer_item =
 +                              string_list_lookup(&existing_refs,
 +                                                 rm->peer_ref->name);
                        if (peer_item)
                                hashcpy(rm->peer_ref->old_sha1,
                                        peer_item->util);
                transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
        if (fetch_refs(transport, ref_map)) {
                free_refs(ref_map);
 -              return 1;
 +              retcode = 1;
 +              goto cleanup;
        }
        if (prune) {
-               /* If --tags was specified, pretend the user gave us the canonical tags refspec */
+               /*
+                * If --tags was specified, pretend that the user gave us
+                * the canonical tags refspec
+                */
                if (tags == TAGS_SET) {
                        const char *tags_str = "refs/tags/*:refs/tags/*";
                        struct refspec *tags_refspec, *refspec;
                free_refs(ref_map);
        }
  
 -      return 0;
 + cleanup:
 +      string_list_clear(&existing_refs, 1);
 +      return retcode;
  }
  
  static void set_option(const char *name, const char *value)
@@@ -882,7 -864,7 +898,7 @@@ static void add_options_to_argv(struct 
  {
        if (dry_run)
                argv_array_push(argv, "--dry-run");
-       if (prune)
+       if (prune > 0)
                argv_array_push(argv, "--prune");
        if (update_head_ok)
                argv_array_push(argv, "--update-head-ok");
@@@ -950,6 -932,17 +966,17 @@@ static int fetch_one(struct remote *rem
                    "remote name from which new revisions should be fetched."));
  
        transport = transport_get(remote, NULL);
+       if (prune < 0) {
+               /* no command line request */
+               if (0 <= transport->remote->prune)
+                       prune = transport->remote->prune;
+               else if (0 <= fetch_prune_config)
+                       prune = fetch_prune_config;
+               else
+                       prune = PRUNE_BY_DEFAULT;
+       }
        transport_set_verbosity(transport, verbosity, progress);
        if (upload_pack)
                set_option(TRANS_OPT_UPLOADPACK, upload_pack);
@@@ -1007,6 -1000,8 +1034,8 @@@ int cmd_fetch(int argc, const char **ar
        for (i = 1; i < argc; i++)
                strbuf_addf(&default_rla, " %s", argv[i]);
  
+       git_config(git_fetch_config, NULL);
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
  
diff --combined remote.c
index efcba931eca963bd6a5fd13f01a4859e0ae9e14d,89be21166d091e522bf6af8fe57c0cecd596bc93..8f0f2dd10e13ea8bd75e622af89acb02fe494440
+++ b/remote.c
@@@ -148,6 -148,7 +148,7 @@@ static struct remote *make_remote(cons
        }
  
        ret = xcalloc(1, sizeof(struct remote));
+       ret->prune = -1;  /* unspecified */
        ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
        remotes[remotes_nr++] = ret;
        if (len)
@@@ -276,9 -277,10 +277,9 @@@ static void read_remotes_file(struct re
  
  static void read_branches_file(struct remote *remote)
  {
 -      const char *slash = strchr(remote->name, '/');
        char *frag;
        struct strbuf branch = STRBUF_INIT;
 -      int n = slash ? slash - remote->name : 1000;
 +      int n = 1000;
        FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
        char *s, *p;
        int len;
        while (isspace(p[-1]))
                *--p = 0;
        len = p - s;
 -      if (slash)
 -              len += strlen(slash);
        p = xmalloc(len + 1);
        strcpy(p, s);
 -      if (slash)
 -              strcat(p, slash);
  
        /*
 -       * With "slash", e.g. "git fetch jgarzik/netdev-2.6" when
 -       * reading from $GIT_DIR/branches/jgarzik fetches "HEAD" from
 -       * the partial URL obtained from the branches file plus
 -       * "/netdev-2.6" and does not store it in any tracking ref.
 -       * #branch specifier in the file is ignored.
 -       *
 -       * Otherwise, the branches file would have URL and optionally
 +       * The branches file would have URL and optionally
         * #branch specified.  The "master" (or specified) branch is
         * fetched and stored in the local branch of the same name.
         */
                strbuf_addf(&branch, "refs/heads/%s", frag);
        } else
                strbuf_addstr(&branch, "refs/heads/master");
 -      if (!slash) {
 -              strbuf_addf(&branch, ":refs/heads/%s", remote->name);
 -      } else {
 -              strbuf_reset(&branch);
 -              strbuf_addstr(&branch, "HEAD:");
 -      }
 +
 +      strbuf_addf(&branch, ":refs/heads/%s", remote->name);
        add_url_alias(remote, p);
        add_fetch_refspec(remote, strbuf_detach(&branch, NULL));
        /*
@@@ -404,6 -420,8 +405,8 @@@ static int handle_config(const char *ke
                remote->skip_default_update = git_config_bool(key, value);
        else if (!strcmp(subkey, ".skipfetchall"))
                remote->skip_default_update = git_config_bool(key, value);
+       else if (!strcmp(subkey, ".prune"))
+               remote->prune = git_config_bool(key, value);
        else if (!strcmp(subkey, ".url")) {
                const char *v;
                if (git_config_string(&v, key, value))
@@@ -1302,14 -1320,6 +1305,14 @@@ static void add_missing_tags(struct re
        free(sent_tips.tip);
  }
  
 +static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
 +{
 +      for ( ; ref; ref = ref->next)
 +              string_list_append_nodup(ref_index, ref->name)->util = ref;
 +
 +      sort_string_list(ref_index);
 +}
 +
  /*
   * Given the set of refs the local repository has, the set of refs the
   * remote repository has, and the refspec used for push, determine
@@@ -1328,7 -1338,6 +1331,7 @@@ int match_push_refs(struct ref *src, st
        int errs;
        static const char *default_refspec[] = { ":", NULL };
        struct ref *ref, **dst_tail = tail_ref(dst);
 +      struct string_list dst_ref_index = STRING_LIST_INIT_NODUP;
  
        if (!nr_refspec) {
                nr_refspec = 1;
  
        /* pick the remainder */
        for (ref = src; ref; ref = ref->next) {
 +              struct string_list_item *dst_item;
                struct ref *dst_peer;
                const struct refspec *pat = NULL;
                char *dst_name;
                if (!dst_name)
                        continue;
  
 -              dst_peer = find_ref_by_name(*dst, dst_name);
 +              if (!dst_ref_index.nr)
 +                      prepare_ref_index(&dst_ref_index, *dst);
 +
 +              dst_item = string_list_lookup(&dst_ref_index, dst_name);
 +              dst_peer = dst_item ? dst_item->util : NULL;
                if (dst_peer) {
                        if (dst_peer->peer_ref)
                                /* We're already sending something to this ref. */
                        /* Create a new one and link it */
                        dst_peer = make_linked_ref(dst_name, &dst_tail);
                        hashcpy(dst_peer->new_sha1, ref->new_sha1);
 +                      string_list_insert(&dst_ref_index,
 +                              dst_peer->name)->util = dst_peer;
                }
                dst_peer->peer_ref = copy_ref(ref);
                dst_peer->force = pat->force;
                free(dst_name);
        }
  
 +      string_list_clear(&dst_ref_index, 0);
 +
        if (flags & MATCH_REFS_FOLLOW_TAGS)
                add_missing_tags(src, dst, &dst_tail);
  
        if (send_prune) {
 +              struct string_list src_ref_index = STRING_LIST_INIT_NODUP;
                /* check for missing refs on the remote */
                for (ref = *dst; ref; ref = ref->next) {
                        char *src_name;
  
                        src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL);
                        if (src_name) {
 -                              if (!find_ref_by_name(src, src_name))
 +                              if (!src_ref_index.nr)
 +                                      prepare_ref_index(&src_ref_index, src);
 +                              if (!string_list_has_string(&src_ref_index,
 +                                          src_name))
                                        ref->peer_ref = alloc_delete_ref();
                                free(src_name);
                        }
                }
 +              string_list_clear(&src_ref_index, 0);
        }
        if (errs)
                return -1;
@@@ -1482,7 -1477,8 +1485,7 @@@ struct branch *branch_get(const char *n
                ret->remote = remote_get(ret->remote_name);
                if (ret->merge_nr) {
                        int i;
 -                      ret->merge = xcalloc(sizeof(*ret->merge),
 -                                           ret->merge_nr);
 +                      ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
                        for (i = 0; i < ret->merge_nr; i++) {
                                ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
                                ret->merge[i]->src = xstrdup(ret->merge_name[i]);
diff --combined t/t5510-fetch.sh
index fde689166a5dcb419e0519580d628f91f1f29f83,019535f18e32816830a7e9f6a7baa34b92ab2cda..1f0f8e6827773b1bb9a419a822de2d1ebde8e37a
@@@ -370,39 -370,30 +370,39 @@@ test_expect_success 'bundle should reco
  
  '
  
 -test_expect_success 'explicit fetch should not update tracking' '
 +test_expect_success 'mark initial state of origin/master' '
 +      (
 +              cd three &&
 +              git tag base-origin-master refs/remotes/origin/master
 +      )
 +'
 +
 +test_expect_success 'explicit fetch should update tracking' '
  
        cd "$D" &&
        git branch -f side &&
        (
                cd three &&
 +              git update-ref refs/remotes/origin/master base-origin-master &&
                o=$(git rev-parse --verify refs/remotes/origin/master) &&
                git fetch origin master &&
                n=$(git rev-parse --verify refs/remotes/origin/master) &&
 -              test "$o" = "$n" &&
 +              test "$o" != "$n" &&
                test_must_fail git rev-parse --verify refs/remotes/origin/side
        )
  '
  
 -test_expect_success 'explicit pull should not update tracking' '
 +test_expect_success 'explicit pull should update tracking' '
  
        cd "$D" &&
        git branch -f side &&
        (
                cd three &&
 +              git update-ref refs/remotes/origin/master base-origin-master &&
                o=$(git rev-parse --verify refs/remotes/origin/master) &&
                git pull origin master &&
                n=$(git rev-parse --verify refs/remotes/origin/master) &&
 -              test "$o" = "$n" &&
 +              test "$o" != "$n" &&
                test_must_fail git rev-parse --verify refs/remotes/origin/side
        )
  '
@@@ -413,7 -404,6 +413,7 @@@ test_expect_success 'configured fetch u
        git branch -f side &&
        (
                cd three &&
 +              git update-ref refs/remotes/origin/master base-origin-master &&
                o=$(git rev-parse --verify refs/remotes/origin/master) &&
                git fetch origin &&
                n=$(git rev-parse --verify refs/remotes/origin/master) &&
        )
  '
  
 +test_expect_success 'non-matching refspecs do not confuse tracking update' '
 +      cd "$D" &&
 +      git update-ref refs/odd/location HEAD &&
 +      (
 +              cd three &&
 +              git update-ref refs/remotes/origin/master base-origin-master &&
 +              git config --add remote.origin.fetch \
 +                      refs/odd/location:refs/remotes/origin/odd &&
 +              o=$(git rev-parse --verify refs/remotes/origin/master) &&
 +              git fetch origin master &&
 +              n=$(git rev-parse --verify refs/remotes/origin/master) &&
 +              test "$o" != "$n" &&
 +              test_must_fail git rev-parse --verify refs/remotes/origin/odd
 +      )
 +'
 +
  test_expect_success 'pushing nonexistent branch by mistake should not segv' '
  
        cd "$D" &&
@@@ -497,6 -471,88 +497,88 @@@ test_expect_success "should be able to 
        )
  '
  
+ # configured prune tests
+ set_config_tristate () {
+       # var=$1 val=$2
+       case "$2" in
+       unset)  test_unconfig "$1" ;;
+       *)      git config "$1" "$2" ;;
+       esac
+ }
+ test_configured_prune () {
+       fetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4
+       test_expect_success "prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4" '
+               # make sure a newbranch is there in . and also in one
+               git branch -f newbranch &&
+               (
+                       cd one &&
+                       test_unconfig fetch.prune &&
+                       test_unconfig remote.origin.prune &&
+                       git fetch &&
+                       git rev-parse --verify refs/remotes/origin/newbranch
+               )
+               # now remove it
+               git branch -d newbranch &&
+               # then test
+               (
+                       cd one &&
+                       set_config_tristate fetch.prune $fetch_prune &&
+                       set_config_tristate remote.origin.prune $remote_origin_prune &&
+                       git fetch $cmdline &&
+                       case "$expected" in
+                       pruned)
+                               test_must_fail git rev-parse --verify refs/remotes/origin/newbranch
+                               ;;
+                       kept)
+                               git rev-parse --verify refs/remotes/origin/newbranch
+                               ;;
+                       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
  test_expect_success 'all boundary commits are excluded' '
        test_commit base &&
        test_commit oneside &&