Merge branch 'bg/fetch-multi'
authorJunio C Hamano <gitster@pobox.com>
Mon, 23 Nov 2009 08:03:15 +0000 (00:03 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 23 Nov 2009 08:03:15 +0000 (00:03 -0800)
* bg/fetch-multi:
Re-implement 'git remote update' using 'git fetch'
builtin-fetch: add --dry-run option
builtin-fetch: add --prune option
teach warn_dangling_symref to take a FILE argument
remote: refactor some logic into get_stale_heads()
Add missing test for 'git remote update --prune'
Add the configuration option skipFetchAll
Teach the --multiple option to 'git fetch'
Teach the --all option to 'git fetch'

1  2 
Documentation/config.txt
Documentation/git-fetch.txt
Documentation/pull-fetch-param.txt
builtin-fetch.c
builtin-remote.c
remote.c
t/t5505-remote.sh
diff --combined Documentation/config.txt
index 55d7e11da265843801ba53634dbb5f2f1d0ef20a,50a65ab8d1b375d2987732dfa636447c59daa16d..657f0016d39863770bd41f0eed5f746b6915516b
@@@ -131,11 -131,7 +131,11 @@@ advice.*:
  core.fileMode::
        If false, the executable bit differences between the index and
        the working copy are ignored; useful on broken filesystems like FAT.
 -      See linkgit:git-update-index[1]. True by default.
 +      See linkgit:git-update-index[1].
 ++
 +The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
 +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,
        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,
 +      like FAT. For example, if a directory listing finds
 +      "makefile" when git expects "Makefile", git will assume
 +      it is really the same file, and continue to remember it as
 +      "Makefile".
 ++
 +The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
 +will probe and set core.ignorecase true if appropriate when the repository
 +is created.
 +
  core.trustctime::
        If false, the ctime differences between the index and the
        working copy are ignored; useful when the inode change time
@@@ -185,10 -169,9 +185,10 @@@ core.autocrlf:
        writing to the filesystem.  The variable can be set to
        'input', in which case the conversion happens only while
        reading from the filesystem but files are written out with
 -      `LF` at the end of lines.  Currently, which paths to consider
 -      "text" (i.e. be subjected to the autocrlf mechanism) is
 -      decided purely based on the contents.
 +      `LF` at the end of lines.  A file is considered
 +      "text" (i.e. be subjected to the autocrlf mechanism) based on
 +      the file's `crlf` attribute, or if `crlf` is unspecified,
 +      based on the file's contents.  See linkgit:gitattributes[5].
  
  core.safecrlf::
        If true, makes git check if converting `CRLF` as controlled by
@@@ -240,11 -223,7 +240,11 @@@ core.symlinks:
        contain the link text. linkgit:git-update-index[1] and
        linkgit:git-add[1] will not change the recorded type to regular
        file. Useful on filesystems like FAT that do not support
 -      symbolic links. True by default.
 +      symbolic links.
 ++
 +The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
 +will probe and set core.symlinks false if appropriate when the repository
 +is created.
  
  core.gitProxy::
        A "proxy command" to execute (as 'command host port') instead
@@@ -401,15 -380,16 +401,15 @@@ Common unit suffixes of 'k', 'm', or 'g
  core.excludesfile::
        In addition to '.gitignore' (per-directory) and
        '.git/info/exclude', git looks into this file for patterns
 -      of files which are not meant to be tracked.  See
 -      linkgit:gitignore[5].
 +      of files which are not meant to be tracked.  "{tilde}/" is expanded
 +      to the value of `$HOME` and "{tilde}user/" to the specified user's
 +      home directory.  See linkgit:gitignore[5].
  
  core.editor::
        Commands such as `commit` and `tag` that lets you edit
        messages by launching an editor uses the value of this
        variable when it is set, and the environment variable
 -      `GIT_EDITOR` is not set.  The order of preference is
 -      `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
 -      `EDITOR` environment variables and then finally `vi`.
 +      `GIT_EDITOR` is not set.  See linkgit:git-var[1].
  
  core.pager::
        The command that git will use to paginate output.  Can
@@@ -436,17 -416,13 +436,17 @@@ core.whitespace:
        consider them as errors.  You can prefix `-` to disable
        any of them (e.g. `-trailing-space`):
  +
 -* `trailing-space` treats trailing whitespaces at the end of the line
 +* `blank-at-eol` treats trailing whitespaces at the end of the line
    as an error (enabled by default).
  * `space-before-tab` treats a space character that appears immediately
    before a tab character in the initial indent part of the line as an
    error (enabled by default).
  * `indent-with-non-tab` treats a line that is indented with 8 or more
    space characters as an error (not enabled by default).
 +* `blank-at-eof` treats blank lines added at the end of file as an error
 +  (enabled by default).
 +* `trailing-space` is a short-hand to cover both `blank-at-eol` and
 +  `blank-at-eof`.
  * `cr-at-eol` treats a carriage-return at the end of line as
    part of the line terminator, i.e. with it, `trailing-space`
    does not trigger if the character before such a carriage-return
@@@ -478,19 -454,6 +478,19 @@@ On some file system/operating system co
  Set this config setting to 'rename' there; However, This will remove the
  check that makes sure that existing object files will not get overwritten.
  
 +core.notesRef::
 +      When showing commit messages, also show notes which are stored in
 +      the given ref.  This ref is expected to contain files named
 +      after the full SHA-1 of the commit they annotate.
 ++
 +If such a file exists in the given ref, the referenced blob is read, and
 +appended to the commit message, separated by a "Notes:" line.  If the
 +given ref itself does not exist, it is not an error, but means that no
 +notes should be printed.
 ++
 +This setting defaults to "refs/notes/commits", and can be overridden by
 +the `GIT_NOTES_REF` environment variable.
 +
  add.ignore-errors::
        Tells 'git-add' to continue adding files when some files cannot be
        added due to indexing errors. Equivalent to the '--ignore-errors'
@@@ -703,8 -666,6 +703,8 @@@ color.ui:
  
  commit.template::
        Specify a file to use as the template for new commit messages.
 +      "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
 +      specified user's home directory.
  
  diff.autorefreshindex::
        When using 'git-diff' to compare with work tree
@@@ -1128,14 -1089,6 +1128,14 @@@ http.maxRequests:
        How many HTTP requests to launch in parallel. Can be overridden
        by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
  
 +http.postBuffer::
 +      Maximum size in bytes of the buffer used by smart HTTP
 +      transports when POSTing data to the remote system.
 +      For requests larger than this buffer size, HTTP/1.1 and
 +      Transfer-Encoding: chunked is used to avoid creating a
 +      massive pack file locally.  Default is 1 MiB, which is
 +      sufficient for most requests.
 +
  http.lowSpeedLimit, http.lowSpeedTime::
        If the HTTP transfer speed is less than 'http.lowSpeedLimit'
        for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
@@@ -1367,11 -1320,6 +1367,11 @@@ rebase.stat:
        Whether to show a diffstat of what changed upstream since the last
        rebase. False by default.
  
 +receive.autogc::
 +      By default, git-receive-pack will run "git-gc --auto" after
 +      receiving data from git-push and updating refs.  You can stop
 +      it by setting this variable to false.
 +
  receive.fsckObjects::
        If it is set to true, git-receive-pack will check all received
        objects. It will abort in the case of a malformed object or a
@@@ -1403,14 -1351,10 +1403,14 @@@ receive.denyCurrentBranch:
  
  receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
 -      not a fast forward. Use this to prevent such an update via a push,
 +      not a fast-forward. Use this to prevent such an update via a push,
        even if that push is forced. This configuration variable is
        set when initializing a shared repository.
  
 +receive.updateserverinfo::
 +      If set to true, git-receive-pack will run git-update-server-info
 +      after receiving data from git-push and updating refs.
 +
  remote.<name>.url::
        The URL of a remote repository.  See linkgit:git-fetch[1] or
        linkgit:git-push[1].
@@@ -1437,7 -1381,13 +1437,13 @@@ remote.<name>.mirror:
  
  remote.<name>.skipDefaultUpdate::
        If true, this remote will be skipped by default when updating
-       using the update subcommand of linkgit:git-remote[1].
+       using linkgit:git-fetch[1] or the `update` subcommand of
+       linkgit:git-remote[1].
+ remote.<name>.skipFetchAll::
+       If true, this remote will be skipped by default when updating
+       using linkgit:git-fetch[1] or the `update` subcommand of
+       linkgit:git-remote[1].
  
  remote.<name>.receivepack::
        The default program to execute on the remote side when pushing.  See
index f2483d624ead24031ef3cf320a0c151cc6f6cb2b,edb77dc54ef8789fc210731ec3c4e32b5d9d57b6..9b9e5686e4d4e1ca41bba1c5a50715463975cad1
@@@ -10,11 -10,17 +10,17 @@@ SYNOPSI
  --------
  'git fetch' <options> <repository> <refspec>...
  
+ 'git fetch' <options> <group>
+ 'git fetch' --multiple <options> [<repository> | <group>]...
+ 'git fetch' --all <options>
  
  DESCRIPTION
  -----------
- Fetches named heads or tags from another repository, along with
- the objects necessary to complete them.
+ Fetches named heads or tags from one or more other repositories,
along with the objects necessary to complete them.
  
  The ref names and their object names of fetched refs are stored
  in `.git/FETCH_HEAD`.  This information is left for a later merge
@@@ -28,6 -34,10 +34,10 @@@ pointed by remote tags that it does no
  those missing tags.  If the other end has tags that point at
  branches you are not interested in, you will not get them.
  
+ 'git fetch' can fetch from either a single named repository, or
+ or from several repositories at once if <group> is given and
+ there is a remotes.<group> entry in the configuration file.
+ (See linkgit:git-config[1]).
  
  OPTIONS
  -------
@@@ -37,35 -47,6 +47,35 @@@ include::pull-fetch-param.txt[
  
  include::urls-remotes.txt[]
  
 +
 +EXAMPLES
 +--------
 +
 +* Update the remote-tracking branches:
 ++
 +------------------------------------------------
 +$ git fetch origin
 +------------------------------------------------
 ++
 +The above command copies all branches from the remote refs/heads/
 +namespace and stores them to the local refs/remotes/origin/ namespace,
 +unless the branch.<name>.fetch option is used to specify a non-default
 +refspec.
 +
 +* Using refspecs explicitly:
 ++
 +------------------------------------------------
 +$ git fetch origin +pu:pu maint:tmp
 +------------------------------------------------
 ++
 +This updates (or creates, as necessary) branches `pu` and `tmp` in
 +the local repository by fetching from the branches (respectively)
 +`pu` and `maint` from the remote repository.
 ++
 +The `pu` branch will be updated even if it is does not fast-forward,
 +because it is prefixed with a plus sign; `tmp` will not be.
 +
 +
  SEE ALSO
  --------
  linkgit:git-pull[1]
index 44d936341fffe12d74b0ae6ec005f559f1de2af7,712b91aab3d149663d136f623b7526fd7f1bccd6..0551ebdfafb3f41dd0e31d3c74ce42fff33e38e1
@@@ -4,6 -4,13 +4,13 @@@
        (see the section <<URLS,GIT URLS>> below) or the name
        of a remote (see the section <<REMOTES,REMOTES>> below).
  
+ ifndef::git-pull[]
+ <group>::
+       A name referring to a list of repositories as the value
+       of remotes.<group> in the configuration file.
+       (See linkgit:git-config[1]).
+ endif::git-pull[]
  <refspec>::
        The format of a <refspec> parameter is an optional plus
        `{plus}`, followed by the source ref <src>, followed
@@@ -11,9 -18,9 +18,9 @@@
  +
  The remote ref that matches <src>
  is fetched, and if <dst> is not empty string, the local
 -ref that matches it is fast forwarded using <src>.
 +ref that matches it is fast-forwarded using <src>.
  If the optional plus `+` is used, the local ref
 -is updated even if it does not result in a fast forward
 +is updated even if it does not result in a fast-forward
  update.
  +
  [NOTE]
diff --combined builtin-fetch.c
index cd0bcf7140f11793f0e1e7e40149b9b101cf80c8,76ec5eab48bd08eea4da32ca802e720cff35d5ba..5b7db616dcf5cc3bb178b2c4dbb989322c6b2374
@@@ -14,6 -14,9 +14,9 @@@
  
  static const char * const builtin_fetch_usage[] = {
        "git fetch [options] [<repository> <refspec>...]",
+       "git fetch [options] <group>",
+       "git fetch --multiple [options] [<repository> | <group>]...",
+       "git fetch --all [options]",
        NULL
  };
  
@@@ -23,7 -26,7 +26,7 @@@ enum 
        TAGS_SET = 2
  };
  
- static int append, force, keep, update_head_ok, verbosity;
+ static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
  static int tags = TAGS_DEFAULT;
  static const char *depth;
  static const char *upload_pack;
@@@ -32,16 -35,24 +35,24 @@@ static struct transport *transport
  
  static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
+       OPT_BOOLEAN(0, "all", &all,
+                   "fetch from all remotes"),
        OPT_BOOLEAN('a', "append", &append,
                    "append to .git/FETCH_HEAD instead of overwriting"),
        OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
                   "path to upload pack on remote end"),
        OPT_BOOLEAN('f', "force", &force,
                    "force overwrite of local branch"),
+       OPT_BOOLEAN('m', "multiple", &multiple,
+                   "fetch from multiple remotes"),
        OPT_SET_INT('t', "tags", &tags,
                    "fetch all tags and associated objects", TAGS_SET),
        OPT_SET_INT('n', NULL, &tags,
                    "do not fetch all tags (--no-tags)", TAGS_UNSET),
+       OPT_BOOLEAN('p', "prune", &prune,
+                   "prune tracking branches no longer on remote"),
+       OPT_BOOLEAN(0, "dry-run", &dry_run,
+                   "dry run"),
        OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
        OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
                    "allow updating of HEAD ref"),
@@@ -178,6 -189,8 +189,8 @@@ static int s_update_ref(const char *act
        char *rla = getenv("GIT_REFLOG_ACTION");
        static struct ref_lock *lock;
  
+       if (dry_run)
+               return 0;
        if (!rla)
                rla = default_rla.buf;
        snprintf(msg, sizeof(msg), "%s: %s", rla, action);
@@@ -269,7 -282,7 +282,7 @@@ static int update_local_ref(struct ref 
                strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
                strcat(quickref, "..");
                strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
 -              r = s_update_ref("fast forward", ref, 1);
 +              r = s_update_ref("fast-forward", ref, 1);
                sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
                        SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
                        pretty_ref, r ? "  (unable to update local ref)" : "");
                        r ? "unable to update local ref" : "forced update");
                return r;
        } else {
 -              sprintf(display, "! %-*s %-*s -> %s  (non fast forward)",
 +              sprintf(display, "! %-*s %-*s -> %s  (non-fast-forward)",
                        SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
                        pretty_ref);
                return 1;
@@@ -303,7 -316,7 +316,7 @@@ static int store_updated_refs(const cha
        char note[1024];
        const char *what, *kind;
        struct ref *rm;
-       char *url, *filename = git_path("FETCH_HEAD");
+       char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
  
        fp = fopen(filename, "a");
        if (!fp)
@@@ -485,12 -498,33 +498,34 @@@ static int fetch_refs(struct transport 
        return ret;
  }
  
+ static int prune_refs(struct transport *transport, struct ref *ref_map)
+ {
+       int result = 0;
+       struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+       const char *dangling_msg = dry_run
+               ? "   (%s will become dangling)\n"
+               : "   (%s has become dangling)\n";
+       for (ref = stale_refs; ref; ref = ref->next) {
+               if (!dry_run)
+                       result |= delete_ref(ref->name, NULL, 0);
+               if (verbosity >= 0) {
+                       fprintf(stderr, " x %-*s %-*s -> %s\n",
+                               SUMMARY_WIDTH, "[deleted]",
+                               REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+                       warn_dangling_symref(stderr, dangling_msg, ref->name);
+               }
+       }
+       free_refs(stale_refs);
+       return result;
+ }
  static int add_existing(const char *refname, const unsigned char *sha1,
                        int flag, void *cbdata)
  {
        struct string_list *list = (struct string_list *)cbdata;
 -      string_list_insert(refname, list);
 +      struct string_list_item *item = string_list_insert(refname, list);
 +      item->util = (void *)sha1;
        return 0;
  }
  
@@@ -505,98 -539,57 +540,98 @@@ static int will_fetch(struct ref **head
        return 0;
  }
  
 +struct tag_data {
 +      struct ref **head;
 +      struct ref ***tail;
 +};
 +
 +static int add_to_tail(struct string_list_item *item, void *cb_data)
 +{
 +      struct tag_data *data = (struct tag_data *)cb_data;
 +      struct ref *rm = NULL;
 +
 +      /* We have already decided to ignore this item */
 +      if (!item->util)
 +              return 0;
 +
 +      rm = alloc_ref(item->string);
 +      rm->peer_ref = alloc_ref(item->string);
 +      hashcpy(rm->old_sha1, item->util);
 +
 +      **data->tail = rm;
 +      *data->tail = &rm->next;
 +
 +      return 0;
 +}
 +
  static void find_non_local_tags(struct transport *transport,
                        struct ref **head,
                        struct ref ***tail)
  {
        struct string_list existing_refs = { NULL, 0, 0, 0 };
 -      struct string_list new_refs = { NULL, 0, 0, 1 };
 -      char *ref_name;
 -      int ref_name_len;
 -      const unsigned char *ref_sha1;
 -      const struct ref *tag_ref;
 -      struct ref *rm = NULL;
 +      struct string_list remote_refs = { NULL, 0, 0, 0 };
 +      struct tag_data data = {head, tail};
        const struct ref *ref;
 +      struct string_list_item *item = NULL;
  
        for_each_ref(add_existing, &existing_refs);
        for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
                if (prefixcmp(ref->name, "refs/tags"))
                        continue;
  
 -              ref_name = xstrdup(ref->name);
 -              ref_name_len = strlen(ref_name);
 -              ref_sha1 = ref->old_sha1;
 -
 -              if (!strcmp(ref_name + ref_name_len - 3, "^{}")) {
 -                      ref_name[ref_name_len - 3] = 0;
 -                      tag_ref = transport_get_remote_refs(transport);
 -                      while (tag_ref) {
 -                              if (!strcmp(tag_ref->name, ref_name)) {
 -                                      ref_sha1 = tag_ref->old_sha1;
 -                                      break;
 -                              }
 -                              tag_ref = tag_ref->next;
 -                      }
 +              /*
 +               * The peeled ref always follows the matching base
 +               * ref, so if we see a peeled ref that we don't want
 +               * to fetch then we can mark the ref entry in the list
 +               * as one to ignore by setting util to NULL.
 +               */
 +              if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
 +                      if (item && !has_sha1_file(ref->old_sha1) &&
 +                          !will_fetch(head, ref->old_sha1) &&
 +                          !has_sha1_file(item->util) &&
 +                          !will_fetch(head, item->util))
 +                              item->util = NULL;
 +                      item = NULL;
 +                      continue;
                }
  
 -              if (!string_list_has_string(&existing_refs, ref_name) &&
 -                  !string_list_has_string(&new_refs, ref_name) &&
 -                  (has_sha1_file(ref->old_sha1) ||
 -                   will_fetch(head, ref->old_sha1))) {
 -                      string_list_insert(ref_name, &new_refs);
 +              /*
 +               * If item is non-NULL here, then we previously saw a
 +               * ref not followed by a peeled reference, so we need
 +               * to check if it is a lightweight tag that we want to
 +               * fetch.
 +               */
 +              if (item && !has_sha1_file(item->util) &&
 +                  !will_fetch(head, item->util))
 +                      item->util = NULL;
  
 -                      rm = alloc_ref(ref_name);
 -                      rm->peer_ref = alloc_ref(ref_name);
 -                      hashcpy(rm->old_sha1, ref_sha1);
 +              item = NULL;
  
 -                      **tail = rm;
 -                      *tail = &rm->next;
 -              }
 -              free(ref_name);
 +              /* skip duplicates and refs that we already have */
 +              if (string_list_has_string(&remote_refs, ref->name) ||
 +                  string_list_has_string(&existing_refs, ref->name))
 +                      continue;
 +
 +              item = string_list_insert(ref->name, &remote_refs);
 +              item->util = (void *)ref->old_sha1;
        }
        string_list_clear(&existing_refs, 0);
 -      string_list_clear(&new_refs, 0);
 +
 +      /*
 +       * We may have a final lightweight tag that needs to be
 +       * checked to see if it needs fetching.
 +       */
 +      if (item && !has_sha1_file(item->util) &&
 +          !will_fetch(head, item->util))
 +              item->util = NULL;
 +
 +      /*
 +       * For all the tags in the remote_refs string list, call
 +       * add_to_tail to add them to the list of refs to be fetched
 +       */
 +      for_each_string_list(add_to_tail, &remote_refs, &data);
 +
 +      string_list_clear(&remote_refs, 0);
  }
  
  static void check_not_current_branch(struct ref *ref_map)
  static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
  {
 +      struct string_list existing_refs = { NULL, 0, 0, 0 };
 +      struct string_list_item *peer_item = NULL;
        struct ref *ref_map;
        struct ref *rm;
        int autotags = (transport->remote->fetch_tags == 1);
 +
 +      for_each_ref(add_existing, &existing_refs);
 +
        if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
                tags = TAGS_SET;
        if (transport->remote->fetch_tags == -1)
                die("Don't know how to fetch from %s", transport->url);
  
        /* if not appending, truncate FETCH_HEAD */
-       if (!append) {
+       if (!append && !dry_run) {
                char *filename = git_path("FETCH_HEAD");
                FILE *fp = fopen(filename, "w");
                if (!fp)
                check_not_current_branch(ref_map);
  
        for (rm = ref_map; rm; rm = rm->next) {
 -              if (rm->peer_ref)
 -                      read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
 +              if (rm->peer_ref) {
 +                      peer_item = string_list_lookup(rm->peer_ref->name,
 +                                                     &existing_refs);
 +                      if (peer_item)
 +                              hashcpy(rm->peer_ref->old_sha1,
 +                                      peer_item->util);
 +              }
        }
  
        if (tags == TAGS_DEFAULT && autotags)
                free_refs(ref_map);
                return 1;
        }
+       if (prune)
+               prune_refs(transport, ref_map);
        free_refs(ref_map);
  
        /* if neither --no-tags nor --tags was specified, do automated tag
@@@ -691,33 -676,100 +728,100 @@@ static void set_option(const char *name
                        name, transport->url);
  }
  
- int cmd_fetch(int argc, const char **argv, const char *prefix)
+ static int get_one_remote_for_fetch(struct remote *remote, void *priv)
+ {
+       struct string_list *list = priv;
+       if (!remote->skip_default_update)
+               string_list_append(remote->name, list);
+       return 0;
+ }
+ struct remote_group_data {
+       const char *name;
+       struct string_list *list;
+ };
+ static int get_remote_group(const char *key, const char *value, void *priv)
+ {
+       struct remote_group_data *g = priv;
+       if (!prefixcmp(key, "remotes.") &&
+                       !strcmp(key + 8, g->name)) {
+               /* split list by white space */
+               int space = strcspn(value, " \t\n");
+               while (*value) {
+                       if (space > 1) {
+                               string_list_append(xstrndup(value, space),
+                                                  g->list);
+                       }
+                       value += space + (value[space] != '\0');
+                       space = strcspn(value, " \t\n");
+               }
+       }
+       return 0;
+ }
+ static int add_remote_or_group(const char *name, struct string_list *list)
+ {
+       int prev_nr = list->nr;
+       struct remote_group_data g = { name, list };
+       git_config(get_remote_group, &g);
+       if (list->nr == prev_nr) {
+               struct remote *remote;
+               if (!remote_is_configured(name))
+                       return 0;
+               remote = remote_get(name);
+               string_list_append(remote->name, list);
+       }
+       return 1;
+ }
+ static int fetch_multiple(struct string_list *list)
+ {
+       int i, result = 0;
+       const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL };
+       int argc = 1;
+       if (dry_run)
+               argv[argc++] = "--dry-run";
+       if (prune)
+               argv[argc++] = "--prune";
+       if (verbosity >= 2)
+               argv[argc++] = "-v";
+       if (verbosity >= 1)
+               argv[argc++] = "-v";
+       else if (verbosity < 0)
+               argv[argc++] = "-q";
+       for (i = 0; i < list->nr; i++) {
+               const char *name = list->items[i].string;
+               argv[argc] = name;
+               if (verbosity >= 0)
+                       printf("Fetching %s\n", name);
+               if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+                       error("Could not fetch %s", name);
+                       result = 1;
+               }
+       }
+       return result;
+ }
+ static int fetch_one(struct remote *remote, int argc, const char **argv)
  {
-       struct remote *remote;
        int i;
        static const char **refs = NULL;
        int ref_nr = 0;
        int exit_code;
  
-       /* Record the command line for the reflog */
-       strbuf_addstr(&default_rla, "fetch");
-       for (i = 1; i < argc; i++)
-               strbuf_addf(&default_rla, " %s", argv[i]);
-       argc = parse_options(argc, argv, prefix,
-                            builtin_fetch_options, builtin_fetch_usage, 0);
-       if (argc == 0)
-               remote = remote_get(NULL);
-       else
-               remote = remote_get(argv[0]);
        if (!remote)
                die("Where do you want to fetch from today?");
  
        transport = transport_get(remote, remote->url[0]);
        if (verbosity >= 2)
 -              transport->verbose = 1;
 +              transport->verbose = verbosity <= 3 ? verbosity : 3;
        if (verbosity < 0)
                transport->verbose = -1;
        if (upload_pack)
        if (depth)
                set_option(TRANS_OPT_DEPTH, depth);
  
-       if (argc > 1) {
+       if (argc > 0) {
                int j = 0;
                refs = xcalloc(argc + 1, sizeof(const char *));
-               for (i = 1; i < argc; i++) {
+               for (i = 0; i < argc; i++) {
                        if (!strcmp(argv[i], "tag")) {
                                char *ref;
                                i++;
        transport = NULL;
        return exit_code;
  }
+ int cmd_fetch(int argc, const char **argv, const char *prefix)
+ {
+       int i;
+       struct string_list list = { NULL, 0, 0, 0 };
+       struct remote *remote;
+       int result = 0;
+       /* Record the command line for the reflog */
+       strbuf_addstr(&default_rla, "fetch");
+       for (i = 1; i < argc; i++)
+               strbuf_addf(&default_rla, " %s", argv[i]);
+       argc = parse_options(argc, argv, prefix,
+                            builtin_fetch_options, builtin_fetch_usage, 0);
+       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);
+       } 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);
+               if (list.nr > 1) {
+                       /* 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);
+               }
+       }
+       /* All names were strdup()ed or strndup()ed */
+       list.strdup_strings = 1;
+       string_list_clear(&list, 0);
+       return result;
+ }
diff --combined builtin-remote.c
index b08608ef967a86ae6838bc5e5fd775697d212e0b,fb0d66d8c5ab94156326fe5eb9ef91acb4992c00..79166262182a1cb51839e20b8abcef8f6752b59f
@@@ -12,45 -12,10 +12,45 @@@ static const char * const builtin_remot
        "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
        "git remote rename <old> <new>",
        "git remote rm <name>",
 -      "git remote set-head <name> [-a | -d | <branch>]",
 -      "git remote show [-n] <name>",
 +      "git remote set-head <name> (-a | -d | <branch>)",
 +      "git remote [-v | --verbose] show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
 -      "git remote [-v | --verbose] update [-p | --prune] [group]",
 +      "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
 +      NULL
 +};
 +
 +static const char * const builtin_remote_add_usage[] = {
 +      "git remote add [<options>] <name> <url>",
 +      NULL
 +};
 +
 +static const char * const builtin_remote_rename_usage[] = {
 +      "git remote rename <old> <new>",
 +      NULL
 +};
 +
 +static const char * const builtin_remote_rm_usage[] = {
 +      "git remote rm <name>",
 +      NULL
 +};
 +
 +static const char * const builtin_remote_sethead_usage[] = {
 +      "git remote set-head <name> (-a | -d | <branch>])",
 +      NULL
 +};
 +
 +static const char * const builtin_remote_show_usage[] = {
 +      "git remote show [<options>] <name>",
 +      NULL
 +};
 +
 +static const char * const builtin_remote_prune_usage[] = {
 +      "git remote prune [<options>] <name>",
 +      NULL
 +};
 +
 +static const char * const builtin_remote_update_usage[] = {
 +      "git remote update [<options>] [<group> | <remote>]...",
        NULL
  };
  
@@@ -105,6 -70,7 +105,6 @@@ static int add(int argc, const char **a
        int i;
  
        struct option options[] = {
 -              OPT_GROUP("add specific options"),
                OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
                OPT_CALLBACK('t', "track", &track, "branch",
                        "branch(es) to track", opt_parse_track),
                OPT_END()
        };
  
 -      argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
 +      argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
                             0);
  
        if (argc < 2)
 -              usage_with_options(builtin_remote_usage, options);
 +              usage_with_options(builtin_remote_add_usage, options);
  
        name = argv[0];
        url = argv[1];
@@@ -261,32 -227,10 +261,10 @@@ struct ref_states 
        int queried;
  };
  
- static int handle_one_branch(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
- {
-       struct ref_states *states = cb_data;
-       struct refspec refspec;
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(states->remote, &refspec)) {
-               struct string_list_item *item;
-               const char *name = abbrev_branch(refspec.src);
-               /* symbolic refs pointing nowhere were handled already */
-               if ((flags & REF_ISSYMREF) ||
-                   string_list_has_string(&states->tracked, name) ||
-                   string_list_has_string(&states->new, name))
-                       return 0;
-               item = string_list_append(name, &states->stale);
-               item->util = xstrdup(refname);
-       }
-       return 0;
- }
  static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
  {
        struct ref *fetch_map = NULL, **tail = &fetch_map;
-       struct ref *ref;
+       struct ref *ref, *stale_refs;
        int i;
  
        for (i = 0; i < states->remote->fetch_refspec_nr; i++)
                else
                        string_list_append(abbrev_branch(ref->name), &states->tracked);
        }
+       stale_refs = get_stale_heads(states->remote, fetch_map);
+       for (ref = stale_refs; ref; ref = ref->next) {
+               struct string_list_item *item =
+                       string_list_append(abbrev_branch(ref->name), &states->stale);
+               item->util = xstrdup(ref->name);
+       }
+       free_refs(stale_refs);
        free_refs(fetch_map);
  
        sort_string_list(&states->new);
        sort_string_list(&states->tracked);
-       for_each_ref(handle_one_branch, states);
        sort_string_list(&states->stale);
  
        return 0;
@@@ -574,7 -524,7 +558,7 @@@ static int mv(int argc, const char **ar
        int i;
  
        if (argc != 3)
 -              usage_with_options(builtin_remote_usage, options);
 +              usage_with_options(builtin_remote_rename_usage, options);
  
        rename.old = argv[1];
        rename.new = argv[2];
@@@ -715,7 -665,7 +699,7 @@@ static int rm(int argc, const char **ar
        int i, result;
  
        if (argc != 2)
 -              usage_with_options(builtin_remote_usage, options);
 +              usage_with_options(builtin_remote_rm_usage, options);
  
        remote = remote_get(argv[1]);
        if (!remote)
@@@ -987,7 -937,7 +971,7 @@@ static int show_push_info_item(struct s
                status = "up to date";
                break;
        case PUSH_STATUS_FASTFORWARD:
 -              status = "fast forwardable";
 +              status = "fast-forwardable";
                break;
        case PUSH_STATUS_OUTOFDATE:
                status = "local out of date";
@@@ -1010,6 -960,7 +994,6 @@@ static int show(int argc, const char **
  {
        int no_query = 0, result = 0, query_flag = 0;
        struct option options[] = {
 -              OPT_GROUP("show specific options"),
                OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
                OPT_END()
        };
        struct string_list info_list = { NULL, 0, 0, 0 };
        struct show_info info;
  
 -      argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
 +      argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
                             0);
  
        if (argc < 1)
@@@ -1114,13 -1065,14 +1098,13 @@@ static int set_head(int argc, const cha
        char *head_name = NULL;
  
        struct option options[] = {
 -              OPT_GROUP("set-head specific options"),
                OPT_BOOLEAN('a', "auto", &opt_a,
                            "set refs/remotes/<name>/HEAD according to remote"),
                OPT_BOOLEAN('d', "delete", &opt_d,
                            "delete refs/remotes/<name>/HEAD"),
                OPT_END()
        };
 -      argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
 +      argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
                             0);
        if (argc)
                strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
                if (delete_ref(buf.buf, NULL, REF_NODEREF))
                        result |= error("Could not delete %s", buf.buf);
        } else
 -              usage_with_options(builtin_remote_usage, options);
 +              usage_with_options(builtin_remote_sethead_usage, options);
  
        if (head_name) {
                unsigned char sha1[20];
@@@ -1170,15 -1122,16 +1154,15 @@@ static int prune(int argc, const char *
  {
        int dry_run = 0, result = 0;
        struct option options[] = {
 -              OPT_GROUP("prune specific options"),
                OPT__DRY_RUN(&dry_run),
                OPT_END()
        };
  
 -      argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
 +      argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
                             0);
  
        if (argc < 1)
 -              usage_with_options(builtin_remote_usage, options);
 +              usage_with_options(builtin_remote_prune_usage, options);
  
        for (; argc; argc--, argv++)
                result |= prune_remote(*argv, dry_run);
@@@ -1213,94 -1166,63 +1197,62 @@@ static int prune_remote(const char *rem
  
                printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
                       abbrev_ref(refname, "refs/remotes/"));
-               warn_dangling_symref(dangling_msg, refname);
+               warn_dangling_symref(stdout, dangling_msg, refname);
        }
  
        free_remote_ref_states(&states);
        return result;
  }
  
- static int get_one_remote_for_update(struct remote *remote, void *priv)
+ static int get_remote_default(const char *key, const char *value, void *priv)
  {
-       struct string_list *list = priv;
-       if (!remote->skip_default_update)
-               string_list_append(remote->name, list);
-       return 0;
- }
- static struct remote_group {
-       const char *name;
-       struct string_list *list;
- } remote_group;
- static int get_remote_group(const char *key, const char *value, void *num_hits)
- {
-       if (!prefixcmp(key, "remotes.") &&
-                       !strcmp(key + 8, remote_group.name)) {
-               /* split list by white space */
-               int space = strcspn(value, " \t\n");
-               while (*value) {
-                       if (space > 1) {
-                               string_list_append(xstrndup(value, space),
-                                               remote_group.list);
-                               ++*((int *)num_hits);
-                       }
-                       value += space + (value[space] != '\0');
-                       space = strcspn(value, " \t\n");
-               }
+       if (strcmp(key, "remotes.default") == 0) {
+               int *found = priv;
+               *found = 1;
        }
        return 0;
  }
  
  static int update(int argc, const char **argv)
  {
-       int i, result = 0, prune = 0;
-       struct string_list list = { NULL, 0, 0, 0 };
-       static const char *default_argv[] = { NULL, "default", NULL };
+       int i, prune = 0;
        struct option options[] = {
 -              OPT_GROUP("update specific options"),
                OPT_BOOLEAN('p', "prune", &prune,
                            "prune remotes after fetching"),
                OPT_END()
        };
+       const char **fetch_argv;
+       int fetch_argc = 0;
+       int default_defined = 0;
+       fetch_argv = xmalloc(sizeof(char *) * (argc+5));
  
 -      argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
 +      argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
                             PARSE_OPT_KEEP_ARGV0);
-       if (argc < 2) {
-               argc = 2;
-               argv = default_argv;
-       }
  
-       remote_group.list = &list;
-       for (i = 1; i < argc; i++) {
-               int groups_found = 0;
-               remote_group.name = argv[i];
-               result = git_config(get_remote_group, &groups_found);
-               if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) {
-                       struct remote *remote;
-                       if (!remote_is_configured(argv[i]))
-                               die("No such remote or remote group: %s",
-                                   argv[i]);
-                       remote = remote_get(argv[i]);
-                       string_list_append(remote->name, remote_group.list);
-               }
-       }
+       fetch_argv[fetch_argc++] = "fetch";
  
-       if (!result && !list.nr  && argc == 2 && !strcmp(argv[1], "default"))
-               result = for_each_remote(get_one_remote_for_update, &list);
+       if (prune)
+               fetch_argv[fetch_argc++] = "--prune";
+       if (verbose)
+               fetch_argv[fetch_argc++] = "-v";
+       if (argc < 2) {
+               fetch_argv[fetch_argc++] = "default";
+       } else {
+               fetch_argv[fetch_argc++] = "--multiple";
+               for (i = 1; i < argc; i++)
+                       fetch_argv[fetch_argc++] = argv[i];
+       }
  
-       for (i = 0; i < list.nr; i++) {
-               int err = fetch_remote(list.items[i].string);
-               result |= err;
-               if (!err && prune)
-                       result |= prune_remote(list.items[i].string, 0);
+       if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
+               git_config(get_remote_default, &default_defined);
+               if (!default_defined)
+                       fetch_argv[fetch_argc-1] = "--all";
        }
  
-       /* all names were strdup()ed or strndup()ed */
-       list.strdup_strings = 1;
-       string_list_clear(&list, 0);
+       fetch_argv[fetch_argc] = NULL;
  
-       return result;
+       return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
  }
  
  static int get_one_entry(struct remote *remote, void *priv)
@@@ -1364,7 -1286,7 +1316,7 @@@ static int show_all(void
  int cmd_remote(int argc, const char **argv, const char *prefix)
  {
        struct option options[] = {
 -              OPT__VERBOSE(&verbose),
 +              OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
                OPT_END()
        };
        int result;
diff --combined remote.c
index e0d17bb83060561c17114905c253eebdf925a01c,eae586667405ef7b01d2a2176b9a55cebe22d924..b979a9642b81b456ab92cdf57e8eda944f01f92e
+++ b/remote.c
@@@ -397,7 -397,8 +397,8 @@@ static int handle_config(const char *ke
                remote->mirror = git_config_bool(key, value);
        else if (!strcmp(subkey, ".skipdefaultupdate"))
                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, ".url")) {
                const char *v;
                if (git_config_string(&v, key, value))
@@@ -735,33 -736,29 +736,33 @@@ int for_each_remote(each_remote_fn fn, 
  
  void ref_remove_duplicates(struct ref *ref_map)
  {
 -      struct ref **posn;
 -      struct ref *next;
 -      for (; ref_map; ref_map = ref_map->next) {
 +      struct string_list refs = { NULL, 0, 0, 0 };
 +      struct string_list_item *item = NULL;
 +      struct ref *prev = NULL, *next = NULL;
 +      for (; ref_map; prev = ref_map, ref_map = next) {
 +              next = ref_map->next;
                if (!ref_map->peer_ref)
                        continue;
 -              posn = &ref_map->next;
 -              while (*posn) {
 -                      if ((*posn)->peer_ref &&
 -                          !strcmp((*posn)->peer_ref->name,
 -                                  ref_map->peer_ref->name)) {
 -                              if (strcmp((*posn)->name, ref_map->name))
 -                                      die("%s tracks both %s and %s",
 -                                          ref_map->peer_ref->name,
 -                                          (*posn)->name, ref_map->name);
 -                              next = (*posn)->next;
 -                              free((*posn)->peer_ref);
 -                              free(*posn);
 -                              *posn = next;
 -                      } else {
 -                              posn = &(*posn)->next;
 -                      }
 +
 +              item = string_list_lookup(ref_map->peer_ref->name, &refs);
 +              if (item) {
 +                      if (strcmp(((struct ref *)item->util)->name,
 +                                 ref_map->name))
 +                              die("%s tracks both %s and %s",
 +                                  ref_map->peer_ref->name,
 +                                  ((struct ref *)item->util)->name,
 +                                  ref_map->name);
 +                      prev->next = ref_map->next;
 +                      free(ref_map->peer_ref);
 +                      free(ref_map);
 +                      ref_map = prev; /* skip this; we freed it */
 +                      continue;
                }
 +
 +              item = string_list_insert(ref_map->peer_ref->name, &refs);
 +              item->util = ref_map;
        }
 +      string_list_clear(&refs, 0);
  }
  
  int remote_has_url(struct remote *remote, const char *url)
@@@ -1591,3 -1588,42 +1592,42 @@@ struct ref *guess_remote_head(const str
  
        return list;
  }
+ struct stale_heads_info {
+       struct remote *remote;
+       struct string_list *ref_names;
+       struct ref **stale_refs_tail;
+ };
+ static int get_stale_heads_cb(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+ {
+       struct stale_heads_info *info = cb_data;
+       struct refspec refspec;
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (!remote_find_tracking(info->remote, &refspec)) {
+               if (!((flags & REF_ISSYMREF) ||
+                   string_list_has_string(info->ref_names, refspec.src))) {
+                       struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
+                       hashcpy(ref->new_sha1, sha1);
+               }
+       }
+       return 0;
+ }
+ struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
+ {
+       struct ref *ref, *stale_refs = NULL;
+       struct string_list ref_names = { NULL, 0, 0, 0 };
+       struct stale_heads_info info;
+       info.remote = remote;
+       info.ref_names = &ref_names;
+       info.stale_refs_tail = &stale_refs;
+       for (ref = fetch_map; ref; ref = ref->next)
+               string_list_append(ref->name, &ref_names);
+       sort_string_list(&ref_names);
+       for_each_ref(get_stale_heads_cb, &info);
+       string_list_clear(&ref_names, 0);
+       return stale_refs;
+ }
diff --combined t/t5505-remote.sh
index 220b6a341377fadfef34208dd7316bbb7e39e12d,e931ce6c69c8819b4273e0c81eb17bf8d8eacc5d..fd166d9de356dafb000504506285a6f77fcc0a37
@@@ -158,7 -158,7 +158,7 @@@ cat > test/expect << EO
      another
      master
    Local refs configured for 'git push':
 -    ahead  forces to master  (fast forwardable)
 +    ahead  forces to master  (fast-forwardable)
      master pushes to another (up to date)
  EOF
  
@@@ -365,6 -365,17 +365,17 @@@ test_expect_success 'update with argume
  
  '
  
+ test_expect_success 'update --prune' '
+       (cd one &&
+        git branch -m side2 side3) &&
+       (cd test &&
+        git remote update --prune &&
+        (cd ../one && git branch -m side3 side2)
+        git rev-parse refs/remotes/origin/side3 &&
+        test_must_fail git rev-parse refs/remotes/origin/side2)
+ '
  cat > one/expect << EOF
    apis/master
    apis/side