Merge branch 'jk/detect-push-typo-early'
authorJunio C Hamano <gitster@pobox.com>
Tue, 18 Mar 2014 20:50:33 +0000 (13:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 18 Mar 2014 20:50:33 +0000 (13:50 -0700)
Catch "git push $there no-such-branch" early.

* jk/detect-push-typo-early:
push: detect local refspec errors early
match_explicit_lhs: allow a "verify only" mode
match_explicit: hoist refspec lhs checks into their own function

1  2 
remote.c
diff --combined remote.c
index 5f63d55056e7f1087766374a20a23e794bf3530a,9bf498af1b4c266c92585cbf3e95ad09db64fd0d..21b096932e033f0ebff92fc2224a79a132256ad2
+++ b/remote.c
@@@ -49,7 -49,6 +49,7 @@@ static int branches_nr
  
  static struct branch *current_branch;
  static const char *default_remote_name;
 +static const char *branch_pushremote_name;
  static const char *pushremote_name;
  static int explicit_default_remote_name;
  
@@@ -353,7 -352,7 +353,7 @@@ static int handle_config(const char *ke
                        }
                } else if (!strcmp(subkey, ".pushremote")) {
                        if (branch == current_branch)
 -                              if (git_config_string(&pushremote_name, key, value))
 +                              if (git_config_string(&branch_pushremote_name, key, value))
                                        return -1;
                } else if (!strcmp(subkey, ".merge")) {
                        if (!value)
@@@ -493,10 -492,6 +493,10 @@@ static void read_config(void
                        make_branch(head_ref + strlen("refs/heads/"), 0);
        }
        git_config(handle_config, NULL);
 +      if (branch_pushremote_name) {
 +              free((char *)pushremote_name);
 +              pushremote_name = branch_pushremote_name;
 +      }
        alias_all_urls();
  }
  
@@@ -1036,11 -1031,13 +1036,13 @@@ int count_refspec_match(const char *pat
                }
        }
        if (!matched) {
-               *matched_ref = matched_weak;
+               if (matched_ref)
+                       *matched_ref = matched_weak;
                return weak_match;
        }
        else {
-               *matched_ref = matched;
+               if (matched_ref)
+                       *matched_ref = matched;
                return match;
        }
  }
@@@ -1060,18 -1057,25 +1062,25 @@@ static struct ref *alloc_delete_ref(voi
        return ref;
  }
  
- static struct ref *try_explicit_object_name(const char *name)
+ static int try_explicit_object_name(const char *name,
+                                   struct ref **match)
  {
        unsigned char sha1[20];
-       struct ref *ref;
  
-       if (!*name)
-               return alloc_delete_ref();
+       if (!*name) {
+               if (match)
+                       *match = alloc_delete_ref();
+               return 0;
+       }
        if (get_sha1(name, sha1))
-               return NULL;
-       ref = alloc_ref(name);
-       hashcpy(ref->new_sha1, sha1);
-       return ref;
+               return -1;
+       if (match) {
+               *match = alloc_ref(name);
+               hashcpy((*match)->new_sha1, sha1);
+       }
+       return 0;
  }
  
  static struct ref *make_linked_ref(const char *name, struct ref ***tail)
@@@ -1101,12 -1105,37 +1110,37 @@@ static char *guess_ref(const char *name
        return strbuf_detach(&buf, NULL);
  }
  
+ static int match_explicit_lhs(struct ref *src,
+                             struct refspec *rs,
+                             struct ref **match,
+                             int *allocated_match)
+ {
+       switch (count_refspec_match(rs->src, src, match)) {
+       case 1:
+               if (allocated_match)
+                       *allocated_match = 0;
+               return 0;
+       case 0:
+               /* The source could be in the get_sha1() format
+                * not a reference name.  :refs/other is a
+                * way to delete 'other' ref at the remote end.
+                */
+               if (try_explicit_object_name(rs->src, match) < 0)
+                       return error("src refspec %s does not match any.", rs->src);
+               if (allocated_match)
+                       *allocated_match = 1;
+               return 0;
+       default:
+               return error("src refspec %s matches more than one.", rs->src);
+       }
+ }
  static int match_explicit(struct ref *src, struct ref *dst,
                          struct ref ***dst_tail,
                          struct refspec *rs)
  {
        struct ref *matched_src, *matched_dst;
-       int copy_src;
+       int allocated_src;
  
        const char *dst_value = rs->dst;
        char *dst_guess;
                return 0;
  
        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
-                * not a reference name.  :refs/other is a
-                * way to delete 'other' ref at the remote end.
-                */
-               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);
-       }
+       if (match_explicit_lhs(src, rs, &matched_src, &allocated_src) < 0)
+               return -1;
  
        if (!dst_value) {
                unsigned char sha1[20];
                return error("dst ref %s receives from more than one src.",
                      matched_dst->name);
        else {
-               matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src;
+               matched_dst->peer_ref = allocated_src ?
+                                       matched_src :
+                                       copy_ref(matched_src);
                matched_dst->force = rs->force;
        }
        return 0;
@@@ -1357,6 -1373,31 +1378,31 @@@ static void prepare_ref_index(struct st
        sort_string_list(ref_index);
  }
  
+ /*
+  * Given only the set of local refs, sanity-check the set of push
+  * refspecs. We can't catch all errors that match_push_refs would,
+  * 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)
+ {
+       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;
+               if (rs->pattern || rs->matching)
+                       continue;
+               ret |= match_explicit_lhs(src, rs, NULL, NULL);
+       }
+       free_refspec(nr_refspec, refspec);
+       return ret;
+ }
  /*
   * Given the set of refs the local repository has, the set of refs the
   * remote repository has, and the refspec used for push, determine