Adjust js/remote-improvements and db/refspec-wildcard-in-the-middle
authorJunio C Hamano <gitster@pobox.com>
Sun, 8 Mar 2009 08:12:33 +0000 (00:12 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 8 Mar 2009 08:24:21 +0000 (00:24 -0800)
The latter topic changes the definition of how refspec's src and dst side
is stored in-core; it used to be that the asterisk for pattern was
omitted, but now it is included. The former topic handcrafts an old style
refspec to feed the refspec matching machinery that lacks the asterisk and
triggers an error.

This resolves the semantic clash between the two topics early before they
need to be merged to integration branches.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
1  2 
builtin-clone.c
builtin-remote.c
contrib/completion/git-completion.bash
refs.c
remote.c
t/t5540-http-push.sh
diff --combined builtin-clone.c
index 06b5a7fc391fd8da0d963f9c9cd0c5cd1675a38b,3146ca87f88e8f187c47b4a375c19b499c024b33..b385b97828fa3973494666a16f939578209fdef7
@@@ -20,6 -20,7 +20,7 @@@
  #include "dir.h"
  #include "pack-refs.h"
  #include "sigchain.h"
+ #include "remote.h"
  
  /*
   * Overall FIXMEs:
@@@ -293,43 -294,6 +294,6 @@@ static void remove_junk_on_signal(int s
        raise(signo);
  }
  
- static const struct ref *locate_head(const struct ref *refs,
-                                    const struct ref *mapped_refs,
-                                    const struct ref **remote_head_p)
- {
-       const struct ref *remote_head = NULL;
-       const struct ref *remote_master = NULL;
-       const struct ref *r;
-       for (r = refs; r; r = r->next)
-               if (!strcmp(r->name, "HEAD"))
-                       remote_head = r;
-       for (r = mapped_refs; r; r = r->next)
-               if (!strcmp(r->name, "refs/heads/master"))
-                       remote_master = r;
-       if (remote_head_p)
-               *remote_head_p = remote_head;
-       /* If there's no HEAD value at all, never mind. */
-       if (!remote_head)
-               return NULL;
-       /* If refs/heads/master could be right, it is. */
-       if (remote_master && !hashcmp(remote_master->old_sha1,
-                                     remote_head->old_sha1))
-               return remote_master;
-       /* Look for another ref that points there */
-       for (r = mapped_refs; r; r = r->next)
-               if (r != remote_head &&
-                   !hashcmp(r->old_sha1, remote_head->old_sha1))
-                       return r;
-       /* Nothing is the same */
-       return NULL;
- }
  static struct ref *write_remote_refs(const struct ref *refs,
                struct refspec *refspec, const char *reflog)
  {
@@@ -378,8 -342,7 +342,8 @@@ int cmd_clone(int argc, const char **ar
        struct transport *transport = NULL;
        char *src_ref_prefix = "refs/heads/";
  
 -      struct refspec refspec;
 +      struct refspec *refspec;
 +      const char *fetch_pattern;
  
        junk_pid = getpid();
  
                strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
        }
  
 +      strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
 +
        if (option_mirror || !option_bare) {
                /* Configure the remote */
 +              strbuf_addf(&key, "remote.%s.fetch", option_origin);
 +              git_config_set_multivar(key.buf, value.buf, "^$", 0);
 +              strbuf_reset(&key);
 +
                if (option_mirror) {
                        strbuf_addf(&key, "remote.%s.mirror", option_origin);
                        git_config_set(key.buf, "true");
  
                strbuf_addf(&key, "remote.%s.url", option_origin);
                git_config_set(key.buf, repo);
 -                      strbuf_reset(&key);
 -
 -              strbuf_addf(&key, "remote.%s.fetch", option_origin);
 -              strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
 -              git_config_set_multivar(key.buf, value.buf, "^$", 0);
                strbuf_reset(&key);
 -              strbuf_reset(&value);
        }
  
 -      refspec.force = 0;
 -      refspec.pattern = 1;
 -      refspec.src = src_ref_prefix;
 -      refspec.dst = branch_top.buf;
 +      fetch_pattern = value.buf;
 +      refspec = parse_fetch_refspec(1, &fetch_pattern);
 +
 +      strbuf_reset(&value);
  
        if (path && !is_bundle)
                refs = clone_local(path, git_dir);
        if (refs) {
                clear_extra_refs();
  
 -              mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
 +              mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);
  
-               head_points_at = locate_head(refs, mapped_refs, &remote_head);
+               remote_head = find_ref_by_name(refs, "HEAD");
+               head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
        }
        else {
                warning("You appear to have cloned an empty repository.");
diff --combined builtin-remote.c
index ac69d37c8af415a64cba88a888e9b80db95ef97a,7b31e554e97c320562ab3c03c3de140e69cffb37..3e4a41b11bbb5b30bec52b239708ad244fa95b88
@@@ -12,12 -12,17 +12,17 @@@ static const char * const builtin_remot
        "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
        "git remote rename <old> <new>",
        "git remote rm <name>",
+       "git remote set-head <name> [-a | -d | <branch>]",
        "git remote show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [group]",
        NULL
  };
  
+ #define GET_REF_STATES (1<<0)
+ #define GET_HEAD_NAMES (1<<1)
+ #define GET_PUSH_REF_STATES (1<<2)
  static int verbose;
  
  static int show_all(void);
@@@ -143,8 -148,9 +148,9 @@@ static int add(int argc, const char **a
  }
  
  struct branch_info {
-       char *remote;
+       char *remote_name;
        struct string_list merge;
+       int rebase;
  };
  
  static struct string_list branch_list;
@@@ -161,10 -167,11 +167,11 @@@ static const char *abbrev_ref(const cha
  static int config_read_branches(const char *key, const char *value, void *cb)
  {
        if (!prefixcmp(key, "branch.")) {
+               const char *orig_key = key;
                char *name;
                struct string_list_item *item;
                struct branch_info *info;
-               enum { REMOTE, MERGE } type;
+               enum { REMOTE, MERGE, REBASE } type;
  
                key += 7;
                if (!postfixcmp(key, ".remote")) {
                } else if (!postfixcmp(key, ".merge")) {
                        name = xstrndup(key, strlen(key) - 6);
                        type = MERGE;
+               } else if (!postfixcmp(key, ".rebase")) {
+                       name = xstrndup(key, strlen(key) - 7);
+                       type = REBASE;
                } else
                        return 0;
  
                        item->util = xcalloc(sizeof(struct branch_info), 1);
                info = item->util;
                if (type == REMOTE) {
-                       if (info->remote)
-                               warning("more than one branch.%s", key);
-                       info->remote = xstrdup(value);
-               } else {
+                       if (info->remote_name)
+                               warning("more than one %s", orig_key);
+                       info->remote_name = xstrdup(value);
+               } else if (type == MERGE) {
                        char *space = strchr(value, ' ');
                        value = abbrev_branch(value);
                        while (space) {
                                space = strchr(value, ' ');
                        }
                        string_list_append(xstrdup(value), &info->merge);
-               }
+               } else
+                       info->rebase = git_config_bool(orig_key, value);
        }
        return 0;
  }
@@@ -206,12 -217,12 +217,12 @@@ static void read_branches(void
        if (branch_list.nr)
                return;
        git_config(config_read_branches, NULL);
-       sort_string_list(&branch_list);
  }
  
  struct ref_states {
        struct remote *remote;
-       struct string_list new, stale, tracked;
+       struct string_list new, stale, tracked, heads, push;
+       int queried;
  };
  
  static int handle_one_branch(const char *refname,
                const char *name = abbrev_branch(refspec.src);
                /* symbolic refs pointing nowhere were handled already */
                if ((flags & REF_ISSYMREF) ||
-                               unsorted_string_list_has_string(&states->tracked,
-                                       name) ||
-                               unsorted_string_list_has_string(&states->new,
-                                       name))
+                   string_list_has_string(&states->tracked, name) ||
+                   string_list_has_string(&states->new, name))
                        return 0;
                item = string_list_append(name, &states->stale);
                item->util = xstrdup(refname);
        return 0;
  }
  
