Merge branch 'bw/ref-prefix-for-configured-refspec'
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 May 2018 12:51:26 +0000 (21:51 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 May 2018 12:51:26 +0000 (21:51 +0900)
"git fetch $there $refspec" that talks over protocol v2 can take
advantage of server-side ref filtering; the code has been extended
so that this mechanism triggers also when fetching with configured
refspec.

* bw/ref-prefix-for-configured-refspec: (38 commits)
fetch: generate ref-prefixes when using a configured refspec
refspec: consolidate ref-prefix generation logic
submodule: convert push_unpushed_submodules to take a struct refspec
remote: convert check_push_refs to take a struct refspec
remote: convert match_push_refs to take a struct refspec
http-push: store refspecs in a struct refspec
transport: remove transport_verify_remote_names
send-pack: store refspecs in a struct refspec
transport: convert transport_push to take a struct refspec
push: convert to use struct refspec
push: check for errors earlier
remote: convert match_explicit_refs to take a struct refspec
remote: convert get_ref_match to take a struct refspec
remote: convert query_refspecs to take a struct refspec
remote: convert apply_refspecs to take a struct refspec
remote: convert get_stale_heads to take a struct refspec
fetch: convert prune_refs to take a struct refspec
fetch: convert get_ref_map to take a struct refspec
fetch: convert do_fetch to take a struct refspec
refspec: remove the deprecated functions
...

14 files changed:
1  2 
builtin/clone.c
builtin/fast-export.c
builtin/fetch.c
builtin/merge.c
builtin/pull.c
builtin/remote.c
builtin/submodule--helper.c
http-push.c
remote.c
remote.h
submodule.c
t/t5702-protocol-v2.sh
transport.c
transport.h
diff --combined builtin/clone.c
index ecd8495dab30953a87cdc5f0125b8d7dd18abcbd,8c5f4d8f074f13c62b7d6d5a3ea4deff80de2d58..99e73dae8595d37f704d0fe7f7fd84a190edfcf6
@@@ -14,6 -14,7 +14,7 @@@
  #include "parse-options.h"
  #include "fetch-pack.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "tree.h"
  #include "tree-walk.h"
  #include "unpack-trees.h"
@@@ -546,7 -547,7 +547,7 @@@ static struct ref *find_remote_branch(c
  }
  
  static struct ref *wanted_peer_refs(const struct ref *refs,
-               struct refspec *refspec)
+               struct refspec_item *refspec)
  {
        struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
        struct ref *local_refs = head;
@@@ -823,7 -824,7 +824,7 @@@ static void write_refspec_config(const 
                        } else if (remote_head_points_at) {
                                const char *head = remote_head_points_at->name;
                                if (!skip_prefix(head, "refs/heads/", &head))
 -                                      die("BUG: remote HEAD points at non-head?");
 +                                      BUG("remote HEAD points at non-head?");
  
                                strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
                                                branch_top->buf, head);
@@@ -894,8 -895,7 +895,7 @@@ int cmd_clone(int argc, const char **ar
        int err = 0, complete_refs_before_fetch = 1;
        int submodule_progress;
  
-       struct refspec *refspec;
-       const char *fetch_pattern;
+       struct refspec_item refspec;
  
        fetch_if_missing = 0;
  
        if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
  
-       fetch_pattern = value.buf;
-       refspec = parse_fetch_refspec(1, &fetch_pattern);
+       refspec_item_init(&refspec, value.buf, REFSPEC_FETCH);
  
        strbuf_reset(&value);
  
        refs = transport_get_remote_refs(transport, NULL);
  
        if (refs) {
-               mapped_refs = wanted_peer_refs(refs, refspec);
+               mapped_refs = wanted_peer_refs(refs, &refspec);
                /*
                 * transport_get_remote_refs() may return refs with null sha-1
                 * in mapped_refs (see struct transport->get_refs_list
        strbuf_release(&value);
        junk_mode = JUNK_LEAVE_ALL;
  
-       free(refspec);
+       refspec_item_clear(&refspec);
        return err;
  }
diff --combined builtin/fast-export.c
index f9568ddba07c2de09a95b52b8b101db29ce203ab,41fe49e4d6b76d4032d5768960bcc2ec1b4399fb..6c9768742fd4faecb3719e9c87997df3d490502a
@@@ -7,6 -7,7 +7,7 @@@
  #include "cache.h"
  #include "config.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "commit.h"
  #include "object.h"
  #include "tag.h"
@@@ -35,8 -36,7 +36,7 @@@ static int use_done_feature
  static int no_data;
  static int full_tree;
  static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
- static struct refspec *refspecs;
- static int refspecs_nr;
+ static struct refspec refspecs = REFSPEC_INIT_FETCH;
  static int anonymize;
  
  static int parse_opt_signed_tag_mode(const struct option *opt,
@@@ -156,14 -156,15 +156,14 @@@ static void anonymize_path(struct strbu
        }
  }
  
 -/* Since intptr_t is C99, we do not use it here */
 -static inline uint32_t *mark_to_ptr(uint32_t mark)
 +static inline void *mark_to_ptr(uint32_t mark)
  {
 -      return ((uint32_t *)NULL) + mark;
 +      return (void *)(uintptr_t)mark;
  }
  
  static inline uint32_t ptr_to_mark(void * mark)
  {
 -      return (uint32_t *)mark - (uint32_t *)NULL;
 +      return (uint32_t)(uintptr_t)mark;
  }
  
  static inline void mark_object(struct object *object, uint32_t mark)
@@@ -516,7 -517,7 +516,7 @@@ static void anonymize_ident_line(const 
        /* skip "committer", "author", "tagger", etc */
        end_of_header = strchr(*beg, ' ');
        if (!end_of_header)
 -              die("BUG: malformed line fed to anonymize_ident_line: %.*s",
 +              BUG("malformed line fed to anonymize_ident_line: %.*s",
                    (int)(*end - *beg), *beg);
        end_of_header++;
        strbuf_add(out, *beg, end_of_header - *beg);
@@@ -577,11 -578,11 +577,11 @@@ static void handle_commit(struct commi
            get_object_mark(&commit->parents->item->object) != 0 &&
            !full_tree) {
                parse_commit_or_die(commit->parents->item);
 -              diff_tree_oid(&commit->parents->item->tree->object.oid,
 -                            &commit->tree->object.oid, "", &rev->diffopt);
 +              diff_tree_oid(get_commit_tree_oid(commit->parents->item),
 +                            get_commit_tree_oid(commit), "", &rev->diffopt);
        }
        else
 -              diff_root_tree_oid(&commit->tree->object.oid,
 +              diff_root_tree_oid(get_commit_tree_oid(commit),
                                   "", &rev->diffopt);
  
        /* Export the referenced blobs, and remember the marks. */
@@@ -828,9 -829,9 +828,9 @@@ static void get_tags_and_duplicates(str
                if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1)
                        continue;
  
-               if (refspecs) {
+               if (refspecs.nr) {
                        char *private;
-                       private = apply_refspecs(refspecs, refspecs_nr, full_name);
+                       private = apply_refspecs(&refspecs, full_name);
                        if (private) {
                                free(full_name);
                                full_name = private;
@@@ -949,7 -950,7 +949,7 @@@ static void import_marks(char *input_fi
                if (last_idnum < mark)
                        last_idnum = mark;
  
 -              type = oid_object_info(&oid, NULL);
 +              type = oid_object_info(the_repository, &oid, NULL);
                if (type < 0)
                        die("object not found: %s", oid_to_hex(&oid));
  
  static void handle_deletes(void)
  {
        int i;
-       for (i = 0; i < refspecs_nr; i++) {
-               struct refspec *refspec = &refspecs[i];
+       for (i = 0; i < refspecs.nr; i++) {
+               struct refspec_item *refspec = &refspecs.items[i];
                if (*refspec->src)
                        continue;
  
@@@ -1038,18 -1039,12 +1038,12 @@@ int cmd_fast_export(int argc, const cha
                usage_with_options (fast_export_usage, options);
  
        if (refspecs_list.nr) {
-               const char **refspecs_str;
                int i;
  
-               ALLOC_ARRAY(refspecs_str, refspecs_list.nr);
                for (i = 0; i < refspecs_list.nr; i++)
-                       refspecs_str[i] = refspecs_list.items[i].string;
-               refspecs_nr = refspecs_list.nr;
-               refspecs = parse_fetch_refspec(refspecs_nr, refspecs_str);
+                       refspec_append(&refspecs, refspecs_list.items[i].string);
  
                string_list_clear(&refspecs_list, 1);
-               free(refspecs_str);
        }
  
        if (use_done_feature)
        if (use_done_feature)
                printf("done\n");
  
-       free_refspec(refspecs_nr, refspecs);
+       refspec_clear(&refspecs);
  
        return 0;
  }
diff --combined builtin/fetch.c
index 1f037e8e4b8ba41b66314c5dd9633e0164f86c0e,7cc7a52deefd5c490103e4c154e864b211c2bd29..c0d8ad1fe2aa7fe1ddbafa660746418301239463
@@@ -5,6 -5,7 +5,7 @@@
  #include "config.h"
  #include "repository.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "commit.h"
  #include "builtin.h"
  #include "string-list.h"
@@@ -59,10 -60,8 +60,9 @@@ static const char *submodule_prefix = "
  static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
  static int shown_url = 0;
- static int refmap_alloc, refmap_nr;
- static const char **refmap_array;
+ static struct refspec refmap = REFSPEC_INIT_FETCH;
  static struct list_objects_filter_options filter_options;
 +static struct string_list server_options = STRING_LIST_INIT_DUP;
  
  static int git_fetch_config(const char *k, const char *v, void *cb)
  {
@@@ -108,14 -107,12 +108,12 @@@ static int gitmodules_fetch_config(cons
  
  static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)
  {
-       ALLOC_GROW(refmap_array, refmap_nr + 1, refmap_alloc);
        /*
         * "git fetch --refmap='' origin foo"
         * can be used to tell the command not to store anywhere
         */
-       if (*arg)
-               refmap_array[refmap_nr++] = arg;
+       refspec_append(&refmap, arg);
        return 0;
  }
  
@@@ -171,7 -168,6 +169,7 @@@ static struct option builtin_fetch_opti
                 N_("accept refs that update .git/shallow")),
        { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"),
          N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg },
 +      OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")),
        OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
                        TRANSPORT_FAMILY_IPV4),
        OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@@ -204,7 -200,7 +202,7 @@@ static void add_merge_config(struct re
  
        for (i = 0; i < branch->merge_nr; i++) {
                struct ref *rm, **old_tail = *tail;
-               struct refspec refspec;
+               struct refspec_item refspec;
  
                for (rm = *head; rm; rm = rm->next) {
                        if (branch_merge_matches(branch, i, rm->name)) {
@@@ -341,7 -337,7 +339,7 @@@ static void find_non_local_tags(struct 
  }
  
  static struct ref *get_ref_map(struct transport *transport,
-                              struct refspec *refspecs, int refspec_count,
+                              struct refspec *rs,
                               int tags, int *autotags)
  {
        int i;
  
        const struct ref *remote_refs;
  
-       for (i = 0; i < refspec_count; i++) {
-               if (!refspecs[i].exact_sha1) {
-                       const char *glob = strchr(refspecs[i].src, '*');
-                       if (glob)
-                               argv_array_pushf(&ref_prefixes, "%.*s",
-                                                (int)(glob - refspecs[i].src),
-                                                refspecs[i].src);
-                       else
-                               expand_ref_prefix(&ref_prefixes, refspecs[i].src);
-               }
+       if (rs->nr)
+               refspec_ref_prefixes(rs, &ref_prefixes);
+       else if (transport->remote && transport->remote->fetch.nr)
+               refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);
+       if (ref_prefixes.argc &&
+           (tags == TAGS_SET || (tags == TAGS_DEFAULT && !rs->nr))) {
+               argv_array_push(&ref_prefixes, "refs/tags/");
        }
  
        remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
  
        argv_array_clear(&ref_prefixes);
  
-       if (refspec_count) {
+       if (rs->nr) {
                struct refspec *fetch_refspec;
-               int fetch_refspec_nr;
  
-               for (i = 0; i < refspec_count; i++) {
-                       get_fetch_map(remote_refs, &refspecs[i], &tail, 0);
-                       if (refspecs[i].dst && refspecs[i].dst[0])
+               for (i = 0; i < rs->nr; i++) {
+                       get_fetch_map(remote_refs, &rs->items[i], &tail, 0);
+                       if (rs->items[i].dst && rs->items[i].dst[0])
                                *autotags = 1;
                }
                /* Merge everything on the command line (but not --tags) */
                 * by ref_remove_duplicates() in favor of one of these
                 * opportunistic entries with FETCH_HEAD_IGNORE.
                 */
-               if (refmap_array) {
-                       fetch_refspec = parse_fetch_refspec(refmap_nr, refmap_array);
-                       fetch_refspec_nr = refmap_nr;
-               } else {
-                       fetch_refspec = transport->remote->fetch;
-                       fetch_refspec_nr = transport->remote->fetch_refspec_nr;
-               }
+               if (refmap.nr)
+                       fetch_refspec = &refmap;
+               else
+                       fetch_refspec = &transport->remote->fetch;
  
-               for (i = 0; i < fetch_refspec_nr; i++)
-                       get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1);
-       } else if (refmap_array) {
+               for (i = 0; i < fetch_refspec->nr; i++)
+                       get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1);
+       } else if (refmap.nr) {
                die("--refmap option is only meaningful with command-line refspec(s).");
        } else {
                /* Use the defaults */
                struct branch *branch = branch_get(NULL);
                int has_merge = branch_has_merge_config(branch);
                if (remote &&
-                   (remote->fetch_refspec_nr ||
+                   (remote->fetch.nr ||
                     /* Note: has_merge implies non-NULL branch->remote_name */
                     (has_merge && !strcmp(branch->remote_name, remote->name)))) {
-                       for (i = 0; i < remote->fetch_refspec_nr; i++) {
-                               get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
-                               if (remote->fetch[i].dst &&
-                                   remote->fetch[i].dst[0])
+                       for (i = 0; i < remote->fetch.nr; i++) {
+                               get_fetch_map(remote_refs, &remote->fetch.items[i], &tail, 0);
+                               if (remote->fetch.items[i].dst &&
+                                   remote->fetch.items[i].dst[0])
                                        *autotags = 1;
                                if (!i && !has_merge && ref_map &&
-                                   !remote->fetch[0].pattern)
+                                   !remote->fetch.items[0].pattern)
                                        ref_map->fetch_head_status = FETCH_HEAD_MERGE;
                        }
                        /*
@@@ -656,7 -646,7 +648,7 @@@ static int update_local_ref(struct ref 
        struct branch *current_branch = branch_get(NULL);
        const char *pretty_ref = prettify_refname(ref->name);
  
 -      type = oid_object_info(&ref->new_oid, NULL);
 +      type = oid_object_info(the_repository, &ref->new_oid, NULL);
        if (type < 0)
                die(_("object %s not found"), oid_to_hex(&ref->new_oid));
  
@@@ -966,11 -956,11 +958,11 @@@ static int fetch_refs(struct transport 
        return ret;
  }
  
- static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
-               const char *raw_url)
+ static int prune_refs(struct refspec *rs, struct ref *ref_map,
+                     const char *raw_url)
  {
        int url_len, i, result = 0;
-       struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
+       struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);
        char *url;
        int summary_width = transport_summary_width(stale_refs);
        const char *dangling_msg = dry_run
@@@ -1116,7 -1106,7 +1108,7 @@@ static void backfill_tags(struct transp
  }
  
  static int do_fetch(struct transport *transport,
-                   struct refspec *refs, int ref_count)
+                   struct refspec *rs)
  {
        struct string_list existing_refs = STRING_LIST_INIT_DUP;
        struct ref *ref_map;
                        goto cleanup;
        }
  
-       ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
+       ref_map = get_ref_map(transport, rs, tags, &autotags);
        if (!update_head_ok)
                check_not_current_branch(ref_map);
  
                 * explicitly (via command line or configuration); we
                 * don't care whether --tags was specified.
                 */
-               if (ref_count) {
-                       prune_refs(refs, ref_count, ref_map, transport->url);
+               if (rs->nr) {
+                       prune_refs(rs, ref_map, transport->url);
                } else {
-                       prune_refs(transport->remote->fetch,
-                                  transport->remote->fetch_refspec_nr,
+                       prune_refs(&transport->remote->fetch,
                                   ref_map,
                                   transport->url);
                }
@@@ -1357,10 -1346,8 +1348,8 @@@ static inline void fetch_one_setup_part
  
  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;
+       struct refspec rs = REFSPEC_INIT_FETCH;
+       int i;
        int exit_code;
        int maybe_prune_tags;
        int remote_via_config = remote_is_configured(remote, 0);
  
        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 i;
-               for (i = 0; i < argc; i++) {
-                       if (!strcmp(argv[i], "tag")) {
-                               i++;
-                               if (i >= argc)
-                                       die(_("You need to specify a tag name."));
-                               refs[j++] = xstrfmt("refs/tags/%s:refs/tags/%s",
-                                                   argv[i], argv[i]);
-                       } else
-                               refs[j++] = argv[i];
-                       ref_nr++;
+               refspec_append(&remote->fetch, TAG_REFSPEC);
+       if (maybe_prune_tags && (argc || !remote_via_config))
+               refspec_append(&rs, TAG_REFSPEC);
+       for (i = 0; i < argc; i++) {
+               if (!strcmp(argv[i], "tag")) {
+                       char *tag;
+                       i++;
+                       if (i >= argc)
+                               die(_("You need to specify a tag name."));
+                       tag = xstrfmt("refs/tags/%s:refs/tags/%s",
+                                     argv[i], argv[i]);
+                       refspec_append(&rs, tag);
+                       free(tag);
+               } else {
+                       refspec_append(&rs, argv[i]);
                }
        }
  
 +      if (server_options.nr)
 +              gtransport->server_options = &server_options;
 +
        sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
-       refspec = parse_fetch_refspec(ref_nr, refs);
-       exit_code = do_fetch(gtransport, refspec, ref_nr);
-       free_refspec(ref_nr, refspec);
+       exit_code = do_fetch(gtransport, &rs);
+       refspec_clear(&rs);
        transport_disconnect(gtransport);
        gtransport = NULL;
        return exit_code;
diff --combined builtin/merge.c
index fba0a7fc79881540826fd974a997dee956eb39de,c362cfe90dbaf73d755aa54aa87237d8658ca853..d85f99b7817d7b536d2749ee38f2d7c434286478
@@@ -14,6 -14,7 +14,7 @@@
  #include "run-command.h"
  #include "diff.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "commit.h"
  #include "diffcore.h"
  #include "revision.h"
@@@ -280,7 -281,7 +281,7 @@@ out
        return rc;
  }
  
 -static void read_empty(unsigned const char *sha1, int verbose)
 +static void read_empty(const struct object_id *oid, int verbose)
  {
        int i = 0;
        const char *args[7];
                args[i++] = "-v";
        args[i++] = "-m";
        args[i++] = "-u";
 -      args[i++] = EMPTY_TREE_SHA1_HEX;
 -      args[i++] = sha1_to_hex(sha1);
 +      args[i++] = empty_tree_oid_hex();
 +      args[i++] = oid_to_hex(oid);
        args[i] = NULL;
  
        if (run_command_v_opt(args, RUN_GIT_CMD))
                die(_("read-tree failed"));
  }
  
 -static void reset_hard(unsigned const char *sha1, int verbose)
 +static void reset_hard(const struct object_id *oid, int verbose)
  {
        int i = 0;
        const char *args[6];
                args[i++] = "-v";
        args[i++] = "--reset";
        args[i++] = "-u";
 -      args[i++] = sha1_to_hex(sha1);
 +      args[i++] = oid_to_hex(oid);
        args[i] = NULL;
  
        if (run_command_v_opt(args, RUN_GIT_CMD))
@@@ -324,7 -325,7 +325,7 @@@ static void restore_state(const struct 
        if (is_null_oid(stash))
                return;
  
 -      reset_hard(head->hash, 1);
 +      reset_hard(head, 1);
  
        args[2] = oid_to_hex(stash);
  
@@@ -647,7 -648,7 +648,7 @@@ static int try_merge_strategy(const cha
                              struct commit_list *remoteheads,
                              struct commit *head)
  {
 -      static struct lock_file lock;
 +      struct lock_file lock = LOCK_INIT;
        const char *head_arg = "HEAD";
  
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
@@@ -805,7 -806,7 +806,7 @@@ static int merge_trivial(struct commit 
  {
        struct object_id result_tree, result_commit;
        struct commit_list *parents, **pptr = &parents;
 -      static struct lock_file lock;
 +      struct lock_file lock = LOCK_INIT;
  
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        refresh_cache(REFRESH_QUIET);
@@@ -1297,7 -1298,7 +1298,7 @@@ int cmd_merge(int argc, const char **ar
                if (remoteheads->next)
                        die(_("Can merge only exactly one commit into empty head"));
                remote_head_oid = &remoteheads->item->object.oid;
 -              read_empty(remote_head_oid->hash, 0);
 +              read_empty(remote_head_oid, 0);
                update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0,
                           UPDATE_REFS_DIE_ON_ERR);
                goto done;
diff --combined builtin/pull.c
index 25f7db5b3a2bcd6f4ad477b752a3f7c77c64f943,09575fd23ce75b4bbf4e6a541649adc2f2107cba..1f2ecf3a88d95de5f819285aa1fc1558b63b6c4e
@@@ -15,6 -15,7 +15,7 @@@
  #include "remote.h"
  #include "dir.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "revision.h"
  #include "submodule.h"
  #include "submodule-config.h"
@@@ -27,16 -28,14 +28,16 @@@ enum rebase_type 
        REBASE_FALSE = 0,
        REBASE_TRUE,
        REBASE_PRESERVE,
 +      REBASE_MERGES,
        REBASE_INTERACTIVE
  };
  
  /**
   * Parses the value of --rebase. If value is a false value, returns
   * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
 - * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with
 - * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
 + * "merges", returns REBASE_MERGES. If value is "preserve", returns
 + * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if
 + * fatal is true, otherwise returns REBASE_INVALID.
   */
  static enum rebase_type parse_config_rebase(const char *key, const char *value,
                int fatal)
@@@ -49,8 -48,6 +50,8 @@@
                return REBASE_TRUE;
        else if (!strcmp(value, "preserve"))
                return REBASE_PRESERVE;
 +      else if (!strcmp(value, "merges"))
 +              return REBASE_MERGES;
        else if (!strcmp(value, "interactive"))
                return REBASE_INTERACTIVE;
  
@@@ -134,7 -131,7 +135,7 @@@ static struct option pull_options[] = 
        /* Options passed to git-merge or git-rebase */
        OPT_GROUP(N_("Options related to merging")),
        { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
 -        "false|true|preserve|interactive",
 +        "false|true|merges|preserve|interactive",
          N_("incorporate changes by rebasing rather than merging"),
          PARSE_OPT_OPTARG, parse_opt_rebase },
        OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
@@@ -543,7 -540,7 +544,7 @@@ static int run_fetch(const char *repo, 
                argv_array_push(&args, repo);
                argv_array_pushv(&args, refspecs);
        } else if (*refspecs)
 -              die("BUG: refspecs without repo?");
 +              BUG("refspecs without repo?");
        ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
        argv_array_clear(&args);
        return ret;
@@@ -679,12 -676,12 +680,12 @@@ static const char *get_upstream_branch(
   */
  static const char *get_tracking_branch(const char *remote, const char *refspec)
  {
-       struct refspec *spec;
+       struct refspec_item spec;
        const char *spec_src;
        const char *merge_branch;
  
-       spec = parse_fetch_refspec(1, &refspec);
-       spec_src = spec->src;
+       refspec_item_init(&spec, refspec, REFSPEC_FETCH);
+       spec_src = spec.src;
        if (!*spec_src || !strcmp(spec_src, "HEAD"))
                spec_src = "HEAD";
        else if (skip_prefix(spec_src, "heads/", &spec_src))
        } else
                merge_branch = NULL;
  
-       free_refspec(1, spec);
+       refspec_item_clear(&spec);
        return merge_branch;
  }
  
@@@ -804,9 -801,7 +805,9 @@@ static int run_rebase(const struct obje
        argv_push_verbosity(&args);
  
        /* Options passed to git-rebase */
 -      if (opt_rebase == REBASE_PRESERVE)
 +      if (opt_rebase == REBASE_MERGES)
 +              argv_array_push(&args, "--rebase-merges");
 +      else if (opt_rebase == REBASE_PRESERVE)
                argv_array_push(&args, "--preserve-merges");
        else if (opt_rebase == REBASE_INTERACTIVE)
                argv_array_push(&args, "--interactive");
diff --combined builtin/remote.c
index 0bbf9f4c9e81f92b7a74b112f7c4f328bb1c92a2,b84175cc6ce98c85f5596d1029a09398912bd097..1a82d850a22c3bfc3176eeda3f8b061b6feb7dc4
@@@ -7,6 -7,7 +7,7 @@@
  #include "strbuf.h"
  #include "run-command.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "argv-array.h"
  
  static const char * const builtin_remote_usage[] = {
@@@ -245,9 -246,7 +246,9 @@@ static int add(int argc, const char **a
  struct branch_info {
        char *remote_name;
        struct string_list merge;
 -      enum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;
 +      enum {
 +              NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES
 +      } rebase;
  };
  
  static struct string_list branch_list = STRING_LIST_INIT_NODUP;
@@@ -308,8 -307,6 +309,8 @@@ static int config_read_branches(const c
                                info->rebase = v;
                        else if (!strcmp(value, "preserve"))
                                info->rebase = NORMAL_REBASE;
 +                      else if (!strcmp(value, "merges"))
 +                              info->rebase = REBASE_MERGES;
                        else if (!strcmp(value, "interactive"))
                                info->rebase = INTERACTIVE_REBASE;
                }
@@@ -336,10 -333,10 +337,10 @@@ static int get_ref_states(const struct 
        struct ref *ref, *stale_refs;
        int i;
  
-       for (i = 0; i < states->remote->fetch_refspec_nr; i++)
-               if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
+       for (i = 0; i < states->remote->fetch.nr; i++)
+               if (get_fetch_map(remote_refs, &states->remote->fetch.items[i], &tail, 1))
                        die(_("Could not get fetch map for refspec %s"),
-                               states->remote->fetch_refspec[i]);
+                               states->remote->fetch.raw[i]);
  
        states->new_refs.strdup_strings = 1;
        states->tracked.strdup_strings = 1;
                else
                        string_list_append(&states->tracked, abbrev_branch(ref->name));
        }
-       stale_refs = get_stale_heads(states->remote->fetch,
-                                    states->remote->fetch_refspec_nr, fetch_map);
+       stale_refs = get_stale_heads(&states->remote->fetch, fetch_map);
        for (ref = stale_refs; ref; ref = ref->next) {
                struct string_list_item *item =
                        string_list_append(&states->stale, abbrev_branch(ref->name));
@@@ -391,8 -387,7 +391,7 @@@ static int get_push_ref_states(const st
        local_refs = get_local_heads();
        push_map = copy_ref_list(remote_refs);
  
-       match_push_refs(local_refs, &push_map, remote->push_refspec_nr,
-                       remote->push_refspec, MATCH_REFS_NONE);
+       match_push_refs(local_refs, &push_map, &remote->push, MATCH_REFS_NONE);
  
        states->push.strdup_strings = 1;
        for (ref = push_map; ref; ref = ref->next) {
@@@ -438,14 -433,14 +437,14 @@@ static int get_push_ref_states_noquery(
                return 0;
  
        states->push.strdup_strings = 1;
-       if (!remote->push_refspec_nr) {
+       if (!remote->push.nr) {
                item = string_list_append(&states->push, _("(matching)"));
                info = item->util = xcalloc(1, sizeof(struct push_info));
                info->status = PUSH_STATUS_NOTQUERIED;
                info->dest = xstrdup(item->string);
        }
-       for (i = 0; i < remote->push_refspec_nr; i++) {
-               struct refspec *spec = remote->push + i;
+       for (i = 0; i < remote->push.nr; i++) {
+               const struct refspec_item *spec = &remote->push.items[i];
                if (spec->matching)
                        item = string_list_append(&states->push, _("(matching)"));
                else if (strlen(spec->src))
@@@ -465,7 -460,7 +464,7 @@@ static int get_head_names(const struct 
  {
        struct ref *ref, *matches;
        struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
-       struct refspec refspec;
+       struct refspec_item refspec;
  
        refspec.force = 0;
        refspec.pattern = 1;
@@@ -518,7 -513,7 +517,7 @@@ static int add_branch_for_removal(cons
        const struct object_id *oid, int flags, void *cb_data)
  {
        struct branches_for_remote *branches = cb_data;
-       struct refspec refspec;
+       struct refspec_item refspec;
        struct known_remote *kr;
  
        memset(&refspec, 0, sizeof(refspec));
@@@ -589,12 -584,12 +588,12 @@@ static int migrate_file(struct remote *
                git_config_set_multivar(buf.buf, remote->url[i], "^$", 0);
        strbuf_reset(&buf);
        strbuf_addf(&buf, "remote.%s.push", remote->name);
-       for (i = 0; i < remote->push_refspec_nr; i++)
-               git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0);
+       for (i = 0; i < remote->push.raw_nr; i++)
+               git_config_set_multivar(buf.buf, remote->push.raw[i], "^$", 0);
        strbuf_reset(&buf);
        strbuf_addf(&buf, "remote.%s.fetch", remote->name);
-       for (i = 0; i < remote->fetch_refspec_nr; i++)
-               git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0);
+       for (i = 0; i < remote->fetch.raw_nr; i++)
+               git_config_set_multivar(buf.buf, remote->fetch.raw[i], "^$", 0);
        if (remote->origin == REMOTE_REMOTES)
                unlink_or_warn(git_path("remotes/%s", remote->name));
        else if (remote->origin == REMOTE_BRANCHES)
@@@ -649,11 -644,11 +648,11 @@@ static int mv(int argc, const char **ar
        strbuf_addf(&buf, "remote.%s.fetch", rename.new_name);
        git_config_set_multivar(buf.buf, NULL, NULL, 1);
        strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name);
-       for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
+       for (i = 0; i < oldremote->fetch.raw_nr; i++) {
                char *ptr;
  
                strbuf_reset(&buf2);
-               strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
+               strbuf_addstr(&buf2, oldremote->fetch.raw[i]);
                ptr = strstr(buf2.buf, old_remote_context.buf);
                if (ptr) {
                        refspec_updated = 1;
@@@ -837,7 -832,7 +836,7 @@@ static int append_ref_to_tracked_list(c
        const struct object_id *oid, int flags, void *cb_data)
  {
        struct ref_states *states = cb_data;
-       struct refspec refspec;
+       struct refspec_item refspec;
  
        if (flags & REF_ISSYMREF)
                return 0;
@@@ -967,15 -962,9 +966,15 @@@ static int show_local_info_item(struct 
  
        printf("    %-*s ", show_info->width, item->string);
        if (branch_info->rebase) {
 -              printf_ln(branch_info->rebase == INTERACTIVE_REBASE
 -                        ? _("rebases interactively onto remote %s")
 -                        : _("rebases onto remote %s"), merge->items[0].string);
 +              const char *msg;
 +              if (branch_info->rebase == INTERACTIVE_REBASE)
 +                      msg = _("rebases interactively onto remote %s");
 +              else if (branch_info->rebase == REBASE_MERGES)
 +                      msg = _("rebases interactively (with merges) onto "
 +                              "remote %s");
 +              else
 +                      msg = _("rebases onto remote %s");
 +              printf_ln(msg, merge->items[0].string);
                return 0;
        } else if (show_info->any_rebase) {
                printf_ln(_(" merges with remote %s"), merge->items[0].string);
index 7c3cd9dbeba68e8c5581992496ec26e7db025ec5,88a149a2c3846cb0dc103de62a72d256e282e566..bd250ca2164b31356fb42406351761cd2381d115
@@@ -12,6 -12,7 +12,7 @@@
  #include "run-command.h"
  #include "remote.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "connect.h"
  #include "revision.h"
  #include "diffcore.h"
@@@ -1065,7 -1066,7 +1066,7 @@@ static int module_deinit(int argc, cons
  }
  
  static int clone_submodule(const char *path, const char *gitdir, const char *url,
 -                         const char *depth, struct string_list *reference,
 +                         const char *depth, struct string_list *reference, int dissociate,
                           int quiet, int progress)
  {
        struct child_process cp = CHILD_PROCESS_INIT;
                        argv_array_pushl(&cp.args, "--reference",
                                         item->string, NULL);
        }
 +      if (dissociate)
 +              argv_array_push(&cp.args, "--dissociate");
        if (gitdir && *gitdir)
                argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
  
@@@ -1201,7 -1200,6 +1202,7 @@@ static int module_clone(int argc, cons
        char *p, *path = NULL, *sm_gitdir;
        struct strbuf sb = STRBUF_INIT;
        struct string_list reference = STRING_LIST_INIT_NODUP;
 +      int dissociate = 0;
        char *sm_alternate = NULL, *error_strategy = NULL;
  
        struct option module_clone_options[] = {
                OPT_STRING_LIST(0, "reference", &reference,
                           N_("repo"),
                           N_("reference repository")),
 +              OPT_BOOL(0, "dissociate", &dissociate,
 +                         N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &depth,
                           N_("string"),
                           N_("depth for shallow clones")),
  
                prepare_possible_alternates(name, &reference);
  
 -              if (clone_submodule(path, sm_gitdir, url, depth, &reference,
 +              if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
                                    quiet, progress))
                        die(_("clone of '%s' into submodule path '%s' failed"),
                            url, path);
@@@ -1313,7 -1309,6 +1314,7 @@@ struct submodule_update_clone 
        int quiet;
        int recommend_shallow;
        struct string_list references;
 +      int dissociate;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
        int failed_clones_nr, failed_clones_alloc;
  };
  #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
 -      SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
 +      SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
        NULL, NULL, NULL, \
        STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
  
@@@ -1456,8 -1451,6 +1457,8 @@@ static int prepare_to_clone_next_submod
                for_each_string_list_item(item, &suc->references)
                        argv_array_pushl(&child->args, "--reference", item->string, NULL);
        }
 +      if (suc->dissociate)
 +              argv_array_push(&child->args, "--dissociate");
        if (suc->depth)
                argv_array_push(&child->args, suc->depth);
  
@@@ -1591,8 -1584,6 +1592,8 @@@ static int update_clone(int argc, cons
                           N_("rebase, merge, checkout or none")),
                OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
                           N_("reference repository")),
 +              OPT_BOOL(0, "dissociate", &suc.dissociate,
 +                         N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &suc.depth, "<depth>",
                           N_("Create a shallow clone truncated to the "
                              "specified number of revisions")),
@@@ -1753,13 -1744,14 +1754,14 @@@ static int push_check(int argc, const c
  
        /* Check the refspec */
        if (argc > 2) {
-               int i, refspec_nr = argc - 2;
+               int i;
                struct ref *local_refs = get_local_heads();
-               struct refspec *refspec = parse_push_refspec(refspec_nr,
-                                                            argv + 2);
+               struct refspec refspec = REFSPEC_INIT_PUSH;
  
-               for (i = 0; i < refspec_nr; i++) {
-                       struct refspec *rs = refspec + i;
+               refspec_appendn(&refspec, argv + 2, argc - 2);
+               for (i = 0; i < refspec.nr; i++) {
+                       const struct refspec_item *rs = &refspec.items[i];
  
                        if (rs->pattern || rs->matching)
                                continue;
                                    rs->src);
                        }
                }
-               free_refspec(refspec_nr, refspec);
+               refspec_clear(&refspec);
        }
        free(head);
  
@@@ -1835,29 -1827,6 +1837,29 @@@ static int is_active(int argc, const ch
        return !is_submodule_active(the_repository, argv[1]);
  }
  
 +/*
 + * Exit non-zero if any of the submodule names given on the command line is
 + * invalid. If no names are given, filter stdin to print only valid names
 + * (which is primarily intended for testing).
 + */
 +static int check_name(int argc, const char **argv, const char *prefix)
 +{
 +      if (argc > 1) {
 +              while (*++argv) {
 +                      if (check_submodule_name(*argv) < 0)
 +                              return 1;
 +              }
 +      } else {
 +              struct strbuf buf = STRBUF_INIT;
 +              while (strbuf_getline(&buf, stdin) != EOF) {
 +                      if (!check_submodule_name(buf.buf))
 +                              printf("%s\n", buf.buf);
 +              }
 +              strbuf_release(&buf);
 +      }
 +      return 0;
 +}
 +
  #define SUPPORT_SUPER_PREFIX (1<<0)
  
  struct cmd_struct {
@@@ -1883,7 -1852,6 +1885,7 @@@ static struct cmd_struct commands[] = 
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
        {"is-active", is_active, 0},
 +      {"check-name", check_name, 0},
  };
  
  int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --combined http-push.c
index 2669f4bfa1e2e47226f6e58084f35f4a64b21226,ea5af6227e075389417b4f541b3cfbe3041bb354..7e38522098bf91c144adaee7f9f5476f8bc72cd2
@@@ -1331,7 -1331,7 +1331,7 @@@ static int get_delta(struct rev_info *r
        int count = 0;
  
        while ((commit = get_revision(revs)) != NULL) {
 -              p = process_tree(commit->tree, p);
 +              p = process_tree(get_commit_tree(commit), p);
                commit->object.flags |= LOCAL;
                if (!(commit->object.flags & UNINTERESTING))
                        count += add_send_request(&commit->object, lock);
@@@ -1692,8 -1692,7 +1692,7 @@@ int cmd_main(int argc, const char **arg
  {
        struct transfer_request *request;
        struct transfer_request *next_request;
-       int nr_refspec = 0;
-       const char **refspec = NULL;
+       struct refspec rs = REFSPEC_INIT_PUSH;
        struct remote_lock *ref_lock = NULL;
        struct remote_lock *info_ref_lock = NULL;
        struct rev_info revs;
                        }
                        continue;
                }
-               refspec = argv;
-               nr_refspec = argc - i;
+               refspec_appendn(&rs, argv, argc - i);
                break;
        }
  
        if (!repo->url)
                usage(http_push_usage);
  
-       if (delete_branch && nr_refspec != 1)
+       if (delete_branch && rs.nr != 1)
                die("You must specify only one branch name when deleting a remote branch");
  
        setup_git_directory();
  
        /* Remove a remote branch if -d or -D was specified */
        if (delete_branch) {
-               if (delete_remote_branch(refspec[0], force_delete) == -1) {
+               const char *branch = rs.items[i].src;
+               if (delete_remote_branch(branch, force_delete) == -1) {
                        fprintf(stderr, "Unable to delete remote branch %s\n",
-                               refspec[0]);
+                               branch);
                        if (helper_status)
-                               printf("error %s cannot remove\n", refspec[0]);
+                               printf("error %s cannot remove\n", branch);
                }
                goto cleanup;
        }
  
        /* match them up */
-       if (match_push_refs(local_refs, &remote_refs,
-                           nr_refspec, (const char **) refspec, push_all)) {
+       if (match_push_refs(local_refs, &remote_refs, &rs, push_all)) {
                rc = -1;
                goto cleanup;
        }
diff --combined remote.c
index 238689ae28db350a3a31ba1fae5e86a12df80ca9,0d1a3d07f8b8f31240da5ffc8f22b16db4855699..abe80c13972c718bd5d738063a2f526f2d12ee87
+++ b/remote.c
@@@ -2,6 -2,7 +2,7 @@@
  #include "config.h"
  #include "remote.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "commit.h"
  #include "diff.h"
  #include "revision.h"
  
  enum map_direction { FROM_SRC, FROM_DST };
  
- static struct refspec s_tag_refspec = {
-       0,
-       1,
-       0,
-       0,
-       "refs/tags/*",
-       "refs/tags/*"
- };
- /* See TAG_REFSPEC for the string version */
- const struct refspec *tag_refspec = &s_tag_refspec;
  struct counted_string {
        size_t len;
        const char *s;
@@@ -88,33 -77,6 +77,6 @@@ static const char *alias_url(const cha
        return xstrfmt("%s%s", r->rewrite[longest_i]->base, url + longest->len);
  }
  
- static void add_push_refspec(struct remote *remote, const char *ref)
- {
-       ALLOC_GROW(remote->push_refspec,
-                  remote->push_refspec_nr + 1,
-                  remote->push_refspec_alloc);
-       remote->push_refspec[remote->push_refspec_nr++] = ref;
- }
- static void add_fetch_refspec(struct remote *remote, const char *ref)
- {
-       ALLOC_GROW(remote->fetch_refspec,
-                  remote->fetch_refspec_nr + 1,
-                  remote->fetch_refspec_alloc);
-       remote->fetch_refspec[remote->fetch_refspec_nr++] = ref;
- }
- void add_prune_tags_to_fetch_refspec(struct remote *remote)
- {
-       int nr = remote->fetch_refspec_nr;
-       int bufsize = nr  + 1;
-       int size = sizeof(struct refspec);
-       remote->fetch = xrealloc(remote->fetch, size  * bufsize);
-       memcpy(&remote->fetch[nr], tag_refspec, size);
-       add_fetch_refspec(remote, xstrdup(TAG_REFSPEC));
- }
  static void add_url(struct remote *remote, const char *url)
  {
        ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
@@@ -186,9 -148,12 +148,12 @@@ static struct remote *make_remote(cons
        ret = xcalloc(1, sizeof(struct remote));
        ret->prune = -1;  /* unspecified */
        ret->prune_tags = -1;  /* unspecified */
+       ret->name = xstrndup(name, len);
+       refspec_init(&ret->push, REFSPEC_PUSH);
+       refspec_init(&ret->fetch, REFSPEC_FETCH);
        ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
        remotes[remotes_nr++] = ret;
-       ret->name = xstrndup(name, len);
  
        hashmap_entry_init(ret, lookup_entry.hash);
        replaced = hashmap_put(&remotes_hash, ret);
@@@ -286,9 -251,9 +251,9 @@@ static void read_remotes_file(struct re
                if (skip_prefix(buf.buf, "URL:", &v))
                        add_url_alias(remote, xstrdup(skip_spaces(v)));
                else if (skip_prefix(buf.buf, "Push:", &v))
-                       add_push_refspec(remote, xstrdup(skip_spaces(v)));
+                       refspec_append(&remote->push, skip_spaces(v));
                else if (skip_prefix(buf.buf, "Pull:", &v))
-                       add_fetch_refspec(remote, xstrdup(skip_spaces(v)));
+                       refspec_append(&remote->fetch, skip_spaces(v));
        }
        strbuf_release(&buf);
        fclose(f);
@@@ -327,15 -292,19 +292,19 @@@ static void read_branches_file(struct r
                frag = "master";
  
        add_url_alias(remote, strbuf_detach(&buf, NULL));
-       add_fetch_refspec(remote, xstrfmt("refs/heads/%s:refs/heads/%s",
-                                         frag, remote->name));
+       strbuf_addf(&buf, "refs/heads/%s:refs/heads/%s",
+                   frag, remote->name);
+       refspec_append(&remote->fetch, buf.buf);
  
        /*
         * Cogito compatible push: push current HEAD to remote #branch
         * (master if missing)
         */
-       add_push_refspec(remote, xstrfmt("HEAD:refs/heads/%s", frag));
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "HEAD:refs/heads/%s", frag);
+       refspec_append(&remote->push, buf.buf);
        remote->fetch_tags = 1; /* always auto-follow */
+       strbuf_release(&buf);
  }
  
  static int handle_config(const char *key, const char *value, void *cb)
                const char *v;
                if (git_config_string(&v, key, value))
                        return -1;
-               add_push_refspec(remote, v);
+               refspec_append(&remote->push, v);
+               free((char *)v);
        } else if (!strcmp(subkey, "fetch")) {
                const char *v;
                if (git_config_string(&v, key, value))
                        return -1;
-               add_fetch_refspec(remote, v);
+               refspec_append(&remote->fetch, v);
+               free((char *)v);
        } else if (!strcmp(subkey, "receivepack")) {
                const char *v;
                if (git_config_string(&v, key, value))
@@@ -499,158 -470,6 +470,6 @@@ static void read_config(void
        alias_all_urls();
  }
  
- static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
- {
-       int i;
-       struct refspec *rs = xcalloc(nr_refspec, sizeof(*rs));
-       for (i = 0; i < nr_refspec; i++) {
-               size_t llen;
-               int is_glob;
-               const char *lhs, *rhs;
-               int flags;
-               is_glob = 0;
-               lhs = refspec[i];
-               if (*lhs == '+') {
-                       rs[i].force = 1;
-                       lhs++;
-               }
-               rhs = strrchr(lhs, ':');
-               /*
-                * Before going on, special case ":" (or "+:") as a refspec
-                * for pushing matching refs.
-                */
-               if (!fetch && rhs == lhs && rhs[1] == '\0') {
-                       rs[i].matching = 1;
-                       continue;
-               }
-               if (rhs) {
-                       size_t rlen = strlen(++rhs);
-                       is_glob = (1 <= rlen && strchr(rhs, '*'));
-                       rs[i].dst = xstrndup(rhs, rlen);
-               }
-               llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
-               if (1 <= llen && memchr(lhs, '*', llen)) {
-                       if ((rhs && !is_glob) || (!rhs && fetch))
-                               goto invalid;
-                       is_glob = 1;
-               } else if (rhs && is_glob) {
-                       goto invalid;
-               }
-               rs[i].pattern = is_glob;
-               rs[i].src = xstrndup(lhs, llen);
-               flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
-               if (fetch) {
-                       struct object_id unused;
-                       /* LHS */
-                       if (!*rs[i].src)
-                               ; /* empty is ok; it means "HEAD" */
-                       else if (llen == GIT_SHA1_HEXSZ && !get_oid_hex(rs[i].src, &unused))
-                               rs[i].exact_sha1 = 1; /* ok */
-                       else if (!check_refname_format(rs[i].src, flags))
-                               ; /* valid looking ref is ok */
-                       else
-                               goto invalid;
-                       /* RHS */
-                       if (!rs[i].dst)
-                               ; /* missing is ok; it is the same as empty */
-                       else if (!*rs[i].dst)
-                               ; /* empty is ok; it means "do not store" */
-                       else if (!check_refname_format(rs[i].dst, flags))
-                               ; /* valid looking ref is ok */
-                       else
-                               goto invalid;
-               } else {
-                       /*
-                        * LHS
-                        * - empty is allowed; it means delete.
-                        * - when wildcarded, it must be a valid looking ref.
-                        * - otherwise, it must be an extended SHA-1, but
-                        *   there is no existing way to validate this.
-                        */
-                       if (!*rs[i].src)
-                               ; /* empty is ok */
-                       else if (is_glob) {
-                               if (check_refname_format(rs[i].src, flags))
-                                       goto invalid;
-                       }
-                       else
-                               ; /* anything goes, for now */
-                       /*
-                        * RHS
-                        * - missing is allowed, but LHS then must be a
-                        *   valid looking ref.
-                        * - empty is not allowed.
-                        * - otherwise it must be a valid looking ref.
-                        */
-                       if (!rs[i].dst) {
-                               if (check_refname_format(rs[i].src, flags))
-                                       goto invalid;
-                       } else if (!*rs[i].dst) {
-                               goto invalid;
-                       } else {
-                               if (check_refname_format(rs[i].dst, flags))
-                                       goto invalid;
-                       }
-               }
-       }
-       return rs;
-  invalid:
-       if (verify) {
-               /*
-                * nr_refspec must be greater than zero and i must be valid
-                * since it is only possible to reach this point from within
-                * the for loop above.
-                */
-               free_refspec(i+1, rs);
-               return NULL;
-       }
-       die("Invalid refspec '%s'", refspec[i]);
- }
- int valid_fetch_refspec(const char *fetch_refspec_str)
- {
-       struct refspec *refspec;
-       refspec = parse_refspec_internal(1, &fetch_refspec_str, 1, 1);
-       free_refspec(1, refspec);
-       return !!refspec;
- }
- struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec)
- {
-       return parse_refspec_internal(nr_refspec, refspec, 1, 0);
- }
- struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
- {
-       return parse_refspec_internal(nr_refspec, refspec, 0, 0);
- }
- void free_refspec(int nr_refspec, struct refspec *refspec)
- {
-       int i;
-       if (!refspec)
-               return;
-       for (i = 0; i < nr_refspec; i++) {
-               free(refspec[i].src);
-               free(refspec[i].dst);
-       }
-       free(refspec);
- }
  static int valid_remote_nick(const char *name)
  {
        if (!name[0] || is_dot_or_dotdot(name))
@@@ -705,9 -524,8 +524,8 @@@ const char *remote_ref_for_branch(struc
                                pushremote_for_branch(branch, NULL);
                        struct remote *remote = remote_get(remote_name);
  
-                       if (remote && remote->push_refspec_nr &&
-                           (dst = apply_refspecs(remote->push,
-                                                 remote->push_refspec_nr,
+                       if (remote && remote->push.nr &&
+                           (dst = apply_refspecs(&remote->push,
                                                  branch->refname))) {
                                if (explicit)
                                        *explicit = 1;
@@@ -744,8 -562,6 +562,6 @@@ static struct remote *remote_get_1(cons
                add_url_alias(ret, name);
        if (!valid_remote(ret))
                return NULL;
-       ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec);
-       ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec);
        return ret;
  }
  
@@@ -776,12 -592,6 +592,6 @@@ int for_each_remote(each_remote_fn fn, 
                struct remote *r = remotes[i];
                if (!r)
                        continue;
-               if (!r->fetch)
-                       r->fetch = parse_fetch_refspec(r->fetch_refspec_nr,
-                                                      r->fetch_refspec);
-               if (!r->push)
-                       r->push = parse_push_refspec(r->push_refspec_nr,
-                                                    r->push_refspec);
                result = fn(r, priv);
        }
        return result;
@@@ -887,7 -697,9 +697,9 @@@ static int match_name_with_pattern(cons
        return ret;
  }
  
- static void query_refspecs_multiple(struct refspec *refs, int ref_count, struct refspec *query, struct string_list *results)
+ static void query_refspecs_multiple(struct refspec *rs,
+                                   struct refspec_item *query,
+                                   struct string_list *results)
  {
        int i;
        int find_src = !query->src;
        if (find_src && !query->dst)
                error("query_refspecs_multiple: need either src or dst");
  
-       for (i = 0; i < ref_count; i++) {
-               struct refspec *refspec = &refs[i];
+       for (i = 0; i < rs->nr; i++) {
+               struct refspec_item *refspec = &rs->items[i];
                const char *key = find_src ? refspec->dst : refspec->src;
                const char *value = find_src ? refspec->src : refspec->dst;
                const char *needle = find_src ? query->dst : query->src;
        }
  }
  
- int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
+ int query_refspecs(struct refspec *rs, struct refspec_item *query)
  {
        int i;
        int find_src = !query->src;
        if (find_src && !query->dst)
                return error("query_refspecs: need either src or dst");
  
-       for (i = 0; i < ref_count; i++) {
-               struct refspec *refspec = &refs[i];
+       for (i = 0; i < rs->nr; i++) {
+               struct refspec_item *refspec = &rs->items[i];
                const char *key = find_src ? refspec->dst : refspec->src;
                const char *value = find_src ? refspec->src : refspec->dst;
  
        return -1;
  }
  
- char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
-                    const char *name)
+ char *apply_refspecs(struct refspec *rs, const char *name)
  {
-       struct refspec query;
+       struct refspec_item query;
  
-       memset(&query, 0, sizeof(struct refspec));
+       memset(&query, 0, sizeof(struct refspec_item));
        query.src = (char *)name;
  
-       if (query_refspecs(refspecs, nr_refspec, &query))
+       if (query_refspecs(rs, &query))
                return NULL;
  
        return query.dst;
  }
  
- int remote_find_tracking(struct remote *remote, struct refspec *refspec)
+ int remote_find_tracking(struct remote *remote, struct refspec_item *refspec)
  {
-       return query_refspecs(remote->fetch, remote->fetch_refspec_nr, refspec);
+       return query_refspecs(&remote->fetch, refspec);
  }
  
  static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
@@@ -1167,7 -978,7 +978,7 @@@ static char *guess_ref(const char *name
  }
  
  static int match_explicit_lhs(struct ref *src,
-                             struct refspec *rs,
+                             struct refspec_item *rs,
                              struct ref **match,
                              int *allocated_match)
  {
  
  static int match_explicit(struct ref *src, struct ref *dst,
                          struct ref ***dst_tail,
-                         struct refspec *rs)
+                         struct refspec_item *rs)
  {
        struct ref *matched_src, *matched_dst;
        int allocated_src;
  }
  
  static int match_explicit_refs(struct ref *src, struct ref *dst,
-                              struct ref ***dst_tail, struct refspec *rs,
-                              int rs_nr)
+                              struct ref ***dst_tail, struct refspec *rs)
  {
        int i, errs;
-       for (i = errs = 0; i < rs_nr; i++)
-               errs += match_explicit(src, dst, dst_tail, &rs[i]);
+       for (i = errs = 0; i < rs->nr; i++)
+               errs += match_explicit(src, dst, dst_tail, &rs->items[i]);
        return errs;
  }
  
- static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref *ref,
-               int send_mirror, int direction, const struct refspec **ret_pat)
+ static char *get_ref_match(const struct refspec *rs, const struct ref *ref,
+                          int send_mirror, int direction,
+                          const struct refspec_item **ret_pat)
  {
-       const struct refspec *pat;
+       const struct refspec_item *pat;
        char *name;
        int i;
        int matching_refs = -1;
-       for (i = 0; i < rs_nr; i++) {
-               if (rs[i].matching &&
-                   (matching_refs == -1 || rs[i].force)) {
+       for (i = 0; i < rs->nr; i++) {
+               const struct refspec_item *item = &rs->items[i];
+               if (item->matching &&
+                   (matching_refs == -1 || item->force)) {
                        matching_refs = i;
                        continue;
                }
  
-               if (rs[i].pattern) {
-                       const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src;
+               if (item->pattern) {
+                       const char *dst_side = item->dst ? item->dst : item->src;
                        int match;
                        if (direction == FROM_SRC)
-                               match = match_name_with_pattern(rs[i].src, ref->name, dst_side, &name);
+                               match = match_name_with_pattern(item->src, ref->name, dst_side, &name);
                        else
-                               match = match_name_with_pattern(dst_side, ref->name, rs[i].src, &name);
+                               match = match_name_with_pattern(dst_side, ref->name, item->src, &name);
                        if (match) {
                                matching_refs = i;
                                break;
        if (matching_refs == -1)
                return NULL;
  
-       pat = rs + matching_refs;
+       pat = &rs->items[matching_refs];
        if (pat->matching) {
                /*
                 * "matching refs"; traditionally we pushed everything
@@@ -1376,7 -1188,7 +1188,7 @@@ static void add_missing_tags(struct re
                        continue; /* not a tag */
                if (string_list_has_string(&dst_tag, ref->name))
                        continue; /* they already have it */
 -              if (oid_object_info(&ref->new_oid, NULL) != OBJ_TAG)
 +              if (oid_object_info(the_repository, &ref->new_oid, NULL) != OBJ_TAG)
                        continue; /* be conservative */
                item = string_list_append(&src_tag, ref->name);
                item->util = ref;
@@@ -1443,22 -1255,20 +1255,20 @@@ static void prepare_ref_index(struct st
   * but we can catch some errors early before even talking to the
   * remote side.
   */
- int check_push_refs(struct ref *src, int nr_refspec, const char **refspec_names)
+ int check_push_refs(struct ref *src, struct refspec *rs)
  {
-       struct refspec *refspec = parse_push_refspec(nr_refspec, refspec_names);
        int ret = 0;
        int i;
  
-       for (i = 0; i < nr_refspec; i++) {
-               struct refspec *rs = refspec + i;
+       for (i = 0; i < rs->nr; i++) {
+               struct refspec_item *item = &rs->items[i];
  
-               if (rs->pattern || rs->matching)
+               if (item->pattern || item->matching)
                        continue;
  
-               ret |= match_explicit_lhs(src, rs, NULL, NULL);
+               ret |= match_explicit_lhs(src, item, NULL, NULL);
        }
  
-       free_refspec(nr_refspec, refspec);
        return ret;
  }
  
   * dst (e.g. pushing to a new branch, done in match_explicit_refs).
   */
  int match_push_refs(struct ref *src, struct ref **dst,
-                   int nr_refspec, const char **refspec, int flags)
+                   struct refspec *rs, int flags)
  {
-       struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
        int send_prune = flags & MATCH_REFS_PRUNE;
        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;
-               refspec = default_refspec;
-       }
-       rs = parse_push_refspec(nr_refspec, (const char **) refspec);
-       errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec);
+       /* If no refspec is provided, use the default ":" */
+       if (!rs->nr)
+               refspec_append(rs, ":");
+       errs = match_explicit_refs(src, *dst, &dst_tail, rs);
  
        /* 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;
+               const struct refspec_item *pat = NULL;
                char *dst_name;
  
-               dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat);
+               dst_name = get_ref_match(rs, ref, send_mirror, FROM_SRC, &pat);
                if (!dst_name)
                        continue;
  
                                /* We're already sending something to this ref. */
                                continue;
  
-                       src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL);
+                       src_name = get_ref_match(rs, ref, send_mirror, FROM_DST, NULL);
                        if (src_name) {
                                if (!src_ref_index.nr)
                                        prepare_ref_index(&src_ref_index, src);
                }
                string_list_clear(&src_ref_index, 0);
        }
        if (errs)
                return -1;
        return 0;
@@@ -1753,7 -1561,7 +1561,7 @@@ static const char *tracking_for_push_de
  {
        char *ret;
  
-       ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+       ret = apply_refspecs(&remote->fetch, refname);
        if (!ret)
                return error_buf(err,
                                 _("push destination '%s' on remote '%s' has no local tracking branch"),
@@@ -1771,12 -1579,11 +1579,11 @@@ static const char *branch_get_push_1(st
                                 _("branch '%s' has no remote for pushing"),
                                 branch->name);
  
-       if (remote->push_refspec_nr) {
+       if (remote->push.nr) {
                char *dst;
                const char *ret;
  
-               dst = apply_refspecs(remote->push, remote->push_refspec_nr,
-                                    branch->refname);
+               dst = apply_refspecs(&remote->push, branch->refname);
                if (!dst)
                        return error_buf(err,
                                         _("push refspecs for '%s' do not include '%s'"),
                }
        }
  
 -      die("BUG: unhandled push situation");
 +      BUG("unhandled push situation");
  }
  
  const char *branch_get_push(struct branch *branch, struct strbuf *err)
@@@ -1849,7 -1656,7 +1656,7 @@@ static int ignore_symref_update(const c
   * local symbolic ref.
   */
  static struct ref *get_expanded_map(const struct ref *remote_refs,
-                                   const struct refspec *refspec)
+                                   const struct refspec_item *refspec)
  {
        const struct ref *ref;
        struct ref *ret = NULL;
@@@ -1914,7 -1721,7 +1721,7 @@@ static struct ref *get_local_ref(const 
  }
  
  int get_fetch_map(const struct ref *remote_refs,
-                 const struct refspec *refspec,
+                 const struct refspec_item *refspec,
                  struct ref ***tail,
                  int missing_ok)
  {
@@@ -2252,8 -2059,7 +2059,7 @@@ struct ref *guess_remote_head(const str
  struct stale_heads_info {
        struct string_list *ref_names;
        struct ref **stale_refs_tail;
-       struct refspec *refs;
-       int ref_count;
+       struct refspec *rs;
  };
  
  static int get_stale_heads_cb(const char *refname, const struct object_id *oid,
  {
        struct stale_heads_info *info = cb_data;
        struct string_list matches = STRING_LIST_INIT_DUP;
-       struct refspec query;
+       struct refspec_item query;
        int i, stale = 1;
-       memset(&query, 0, sizeof(struct refspec));
+       memset(&query, 0, sizeof(struct refspec_item));
        query.dst = (char *)refname;
  
-       query_refspecs_multiple(info->refs, info->ref_count, &query, &matches);
+       query_refspecs_multiple(info->rs, &query, &matches);
        if (matches.nr == 0)
                goto clean_exit; /* No matches */
  
@@@ -2294,7 -2100,7 +2100,7 @@@ clean_exit
        return 0;
  }
  
- struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map)
+ struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map)
  {
        struct ref *ref, *stale_refs = NULL;
        struct string_list ref_names = STRING_LIST_INIT_NODUP;
  
        info.ref_names = &ref_names;
        info.stale_refs_tail = &stale_refs;
-       info.refs = refs;
-       info.ref_count = ref_count;
+       info.rs = rs;
        for (ref = fetch_map; ref; ref = ref->next)
                string_list_append(&ref_names, ref->name);
        string_list_sort(&ref_names);
@@@ -2387,7 -2192,7 +2192,7 @@@ static int remote_tracking(struct remot
  {
        char *dst;
  
-       dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+       dst = apply_refspecs(&remote->fetch, refname);
        if (!dst)
                return -1; /* no tracking ref for refname at remote */
        if (read_ref(dst, oid))
diff --combined remote.h
index 93dd97e25f75ff88090dce0d92e0cf18dbdc3295,62a65665946f061cf28914d15611d89044cc879f..45ecc6cefafdea435d0fee39691f7458b940b877
+++ b/remote.h
@@@ -3,6 -3,7 +3,7 @@@
  
  #include "parse-options.h"
  #include "hashmap.h"
+ #include "refspec.h"
  
  enum {
        REMOTE_UNCONFIGURED = 0,
@@@ -27,15 -28,9 +28,9 @@@ struct remote 
        int pushurl_nr;
        int pushurl_alloc;
  
-       const char **push_refspec;
-       struct refspec *push;
-       int push_refspec_nr;
-       int push_refspec_alloc;
+       struct refspec push;
  
-       const char **fetch_refspec;
-       struct refspec *fetch;
-       int fetch_refspec_nr;
-       int fetch_refspec_alloc;
+       struct refspec fetch;
  
        /*
         * -1 to never fetch tags
@@@ -68,18 -63,6 +63,6 @@@ int for_each_remote(each_remote_fn fn, 
  
  int remote_has_url(struct remote *remote, const char *url);
  
- struct refspec {
-       unsigned force : 1;
-       unsigned pattern : 1;
-       unsigned matching : 1;
-       unsigned exact_sha1 : 1;
-       char *src;
-       char *dst;
- };
- extern const struct refspec *tag_refspec;
  struct ref {
        struct ref *next;
        struct object_id old_oid;
@@@ -153,7 -136,6 +136,7 @@@ void free_refs(struct ref *ref)
  struct oid_array;
  struct packet_reader;
  struct argv_array;
 +struct string_list;
  extern struct ref **get_remote_heads(struct packet_reader *reader,
                                     struct ref **list, unsigned int flags,
                                     struct oid_array *extra_have,
  /* Used for protocol v2 in order to retrieve refs from a remote */
  extern struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
                                    struct ref **list, int for_push,
 -                                  const struct argv_array *ref_prefixes);
 +                                  const struct argv_array *ref_prefixes,
 +                                  const struct string_list *server_options);
  
  int resolve_remote_symref(struct ref *ref, struct ref *list);
  int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
   */
  struct ref *ref_remove_duplicates(struct ref *ref_map);
  
- int valid_fetch_refspec(const char *refspec);
- struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
- extern struct refspec *parse_push_refspec(int nr_refspec, const char **refspec);
+ int query_refspecs(struct refspec *rs, struct refspec_item *query);
+ char *apply_refspecs(struct refspec *rs, const char *name);
  
- void free_refspec(int nr_refspec, struct refspec *refspec);
- extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
- char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
-                    const char *name);
- int check_push_refs(struct ref *src, int nr_refspec, const char **refspec);
+ int check_push_refs(struct ref *src, struct refspec *rs);
  int match_push_refs(struct ref *src, struct ref **dst,
-                   int nr_refspec, const char **refspec, int all);
+                   struct refspec *rs, int flags);
  void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
        int force_update);
  
   * missing_ok is usually false, but when we are adding branch.$name.merge
   * it is Ok if the branch is not at the remote anymore.
   */
- int get_fetch_map(const struct ref *remote_refs, const struct refspec *refspec,
+ int get_fetch_map(const struct ref *remote_refs, const struct refspec_item *refspec,
                  struct ref ***tail, int missing_ok);
  
  struct ref *get_remote_ref(const struct ref *remote_refs, const char *name);
  /*
   * For the given remote, reads the refspec's src and sets the other fields.
   */
- int remote_find_tracking(struct remote *remote, struct refspec *refspec);
+ int remote_find_tracking(struct remote *remote, struct refspec_item *refspec);
  
  struct branch {
        const char *name;
        const char *pushremote_name;
  
        const char **merge_name;
-       struct refspec **merge;
+       struct refspec_item **merge;
        int merge_nr;
        int merge_alloc;
  
@@@ -292,7 -266,7 +268,7 @@@ struct ref *guess_remote_head(const str
                              int all);
  
  /* Return refs which no longer exist on remote */
- struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);
+ struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map);
  
  /*
   * Compare-and-swap
@@@ -315,8 -289,4 +291,4 @@@ extern int parseopt_push_cas_option(con
  extern int is_empty_cas(const struct push_cas_option *);
  void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
  
- #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
- void add_prune_tags_to_fetch_refspec(struct remote *remote);
  #endif
diff --combined submodule.c
index 1f886238936f1114470530aed4a4bf97293b5a11,cdeadd80e7890ce3ed77ce3793f4468033c7b3bb..47d005797e3951ac1878d9bdb1ac8de99b392464
@@@ -820,7 -820,7 +820,7 @@@ static int check_has_commit(const struc
  {
        struct has_commit_data *cb = data;
  
 -      enum object_type type = oid_object_info(oid, NULL);
 +      enum object_type type = oid_object_info(the_repository, oid, NULL);
  
        switch (type) {
        case OBJ_COMMIT:
@@@ -968,7 -968,7 +968,7 @@@ int find_unpushed_submodules(struct oid
  
  static int push_submodule(const char *path,
                          const struct remote *remote,
-                         const char **refspec, int refspec_nr,
+                         const struct refspec *rs,
                          const struct string_list *push_options,
                          int dry_run)
  {
                if (remote->origin != REMOTE_UNCONFIGURED) {
                        int i;
                        argv_array_push(&cp.args, remote->name);
-                       for (i = 0; i < refspec_nr; i++)
-                               argv_array_push(&cp.args, refspec[i]);
+                       for (i = 0; i < rs->raw_nr; i++)
+                               argv_array_push(&cp.args, rs->raw[i]);
                }
  
                prepare_submodule_repo_env(&cp.env_array);
   */
  static void submodule_push_check(const char *path, const char *head,
                                 const struct remote *remote,
-                                const char **refspec, int refspec_nr)
+                                const struct refspec *rs)
  {
        struct child_process cp = CHILD_PROCESS_INIT;
        int i;
        argv_array_push(&cp.args, head);
        argv_array_push(&cp.args, remote->name);
  
-       for (i = 0; i < refspec_nr; i++)
-               argv_array_push(&cp.args, refspec[i]);
+       for (i = 0; i < rs->raw_nr; i++)
+               argv_array_push(&cp.args, rs->raw[i]);
  
        prepare_submodule_repo_env(&cp.env_array);
        cp.git_cmd = 1;
  
  int push_unpushed_submodules(struct oid_array *commits,
                             const struct remote *remote,
-                            const char **refspec, int refspec_nr,
+                            const struct refspec *rs,
                             const struct string_list *push_options,
                             int dry_run)
  {
  
                for (i = 0; i < needs_pushing.nr; i++)
                        submodule_push_check(needs_pushing.items[i].string,
-                                            head, remote,
-                                            refspec, refspec_nr);
+                                            head, remote, rs);
                free(head);
        }
  
        for (i = 0; i < needs_pushing.nr; i++) {
                const char *path = needs_pushing.items[i].string;
                fprintf(stderr, "Pushing submodule '%s'\n", path);
-               if (!push_submodule(path, remote, refspec, refspec_nr,
+               if (!push_submodule(path, remote, rs,
                                    push_options, dry_run)) {
                        fprintf(stderr, "Unable to push submodule '%s'\n", path);
                        ret = 0;
@@@ -1400,7 -1399,7 +1399,7 @@@ unsigned is_submodule_modified(const ch
                    buf.buf[0] == '2') {
                        /* T = line type, XY = status, SSSS = submodule state */
                        if (buf.len < strlen("T XY SSSS"))
 -                              die("BUG: invalid status --porcelain=2 line %s",
 +                              BUG("invalid status --porcelain=2 line %s",
                                    buf.buf);
  
                        if (buf.buf[5] == 'S' && buf.buf[8] == 'U')
@@@ -1569,7 -1568,7 +1568,7 @@@ static void submodule_reset_index(cons
                                   get_super_prefix_or_empty(), path);
        argv_array_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
  
 -      argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
 +      argv_array_push(&cp.args, empty_tree_oid_hex());
  
        if (run_command(&cp))
                die("could not reset submodule index");
@@@ -1609,7 -1608,7 +1608,7 @@@ int submodule_move_head(const char *pat
        sub = submodule_from_path(the_repository, &null_oid, path);
  
        if (!sub)
 -              die("BUG: could not get submodule information for '%s'", path);
 +              BUG("could not get submodule information for '%s'", path);
  
        if (old_head && !(flags & SUBMODULE_MOVE_HEAD_FORCE)) {
                /* Check if the submodule has a dirty index. */
                argv_array_push(&cp.args, "-m");
  
        if (!(flags & SUBMODULE_MOVE_HEAD_FORCE))
 -              argv_array_push(&cp.args, old_head ? old_head : EMPTY_TREE_SHA1_HEX);
 +              argv_array_push(&cp.args, old_head ? old_head : empty_tree_oid_hex());
  
 -      argv_array_push(&cp.args, new_head ? new_head : EMPTY_TREE_SHA1_HEX);
 +      argv_array_push(&cp.args, new_head ? new_head : empty_tree_oid_hex());
  
        if (run_command(&cp)) {
                ret = -1;
@@@ -1967,7 -1966,7 +1966,7 @@@ void absorb_git_dir_into_superproject(c
                struct strbuf sb = STRBUF_INIT;
  
                if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
 -                      die("BUG: we don't know how to pass the flags down?");
 +                      BUG("we don't know how to pass the flags down?");
  
                strbuf_addstr(&sb, get_super_prefix_or_empty());
                strbuf_addstr(&sb, path);
@@@ -2045,7 -2044,7 +2044,7 @@@ const char *get_superproject_working_tr
  
                if (super_sub_len > cwd_len ||
                    strcmp(&cwd[cwd_len - super_sub_len], super_sub))
 -                      die (_("BUG: returned path string doesn't match cwd?"));
 +                      BUG("returned path string doesn't match cwd?");
  
                super_wt = xstrdup(cwd);
                super_wt[cwd_len - super_sub_len] = '\0';
diff --combined t/t5702-protocol-v2.sh
index 4ad5c5769b4eab56d840709ee0bc0affa8de2e01,b6c72ab51ed2f4ab3012252b70c6b03fafbcaf65..a4fe6508bdd73784758b2ea80ab81752e15c01b3
@@@ -154,22 -154,6 +154,22 @@@ test_expect_success 'ref advertisment i
        test_cmp actual expect
  '
  
 +test_expect_success 'server-options are sent when using ls-remote' '
 +      test_when_finished "rm -f log" &&
 +
 +      GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
 +              ls-remote -o hello -o world "file://$(pwd)/file_parent" master >actual &&
 +
 +      cat >expect <<-EOF &&
 +      $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master
 +      EOF
 +
 +      test_cmp actual expect &&
 +      grep "server-option=hello" log &&
 +      grep "server-option=world" log
 +'
 +
 +
  test_expect_success 'clone with file:// using protocol v2' '
        test_when_finished "rm -f log" &&
  
@@@ -217,134 -201,20 +217,148 @@@ test_expect_success 'ref advertisment i
        ! grep "refs/tags/three" log
  '
  
 +test_expect_success 'server-options are sent when fetching' '
 +      test_when_finished "rm -f log" &&
 +
 +      test_commit -C file_parent four &&
 +
 +      GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \
 +              fetch -o hello -o world origin master &&
 +
 +      git -C file_child log -1 --format=%s origin/master >actual &&
 +      git -C file_parent log -1 --format=%s >expect &&
 +      test_cmp expect actual &&
 +
 +      grep "server-option=hello" log &&
 +      grep "server-option=world" log
 +'
 +
 +test_expect_success 'upload-pack respects config using protocol v2' '
 +      git init server &&
 +      write_script server/.git/hook <<-\EOF &&
 +              touch hookout
 +              "$@"
 +      EOF
 +      test_commit -C server one &&
 +
 +      test_config_global uploadpack.packobjectshook ./hook &&
 +      test_path_is_missing server/.git/hookout &&
 +      git -c protocol.version=2 clone "file://$(pwd)/server" client &&
 +      test_path_is_file server/.git/hookout
 +'
 +
 +test_expect_success 'setup filter tests' '
 +      rm -rf server client &&
 +      git init server &&
 +
 +      # 1 commit to create a file, and 1 commit to modify it
 +      test_commit -C server message1 a.txt &&
 +      test_commit -C server message2 a.txt &&
 +      git -C server config protocol.version 2 &&
 +      git -C server config uploadpack.allowfilter 1 &&
 +      git -C server config uploadpack.allowanysha1inwant 1 &&
 +      git -C server config protocol.version 2
 +'
 +
 +test_expect_success 'partial clone' '
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \
 +              clone --filter=blob:none "file://$(pwd)/server" client &&
 +      grep "version 2" trace &&
 +
 +      # Ensure that the old version of the file is missing
 +      git -C client rev-list master --quiet --objects --missing=print \
 +              >observed.oids &&
 +      grep "$(git -C server rev-parse message1:a.txt)" observed.oids &&
 +
 +      # Ensure that client passes fsck
 +      git -C client fsck
 +'
 +
 +test_expect_success 'dynamically fetch missing object' '
 +      rm "$(pwd)/trace" &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \
 +              cat-file -p $(git -C server rev-parse message1:a.txt) &&
 +      grep "version 2" trace
 +'
 +
 +test_expect_success 'partial fetch' '
 +      rm -rf client "$(pwd)/trace" &&
 +      git init client &&
 +      SERVER="file://$(pwd)/server" &&
 +      test_config -C client extensions.partialClone "$SERVER" &&
 +
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \
 +              fetch --filter=blob:none "$SERVER" master:refs/heads/other &&
 +      grep "version 2" trace &&
 +
 +      # Ensure that the old version of the file is missing
 +      git -C client rev-list other --quiet --objects --missing=print \
 +              >observed.oids &&
 +      grep "$(git -C server rev-parse message1:a.txt)" observed.oids &&
 +
 +      # Ensure that client passes fsck
 +      git -C client fsck
 +'
 +
 +test_expect_success 'do not advertise filter if not configured to do so' '
 +      SERVER="file://$(pwd)/server" &&
 +
 +      rm "$(pwd)/trace" &&
 +      git -C server config uploadpack.allowfilter 1 &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \
 +              ls-remote "$SERVER" &&
 +      grep "fetch=.*filter" trace &&
 +
 +      rm "$(pwd)/trace" &&
 +      git -C server config uploadpack.allowfilter 0 &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \
 +              ls-remote "$SERVER" &&
 +      grep "fetch=" trace >fetch_capabilities &&
 +      ! grep filter fetch_capabilities
 +'
 +
 +test_expect_success 'partial clone warns if filter is not advertised' '
 +      rm -rf client &&
 +      git -C server config uploadpack.allowfilter 0 &&
 +      git -c protocol.version=2 \
 +              clone --filter=blob:none "file://$(pwd)/server" client 2>err &&
 +      test_i18ngrep "filtering not recognized by server, ignoring" err
 +'
 +
 +test_expect_success 'even with handcrafted request, filter does not work if not advertised' '
 +      git -C server config uploadpack.allowfilter 0 &&
 +
 +      # Custom request that tries to filter even though it is not advertised.
 +      test-pkt-line pack >in <<-EOF &&
 +      command=fetch
 +      0001
 +      want $(git -C server rev-parse master)
 +      filter blob:none
 +      0000
 +      EOF
 +
 +      test_must_fail git -C server serve --stateless-rpc <in >/dev/null 2>err &&
 +      grep "unexpected line: .filter blob:none." err &&
 +
 +      # Exercise to ensure that if advertised, filter works
 +      git -C server config uploadpack.allowfilter 1 &&
 +      git -C server serve --stateless-rpc <in >/dev/null
 +'
 +
+ test_expect_success 'default refspec is used to filter ref when fetchcing' '
+       test_when_finished "rm -f log" &&
+       GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \
+               fetch origin &&
+       git -C file_child log -1 --format=%s three >actual &&
+       git -C file_parent log -1 --format=%s three >expect &&
+       test_cmp expect actual &&
+       grep "ref-prefix refs/heads/" log &&
+       grep "ref-prefix refs/tags/" log
+ '
  # Test protocol v2 with 'http://' transport
  #
  . "$TEST_DIRECTORY"/lib-httpd.sh
diff --combined transport.c
index 6785fcceed245769d7d9515a73b7e29c99ee268b,cbf0044c3ecc4c78e2d94aaeb1aab7bca2423d87..a32da30dee6f4e38433b68d7c9f7e9404aa58858
@@@ -11,6 -11,7 +11,7 @@@
  #include "bundle.h"
  #include "dir.h"
  #include "refs.h"
+ #include "refspec.h"
  #include "branch.h"
  #include "url.h"
  #include "submodule.h"
@@@ -268,7 -269,7 +269,7 @@@ static struct ref *get_refs_via_connect
        switch (data->version) {
        case protocol_v2:
                get_remote_refs(data->fd[1], &reader, &refs, for_push,
 -                              ref_prefixes);
 +                              ref_prefixes, transport->server_options);
                break;
        case protocol_v1:
        case protocol_v0:
@@@ -316,7 -317,6 +317,7 @@@ static int fetch_refs_via_pack(struct t
        args.no_dependents = data->options.no_dependents;
        args.filter_options = data->options.filter_options;
        args.stateless_rpc = transport->stateless_rpc;
 +      args.server_options = transport->server_options;
  
        if (!data->got_remote_heads)
                refs_tmp = get_refs_via_connect(transport, 0, NULL);
@@@ -390,7 -390,7 +391,7 @@@ int transport_refs_pushed(struct ref *r
  
  void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
  {
-       struct refspec rs;
+       struct refspec_item rs;
  
        if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
                return;
@@@ -619,29 -619,6 +620,6 @@@ void transport_print_push_status(const 
        free(head);
  }
  
- void transport_verify_remote_names(int nr_heads, const char **heads)
- {
-       int i;
-       for (i = 0; i < nr_heads; i++) {
-               const char *local = heads[i];
-               const char *remote = strrchr(heads[i], ':');
-               if (*local == '+')
-                       local++;
-               /* A matching refspec is okay.  */
-               if (remote == local && remote[1] == '\0')
-                       continue;
-               remote = remote ? (remote + 1) : local;
-               if (check_refname_format(remote,
-                               REFNAME_ALLOW_ONELEVEL|REFNAME_REFSPEC_PATTERN))
-                       die("remote part of refspec is not a valid name in %s",
-                               heads[i]);
-       }
- }
  static int git_transport_push(struct transport *transport, struct ref *remote_refs, int flags)
  {
        struct git_transport_data *data = transport->data;
@@@ -737,7 -714,7 +715,7 @@@ void transport_take_over(struct transpo
        struct git_transport_data *data;
  
        if (!transport->smart_options)
 -              die("BUG: taking over transport requires non-NULL "
 +              BUG("taking over transport requires non-NULL "
                    "smart_options field.");
  
        data = xcalloc(1, sizeof(*data));
@@@ -862,7 -839,7 +840,7 @@@ int is_transport_allowed(const char *ty
                return from_user;
        }
  
 -      die("BUG: invalid protocol_allow_config type");
 +      BUG("invalid protocol_allow_config type");
  }
  
  void transport_check_allowed(const char *type)
@@@ -1093,11 -1070,10 +1071,10 @@@ static int run_pre_push_hook(struct tra
  }
  
  int transport_push(struct transport *transport,
-                  int refspec_nr, const char **refspec, int flags,
+                  struct refspec *rs, int flags,
                   unsigned int *reject_reasons)
  {
        *reject_reasons = 0;
-       transport_verify_remote_names(refspec_nr, refspec);
  
        if (transport_color_config() < 0)
                return -1;
                int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
                int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
                int push_ret, ret, err;
-               struct refspec *tmp_rs;
                struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
-               int i;
  
-               if (check_push_refs(local_refs, refspec_nr, refspec) < 0)
+               if (check_push_refs(local_refs, rs) < 0)
                        return -1;
  
-               tmp_rs = parse_push_refspec(refspec_nr, refspec);
-               for (i = 0; i < refspec_nr; i++) {
-                       const char *prefix = NULL;
-                       if (tmp_rs[i].dst)
-                               prefix = tmp_rs[i].dst;
-                       else if (tmp_rs[i].src && !tmp_rs[i].exact_sha1)
-                               prefix = tmp_rs[i].src;
-                       if (prefix) {
-                               const char *glob = strchr(prefix, '*');
-                               if (glob)
-                                       argv_array_pushf(&ref_prefixes, "%.*s",
-                                                        (int)(glob - prefix),
-                                                        prefix);
-                               else
-                                       expand_ref_prefix(&ref_prefixes, prefix);
-                       }
-               }
+               refspec_ref_prefixes(rs, &ref_prefixes);
  
                remote_refs = transport->vtable->get_refs_list(transport, 1,
                                                               &ref_prefixes);
  
                argv_array_clear(&ref_prefixes);
-               free_refspec(refspec_nr, tmp_rs);
  
                if (flags & TRANSPORT_PUSH_ALL)
                        match_flags |= MATCH_REFS_ALL;
                if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
                        match_flags |= MATCH_REFS_FOLLOW_TAGS;
  
-               if (match_push_refs(local_refs, &remote_refs,
-                                   refspec_nr, refspec, match_flags)) {
+               if (match_push_refs(local_refs, &remote_refs, rs, match_flags))
                        return -1;
-               }
  
                if (transport->smart_options &&
                    transport->smart_options->cas &&
  
                        if (!push_unpushed_submodules(&commits,
                                                      transport->remote,
-                                                     refspec, refspec_nr,
+                                                     rs,
                                                      transport->push_options,
                                                      pretend)) {
                                oid_array_clear(&commits);
diff --combined transport.h
index 73a7be3c8a4d3b68838a3130599a5f8c628edece,bac085ce0e9a00cafba2a1edcd39a70acd13d300..7792b08582c132e18b495a21b700d9113727c7eb
@@@ -71,12 -71,6 +71,12 @@@ struct transport 
         */
        const struct string_list *push_options;
  
 +      /*
 +       * These strings will be passed to the remote side on each command
 +       * request, if both sides support the server-option capability.
 +       */
 +      const struct string_list *server_options;
 +
        char *pack_lockfile;
        signed verbose : 3;
        /**
@@@ -203,7 -197,7 +203,7 @@@ void transport_set_verbosity(struct tra
  #define REJECT_NEEDS_FORCE     0x10
  
  int transport_push(struct transport *connection,
-                  int refspec_nr, const char **refspec, int flags,
+                  struct refspec *rs, int flags,
                   unsigned int * reject_reasons);
  
  /*
@@@ -233,8 -227,6 +233,6 @@@ int transport_helper_init(struct transp
  int bidirectional_transfer_loop(int input, int output);
  
  /* common methods used by transport.c and builtin/send-pack.c */
- void transport_verify_remote_names(int nr_heads, const char **heads);
  void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose);
  
  int transport_refs_pushed(struct ref *ref);