Merge branch 'jc/push-refmap'
authorJunio C Hamano <gitster@pobox.com>
Fri, 27 Dec 2013 22:57:50 +0000 (14:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 27 Dec 2013 22:57:50 +0000 (14:57 -0800)
Make "git push origin master" update the same ref that would be
updated by our 'master' when "git push origin" (no refspecs) is run
while the 'master' branch is checked out, which makes "git push"
more symmetric to "git fetch" and more usable for the triangular
workflow.

* jc/push-refmap:
push: also use "upstream" mapping when pushing a single ref
push: use remote.$name.push as a refmap
builtin/push.c: use strbuf instead of manual allocation

1  2 
builtin/push.c
remote.c
remote.h
diff --combined builtin/push.c
index a73982a30879f89be939034e462df68ee2c51c0b,9e47c29a36b8d47893cf5daf6ee26e911d8861e5..0e50ddbb01342128d9118217118726000bdeaff6
@@@ -35,35 -35,75 +35,75 @@@ static void add_refspec(const char *ref
        refspec[refspec_nr-1] = ref;
  }
  
- static void set_refspecs(const char **refs, int nr)
+ static const char *map_refspec(const char *ref,
+                              struct remote *remote, struct ref *local_refs)
  {
+       struct ref *matched = NULL;
+       /* Does "ref" uniquely name our ref? */
+       if (count_refspec_match(ref, local_refs, &matched) != 1)
+               return ref;
+       if (remote->push) {
+               struct refspec query;
+               memset(&query, 0, sizeof(struct refspec));
+               query.src = matched->name;
+               if (!query_refspecs(remote->push, remote->push_refspec_nr, &query) &&
+                   query.dst) {
+                       struct strbuf buf = STRBUF_INIT;
+                       strbuf_addf(&buf, "%s%s:%s",
+                                   query.force ? "+" : "",
+                                   query.src, query.dst);
+                       return strbuf_detach(&buf, NULL);
+               }
+       }
+       if (push_default == PUSH_DEFAULT_UPSTREAM &&
+           !prefixcmp(matched->name, "refs/heads/")) {
+               struct branch *branch = branch_get(matched->name + 11);
+               if (branch->merge_nr == 1 && branch->merge[0]->src) {
+                       struct strbuf buf = STRBUF_INIT;
+                       strbuf_addf(&buf, "%s:%s",
+                                   ref, branch->merge[0]->src);
+                       return strbuf_detach(&buf, NULL);
+               }
+       }
+       return ref;
+ }
+ static void set_refspecs(const char **refs, int nr, const char *repo)
+ {
+       struct remote *remote = NULL;
+       struct ref *local_refs = NULL;
        int i;
        for (i = 0; i < nr; i++) {
                const char *ref = refs[i];
                if (!strcmp("tag", ref)) {
-                       char *tag;
-                       int len;
+                       struct strbuf tagref = STRBUF_INIT;
                        if (nr <= ++i)
                                die(_("tag shorthand without <tag>"));
-                       len = strlen(refs[i]) + 11;
-                       if (deleterefs) {
-                               tag = xmalloc(len+1);
-                               strcpy(tag, ":refs/tags/");
-                       } else {
-                               tag = xmalloc(len);
-                               strcpy(tag, "refs/tags/");
+                       ref = refs[i];
+                       if (deleterefs)
+                               strbuf_addf(&tagref, ":refs/tags/%s", ref);
+                       else
+                               strbuf_addf(&tagref, "refs/tags/%s", ref);
+                       ref = strbuf_detach(&tagref, NULL);
+               } else if (deleterefs) {
+                       struct strbuf delref = STRBUF_INIT;
+                       if (strchr(ref, ':'))
+                               die(_("--delete only accepts plain target ref names"));
+                       strbuf_addf(&delref, ":%s", ref);
+                       ref = strbuf_detach(&delref, NULL);
+               } else if (!strchr(ref, ':')) {
+                       if (!remote) {
+                               /* lazily grab remote and local_refs */
+                               remote = remote_get(repo);
+                               local_refs = get_local_heads();
                        }
-                       strcat(tag, refs[i]);
-                       ref = tag;
-               } else if (deleterefs && !strchr(ref, ':')) {
-                       char *delref;
-                       int len = strlen(ref)+1;
-                       delref = xmalloc(len+1);
-                       strcpy(delref, ":");
-                       strcat(delref, ref);
-                       ref = delref;
-               } else if (deleterefs)
-                       die(_("--delete only accepts plain target ref names"));
+                       ref = map_refspec(ref, remote, local_refs);
+               }
                add_refspec(ref);
        }
  }
@@@ -174,13 -214,6 +214,13 @@@ N_("push.default is unset; its implici
     "\n"
     "  git config --global push.default simple\n"
     "\n"
 +   "When push.default is set to 'matching', git will push local branches\n"
 +   "to the remote branches that already exist with the same name.\n"
 +   "\n"
 +   "In Git 2.0, Git will default to the more conservative 'simple'\n"
 +   "behavior, which only pushes the current branch to the corresponding\n"
 +   "remote branch that 'git pull' uses to update the current branch.\n"
 +   "\n"
     "See 'git help config' and search for 'push.default' for further information.\n"
     "(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode\n"
     "'current' instead of 'simple' if you sometimes use older versions of Git)");
@@@ -501,7 -534,7 +541,7 @@@ int cmd_push(int argc, const char **arg
  
        if (argc > 0) {
                repo = argv[0];
-               set_refspecs(argv + 1, argc - 1);
+               set_refspecs(argv + 1, argc - 1, repo);
        }
  
        rc = do_push(repo, flags);
diff --combined remote.c
index 709a9cbd26fea972a6781581a0c345c8e95a7f6d,6ffa14946d509a8d3876319c1d0436b54aace502..a89efab5b766ba9374c3a09656b2d56593884c8e
+++ b/remote.c
@@@ -76,7 -76,7 +76,7 @@@ static const char *alias_url(const cha
                if (!r->rewrite[i])
                        continue;
                for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) {
 -                      if (!prefixcmp(url, r->rewrite[i]->instead_of[j].s) &&
 +                      if (starts_with(url, r->rewrite[i]->instead_of[j].s) &&
                            (!longest ||
                             longest->len < r->rewrite[i]->instead_of[j].len)) {
                                longest = &(r->rewrite[i]->instead_of[j]);
@@@ -239,13 -239,13 +239,13 @@@ static void read_remotes_file(struct re
                int value_list;
                char *s, *p;
  
 -              if (!prefixcmp(buffer, "URL:")) {
 +              if (starts_with(buffer, "URL:")) {
                        value_list = 0;
                        s = buffer + 4;
 -              } else if (!prefixcmp(buffer, "Push:")) {
 +              } else if (starts_with(buffer, "Push:")) {
                        value_list = 1;
                        s = buffer + 5;
 -              } else if (!prefixcmp(buffer, "Pull:")) {
 +              } else if (starts_with(buffer, "Pull:")) {
                        value_list = 2;
                        s = buffer + 5;
                } else
@@@ -337,7 -337,7 +337,7 @@@ static int handle_config(const char *ke
        const char *subkey;
        struct remote *remote;
        struct branch *branch;
 -      if (!prefixcmp(key, "branch.")) {
 +      if (starts_with(key, "branch.")) {
                name = key + 7;
                subkey = strrchr(name, '.');
                if (!subkey)
                }
                return 0;
        }
 -      if (!prefixcmp(key, "url.")) {
 +      if (starts_with(key, "url.")) {
                struct rewrite *rewrite;
                name = key + 4;
                subkey = strrchr(name, '.');
                }
        }
  
 -      if (prefixcmp(key,  "remote."))
 +      if (!starts_with(key,  "remote."))
                return 0;
        name = key + 7;
  
@@@ -487,7 -487,7 +487,7 @@@ static void read_config(void
        current_branch = NULL;
        head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag);
        if (head_ref && (flag & REF_ISSYMREF) &&
 -          !prefixcmp(head_ref, "refs/heads/")) {
 +          starts_with(head_ref, "refs/heads/")) {
                current_branch =
                        make_branch(head_ref + strlen("refs/heads/"), 0);
        }
@@@ -745,66 -745,35 +745,66 @@@ int for_each_remote(each_remote_fn fn, 
        return result;
  }
  
 -void ref_remove_duplicates(struct ref *ref_map)
 +static void handle_duplicate(struct ref *ref1, struct ref *ref2)
 +{
 +      if (strcmp(ref1->name, ref2->name)) {
 +              if (ref1->fetch_head_status != FETCH_HEAD_IGNORE &&
 +                  ref2->fetch_head_status != FETCH_HEAD_IGNORE) {
 +                      die(_("Cannot fetch both %s and %s to %s"),
 +                          ref1->name, ref2->name, ref2->peer_ref->name);
 +              } else if (ref1->fetch_head_status != FETCH_HEAD_IGNORE &&
 +                         ref2->fetch_head_status == FETCH_HEAD_IGNORE) {
 +                      warning(_("%s usually tracks %s, not %s"),
 +                              ref2->peer_ref->name, ref2->name, ref1->name);
 +              } else if (ref1->fetch_head_status == FETCH_HEAD_IGNORE &&
 +                         ref2->fetch_head_status == FETCH_HEAD_IGNORE) {
 +                      die(_("%s tracks both %s and %s"),
 +                          ref2->peer_ref->name, ref1->name, ref2->name);
 +              } else {
 +                      /*
 +                       * This last possibility doesn't occur because
 +                       * FETCH_HEAD_IGNORE entries always appear at
 +                       * the end of the list.
 +                       */
 +                      die(_("Internal error"));
 +              }
 +      }
 +      free(ref2->peer_ref);
 +      free(ref2);
 +}
 +
 +struct ref *ref_remove_duplicates(struct ref *ref_map)
  {
        struct string_list refs = STRING_LIST_INIT_NODUP;
 -      struct string_list_item *item = NULL;
 -      struct ref *prev = NULL, *next = NULL;
 -      for (; ref_map; prev = ref_map, ref_map = next) {
 -              next = ref_map->next;
 -              if (!ref_map->peer_ref)
 -                      continue;
 +      struct ref *retval = NULL;
 +      struct ref **p = &retval;
  
 -              item = string_list_lookup(&refs, ref_map->peer_ref->name);
 -              if (item) {
 -                      if (strcmp(((struct ref *)item->util)->name,
 -                                 ref_map->name))
 -                              die("%s tracks both %s and %s",
 -                                  ref_map->peer_ref->name,
 -                                  ((struct ref *)item->util)->name,
 -                                  ref_map->name);
 -                      prev->next = ref_map->next;
 -                      free(ref_map->peer_ref);
 -                      free(ref_map);
 -                      ref_map = prev; /* skip this; we freed it */
 -                      continue;
 -              }
 +      while (ref_map) {
 +              struct ref *ref = ref_map;
 +
 +              ref_map = ref_map->next;
 +              ref->next = NULL;
  
 -              item = string_list_insert(&refs, ref_map->peer_ref->name);
 -              item->util = ref_map;
 +              if (!ref->peer_ref) {
 +                      *p = ref;
 +                      p = &ref->next;
 +              } else {
 +                      struct string_list_item *item =
 +                              string_list_insert(&refs, ref->peer_ref->name);
 +
 +                      if (item->util) {
 +                              /* Entry already existed */
 +                              handle_duplicate((struct ref *)item->util, ref);
 +                      } else {
 +                              *p = ref;
 +                              p = &ref->next;
 +                              item->util = ref;
 +                      }
 +              }
        }
 +
        string_list_clear(&refs, 0);
 +      return retval;
  }
  
  int remote_has_url(struct remote *remote, const char *url)
@@@ -852,12 -821,10 +852,12 @@@ static int match_name_with_pattern(cons
        return ret;
  }
  
static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
+ int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
  {
        int i;
        int find_src = !query->src;
 +      const char *needle = find_src ? query->dst : query->src;
 +      char **result = find_src ? &query->src : &query->dst;
  
        if (find_src && !query->dst)
                return error("query_refspecs: need either src or dst");
                struct refspec *refspec = &refs[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;
 -              char **result = find_src ? &query->src : &query->dst;
  
                if (!refspec->dst)
                        continue;
@@@ -986,9 -955,9 +986,9 @@@ void sort_ref_list(struct ref **l, int 
        *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
  }
  
static int count_refspec_match(const char *pattern,
-                              struct ref *refs,
-                              struct ref **matched_ref)
+ int count_refspec_match(const char *pattern,
+                       struct ref *refs,
+                       struct ref **matched_ref)
  {
        int patlen = strlen(pattern);
        struct ref *matched_weak = NULL;
                 */
                if (namelen != patlen &&
                    patlen != namelen - 5 &&
 -                  prefixcmp(name, "refs/heads/") &&
 -                  prefixcmp(name, "refs/tags/")) {
 +                  !starts_with(name, "refs/heads/") &&
 +                  !starts_with(name, "refs/tags/")) {
                        /* We want to catch the case where only weak
                         * matches are found and there are multiple
                         * matches, and where more than one strong
@@@ -1085,9 -1054,9 +1085,9 @@@ static char *guess_ref(const char *name
        if (!r)
                return NULL;
  
 -      if (!prefixcmp(r, "refs/heads/"))
 +      if (starts_with(r, "refs/heads/"))
                strbuf_addstr(&buf, "refs/heads/");
 -      else if (!prefixcmp(r, "refs/tags/"))
 +      else if (starts_with(r, "refs/tags/"))
                strbuf_addstr(&buf, "refs/tags/");
        else
                return NULL;
@@@ -1135,7 -1104,7 +1135,7 @@@ static int match_explicit(struct ref *s
                dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag);
                if (!dst_value ||
                    ((flag & REF_ISSYMREF) &&
 -                   prefixcmp(dst_value, "refs/heads/")))
 +                   !starts_with(dst_value, "refs/heads/")))
                        die("%s cannot be resolved to branch.",
                            matched_src->name);
        }
@@@ -1224,7 -1193,7 +1224,7 @@@ static char *get_ref_match(const struc
                 * including refs outside refs/heads/ hierarchy, but
                 * that does not make much sense these days.
                 */
 -              if (!send_mirror && prefixcmp(ref->name, "refs/heads/"))
 +              if (!send_mirror && !starts_with(ref->name, "refs/heads/"))
                        return NULL;
                name = xstrdup(ref->name);
        }
@@@ -1279,7 -1248,7 +1279,7 @@@ static void add_missing_tags(struct re
                        add_to_tips(&sent_tips, ref->peer_ref->new_sha1);
                else
                        add_to_tips(&sent_tips, ref->old_sha1);
 -              if (!prefixcmp(ref->name, "refs/tags/"))
 +              if (starts_with(ref->name, "refs/tags/"))
                        string_list_append(&dst_tag, ref->name);
        }
        clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
  
        /* Collect tags they do not have. */
        for (ref = src; ref; ref = ref->next) {
 -              if (prefixcmp(ref->name, "refs/tags/"))
 +              if (!starts_with(ref->name, "refs/tags/"))
                        continue; /* not a tag */
                if (string_list_has_string(&dst_tag, ref->name))
                        continue; /* they already have it */
@@@ -1512,7 -1481,7 +1512,7 @@@ void set_ref_status_for_push(struct re
                 */
  
                else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
 -                      if (!prefixcmp(ref->name, "refs/tags/"))
 +                      if (starts_with(ref->name, "refs/tags/"))
                                reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
                        else if (!has_sha1_file(ref->old_sha1))
                                reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
@@@ -1584,13 -1553,6 +1584,13 @@@ static int ignore_symref_update(const c
        return (flag & REF_ISSYMREF);
  }
  
 +/*
 + * Create and return a list of (struct ref) consisting of copies of
 + * each remote_ref that matches refspec.  refspec must be a pattern.
 + * Fill in the copies' peer_ref to describe the local tracking refs to
 + * which they map.  Omit any references that would map to an existing
 + * local symbolic ref.
 + */
  static struct ref *get_expanded_map(const struct ref *remote_refs,
                                    const struct refspec *refspec)
  {
        struct ref *ret = NULL;
        struct ref **tail = &ret;
  
 -      char *expn_name;
 -
        for (ref = remote_refs; ref; ref = ref->next) {
 +              char *expn_name = NULL;
 +
                if (strchr(ref->name, '^'))
                        continue; /* a dereference item */
                if (match_name_with_pattern(refspec->src, ref->name,
                        struct ref *cpy = copy_ref(ref);
  
                        cpy->peer_ref = alloc_ref(expn_name);
 -                      free(expn_name);
                        if (refspec->force)
                                cpy->peer_ref->force = 1;
                        *tail = cpy;
                        tail = &cpy->next;
                }
 +              free(expn_name);
        }
  
        return ret;
@@@ -1645,12 -1607,12 +1645,12 @@@ static struct ref *get_local_ref(const 
        if (!name || name[0] == '\0')
                return NULL;
  
 -      if (!prefixcmp(name, "refs/"))
 +      if (starts_with(name, "refs/"))
                return alloc_ref(name);
  
 -      if (!prefixcmp(name, "heads/") ||
 -          !prefixcmp(name, "tags/") ||
 -          !prefixcmp(name, "remotes/"))
 +      if (starts_with(name, "heads/") ||
 +          starts_with(name, "tags/") ||
 +          starts_with(name, "remotes/"))
                return alloc_ref_with_prefix("refs/", 5, name);
  
        return alloc_ref_with_prefix("refs/heads/", 11, name);
@@@ -1685,7 -1647,7 +1685,7 @@@ int get_fetch_map(const struct ref *rem
  
        for (rmp = &ref_map; *rmp; ) {
                if ((*rmp)->peer_ref) {
 -                      if (prefixcmp((*rmp)->peer_ref->name, "refs/") ||
 +                      if (!starts_with((*rmp)->peer_ref->name, "refs/") ||
                            check_refname_format((*rmp)->peer_ref->name, 0)) {
                                struct ref *ignore = *rmp;
                                error("* Ignoring funny ref '%s' locally",
@@@ -1969,7 -1931,7 +1969,7 @@@ struct ref *guess_remote_head(const str
        /* Look for another ref that points there */
        for (r = refs; r; r = r->next) {
                if (r != head &&
 -                  !prefixcmp(r->name, "refs/heads/") &&
 +                  starts_with(r->name, "refs/heads/") &&
                    !hashcmp(r->old_sha1, head->old_sha1)) {
                        *tail = copy_ref(r);
                        tail = &((*tail)->next);
diff --combined remote.h
index c07eb9950a15600f7fde7faa60570ea5a558fb60,f7d43b4e5a4e8662308e0e8e883ce1ee25cb08b5..00c6a76ef803005698e14d7f31eb4acd22b35649
+++ b/remote.h
@@@ -128,6 -128,7 +128,7 @@@ struct ref *alloc_ref(const char *name)
  struct ref *copy_ref(const struct ref *ref);
  struct ref *copy_ref_list(const struct ref *ref);
  void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));
+ extern int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);
  int ref_compare_name(const void *, const void *);
  
  int check_ref_type(const struct ref *ref, int flags);
@@@ -149,19 -150,16 +150,20 @@@ int resolve_remote_symref(struct ref *r
  int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1);
  
  /*
 - * Removes and frees any duplicate refs in the map.
 + * Remove and free all but the first of any entries in the input list
 + * that map the same remote reference to the same local reference.  If
 + * there are two entries that map different remote references to the
 + * same local reference, emit an error message and die.  Return a
 + * pointer to the head of the resulting list.
   */
 -void ref_remove_duplicates(struct ref *ref_map);
 +struct ref *ref_remove_duplicates(struct ref *ref_map);
  
  int valid_fetch_refspec(const char *refspec);
  struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
  
  void free_refspec(int nr_refspec, struct refspec *refspec);
  
+ extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
  char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
                     const char *name);