- static int get_ref_states(const struct ref *ref, struct ref_states *states)
+ static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
  {
        struct ref *fetch_map = NULL, **tail = &fetch_map;
+       struct ref *ref;
        int i;
  
        for (i = 0; i < states->remote->fetch_refspec_nr; i++)
-               if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1))
+               if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
                        die("Could not get fetch map for refspec %s",
                                states->remote->fetch_refspec[i]);
  
        states->new.strdup_strings = states->tracked.strdup_strings = 1;
        for (ref = fetch_map; ref; ref = ref->next) {
-               struct string_list *target = &states->tracked;
                unsigned char sha1[20];
-               void *util = NULL;
                if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
-                       target = &states->new;
-               else {
-                       target = &states->tracked;
-                       if (hashcmp(sha1, ref->new_sha1))
-                               util = &states;
-               }
-               string_list_append(abbrev_branch(ref->name), target)->util = util;
+                       string_list_append(abbrev_branch(ref->name), &states->new);
+               else
+                       string_list_append(abbrev_branch(ref->name), &states->tracked);
        }
        free_refs(fetch_map);
  
+       sort_string_list(&states->new);
+       sort_string_list(&states->tracked);
        for_each_ref(handle_one_branch, states);
        sort_string_list(&states->stale);
  
        return 0;
  }
  
 -              char buf[PATH_MAX];
