Merge branch 'jt/refspec-dwim-precedence-fix'
authorJunio C Hamano <gitster@pobox.com>
Fri, 17 Aug 2018 20:09:55 +0000 (13:09 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 17 Aug 2018 20:09:55 +0000 (13:09 -0700)
"git fetch $there refs/heads/s" ought to fetch the tip of the
branch 's', but when "refs/heads/refs/heads/s", i.e. a branch whose
name is "refs/heads/s" exists at the same time, fetched that one
instead by mistake. This has been corrected to honor the usual
disambiguation rules for abbreviated refnames.

* jt/refspec-dwim-precedence-fix:
remote: make refspec follow the same disambiguation rule as local refs

1  2 
refs.c
remote.c
t/t5510-fetch.sh
diff --combined refs.c
index 5b412c61efb94cfd4549c68ac561b596dbf0463e,2ca715d099ab0970ad0c95993c4bdbd9c998cb8f..de81c7be7ca8d3ca033b34a61f33b0bff069932f
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -9,13 -9,10 +9,13 @@@
  #include "iterator.h"
  #include "refs.h"
  #include "refs/refs-internal.h"
 +#include "object-store.h"
  #include "object.h"
  #include "tag.h"
  #include "submodule.h"
  #include "worktree.h"
 +#include "argv-array.h"
 +#include "repository.h"
  
  /*
   * List of all available backends
@@@ -189,7 -186,7 +189,7 @@@ int ref_resolves_to_object(const char *
        if (flags & REF_ISBROKEN)
                return 0;
        if (!has_sha1_file(oid->hash)) {
 -              error("%s does not point to a valid object!", refname);
 +              error(_("%s does not point to a valid object!"), refname);
                return 0;
        }
        return 1;
@@@ -209,7 -206,7 +209,7 @@@ char *refs_resolve_refdup(struct ref_st
  char *resolve_refdup(const char *refname, int resolve_flags,
                     struct object_id *oid, int *flags)
  {
 -      return refs_resolve_refdup(get_main_ref_store(),
 +      return refs_resolve_refdup(get_main_ref_store(the_repository),
                                   refname, resolve_flags,
                                   oid, flags);
  }
@@@ -231,7 -228,7 +231,7 @@@ int refs_read_ref_full(struct ref_stor
  
  int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
  {
 -      return refs_read_ref_full(get_main_ref_store(), refname,
 +      return refs_read_ref_full(get_main_ref_store(the_repository), refname,
                                  resolve_flags, oid, flags);
  }
  
@@@ -304,8 -301,8 +304,8 @@@ enum peel_status peel_object(const stru
        struct object *o = lookup_unknown_object(name->hash);
  
        if (o->type == OBJ_NONE) {
 -              int type = sha1_object_info(name->hash, NULL);
 -              if (type < 0 || !object_as_type(o, type, 0))
 +              int type = oid_object_info(the_repository, name, NULL);
 +              if (type < 0 || !object_as_type(the_repository, o, type, 0))
                        return PEEL_INVALID;
        }
  
@@@ -378,7 -375,7 +378,7 @@@ int refs_for_each_tag_ref(struct ref_st
  
  int for_each_tag_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_tag_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_tag_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
  
  int for_each_branch_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_branch_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_branch_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
  
  int for_each_remote_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_remote_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_remote_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
@@@ -490,33 -487,28 +490,41 @@@ static const char *ref_rev_parse_rules[
        NULL
  };
  
+ #define NUM_REV_PARSE_RULES (ARRAY_SIZE(ref_rev_parse_rules) - 1)
+ /*
+  * Is it possible that the caller meant full_name with abbrev_name?
+  * If so return a non-zero value to signal "yes"; the magnitude of
+  * the returned value gives the precedence used for disambiguation.
+  *
+  * If abbrev_name cannot mean full_name, return 0.
+  */
  int refname_match(const char *abbrev_name, const char *full_name)
  {
        const char **p;
        const int abbrev_name_len = strlen(abbrev_name);
+       const int num_rules = NUM_REV_PARSE_RULES;
  
-       for (p = ref_rev_parse_rules; *p; p++) {
-               if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
-                       return 1;
-               }
-       }
+       for (p = ref_rev_parse_rules; *p; p++)
+               if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name)))
+                       return &ref_rev_parse_rules[num_rules] - p;
  
        return 0;
  }
  
 +/*
 + * Given a 'prefix' expand it by the rules in 'ref_rev_parse_rules' and add
 + * the results to 'prefixes'
 + */
 +void expand_ref_prefix(struct argv_array *prefixes, const char *prefix)
 +{
 +      const char **p;
 +      int len = strlen(prefix);
 +
 +      for (p = ref_rev_parse_rules; *p; p++)
 +              argv_array_pushf(prefixes, *p, len, prefix);
 +}
 +
  /*
   * *string and *len will only be substituted, and *string returned (for
   * later free()ing) if the string passed in is a magic short-hand form
@@@ -568,9 -560,9 +576,9 @@@ int expand_ref(const char *str, int len
                        if (!warn_ambiguous_refs)
                                break;
                } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) {
 -                      warning("ignoring dangling symref %s.", fullref.buf);
 +                      warning(_("ignoring dangling symref %s"), fullref.buf);
                } else if ((flag & REF_ISBROKEN) && strchr(fullref.buf, '/')) {
 -                      warning("ignoring broken ref %s.", fullref.buf);
 +                      warning(_("ignoring broken ref %s"), fullref.buf);
                }
        }
        strbuf_release(&fullref);
@@@ -616,8 -608,7 +624,8 @@@ int dwim_log(const char *str, int len, 
  static int is_per_worktree_ref(const char *refname)
  {
        return !strcmp(refname, "HEAD") ||
 -              starts_with(refname, "refs/bisect/");
 +              starts_with(refname, "refs/bisect/") ||
 +              starts_with(refname, "refs/rewritten/");
  }
  
  static int is_pseudoref_syntax(const char *refname)
@@@ -661,7 -652,7 +669,7 @@@ static int write_pseudoref(const char *
  {
        const char *filename;
        int fd;
 -      static struct lock_file lock;
 +      struct lock_file lock = LOCK_INIT;
        struct strbuf buf = STRBUF_INIT;
        int ret = -1;
  
        strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
  
        filename = git_path("%s", pseudoref);
 -      fd = hold_lock_file_for_update_timeout(&lock, filename,
 -                                             LOCK_DIE_ON_ERROR,
 +      fd = hold_lock_file_for_update_timeout(&lock, filename, 0,
                                               get_files_ref_lock_timeout_ms());
        if (fd < 0) {
 -              strbuf_addf(err, "could not open '%s' for writing: %s",
 +              strbuf_addf(err, _("could not open '%s' for writing: %s"),
                            filename, strerror(errno));
                goto done;
        }
        if (old_oid) {
                struct object_id actual_old_oid;
  
 -              if (read_ref(pseudoref, &actual_old_oid))
 -                      die("could not read ref '%s'", pseudoref);
 -              if (oidcmp(&actual_old_oid, old_oid)) {
 -                      strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
 +              if (read_ref(pseudoref, &actual_old_oid)) {
 +                      if (!is_null_oid(old_oid)) {
 +                              strbuf_addf(err, _("could not read ref '%s'"),
 +                                          pseudoref);
 +                              rollback_lock_file(&lock);
 +                              goto done;
 +                      }
 +              } else if (is_null_oid(old_oid)) {
 +                      strbuf_addf(err, _("ref '%s' already exists"),
 +                                  pseudoref);
 +                      rollback_lock_file(&lock);
 +                      goto done;
 +              } else if (oidcmp(&actual_old_oid, old_oid)) {
 +                      strbuf_addf(err, _("unexpected object ID when writing '%s'"),
 +                                  pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
                }
        }
  
        if (write_in_full(fd, buf.buf, buf.len) < 0) {
 -              strbuf_addf(err, "could not write to '%s'", filename);
 +              strbuf_addf(err, _("could not write to '%s'"), filename);
                rollback_lock_file(&lock);
                goto done;
        }
@@@ -717,28 -698,24 +725,28 @@@ done
  
  static int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
  {
 -      static struct lock_file lock;
        const char *filename;
  
        filename = git_path("%s", pseudoref);
  
        if (old_oid && !is_null_oid(old_oid)) {
 +              struct lock_file lock = LOCK_INIT;
                int fd;
                struct object_id actual_old_oid;
  
                fd = hold_lock_file_for_update_timeout(
 -                              &lock, filename, LOCK_DIE_ON_ERROR,
 +                              &lock, filename, 0,
                                get_files_ref_lock_timeout_ms());
 -              if (fd < 0)
 -                      die_errno(_("Could not open '%s' for writing"), filename);
 +              if (fd < 0) {
 +                      error_errno(_("could not open '%s' for writing"),
 +                                  filename);
 +                      return -1;
 +              }
                if (read_ref(pseudoref, &actual_old_oid))
 -                      die("could not read ref '%s'", pseudoref);
 +                      die(_("could not read ref '%s'"), pseudoref);
                if (oidcmp(&actual_old_oid, old_oid)) {
 -                      warning("Unexpected sha1 when deleting %s", pseudoref);
 +                      error(_("unexpected object ID when deleting '%s'"),
 +                            pseudoref);
                        rollback_lock_file(&lock);
                        return -1;
                }
@@@ -761,7 -738,7 +769,7 @@@ int refs_delete_ref(struct ref_store *r
        struct strbuf err = STRBUF_INIT;
  
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 -              assert(refs == get_main_ref_store());
 +              assert(refs == get_main_ref_store(the_repository));
                return delete_pseudoref(refname, old_oid);
        }
  
  int delete_ref(const char *msg, const char *refname,
               const struct object_id *old_oid, unsigned int flags)
  {
 -      return refs_delete_ref(get_main_ref_store(), msg, refname,
 +      return refs_delete_ref(get_main_ref_store(the_repository), msg, refname,
                               old_oid, flags);
  }
  
 -int copy_reflog_msg(char *buf, const char *msg)
 +void copy_reflog_msg(struct strbuf *sb, const char *msg)
  {
 -      char *cp = buf;
        char c;
        int wasspace = 1;
  
 -      *cp++ = '\t';
 +      strbuf_addch(sb, '\t');
        while ((c = *msg++)) {
                if (wasspace && isspace(c))
                        continue;
                wasspace = isspace(c);
                if (wasspace)
                        c = ' ';
 -              *cp++ = c;
 +              strbuf_addch(sb, c);
        }
 -      while (buf < cp && isspace(cp[-1]))
 -              cp--;
 -      *cp++ = '\n';
 -      return cp - buf;
 +      strbuf_rtrim(sb);
  }
  
  int should_autocreate_reflog(const char *refname)
@@@ -868,13 -849,13 +876,13 @@@ static int read_ref_at_ent(struct objec
                if (!is_null_oid(&cb->ooid)) {
                        oidcpy(cb->oid, noid);
                        if (oidcmp(&cb->ooid, noid))
 -                              warning("Log for ref %s has gap after %s.",
 +                              warning(_("log for ref %s has gap after %s"),
                                        cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
                }
                else if (cb->date == cb->at_time)
                        oidcpy(cb->oid, noid);
                else if (oidcmp(noid, cb->oid))
 -                      warning("Log for ref %s unexpectedly ended on %s.",
 +                      warning(_("log for ref %s unexpectedly ended on %s"),
                                cb->refname, show_date(cb->date, cb->tz,
                                                       DATE_MODE(RFC2822)));
                oidcpy(&cb->ooid, ooid);
@@@ -932,7 -913,7 +940,7 @@@ int read_ref_at(const char *refname, un
                if (flags & GET_OID_QUIETLY)
                        exit(128);
                else
 -                      die("Log for %s is empty.", refname);
 +                      die(_("log for %s is empty"), refname);
        }
        if (cb.found_it)
                return 0;
@@@ -955,7 -936,7 +963,7 @@@ struct ref_transaction *ref_store_trans
  
  struct ref_transaction *ref_transaction_begin(struct strbuf *err)
  {
 -      return ref_store_transaction_begin(get_main_ref_store(), err);
 +      return ref_store_transaction_begin(get_main_ref_store(the_repository), err);
  }
  
  void ref_transaction_free(struct ref_transaction *transaction)
                /* OK */
                break;
        case REF_TRANSACTION_PREPARED:
 -              die("BUG: free called on a prepared reference transaction");
 +              BUG("free called on a prepared reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -996,7 -977,7 +1004,7 @@@ struct ref_update *ref_transaction_add_
        struct ref_update *update;
  
        if (transaction->state != REF_TRANSACTION_OPEN)
 -              die("BUG: update called for transaction that is not open");
 +              BUG("update called for transaction that is not open");
  
        FLEX_ALLOC_STR(update, refname, refname);
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
@@@ -1024,7 -1005,7 +1032,7 @@@ int ref_transaction_update(struct ref_t
        if ((new_oid && !is_null_oid(new_oid)) ?
            check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
            !refname_is_safe(refname)) {
 -              strbuf_addf(err, "refusing to update ref with bad name '%s'",
 +              strbuf_addf(err, _("refusing to update ref with bad name '%s'"),
                            refname);
                return -1;
        }
@@@ -1046,7 -1027,7 +1054,7 @@@ int ref_transaction_create(struct ref_t
                           struct strbuf *err)
  {
        if (!new_oid || is_null_oid(new_oid))
 -              die("BUG: create called without valid new_oid");
 +              BUG("create called without valid new_oid");
        return ref_transaction_update(transaction, refname, new_oid,
                                      &null_oid, flags, msg, err);
  }
@@@ -1058,7 -1039,7 +1066,7 @@@ int ref_transaction_delete(struct ref_t
                           struct strbuf *err)
  {
        if (old_oid && is_null_oid(old_oid))
 -              die("BUG: delete called with old_oid set to zeros");
 +              BUG("delete called with old_oid set to zeros");
        return ref_transaction_update(transaction, refname,
                                      &null_oid, old_oid,
                                      flags, msg, err);
@@@ -1071,7 -1052,7 +1079,7 @@@ int ref_transaction_verify(struct ref_t
                           struct strbuf *err)
  {
        if (!old_oid)
 -              die("BUG: verify called with old_oid set to NULL");
 +              BUG("verify called with old_oid set to NULL");
        return ref_transaction_update(transaction, refname,
                                      NULL, old_oid,
                                      flags, NULL, err);
@@@ -1087,7 -1068,7 +1095,7 @@@ int refs_update_ref(struct ref_store *r
        int ret = 0;
  
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 -              assert(refs == get_main_ref_store());
 +              assert(refs == get_main_ref_store(the_repository));
                ret = write_pseudoref(refname, new_oid, old_oid, &err);
        } else {
                t = ref_store_transaction_begin(refs, &err);
                }
        }
        if (ret) {
 -              const char *str = "update_ref failed for ref '%s': %s";
 +              const char *str = _("update_ref failed for ref '%s': %s");
  
                switch (onerr) {
                case UPDATE_REFS_MSG_ON_ERR:
@@@ -1126,7 -1107,7 +1134,7 @@@ int update_ref(const char *msg, const c
               const struct object_id *old_oid,
               unsigned int flags, enum action_on_err onerr)
  {
 -      return refs_update_ref(get_main_ref_store(), msg, refname, new_oid,
 +      return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid,
                               old_oid, flags, onerr);
  }
  
@@@ -1159,8 -1140,8 +1167,8 @@@ char *shorten_unambiguous_ref(const cha
                for (i = 0; i < nr_rules; i++) {
                        assert(offset < total_len);
                        scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
 -                      offset += snprintf(scanf_fmts[i], total_len - offset,
 -                                         ref_rev_parse_rules[i], 2, "%s") + 1;
 +                      offset += xsnprintf(scanf_fmts[i], total_len - offset,
 +                                          ref_rev_parse_rules[i], 2, "%s") + 1;
                }
        }
  
@@@ -1347,7 -1328,7 +1355,7 @@@ int refs_head_ref(struct ref_store *ref
  
  int head_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_head_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  struct ref_iterator *refs_ref_iterator_begin(
@@@ -1406,7 -1387,7 +1414,7 @@@ int refs_for_each_ref(struct ref_store 
  
  int for_each_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
  
  int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_ref_in(get_main_ref_store(), prefix, fn, cb_data);
 +      return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
  }
  
  int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
  
        if (broken)
                flag = DO_FOR_EACH_INCLUDE_BROKEN;
 -      return do_for_each_ref(get_main_ref_store(),
 +      return do_for_each_ref(get_main_ref_store(the_repository),
                               prefix, fn, 0, flag, cb_data);
  }
  
@@@ -1441,9 -1422,9 +1449,9 @@@ int refs_for_each_fullref_in(struct ref
        return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
  }
  
 -int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 +int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(get_main_ref_store(),
 +      return do_for_each_ref(get_main_ref_store(r),
                               git_replace_ref_base, fn,
                               strlen(git_replace_ref_base),
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
@@@ -1454,7 -1435,7 +1462,7 @@@ int for_each_namespaced_ref(each_ref_f
        struct strbuf buf = STRBUF_INIT;
        int ret;
        strbuf_addf(&buf, "%srefs/", get_git_namespace());
 -      ret = do_for_each_ref(get_main_ref_store(),
 +      ret = do_for_each_ref(get_main_ref_store(the_repository),
                              buf.buf, fn, 0, 0, cb_data);
        strbuf_release(&buf);
        return ret;
@@@ -1468,7 -1449,7 +1476,7 @@@ int refs_for_each_rawref(struct ref_sto
  
  int for_each_rawref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_rawref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_read_raw_ref(struct ref_store *ref_store,
@@@ -1574,7 -1555,7 +1582,7 @@@ const char *refs_resolve_ref_unsafe(str
  /* backend functions */
  int refs_init_db(struct strbuf *err)
  {
 -      struct ref_store *refs = get_main_ref_store();
 +      struct ref_store *refs = get_main_ref_store(the_repository);
  
        return refs->be->init_db(refs, err);
  }
  const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               struct object_id *oid, int *flags)
  {
 -      return refs_resolve_ref_unsafe(get_main_ref_store(), refname,
 +      return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
                                       resolve_flags, oid, flags);
  }
  
@@@ -1634,6 -1615,9 +1642,6 @@@ static struct ref_store_hash_entry *all
        return entry;
  }
  
 -/* A pointer to the ref_store for the main repository: */
 -static struct ref_store *main_ref_store;
 -
  /* A hashmap of ref_stores, stored by submodule name: */
  static struct hashmap submodule_ref_stores;
  
@@@ -1669,22 -1653,19 +1677,22 @@@ static struct ref_store *ref_store_init
        struct ref_store *refs;
  
        if (!be)
 -              die("BUG: reference backend %s is unknown", be_name);
 +              BUG("reference backend %s is unknown", be_name);
  
        refs = be->init(gitdir, flags);
        return refs;
  }
  
 -struct ref_store *get_main_ref_store(void)
 +struct ref_store *get_main_ref_store(struct repository *r)
  {
 -      if (main_ref_store)
 -              return main_ref_store;
 +      if (r->refs)
 +              return r->refs;
 +
 +      if (!r->gitdir)
 +              BUG("attempting to get main_ref_store outside of repository");
  
 -      main_ref_store = ref_store_init(get_git_dir(), REF_STORE_ALL_CAPS);
 -      return main_ref_store;
 +      r->refs = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
 +      return r->refs;
  }
  
  /*
@@@ -1700,7 -1681,7 +1708,7 @@@ static void register_ref_store_map(stru
                hashmap_init(map, ref_store_hash_cmp, NULL, 0);
  
        if (hashmap_put(map, alloc_ref_store_hash_entry(name, refs)))
 -              die("BUG: %s ref_store '%s' initialized twice", type, name);
 +              BUG("%s ref_store '%s' initialized twice", type, name);
  }
  
  struct ref_store *get_submodule_ref_store(const char *submodule)
@@@ -1753,7 -1734,7 +1761,7 @@@ struct ref_store *get_worktree_ref_stor
        const char *id;
  
        if (wt->is_current)
 -              return get_main_ref_store();
 +              return get_main_ref_store(the_repository);
  
        id = wt->id ? wt->id : "/";
        refs = lookup_ref_store_map(&worktree_ref_stores, id);
@@@ -1809,7 -1790,7 +1817,7 @@@ int refs_peel_ref(struct ref_store *ref
  
  int peel_ref(const char *refname, struct object_id *oid)
  {
 -      return refs_peel_ref(get_main_ref_store(), refname, oid);
 +      return refs_peel_ref(get_main_ref_store(the_repository), refname, oid);
  }
  
  int refs_create_symref(struct ref_store *refs,
  int create_symref(const char *ref_target, const char *refs_heads_master,
                  const char *logmsg)
  {
 -      return refs_create_symref(get_main_ref_store(), ref_target,
 +      return refs_create_symref(get_main_ref_store(the_repository), ref_target,
                                  refs_heads_master, logmsg);
  }
  
@@@ -1842,11 -1823,11 +1850,11 @@@ int ref_update_reject_duplicates(struc
  
                if (!cmp) {
                        strbuf_addf(err,
 -                                  "multiple updates for ref '%s' not allowed.",
 +                                  _("multiple updates for ref '%s' not allowed"),
                                    refnames->items[i].string);
                        return 1;
                } else if (cmp > 0) {
 -                      die("BUG: ref_update_reject_duplicates() received unsorted list");
 +                      BUG("ref_update_reject_duplicates() received unsorted list");
                }
        }
        return 0;
@@@ -1862,13 -1843,13 +1870,13 @@@ int ref_transaction_prepare(struct ref_
                /* Good. */
                break;
        case REF_TRANSACTION_PREPARED:
 -              die("BUG: prepare called twice on reference transaction");
 +              BUG("prepare called twice on reference transaction");
                break;
        case REF_TRANSACTION_CLOSED:
 -              die("BUG: prepare called on a closed reference transaction");
 +              BUG("prepare called on a closed reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -1895,10 -1876,10 +1903,10 @@@ int ref_transaction_abort(struct ref_tr
                ret = refs->be->transaction_abort(refs, transaction, err);
                break;
        case REF_TRANSACTION_CLOSED:
 -              die("BUG: abort called on a closed reference transaction");
 +              BUG("abort called on a closed reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -1923,10 -1904,10 +1931,10 @@@ int ref_transaction_commit(struct ref_t
                /* Fall through to finish. */
                break;
        case REF_TRANSACTION_CLOSED:
 -              die("BUG: commit called on a closed reference transaction");
 +              BUG("commit called on a closed reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -1970,13 -1951,13 +1978,13 @@@ int refs_verify_refname_available(struc
                        continue;
  
                if (!refs_read_raw_ref(refs, dirname.buf, &oid, &referent, &type)) {
 -                      strbuf_addf(err, "'%s' exists; cannot create '%s'",
 +                      strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
                                    dirname.buf, refname);
                        goto cleanup;
                }
  
                if (extras && string_list_has_string(extras, dirname.buf)) {
 -                      strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
 +                      strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
                                    refname, dirname.buf);
                        goto cleanup;
                }
                    string_list_has_string(skip, iter->refname))
                        continue;
  
 -              strbuf_addf(err, "'%s' exists; cannot create '%s'",
 +              strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
                            iter->refname, refname);
                ref_iterator_abort(iter);
                goto cleanup;
        }
  
        if (ok != ITER_DONE)
 -              die("BUG: error while iterating over references");
 +              BUG("error while iterating over references");
  
        extra_refname = find_descendant_ref(dirname.buf, extras, skip);
        if (extra_refname)
 -              strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
 +              strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
                            refname, extra_refname);
        else
                ret = 0;
@@@ -2033,7 -2014,7 +2041,7 @@@ int refs_for_each_reflog(struct ref_sto
  
  int for_each_reflog(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_reflog(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
  int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
                                void *cb_data)
  {
 -      return refs_for_each_reflog_ent_reverse(get_main_ref_store(),
 +      return refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
                                                refname, fn, cb_data);
  }
  
@@@ -2061,7 -2042,7 +2069,7 @@@ int refs_for_each_reflog_ent(struct ref
  int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
                        void *cb_data)
  {
 -      return refs_for_each_reflog_ent(get_main_ref_store(), refname,
 +      return refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname,
                                        fn, cb_data);
  }
  
@@@ -2072,7 -2053,7 +2080,7 @@@ int refs_reflog_exists(struct ref_stor
  
  int reflog_exists(const char *refname)
  {
 -      return refs_reflog_exists(get_main_ref_store(), refname);
 +      return refs_reflog_exists(get_main_ref_store(the_repository), refname);
  }
  
  int refs_create_reflog(struct ref_store *refs, const char *refname,
  int safe_create_reflog(const char *refname, int force_create,
                       struct strbuf *err)
  {
 -      return refs_create_reflog(get_main_ref_store(), refname,
 +      return refs_create_reflog(get_main_ref_store(the_repository), refname,
                                  force_create, err);
  }
  
@@@ -2095,7 -2076,7 +2103,7 @@@ int refs_delete_reflog(struct ref_stor
  
  int delete_reflog(const char *refname)
  {
 -      return refs_delete_reflog(get_main_ref_store(), refname);
 +      return refs_delete_reflog(get_main_ref_store(the_repository), refname);
  }
  
  int refs_reflog_expire(struct ref_store *refs,
@@@ -2118,7 -2099,7 +2126,7 @@@ int reflog_expire(const char *refname, 
                  reflog_expiry_cleanup_fn cleanup_fn,
                  void *policy_cb_data)
  {
 -      return refs_reflog_expire(get_main_ref_store(),
 +      return refs_reflog_expire(get_main_ref_store(the_repository),
                                  refname, oid, flags,
                                  prepare_fn, should_prune_fn,
                                  cleanup_fn, policy_cb_data);
@@@ -2141,7 -2122,7 +2149,7 @@@ int refs_delete_refs(struct ref_store *
  int delete_refs(const char *msg, struct string_list *refnames,
                unsigned int flags)
  {
 -      return refs_delete_refs(get_main_ref_store(), msg, refnames, flags);
 +      return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags);
  }
  
  int refs_rename_ref(struct ref_store *refs, const char *oldref,
  
  int rename_ref(const char *oldref, const char *newref, const char *logmsg)
  {
 -      return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
 +      return refs_rename_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
  }
  
  int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
  
  int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
  {
 -      return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg);
 +      return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
  }
diff --combined remote.c
index 86e6098774d891a3b7045cf1dd9c11959faf13dc,4a3e7ba13613d044cb4ba6e7e025eb51962ba92f..7f6277a1451d147fc5af4ae2910e7c40dd330aec
+++ b/remote.c
@@@ -2,8 -2,6 +2,8 @@@
  #include "config.h"
  #include "remote.h"
  #include "refs.h"
 +#include "refspec.h"
 +#include "object-store.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;
@@@ -78,6 -88,33 +78,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);
@@@ -149,12 -186,9 +149,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);
@@@ -252,9 -286,9 +252,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);
@@@ -293,19 -327,15 +293,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))
@@@ -471,6 -499,158 +471,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))
@@@ -525,8 -705,9 +525,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;
@@@ -563,6 -744,8 +563,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;
  }
  
@@@ -593,6 -776,12 +593,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;
@@@ -698,9 -887,7 +698,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,
@@@ -979,7 -1167,7 +979,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
@@@ -1149,7 -1336,7 +1149,7 @@@ static void add_to_tips(struct tips *ti
  
        if (is_null_oid(oid))
                return;
 -      commit = lookup_commit_reference_gently(oid, 1);
 +      commit = lookup_commit_reference_gently(the_repository, oid, 1);
        if (!commit || (commit->object.flags & TMP_MARK))
                return;
        commit->object.flags |= TMP_MARK;
@@@ -1189,7 -1376,7 +1189,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 (sha1_object_info(ref->new_oid.hash, 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;
  
                        if (is_null_oid(&ref->new_oid))
                                continue;
 -                      commit = lookup_commit_reference_gently(&ref->new_oid,
 +                      commit = lookup_commit_reference_gently(the_repository,
 +                                                              &ref->new_oid,
                                                                1);
                        if (!commit)
                                /* not pushing a commit, which is not an error */
@@@ -1257,20 -1443,22 +1257,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;
@@@ -1436,8 -1626,8 +1436,8 @@@ void set_ref_status_for_push(struct re
                                reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
                        else if (!has_object_file(&ref->old_oid))
                                reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
 -                      else if (!lookup_commit_reference_gently(&ref->old_oid, 1) ||
 -                               !lookup_commit_reference_gently(&ref->new_oid, 1))
 +                      else if (!lookup_commit_reference_gently(the_repository, &ref->old_oid, 1) ||
 +                               !lookup_commit_reference_gently(the_repository, &ref->new_oid, 1))
                                reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
                        else if (!ref_newer(&ref->new_oid, &ref->old_oid))
                                reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
@@@ -1563,7 -1753,7 +1563,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"),
@@@ -1581,11 -1771,12 +1581,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)
@@@ -1658,7 -1849,7 +1658,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;
  static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name)
  {
        const struct ref *ref;
+       const struct ref *best_match = NULL;
+       int best_score = 0;
        for (ref = refs; ref; ref = ref->next) {
-               if (refname_match(name, ref->name))
-                       return ref;
+               int score = refname_match(name, ref->name);
+               if (best_score < score) {
+                       best_match = ref;
+                       best_score = score;
+               }
        }
-       return NULL;
+       return best_match;
  }
  
  struct ref *get_remote_ref(const struct ref *remote_refs, const char *name)
@@@ -1723,7 -1921,7 +1730,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)
  {
                if (refspec->exact_sha1) {
                        ref_map = alloc_ref(name);
                        get_oid_hex(name, &ref_map->old_oid);
 +                      ref_map->exact_oid = 1;
                } else {
                        ref_map = get_remote_ref(remote_refs, name);
                }
@@@ -1803,14 -2000,12 +1810,14 @@@ int ref_newer(const struct object_id *n
         * Both new_commit and old_commit must be commit-ish and new_commit is descendant of
         * old_commit.  Otherwise we require --force.
         */
 -      o = deref_tag(parse_object(old_oid), NULL, 0);
 +      o = deref_tag(the_repository, parse_object(the_repository, old_oid),
 +                    NULL, 0);
        if (!o || o->type != OBJ_COMMIT)
                return 0;
        old_commit = (struct commit *) o;
  
 -      o = deref_tag(parse_object(new_oid), NULL, 0);
 +      o = deref_tag(the_repository, parse_object(the_repository, new_oid),
 +                    NULL, 0);
        if (!o || o->type != OBJ_COMMIT)
                return 0;
        new_commit = (struct commit *) o;
@@@ -1868,13 -2063,13 +1875,13 @@@ int stat_tracking_info(struct branch *b
        /* Cannot stat if what we used to build on no longer exists */
        if (read_ref(base, &oid))
                return -1;
 -      theirs = lookup_commit_reference(&oid);
 +      theirs = lookup_commit_reference(the_repository, &oid);
        if (!theirs)
                return -1;
  
        if (read_ref(branch->refname, &oid))
                return -1;
 -      ours = lookup_commit_reference(&oid);
 +      ours = lookup_commit_reference(the_repository, &oid);
        if (!ours)
                return -1;
  
@@@ -2064,7 -2259,8 +2071,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 */
  
@@@ -2105,7 -2301,7 +2112,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);
@@@ -2197,7 -2394,7 +2204,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 t/t5510-fetch.sh
index 62308be499ffd862484eb23de61850d00f80720c,858381a7881f430c5e779c2e385ebda4bed3a6dc..5e810f494e69625cc365955143a08567b8f783a9
@@@ -63,7 -63,7 +63,7 @@@ test_expect_success "fetch test" 
        git commit -a -m "updated by origin" &&
        cd two &&
        git fetch &&
 -      test -f .git/refs/heads/one &&
 +      git rev-parse --verify refs/heads/one &&
        mine=$(git rev-parse refs/heads/one) &&
        his=$(cd ../one && git rev-parse refs/heads/master) &&
        test "z$mine" = "z$his"
@@@ -73,8 -73,8 +73,8 @@@ test_expect_success "fetch test for-mer
        cd "$D" &&
        cd three &&
        git fetch &&
 -      test -f .git/refs/heads/two &&
 -      test -f .git/refs/heads/one &&
 +      git rev-parse --verify refs/heads/two &&
 +      git rev-parse --verify refs/heads/one &&
        master_in_two=$(cd ../two && git rev-parse master) &&
        one_in_two=$(cd ../two && git rev-parse one) &&
        {
@@@ -535,6 -535,41 +535,41 @@@ test_expect_success "should be able to 
        )
  '
  
+ test_expect_success 'LHS of refspec follows ref disambiguation rules' '
+       mkdir lhs-ambiguous &&
+       (
+               cd lhs-ambiguous &&
+               git init server &&
+               test_commit -C server unwanted &&
+               test_commit -C server wanted &&
+               git init client &&
+               # Check a name coming after "refs" alphabetically ...
+               git -C server update-ref refs/heads/s wanted &&
+               git -C server update-ref refs/heads/refs/heads/s unwanted &&
+               git -C client fetch ../server +refs/heads/s:refs/heads/checkthis &&
+               git -C server rev-parse wanted >expect &&
+               git -C client rev-parse checkthis >actual &&
+               test_cmp expect actual &&
+               # ... and one before.
+               git -C server update-ref refs/heads/q wanted &&
+               git -C server update-ref refs/heads/refs/heads/q unwanted &&
+               git -C client fetch ../server +refs/heads/q:refs/heads/checkthis &&
+               git -C server rev-parse wanted >expect &&
+               git -C client rev-parse checkthis >actual &&
+               test_cmp expect actual &&
+               # Tags are preferred over branches like refs/{heads,tags}/*
+               git -C server update-ref refs/tags/t wanted &&
+               git -C server update-ref refs/heads/t unwanted &&
+               git -C client fetch ../server +t:refs/heads/checkthis &&
+               git -C server rev-parse wanted >expect &&
+               git -C client rev-parse checkthis >actual
+       )
+ '
  # configured prune tests
  
  set_config_tristate () {
@@@ -828,11 -863,9 +863,11 @@@ test_expect_success 'fetching with auto
        test_commit test2 &&
        (
                cd auto-gc &&
 +              git config fetch.unpackLimit 1 &&
                git config gc.autoPackLimit 1 &&
                git config gc.autoDetach false &&
                GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 &&
 +              test_i18ngrep "Auto packing the repository" fetch.out &&
                ! grep "Should I try again" fetch.out
        )
  '
@@@ -842,8 -875,8 +877,8 @@@ test_expect_success C_LOCALE_OUTPUT 'fe
        test_commit looooooooooooong-tag &&
        (
                cd full-output &&
 -              git -c fetch.output=full fetch origin 2>&1 | \
 -                      grep -e "->" | cut -c 22- >../actual
 +              git -c fetch.output=full fetch origin >actual 2>&1 &&
 +              grep -e "->" actual | cut -c 22- >../actual
        ) &&
        cat >expect <<-\EOF &&
        master               -> origin/master
@@@ -857,8 -890,8 +892,8 @@@ test_expect_success C_LOCALE_OUTPUT 'fe
        test_commit extraaa &&
        (
                cd compact &&
 -              git -c fetch.output=compact fetch origin 2>&1 | \
 -                      grep -e "->" | cut -c 22- >../actual
 +              git -c fetch.output=compact fetch origin >actual 2>&1 &&
 +              grep -e "->" actual | cut -c 22- >../actual
        ) &&
        cat >expect <<-\EOF &&
        master     -> origin/*
        test_cmp expect actual
  '
  
 +setup_negotiation_tip () {
 +      SERVER="$1"
 +      URL="$2"
 +      USE_PROTOCOL_V2="$3"
 +
 +      rm -rf "$SERVER" client trace &&
 +      git init "$SERVER" &&
 +      test_commit -C "$SERVER" alpha_1 &&
 +      test_commit -C "$SERVER" alpha_2 &&
 +      git -C "$SERVER" checkout --orphan beta &&
 +      test_commit -C "$SERVER" beta_1 &&
 +      test_commit -C "$SERVER" beta_2 &&
 +
 +      git clone "$URL" client &&
 +
 +      if test "$USE_PROTOCOL_V2" -eq 1
 +      then
 +              git -C "$SERVER" config protocol.version 2 &&
 +              git -C client config protocol.version 2
 +      fi &&
 +
 +      test_commit -C "$SERVER" beta_s &&
 +      git -C "$SERVER" checkout master &&
 +      test_commit -C "$SERVER" alpha_s &&
 +      git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
 +}
 +
 +check_negotiation_tip () {
 +      # Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2
 +      ALPHA_1=$(git -C client rev-parse alpha_1) &&
 +      grep "fetch> have $ALPHA_1" trace &&
 +      BETA_1=$(git -C client rev-parse beta_1) &&
 +      grep "fetch> have $BETA_1" trace &&
 +      ALPHA_2=$(git -C client rev-parse alpha_2) &&
 +      ! grep "fetch> have $ALPHA_2" trace &&
 +      BETA_2=$(git -C client rev-parse beta_2) &&
 +      ! grep "fetch> have $BETA_2" trace
 +}
 +
 +test_expect_success '--negotiation-tip limits "have" lines sent' '
 +      setup_negotiation_tip server server 0 &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
 +              --negotiation-tip=alpha_1 --negotiation-tip=beta_1 \
 +              origin alpha_s beta_s &&
 +      check_negotiation_tip
 +'
 +
 +test_expect_success '--negotiation-tip understands globs' '
 +      setup_negotiation_tip server server 0 &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
 +              --negotiation-tip=*_1 \
 +              origin alpha_s beta_s &&
 +      check_negotiation_tip
 +'
 +
 +test_expect_success '--negotiation-tip understands abbreviated SHA-1' '
 +      setup_negotiation_tip server server 0 &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
 +              --negotiation-tip=$(git -C client rev-parse --short alpha_1) \
 +              --negotiation-tip=$(git -C client rev-parse --short beta_1) \
 +              origin alpha_s beta_s &&
 +      check_negotiation_tip
 +'
 +
 +. "$TEST_DIRECTORY"/lib-httpd.sh
 +start_httpd
 +
 +test_expect_success '--negotiation-tip limits "have" lines sent with HTTP protocol v2' '
 +      setup_negotiation_tip "$HTTPD_DOCUMENT_ROOT_PATH/server" \
 +              "$HTTPD_URL/smart/server" 1 &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \
 +              --negotiation-tip=alpha_1 --negotiation-tip=beta_1 \
 +              origin alpha_s beta_s &&
 +      check_negotiation_tip
 +'
 +
 +stop_httpd
 +
  test_done