+ struct push_info {
+       char *dest;
+       int forced;
+       enum {
+               PUSH_STATUS_CREATE = 0,
+               PUSH_STATUS_DELETE,
+               PUSH_STATUS_UPTODATE,
+               PUSH_STATUS_FASTFORWARD,
+               PUSH_STATUS_OUTOFDATE,
+               PUSH_STATUS_NOTQUERIED,
+       } status;
+ };
+ static int get_push_ref_states(const struct ref *remote_refs,
+       struct ref_states *states)
+ {
+       struct remote *remote = states->remote;
+       struct ref *ref, *local_refs, *push_map, **push_tail;
+       if (remote->mirror)
+               return 0;
+       local_refs = get_local_heads();
+       ref = push_map = copy_ref_list(remote_refs);
+       while (ref->next)
+               ref = ref->next;
+       push_tail = &ref->next;
+       match_refs(local_refs, push_map, &push_tail, remote->push_refspec_nr,
+                  remote->push_refspec, MATCH_REFS_NONE);
+       states->push.strdup_strings = 1;
+       for (ref = push_map; ref; ref = ref->next) {
+               struct string_list_item *item;
+               struct push_info *info;
+               if (!ref->peer_ref)
+                       continue;
+               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+               item = string_list_append(abbrev_branch(ref->peer_ref->name),
+                                         &states->push);
+               item->util = xcalloc(sizeof(struct push_info), 1);
+               info = item->util;
+               info->forced = ref->force;
+               info->dest = xstrdup(abbrev_branch(ref->name));
+               if (is_null_sha1(ref->new_sha1)) {
+                       info->status = PUSH_STATUS_DELETE;
+               } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
+                       info->status = PUSH_STATUS_UPTODATE;
+               else if (is_null_sha1(ref->old_sha1))
+                       info->status = PUSH_STATUS_CREATE;
+               else if (has_sha1_file(ref->old_sha1) &&
+                        ref_newer(ref->new_sha1, ref->old_sha1))
+                       info->status = PUSH_STATUS_FASTFORWARD;
+               else
+                       info->status = PUSH_STATUS_OUTOFDATE;
+       }
+       free_refs(local_refs);
+       free_refs(push_map);
+       return 0;
+ }
+ static int get_push_ref_states_noquery(struct ref_states *states)
+ {
+       int i;
+       struct remote *remote = states->remote;
+       struct string_list_item *item;
+       struct push_info *info;
+       if (remote->mirror)
+               return 0;
+       states->push.strdup_strings = 1;
+       if (!remote->push_refspec_nr) {
+               item = string_list_append("(matching)", &states->push);
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               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;
 -              else if (spec->pattern) {
 -                      snprintf(buf, (sizeof(buf)), "%s*", spec->src);
 -                      item = string_list_append(buf, &states->push);
 -                      snprintf(buf, (sizeof(buf)), "%s*", spec->dst);
 -              } else if (strlen(spec->src))
+               if (spec->matching)
+                       item = string_list_append("(matching)", &states->push);
 -              if (spec->pattern)
 -                      info->dest = xstrdup(buf);
 -              else
 -                      info->dest = xstrdup(spec->dst ? spec->dst : item->string);
++              else if (strlen(spec->src))
+                       item = string_list_append(spec->src, &states->push);
+               else
+                       item = string_list_append("(delete)", &states->push);
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->forced = spec->force;
+               info->status = PUSH_STATUS_NOTQUERIED;
 -      refspec.src = refspec.dst = "refs/heads/";
++              info->dest = xstrdup(spec->dst ? spec->dst : item->string);
+       }
+       return 0;
+ }
+ static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
+ {
+       struct ref *ref, *matches;
+       struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+       struct refspec refspec;
+       refspec.force = 0;
+       refspec.pattern = 1;
++      refspec.src = refspec.dst = "refs/heads/*";
+       states->heads.strdup_strings = 1;
+       get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+       matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+                                   fetch_map, 1);
+       for(ref = matches; ref; ref = ref->next)
+               string_list_append(abbrev_branch(ref->name), &states->heads);
+       free_refs(fetch_map);
+       free_refs(matches);
+       return 0;
+ }
  struct known_remote {
        struct known_remote *next;
        struct remote *remote;
@@@ -466,7 -598,7 +590,7 @@@ static int mv(int argc, const char **ar
        for (i = 0; i < branch_list.nr; i++) {
                struct string_list_item *item = branch_list.items + i;
                struct branch_info *info = item->util;
-               if (info->remote && !strcmp(info->remote, rename.old)) {
+               if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
                        strbuf_reset(&buf);
                        strbuf_addf(&buf, "branch.%s.remote", item->string);
                        if (git_config_set(buf.buf, rename.new)) {
@@@ -576,7 -708,7 +700,7 @@@ static int rm(int argc, const char **ar
        for (i = 0; i < branch_list.nr; i++) {
                struct string_list_item *item = branch_list.items + i;
                struct branch_info *info = item->util;
-               if (info->remote && !strcmp(info->remote, remote->name)) {
+               if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
                        const char *keys[] = { "remote", "merge", NULL }, **k;
                        for (k = keys; *k; k++) {
                                strbuf_reset(&buf);
        return result;
  }
  
- static void show_list(const char *title, struct string_list *list,
-                     const char *extra_arg)
+ void clear_push_info(void *util, const char *string)
  {
-       int i;
+       struct push_info *info = util;
+       free(info->dest);
+       free(info);
+ }
  
-       if (!list->nr)
-               return;
+ static void free_remote_ref_states(struct ref_states *states)
+ {
+       string_list_clear(&states->new, 0);
+       string_list_clear(&states->stale, 0);
+       string_list_clear(&states->tracked, 0);
+       string_list_clear(&states->heads, 0);
+       string_list_clear_func(&states->push, clear_push_info);
+ }
  
-       printf(title, list->nr > 1 ? "es" : "", extra_arg);
-       printf("\n");
-       for (i = 0; i < list->nr; i++)
-               printf("    %s\n", list->items[i].string);
+ static int append_ref_to_tracked_list(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+ {
+       struct ref_states *states = cb_data;
+       struct refspec refspec;
+       if (flags & REF_ISSYMREF)
+               return 0;
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (!remote_find_tracking(states->remote, &refspec))
+               string_list_append(abbrev_branch(refspec.src), &states->tracked);
+       return 0;
  }
  
  static int get_remote_ref_states(const char *name,
                                 int query)
  {
        struct transport *transport;
-       const struct ref *ref;
+       const struct ref *remote_refs;
  
        states->remote = remote_get(name);
        if (!states->remote)
        if (query) {
                transport = transport_get(NULL, states->remote->url_nr > 0 ?
                        states->remote->url[0] : NULL);
-               ref = transport_get_remote_refs(transport);
+               remote_refs = transport_get_remote_refs(transport);
                transport_disconnect(transport);
  
-               get_ref_states(ref, states);
+               states->queried = 1;
+               if (query & GET_REF_STATES)
+                       get_ref_states(remote_refs, states);
+               if (query & GET_HEAD_NAMES)
+                       get_head_names(remote_refs, states);
+               if (query & GET_PUSH_REF_STATES)
+                       get_push_ref_states(remote_refs, states);
+       } else {
+               for_each_ref(append_ref_to_tracked_list, states);
+               sort_string_list(&states->tracked);
+               get_push_ref_states_noquery(states);
        }
  
        return 0;
  }
  
- static int append_ref_to_tracked_list(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
+ struct show_info {
+       struct string_list *list;
+       struct ref_states *states;
+       int width, width2;
+       int any_rebase;
+ };
+ int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
  {
-       struct ref_states *states = cb_data;
-       struct refspec refspec;
+       struct show_info *info = cb_data;
+       int n = strlen(item->string);
+       if (n > info->width)
+               info->width = n;
+       string_list_insert(item->string, info->list);
+       return 0;
+ }
  
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(states->remote, &refspec))
-               string_list_append(abbrev_branch(refspec.src), &states->tracked);
+ int show_remote_info_item(struct string_list_item *item, void *cb_data)
+ {
+       struct show_info *info = cb_data;
+       struct ref_states *states = info->states;
+       const char *name = item->string;
+       if (states->queried) {
+               const char *fmt = "%s";
+               const char *arg = "";
+               if (string_list_has_string(&states->new, name)) {
+                       fmt = " new (next fetch will store in remotes/%s)";
+                       arg = states->remote->name;
+               } else if (string_list_has_string(&states->tracked, name))
+                       arg = " tracked";
+               else if (string_list_has_string(&states->stale, name))
+                       arg = " stale (use 'git remote prune' to remove)";
+               else
+                       arg = " ???";
+               printf("    %-*s", info->width, name);
+               printf(fmt, arg);
+               printf("\n");
+       } else
+               printf("    %s\n", name);
+       return 0;
+ }
+ int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct ref_states *states = show_info->states;
+       struct branch_info *branch_info = branch_item->util;
+       struct string_list_item *item;
+       int n;
+       if (!branch_info->merge.nr || !branch_info->remote_name ||
+           strcmp(states->remote->name, branch_info->remote_name))
+               return 0;
+       if ((n = strlen(branch_item->string)) > show_info->width)
+               show_info->width = n;
+       if (branch_info->rebase)
+               show_info->any_rebase = 1;
+       item = string_list_insert(branch_item->string, show_info->list);
+       item->util = branch_info;
+       return 0;
+ }
+ int show_local_info_item(struct string_list_item *item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct branch_info *branch_info = item->util;
+       struct string_list *merge = &branch_info->merge;
+       const char *also;
+       int i;
+       if (branch_info->rebase && branch_info->merge.nr > 1) {
+               error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+                       item->string);
+               return 0;
+       }
+       printf("    %-*s ", show_info->width, item->string);
+       if (branch_info->rebase) {
+               printf("rebases onto remote %s\n", merge->items[0].string);
+               return 0;
+       } else if (show_info->any_rebase) {
+               printf(" merges with remote %s\n", merge->items[0].string);
+               also = "    and with remote";
+       } else {
+               printf("merges with remote %s\n", merge->items[0].string);
+               also = "   and with remote";
+       }
+       for (i = 1; i < merge->nr; i++)
+               printf("    %-*s %s %s\n", show_info->width, "", also,
+                      merge->items[i].string);
+       return 0;
+ }
  
+ int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = push_item->util;
+       struct string_list_item *item;
+       int n;
+       if ((n = strlen(push_item->string)) > show_info->width)
+               show_info->width = n;
+       if ((n = strlen(push_info->dest)) > show_info->width2)
+               show_info->width2 = n;
+       item = string_list_append(push_item->string, show_info->list);
+       item->util = push_item->util;
+       return 0;
+ }
+ int show_push_info_item(struct string_list_item *item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = item->util;
+       char *src = item->string, *status = NULL;
+       switch (push_info->status) {
+       case PUSH_STATUS_CREATE:
+               status = "create";
+               break;
+       case PUSH_STATUS_DELETE:
+               status = "delete";
+               src = "(none)";
+               break;
+       case PUSH_STATUS_UPTODATE:
+               status = "up to date";
+               break;
+       case PUSH_STATUS_FASTFORWARD:
+               status = "fast forwardable";
+               break;
+       case PUSH_STATUS_OUTOFDATE:
+               status = "local out of date";
+               break;
+       case PUSH_STATUS_NOTQUERIED:
+               break;
+       }
+       if (status)
+               printf("    %-*s %s to %-*s (%s)\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       show_info->width2, push_info->dest, status);
+       else
+               printf("    %-*s %s to %s\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       push_info->dest);
        return 0;
  }
  
  static int show(int argc, const char **argv)
  {
-       int no_query = 0, result = 0;
+       int no_query = 0, result = 0, query_flag = 0;
        struct option options[] = {
                OPT_GROUP("show specific options"),
                OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
                OPT_END()
        };
        struct ref_states states;
+       struct string_list info_list = { NULL, 0, 0, 0 };
+       struct show_info info;
  
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
  
        if (argc < 1)
                return show_all();
  
+       if (!no_query)
+               query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
        memset(&states, 0, sizeof(states));
+       memset(&info, 0, sizeof(info));
+       info.states = &states;
+       info.list = &info_list;
        for (; argc; argc--, argv++) {
                int i;
  
-               get_remote_ref_states(*argv, &states, !no_query);
+               get_remote_ref_states(*argv, &states, query_flag);
  
                printf("* remote %s\n  URL: %s\n", *argv,
                        states.remote->url_nr > 0 ?
                                states.remote->url[0] : "(no URL)");
-               for (i = 0; i < branch_list.nr; i++) {
-                       struct string_list_item *branch = branch_list.items + i;
-                       struct branch_info *info = branch->util;
-                       int j;
-                       if (!info->merge.nr || strcmp(*argv, info->remote))
-                               continue;
-                       printf("  Remote branch%s merged with 'git pull' "
-                               "while on branch %s\n   ",
-                               info->merge.nr > 1 ? "es" : "",
-                               branch->string);
-                       for (j = 0; j < info->merge.nr; j++)
-                               printf(" %s", info->merge.items[j].string);
-                       printf("\n");
+               if (no_query)
+                       printf("  HEAD branch: (not queried)\n");
+               else if (!states.heads.nr)
+                       printf("  HEAD branch: (unknown)\n");
+               else if (states.heads.nr == 1)
+                       printf("  HEAD branch: %s\n", states.heads.items[0].string);
+               else {
+                       printf("  HEAD branch (remote HEAD is ambiguous,"
+                              " may be one of the following):\n");
+                       for (i = 0; i < states.heads.nr; i++)
+                               printf("    %s\n", states.heads.items[i].string);
                }
  
-               if (!no_query) {
-                       show_list("  New remote branch%s (next fetch "
-                               "will store in remotes/%s)",
-                               &states.new, states.remote->name);
-                       show_list("  Stale tracking branch%s (use 'git remote "
-                               "prune')", &states.stale, "");
-               }
+               /* remote branch info */
+               info.width = 0;
+               for_each_string_list(add_remote_to_show_info, &states.new, &info);
+               for_each_string_list(add_remote_to_show_info, &states.tracked, &info);
+               for_each_string_list(add_remote_to_show_info, &states.stale, &info);
+               if (info.list->nr)
+                       printf("  Remote branch%s:%s\n",
+                              info.list->nr > 1 ? "es" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(show_remote_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+               /* git pull info */
+               info.width = 0;
+               info.any_rebase = 0;
+               for_each_string_list(add_local_to_show_info, &branch_list, &info);
+               if (info.list->nr)
+                       printf("  Local branch%s configured for 'git pull':\n",
+                              info.list->nr > 1 ? "es" : "");
+               for_each_string_list(show_local_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+               /* git push info */
+               if (states.remote->mirror)
+                       printf("  Local refs will be mirrored by 'git push'\n");
+               info.width = info.width2 = 0;
+               for_each_string_list(add_push_to_show_info, &states.push, &info);
+               sort_string_list(info.list);
+               if (info.list->nr)
+                       printf("  Local ref%s configured for 'git push'%s:\n",
+                               info.list->nr > 1 ? "s" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(show_push_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+               free_remote_ref_states(&states);
+       }
  
-               if (no_query)
-                       for_each_ref(append_ref_to_tracked_list, &states);
-               show_list("  Tracked remote branch%s", &states.tracked, "");
-               if (states.remote->push_refspec_nr) {
-                       printf("  Local branch%s pushed with 'git push'\n",
-                               states.remote->push_refspec_nr > 1 ?
-                                       "es" : "");
-                       for (i = 0; i < states.remote->push_refspec_nr; i++) {
-                               struct refspec *spec = states.remote->push + i;
-                               printf("    %s%s%s%s\n",
-                                      spec->force ? "+" : "",
-                                      abbrev_branch(spec->src),
-                                      spec->dst ? ":" : "",
-                                      spec->dst ? abbrev_branch(spec->dst) : "");
-                       }
-               }
+       return result;
+ }
  
-               /* NEEDSWORK: free remote */
-               string_list_clear(&states.new, 0);
-               string_list_clear(&states.stale, 0);
-               string_list_clear(&states.tracked, 0);
+ static int set_head(int argc, const char **argv)
+ {
+       int i, opt_a = 0, opt_d = 0, result = 0;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+       char *head_name = NULL;
+       struct option options[] = {
+               OPT_GROUP("set-head specific options"),
+               OPT_BOOLEAN('a', "auto", &opt_a,
+                           "set refs/remotes/<name>/HEAD according to remote"),
+               OPT_BOOLEAN('d', "delete", &opt_d,
+                           "delete refs/remotes/<name>/HEAD"),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
+       if (argc)
+               strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+       if (!opt_a && !opt_d && argc == 2) {
+               head_name = xstrdup(argv[1]);
+       } else if (opt_a && !opt_d && argc == 1) {
+               struct ref_states states;
+               memset(&states, 0, sizeof(states));
+               get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
+               if (!states.heads.nr)
+                       result |= error("Cannot determine remote HEAD");
+               else if (states.heads.nr > 1) {
+                       result |= error("Multiple remote HEAD branches. "
+                                       "Please choose one explicitly with:");
+                       for (i = 0; i < states.heads.nr; i++)
+                               fprintf(stderr, "  git remote set-head %s %s\n",
+                                       argv[0], states.heads.items[i].string);
+               } else
+                       head_name = xstrdup(states.heads.items[0].string);
+               free_remote_ref_states(&states);
+       } else if (opt_d && !opt_a && argc == 1) {
+               if (delete_ref(buf.buf, NULL, REF_NODEREF))
+                       result |= error("Could not delete %s", buf.buf);
+       } else
+               usage_with_options(builtin_remote_usage, options);
+       if (head_name) {
+               unsigned char sha1[20];
+               strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+               /* make sure it's valid */
+               if (!resolve_ref(buf2.buf, sha1, 1, NULL))
+                       result |= error("Not a valid ref: %s", buf2.buf);
+               else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
+                       result |= error("Could not setup %s", buf.buf);
+               if (opt_a)
+                       printf("%s/HEAD set to %s\n", argv[0], head_name);
+               free(head_name);
        }
  
+       strbuf_release(&buf);
+       strbuf_release(&buf2);
        return result;
  }
  
@@@ -771,7 -1138,7 +1130,7 @@@ static int prune(int argc, const char *
        for (; argc; argc--, argv++) {
                int i;
  
-               get_remote_ref_states(*argv, &states, 1);
+               get_remote_ref_states(*argv, &states, GET_REF_STATES);
  
                if (states.stale.nr) {
                        printf("Pruning %s\n", *argv);
                        warn_dangling_symref(dangling_msg, refname);
                }
  
-               /* NEEDSWORK: free remote */
-               string_list_clear(&states.new, 0);
-               string_list_clear(&states.stale, 0);
-               string_list_clear(&states.tracked, 0);
+               free_remote_ref_states(&states);
        }
  
        return result;
@@@ -920,6 -1284,8 +1276,8 @@@ int cmd_remote(int argc, const char **a
                result = mv(argc, argv);
        else if (!strcmp(argv[0], "rm"))
                result = rm(argc, argv);
+       else if (!strcmp(argv[0], "set-head"))
+               result = set_head(argc, argv);
        else if (!strcmp(argv[0], "show"))
                result = show(argc, argv);
        else if (!strcmp(argv[0], "prune"))
index f234c34304181d0c6b00349be3bd9c14d1e538ea,15b938b902fc842fd1adac662be1023cd2839268..3de21809364d4000e45f47cddb238f6c3cdba790
@@@ -62,7 -62,7 +62,7 @@@ esa
  __gitdir ()
  {
        if [ -z "${1-}" ]; then
 -              if [ -n "$__git_dir" ]; then
 +              if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
                elif [ -d .git ]; then
                        echo .git
  # returns text to add to bash PS1 prompt (includes branch name)
  __git_ps1 ()
  {
 -      local g="$(git rev-parse --git-dir 2>/dev/null)"
 +      local g="$(__gitdir)"
        if [ -n "$g" ]; then
                local r
                local b
 -              if [ -d "$g/rebase-apply" ]
 -              then
 -                      if test -f "$g/rebase-apply/rebasing"
 -                      then
 +              if [ -d "$g/rebase-apply" ]; then
 +                      if [ -f "$g/rebase-apply/rebasing" ]; then
                                r="|REBASE"
 -                      elif test -f "$g/rebase-apply/applying"
 -                      then
 +              elif [ -f "$g/rebase-apply/applying" ]; then
                                r="|AM"
                        else
                                r="|AM/REBASE"
                        fi
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
 -              elif [ -f "$g/rebase-merge/interactive" ]
 -              then
 +              elif [ -f "$g/rebase-merge/interactive" ]; then
                        r="|REBASE-i"
                        b="$(cat "$g/rebase-merge/head-name")"
 -              elif [ -d "$g/rebase-merge" ]
 -              then
 +              elif [ -d "$g/rebase-merge" ]; then
                        r="|REBASE-m"
                        b="$(cat "$g/rebase-merge/head-name")"
 -              elif [ -f "$g/MERGE_HEAD" ]
 -              then
 +              elif [ -f "$g/MERGE_HEAD" ]; then
                        r="|MERGING"
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
 -                      if [ -f "$g/BISECT_LOG" ]
 -                      then
 +                      if [ -f "$g/BISECT_LOG" ]; then
                                r="|BISECTING"
                        fi
 -                      if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
 -                      then
 -                              if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
 -                              then
 -                                      b="$(cut -c1-7 "$g/HEAD")..."
 +                      if ! b="$(git symbolic-ref HEAD 2>/dev/null)"; then
 +                              if ! b="$(git describe --exact-match HEAD 2>/dev/null)"; then
 +                                      if [ -r "$g/HEAD" ]; then
 +                                              b="$(cut -c1-7 "$g/HEAD")..."
 +                                      fi
                                fi
                        fi
                fi
  
                local w
                local i
 +              local c
  
 -              if test -n "${GIT_PS1_SHOWDIRTYSTATE-}"; then
 -                      if test "$(git config --bool bash.showDirtyState)" != "false"; then
 -                              git diff --no-ext-diff --ignore-submodules \
 -                                      --quiet --exit-code || w="*"
 -                              if git rev-parse --quiet --verify HEAD >/dev/null; then
 -                                      git diff-index --cached --quiet \
 -                                              --ignore-submodules HEAD -- || i="+"
 -                              else
 -                                      i="#"
 +              if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
 +                      if [ "true" = "$(git config --bool core.bare 2>/dev/null)" ]; then
 +                              c="BARE:"
 +                      else
 +                              b="GIT_DIR!"
 +                      fi
 +              elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
 +                      if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
 +                              if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
 +                                      git diff --no-ext-diff --ignore-submodules \
 +                                              --quiet --exit-code || w="*"
 +                                      if git rev-parse --quiet --verify HEAD >/dev/null; then
 +                                              git diff-index --cached --quiet \
 +                                                      --ignore-submodules HEAD -- || i="+"
 +                                      else
 +                                              i="#"
 +                                      fi
                                fi
                        fi
                fi
  
 -              if [ -n "${1-}" ]; then
 -                      printf "$1" "${b##refs/heads/}$w$i$r"
 -              else
 -                      printf " (%s)" "${b##refs/heads/}$w$i$r"
 +              if [ -n "$b" ]; then
 +                      if [ -n "${1-}" ]; then
 +                              printf "$1" "$c${b##refs/heads/}$w$i$r"
 +                      else
 +                              printf " (%s)" "$c${b##refs/heads/}$w$i$r"
 +                      fi
                fi
        fi
  }
@@@ -303,7 -299,7 +303,7 @@@ __git_remotes (
  
  __git_merge_strategies ()
  {
 -      if [ -n "$__git_merge_strategylist" ]; then
 +      if [ -n "${__git_merge_strategylist-}" ]; then
                echo "$__git_merge_strategylist"
                return
        fi
@@@ -389,7 -385,7 +389,7 @@@ __git_complete_revlist (
  
  __git_all_commands ()
  {
 -      if [ -n "$__git_all_commandlist" ]; then
 +      if [ -n "${__git_all_commandlist-}" ]; then
                echo "$__git_all_commandlist"
                return
        fi
@@@ -407,7 -403,7 +407,7 @@@ __git_all_commandlist="$(__git_all_comm
  
  __git_porcelain_commands ()
  {
 -      if [ -n "$__git_porcelain_commandlist" ]; then
 +      if [ -n "${__git_porcelain_commandlist-}" ]; then
                echo "$__git_porcelain_commandlist"
                return
        fi
@@@ -1018,11 -1014,6 +1018,11 @@@ _git_log (
                        " "" "${cur##--pretty=}"
                return
                ;;
 +      --format=*)
 +              __gitcomp "$__git_log_pretty_formats
 +                      " "" "${cur##--format=}"
 +              return
 +              ;;
        --date=*)
                __gitcomp "
                        relative iso8601 rfc2822 short local default
                        --follow
                        --abbrev-commit --abbrev=
                        --relative-date --date=
 -                      --pretty=
 +                      --pretty= --format= --oneline
                        --cherry-pick
                        --graph
                        --decorate
@@@ -1452,7 -1443,7 +1452,7 @@@ _git_config (
  
  _git_remote ()
  {
-       local subcommands="add rename rm show prune update"
+       local subcommands="add rename rm show prune update set-head"
        local subcommand="$(__git_find_subcommand "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
@@@ -1550,13 -1541,8 +1550,13 @@@ _git_show (
                        " "" "${cur##--pretty=}"
                return
                ;;
 +      --format=*)
 +              __gitcomp "$__git_log_pretty_formats
 +                      " "" "${cur##--format=}"
 +              return
 +              ;;
        --*)
 -              __gitcomp "--pretty=
 +              __gitcomp "--pretty= --format=
                        $__git_diff_common_options
                        "
                return
@@@ -1855,7 -1841,7 +1855,7 @@@ _gitk (
        __git_has_doubledash && return
  
        local cur="${COMP_WORDS[COMP_CWORD]}"
 -      local g="$(git rev-parse --git-dir 2>/dev/null)"
 +      local g="$(__gitdir)"
        local merge=""
        if [ -f $g/MERGE_HEAD ]; then
                merge="--merge"
diff --combined refs.c
index fef7c9f26defb394cfa2ec1e5f77a99f78bc834b,b2a37e1185704733faa4d8395b996dc80d3f19ef..8d3c502a151648abefc4afae70d1ec5f7b5d41b6
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -694,7 -694,6 +694,7 @@@ static inline int bad_ref_char(int ch
  int check_ref_format(const char *ref)
  {
        int ch, level, bad_type;
 +      int ret = CHECK_REF_FORMAT_OK;
        const char *cp = ref;
  
        level = 0;
                        return CHECK_REF_FORMAT_ERROR;
                bad_type = bad_ref_char(ch);
                if (bad_type) {
 -                      return (bad_type == 2 && !*cp)
 -                              ? CHECK_REF_FORMAT_WILDCARD
 -                              : CHECK_REF_FORMAT_ERROR;
 +                      if (bad_type == 2 && (!*cp || *cp == '/') &&
 +                          ret == CHECK_REF_FORMAT_OK)
 +                              ret = CHECK_REF_FORMAT_WILDCARD;
 +                      else
 +                              return CHECK_REF_FORMAT_ERROR;
                }
  
                /* scan the rest of the path component */
                while ((ch = *cp++) != 0) {
                        bad_type = bad_ref_char(ch);
                        if (bad_type) {
 -                              return (bad_type == 2 && !*cp)
 -                                      ? CHECK_REF_FORMAT_WILDCARD
 -                                      : CHECK_REF_FORMAT_ERROR;
 +                              return CHECK_REF_FORMAT_ERROR;
                        }
                        if (ch == '/')
                                break;
                if (!ch) {
                        if (level < 2)
                                return CHECK_REF_FORMAT_ONELEVEL;
 -                      return CHECK_REF_FORMAT_OK;
 +                      return ret;
                }
        }
  }
@@@ -1629,10 -1628,10 +1629,10 @@@ int update_ref(const char *action, cons
        return 0;
  }
  
- struct ref *find_ref_by_name(struct ref *list, const char *name)
+ struct ref *find_ref_by_name(const struct ref *list, const char *name)
  {
        for ( ; list; list = list->next)
                if (!strcmp(list->name, name))
-                       return list;
+                       return (struct ref *)list;
        return NULL;
  }
diff --combined remote.c
index 90203e2fe1dea5462a618fe51fe119fdb75f3dbd,9b8522db35367e5a698bebfcac9c3a107bfa5298..f0acfb5c8642ea521fb9672c5c49759dfd2a3d82
+++ b/remote.c
@@@ -5,13 -5,14 +5,14 @@@
  #include "diff.h"
  #include "revision.h"
  #include "dir.h"
+ #include "tag.h"
  
  static struct refspec s_tag_refspec = {
        0,
        1,
        0,
 -      "refs/tags/",
 -      "refs/tags/"
 +      "refs/tags/*",
 +      "refs/tags/*"
  };
  
  const struct refspec *tag_refspec = &s_tag_refspec;
@@@ -451,11 -452,16 +452,11 @@@ static void read_config(void
   */
  static int verify_refname(char *name, int is_glob)
  {
 -      int result, len = -1;
 +      int result;
  
 -      if (is_glob) {
 -              len = strlen(name);
 -              assert(name[len - 1] == '/');
 -              name[len - 1] = '\0';
 -      }
        result = check_ref_format(name);
 -      if (is_glob)
 -              name[len - 1] = '/';
 +      if (is_glob && result == CHECK_REF_FORMAT_WILDCARD)
 +              result = CHECK_REF_FORMAT_OK;
        return result;
  }
  
@@@ -511,15 -517,16 +512,15 @@@ static struct refspec *parse_refspec_in
  
                if (rhs) {
                        size_t rlen = strlen(++rhs);
 -                      is_glob = (2 <= rlen && !strcmp(rhs + rlen - 2, "/*"));
 -                      rs[i].dst = xstrndup(rhs, rlen - is_glob);
 +                      is_glob = (1 <= rlen && strchr(rhs, '*'));
 +                      rs[i].dst = xstrndup(rhs, rlen);
                }
  
                llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
 -              if (2 <= llen && !memcmp(lhs + llen - 2, "/*", 2)) {
 +              if (1 <= llen && memchr(lhs, '*', llen)) {
                        if ((rhs && !is_glob) || (!rhs && fetch))
                                goto invalid;
                        is_glob = 1;
 -                      llen--;
                } else if (rhs && is_glob) {
                        goto invalid;
                }
@@@ -713,41 -720,6 +714,41 @@@ int remote_has_url(struct remote *remot
        return 0;
  }
  
 +static int match_name_with_pattern(const char *key, const char *name,
 +                                 const char *value, char **result)
 +{
 +      const char *kstar = strchr(key, '*');
 +      size_t klen;
 +      size_t ksuffixlen;
 +      size_t namelen;
 +      int ret;
 +      if (!kstar)
 +              die("Key '%s' of pattern had no '*'", key);
 +      klen = kstar - key;
 +      ksuffixlen = strlen(kstar + 1);
 +      namelen = strlen(name);
 +      ret = !strncmp(name, key, klen) && namelen >= klen + ksuffixlen &&
 +              !memcmp(name + namelen - ksuffixlen, kstar + 1, ksuffixlen);
 +      if (ret && value) {
 +              const char *vstar = strchr(value, '*');
 +              size_t vlen;
 +              size_t vsuffixlen;
 +              if (!vstar)
 +                      die("Value '%s' of pattern has no '*'", value);
 +              vlen = vstar - value;
 +              vsuffixlen = strlen(vstar + 1);
 +              *result = xmalloc(vlen + vsuffixlen +
 +                                strlen(name) -
 +                                klen - ksuffixlen + 1);
 +              strncpy(*result, value, vlen);
 +              strncpy(*result + vlen,
 +                      name + klen, namelen - klen - ksuffixlen);
 +              strcpy(*result + vlen + namelen - klen - ksuffixlen,
 +                     vstar + 1);
 +      }
 +      return ret;
 +}
 +
  int remote_find_tracking(struct remote *remote, struct refspec *refspec)
  {
        int find_src = refspec->src == NULL;
                if (!fetch->dst)
                        continue;
                if (fetch->pattern) {
 -                      if (!prefixcmp(needle, key)) {
 -                              *result = xmalloc(strlen(value) +
 -                                                strlen(needle) -
 -                                                strlen(key) + 1);
 -                              strcpy(*result, value);
 -                              strcpy(*result + strlen(value),
 -                                     needle + strlen(key));
 +                      if (match_name_with_pattern(key, needle, value, result)) {
                                refspec->force = fetch->force;
                                return 0;
                        }
@@@ -801,10 -779,18 +802,18 @@@ struct ref *alloc_ref(const char *name
  
  static struct ref *copy_ref(const struct ref *ref)
  {
-       struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
-       memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
-       ret->next = NULL;
-       return ret;
+       struct ref *cpy;
+       size_t len;
+       if (!ref)
+               return NULL;
+       len = strlen(ref->name);
+       cpy = xmalloc(sizeof(struct ref) + len + 1);
+       memcpy(cpy, ref, sizeof(struct ref) + len + 1);
+       cpy->next = NULL;
+       cpy->symref = ref->symref ? xstrdup(ref->symref) : NULL;
+       cpy->remote_status = ref->remote_status ? xstrdup(ref->remote_status) : NULL;
+       cpy->peer_ref = copy_ref(ref->peer_ref);
+       return cpy;
  }
  
  struct ref *copy_ref_list(const struct ref *ref)
@@@ -823,6 -809,7 +832,7 @@@ static void free_ref(struct ref *ref
  {
        if (!ref)
                return;
+       free_ref(ref->peer_ref);
        free(ref->remote_status);
        free(ref->symref);
        free(ref);
@@@ -833,7 -820,6 +843,6 @@@ void free_refs(struct ref *ref
        struct ref *next;
        while (ref) {
                next = ref->next;
-               free(ref->peer_ref);
                free_ref(ref);
                ref = next;
        }
@@@ -950,6 -936,7 +959,7 @@@ static int match_explicit(struct ref *s
                          struct refspec *rs)
  {
        struct ref *matched_src, *matched_dst;
+       int copy_src;
  
        const char *dst_value = rs->dst;
        char *dst_guess;
        matched_src = matched_dst = NULL;
        switch (count_refspec_match(rs->src, src, &matched_src)) {
        case 1:
+               copy_src = 1;
                break;
        case 0:
                /* The source could be in the get_sha1() format
                matched_src = try_explicit_object_name(rs->src);
                if (!matched_src)
                        return error("src refspec %s does not match any.", rs->src);
+               copy_src = 0;
                break;
        default:
                return error("src refspec %s matches more than one.", rs->src);
                return error("dst ref %s receives from more than one src.",
                      matched_dst->name);
        else {
-               matched_dst->peer_ref = matched_src;
+               matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src;
                matched_dst->force = rs->force;
        }
        return 0;
@@@ -1043,8 -1032,7 +1055,8 @@@ static const struct refspec *check_patt
                        continue;
                }
  
 -              if (rs[i].pattern && !prefixcmp(src->name, rs[i].src))
 +              if (rs[i].pattern && match_name_with_pattern(rs[i].src, src->name,
 +                                                           NULL, NULL))
                        return rs + i;
        }
        if (matching_refs != -1)
@@@ -1064,6 -1052,7 +1076,7 @@@ int match_refs(struct ref *src, struct 
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
+       int errs;
        static const char *default_refspec[] = { ":", 0 };
  
        if (!nr_refspec) {
                refspec = default_refspec;
        }
        rs = parse_push_refspec(nr_refspec, (const char **) refspec);
-       if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
-               return -1;
+       errs = match_explicit_refs(src, dst, dst_tail, rs, nr_refspec);
  
        /* pick the remainder */
        for ( ; src; src = src->next) {
  
                } else {
                        const char *dst_side = pat->dst ? pat->dst : pat->src;
 -                      dst_name = xmalloc(strlen(dst_side) +
 -                                         strlen(src->name) -
 -                                         strlen(pat->src) + 2);
 -                      strcpy(dst_name, dst_side);
 -                      strcat(dst_name, src->name + strlen(pat->src));
 +                      if (!match_name_with_pattern(pat->src, src->name,
 +                                                   dst_side, &dst_name))
 +                              die("Didn't think it matches any more");
                }
                dst_peer = find_ref_by_name(dst, dst_name);
                if (dst_peer) {
                        dst_peer = make_linked_ref(dst_name, dst_tail);
                        hashcpy(dst_peer->new_sha1, src->new_sha1);
                }
-               dst_peer->peer_ref = src;
+               dst_peer->peer_ref = copy_ref(src);
                dst_peer->force = pat->force;
        free_name:
                free(dst_name);
        }
+       if (errs)
+               return -1;
        return 0;
  }
  
@@@ -1176,17 -1168,19 +1190,17 @@@ static struct ref *get_expanded_map(con
        struct ref *ret = NULL;
        struct ref **tail = &ret;
  
 -      int remote_prefix_len = strlen(refspec->src);
 -      int local_prefix_len = strlen(refspec->dst);
 +      char *expn_name;
  
        for (ref = remote_refs; ref; ref = ref->next) {
                if (strchr(ref->name, '^'))
                        continue; /* a dereference item */
 -              if (!prefixcmp(ref->name, refspec->src)) {
 -                      const char *match;
 +              if (match_name_with_pattern(refspec->src, ref->name,
 +                                          refspec->dst, &expn_name)) {
                        struct ref *cpy = copy_ref(ref);
 -                      match = ref->name + remote_prefix_len;
  
 -                      cpy->peer_ref = alloc_ref_with_prefix(refspec->dst,
 -                                      local_prefix_len, match);
 +                      cpy->peer_ref = alloc_ref(expn_name);
 +                      free(expn_name);
                        if (refspec->force)
                                cpy->peer_ref->force = 1;
                        *tail = cpy;
@@@ -1289,6 -1283,54 +1303,54 @@@ int resolve_remote_symref(struct ref *r
        return 1;
  }
  
+ static void unmark_and_free(struct commit_list *list, unsigned int mark)
+ {
+       while (list) {
+               struct commit_list *temp = list;
+               temp->item->object.flags &= ~mark;
+               list = temp->next;
+               free(temp);
+       }
+ }
+ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
+ {
+       struct object *o;
+       struct commit *old, *new;
+       struct commit_list *list, *used;
+       int found = 0;
+       /* Both new and old must be commit-ish and new is descendant of
+        * old.  Otherwise we require --force.
+        */
+       o = deref_tag(parse_object(old_sha1), NULL, 0);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       old = (struct commit *) o;
+       o = deref_tag(parse_object(new_sha1), NULL, 0);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       new = (struct commit *) o;
+       if (parse_commit(new) < 0)
+               return 0;
+       used = list = NULL;
+       commit_list_insert(new, &list);
+       while (list) {
+               new = pop_most_recent_commit(&list, TMP_MARK);
+               commit_list_insert(new, &used);
+               if (new == old) {
+                       found = 1;
+                       break;
+               }
+       }
+       unmark_and_free(list, TMP_MARK);
+       unmark_and_free(used, TMP_MARK);
+       return found;
+ }
  /*
   * Return true if there is anything to report, otherwise false.
   */
@@@ -1396,3 -1438,68 +1458,68 @@@ int format_tracking_info(struct branch 
                            base, num_ours, num_theirs);
        return 1;
  }
+ static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+       struct ref ***local_tail = cb_data;
+       struct ref *ref;
+       int len;
+       /* we already know it starts with refs/ to get here */
+       if (check_ref_format(refname + 5))
+               return 0;
+       len = strlen(refname) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       hashcpy(ref->new_sha1, sha1);
+       memcpy(ref->name, refname, len);
+       **local_tail = ref;
+       *local_tail = &ref->next;
+       return 0;
+ }
+ struct ref *get_local_heads(void)
+ {
+       struct ref *local_refs, **local_tail = &local_refs;
+       for_each_ref(one_local_ref, &local_tail);
+       return local_refs;
+ }
+ struct ref *guess_remote_head(const struct ref *head,
+                             const struct ref *refs,
+                             int all)
+ {
+       const struct ref *r;
+       struct ref *list = NULL;
+       struct ref **tail = &list;
+       if (!head)
+               return NULL;
+       /*
+        * Some transports support directly peeking at
+        * where HEAD points; if that is the case, then
+        * we don't have to guess.
+        */
+       if (head->symref)
+               return copy_ref(find_ref_by_name(refs, head->symref));
+       /* If refs/heads/master could be right, it is. */
+       if (!all) {
+               r = find_ref_by_name(refs, "refs/heads/master");
+               if (r && !hashcmp(r->old_sha1, head->old_sha1))
+                       return copy_ref(r);
+       }
+       /* Look for another ref that points there */
+       for (r = refs; r; r = r->next) {
+               if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) {
+                       *tail = copy_ref(r);
+                       tail = &((*tail)->next);
+                       if (!all)
+                               break;
+               }
+       }
+       return list;
+ }
diff --combined t/t5540-http-push.sh
index 10e5fd0d5a5c38eaa102d88e607f2585e1157526,cefab4543a3cd093b1cde2aa23f96ae2fa2de692..c46592f03de2adfbb04f81ad2167e33a161d6d1b
@@@ -11,6 -11,7 +11,7 @@@ This test runs various sanity checks o
  
  ROOT_PATH="$PWD"
  LIB_HTTPD_DAV=t
+ LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
  
  if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
  then
  fi
  
  . "$TEST_DIRECTORY"/lib-httpd.sh
- if ! start_httpd >&3 2>&4
- then
-       say "skipping test, web server setup failed"
-       test_done
-       exit
- fi
+ start_httpd
  
  test_expect_success 'setup remote repository' '
        cd "$ROOT_PATH" &&
@@@ -94,15 -89,10 +89,15 @@@ test_expect_success 'MKCOL sends direct
  
  '
  
 -test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 +x1="[0-9a-f]"
 +x2="$x1$x1"
 +x5="$x1$x1$x1$x1$x1"
 +x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
 +x40="$x38$x2"
  
 -      grep -P "\"(?:PUT|MOVE) .+objects/[\da-z]{2}/[\da-z]{38}_[\da-z\-]{40} HTTP/[0-9.]+\" 20\d" \
 -              < "$HTTPD_ROOT_PATH"/access.log
 +test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 +      sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log |
 +      grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] "
  
  '