Code

Merge branch 'pb/push'
authorJunio C Hamano <gitster@pobox.com>
Fri, 23 May 2008 23:06:07 +0000 (16:06 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 23 May 2008 23:06:07 +0000 (16:06 -0700)
* pb/push:
  add special "matching refs" refspec

1  2 
Documentation/git-push.txt
remote.c
remote.h
t/t5516-fetch-push.sh

index f06d94e318d6d0bf918430bafc746e96a2abe7a1,7d31263aa213e943bea1a0c0ef4bd3fff583322d..0cc44d79993735a8978af9e191e6846c6ceddb74
@@@ -46,12 -46,6 +46,6 @@@ specified, the same ref that <src> refe
  the optional leading plus `+` is used, the remote ref is updated
  even if it does not result in a fast forward update.
  +
- Note: If no explicit refspec is found, (that is neither
- on the command line nor in any Push line of the
- corresponding remotes file---see below), then "matching" heads are
- pushed: for every head that exists on the local side, the remote side is
- updated if a head of the same name already exists on the remote side.
- +
  `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
  +
  A parameter <ref> without a colon pushes the <ref> from the source
@@@ -59,6 -53,13 +53,13 @@@ repository to the destination repositor
  +
  Pushing an empty <src> allows you to delete the <dst> ref from
  the remote repository.
+ +
+ The special refspec `:` (or `+:` to allow non-fast forward updates)
+ directs git to push "matching" heads: for every head that exists on
+ the local side, the remote side is updated if a head of the same name
+ already exists on the remote side.  This is the default operation mode
+ if no explicit refspec is found (that is neither on the command line
+ nor in any Push line of the corresponding remotes file---see below).
  
  \--all::
        Instead of naming each ref to push, specifies that all
@@@ -70,9 -71,7 +71,9 @@@
        be mirrored to the remote repository.  Newly created local
        refs will be pushed to the remote end, locally updated refs
        will be force updated on the remote end, and deleted refs
 -      will be removed from the remote end.
 +      will be removed from the remote end.  This is the default
 +      if the configuration option `remote.<remote>.mirror` is
 +      set.
  
  \--dry-run::
        Do everything except actually send the updates.
diff --combined remote.c
index 91cbb72ddeba1c60dab7aad2105e1c004ea6e198,7c496f981e89ee2833214f27dcd94a86976f9e67..9e4f2b84d90cb97a6cb19779325f9ea443a76e43
+++ b/remote.c
@@@ -337,49 -337,44 +337,49 @@@ static int handle_config(const char *ke
                return 0;
        }
        remote = make_remote(name, subkey - name);
 -      if (!value) {
 -              /* if we ever have a boolean variable, e.g. "remote.*.disabled"
 -               * [remote "frotz"]
 -               *      disabled
 -               * is a valid way to set it to true; we get NULL in value so
 -               * we need to handle it here.
 -               *
 -               * if (!strcmp(subkey, ".disabled")) {
 -               *      val = git_config_bool(key, value);
 -               *      return 0;
 -               * } else
 -               *
 -               */
 -              return 0; /* ignore unknown booleans */
 -      }
 -      if (!strcmp(subkey, ".url")) {
 -              add_url(remote, xstrdup(value));
 +      if (!strcmp(subkey, ".mirror"))
 +              remote->mirror = git_config_bool(key, value);
 +      else if (!strcmp(subkey, ".skipdefaultupdate"))
 +              remote->skip_default_update = git_config_bool(key, value);
 +
 +      else if (!strcmp(subkey, ".url")) {
 +              const char *v;
 +              if (git_config_string(&v, key, value))
 +                      return -1;
 +              add_url(remote, v);
        } else if (!strcmp(subkey, ".push")) {
 -              add_push_refspec(remote, xstrdup(value));
 +              const char *v;
 +              if (git_config_string(&v, key, value))
 +                      return -1;
 +              add_push_refspec(remote, v);
        } else if (!strcmp(subkey, ".fetch")) {
 -              add_fetch_refspec(remote, xstrdup(value));
 +              const char *v;
 +              if (git_config_string(&v, key, value))
 +                      return -1;
 +              add_fetch_refspec(remote, v);
        } else if (!strcmp(subkey, ".receivepack")) {
 +              const char *v;
 +              if (git_config_string(&v, key, value))
 +                      return -1;
                if (!remote->receivepack)
 -                      remote->receivepack = xstrdup(value);
 +                      remote->receivepack = v;
                else
                        error("more than one receivepack given, using the first");
        } else if (!strcmp(subkey, ".uploadpack")) {
 +              const char *v;
 +              if (git_config_string(&v, key, value))
 +                      return -1;
                if (!remote->uploadpack)
 -                      remote->uploadpack = xstrdup(value);
 +                      remote->uploadpack = v;
                else
                        error("more than one uploadpack given, using the first");
        } else if (!strcmp(subkey, ".tagopt")) {
                if (!strcmp(value, "--no-tags"))
                        remote->fetch_tags = -1;
        } else if (!strcmp(subkey, ".proxy")) {
 -              remote->http_proxy = xstrdup(value);
 -      } else if (!strcmp(subkey, ".skipdefaultupdate"))
 -              remote->skip_default_update = 1;
 +              return git_config_string((const char **)&remote->http_proxy,
 +                                       key, value);
 +      }
        return 0;
  }
  
@@@ -434,6 -429,16 +434,16 @@@ static struct refspec *parse_refspec_in
                }
  
                rhs = strrchr(lhs, ':');
+               /*
+                * Before going on, special case ":" (or "+:") as a refspec
+                * for matching refs.
+                */
+               if (!fetch && rhs == lhs && rhs[1] == '\0') {
+                       rs[i].matching = 1;
+                       continue;
+               }
                if (rhs) {
                        rhs++;
                        rlen = strlen(rhs);
@@@ -691,13 -696,6 +701,13 @@@ struct ref *alloc_ref(unsigned namelen
        return ret;
  }
  
 +struct ref *alloc_ref_from_str(const char* str)
 +{
 +      struct ref *ret = alloc_ref(strlen(str) + 1);
 +      strcpy(ret->name, str);
 +      return ret;
 +}
 +
  static struct ref *copy_ref(const struct ref *ref)
  {
        struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
@@@ -718,22 -716,13 +728,22 @@@ struct ref *copy_ref_list(const struct 
        return ret;
  }
  
 +void free_ref(struct ref *ref)
 +{
 +      if (!ref)
 +              return;
 +      free(ref->remote_status);
 +      free(ref->symref);
 +      free(ref);
 +}
 +
  void free_refs(struct ref *ref)
  {
        struct ref *next;
        while (ref) {
                next = ref->next;
                free(ref->peer_ref);
 -              free(ref);
 +              free_ref(ref);
                ref = next;
        }
  }
@@@ -804,6 -793,7 +814,6 @@@ static struct ref *try_explicit_object_
  {
        unsigned char sha1[20];
        struct ref *ref;
 -      int len;
  
        if (!*name) {
                ref = alloc_ref(20);
        }
        if (get_sha1(name, sha1))
                return NULL;
 -      len = strlen(name) + 1;
 -      ref = alloc_ref(len);
 -      memcpy(ref->name, name, len);
 +      ref = alloc_ref_from_str(name);
        hashcpy(ref->new_sha1, sha1);
        return ref;
  }
  
  static struct ref *make_linked_ref(const char *name, struct ref ***tail)
  {
 -      struct ref *ret;
 -      size_t len;
 -
 -      len = strlen(name) + 1;
 -      ret = alloc_ref(len);
 -      memcpy(ret->name, name, len);
 +      struct ref *ret = alloc_ref_from_str(name);
        tail_link_ref(ret, tail);
        return ret;
  }
@@@ -855,7 -852,7 +865,7 @@@ static int match_explicit(struct ref *s
        const char *dst_value = rs->dst;
        char *dst_guess;
  
-       if (rs->pattern)
+       if (rs->pattern || rs->matching)
                return errs;
  
        matched_src = matched_dst = NULL;
@@@ -945,13 -942,23 +955,23 @@@ static const struct refspec *check_patt
                                                 const struct ref *src)
  {
        int i;
+       int matching_refs = -1;
        for (i = 0; i < rs_nr; i++) {
+               if (rs[i].matching &&
+                   (matching_refs == -1 || rs[i].force)) {
+                       matching_refs = i;
+                       continue;
+               }
                if (rs[i].pattern &&
                    !prefixcmp(src->name, rs[i].src) &&
                    src->name[strlen(rs[i].src)] == '/')
                        return rs + i;
        }
-       return NULL;
+       if (matching_refs != -1)
+               return rs + matching_refs;
+       else
+               return NULL;
  }
  
  /*
  int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
               int nr_refspec, const char **refspec, int flags)
  {
-       struct refspec *rs =
-               parse_push_refspec(nr_refspec, (const char **) refspec);
+       struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
+       static const char *default_refspec[] = { ":", 0 };
  
+       if (!nr_refspec) {
+               nr_refspec = 1;
+               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;
  
                char *dst_name;
                if (src->peer_ref)
                        continue;
-               if (nr_refspec) {
-                       pat = check_pattern_match(rs, nr_refspec, src);
-                       if (!pat)
-                               continue;
-               }
-               else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
+               pat = check_pattern_match(rs, nr_refspec, src);
+               if (!pat)
+                       continue;
+               if (pat->matching) {
                        /*
                         * "matching refs"; traditionally we pushed everything
                         * including refs outside refs/heads/ hierarchy, but
                         * that does not make much sense these days.
                         */
-                       continue;
+                       if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
+                               continue;
+                       dst_name = xstrdup(src->name);
  
-               if (pat) {
+               } 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));
-               } else
-                       dst_name = xstrdup(src->name);
+               }
                dst_peer = find_ref_by_name(dst, dst_name);
-               if (dst_peer && dst_peer->peer_ref)
-                       /* We're already sending something to this ref. */
-                       goto free_name;
+               if (dst_peer) {
+                       if (dst_peer->peer_ref)
+                               /* We're already sending something to this ref. */
+                               goto free_name;
+               } else {
+                       if (pat->matching && !(send_all || send_mirror))
+                               /*
+                                * Remote doesn't have it, and we have no
+                                * explicit pattern, and we don't have
+                                * --all nor --mirror.
+                                */
+                               goto free_name;
  
-               if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
-                       /*
-                        * Remote doesn't have it, and we have no
-                        * explicit pattern, and we don't have
-                        * --all nor --mirror.
-                        */
-                       goto free_name;
-               if (!dst_peer) {
                        /* Create a new one and link it */
                        dst_peer = make_linked_ref(dst_name, dst_tail);
                        hashcpy(dst_peer->new_sha1, src->new_sha1);
                }
                dst_peer->peer_ref = src;
-               if (pat)
-                       dst_peer->force = pat->force;
+               dst_peer->force = pat->force;
        free_name:
                free(dst_name);
        }
@@@ -1124,7 -1138,9 +1151,7 @@@ static struct ref *get_local_ref(const 
                return NULL;
  
        if (!prefixcmp(name, "refs/")) {
 -              ret = alloc_ref(strlen(name) + 1);
 -              strcpy(ret->name, name);
 -              return ret;
 +              return alloc_ref_from_str(name);
        }
  
        if (!prefixcmp(name, "heads/") ||
@@@ -1183,15 -1199,3 +1210,15 @@@ int get_fetch_map(const struct ref *rem
  
        return 0;
  }
 +
 +int resolve_remote_symref(struct ref *ref, struct ref *list)
 +{
 +      if (!ref->symref)
 +              return 0;
 +      for (; list; list = list->next)
 +              if (!strcmp(ref->symref, list->name)) {
 +                      hashcpy(ref->old_sha1, list->old_sha1);
 +                      return 0;
 +              }
 +      return 1;
 +}
diff --combined remote.h
index 2ee83a33b3bb7ddaa5bb33493dacfbeca4ae5dbe,f7b7be84b440bee840fafd583d7b4d7a8757b7d2..c2f557357fd4a0eb247f0e6f688efd10f946a08a
+++ b/remote.h
@@@ -26,7 -26,6 +26,7 @@@ struct remote 
         */
        int fetch_tags;
        int skip_default_update;
 +      int mirror;
  
        const char *receivepack;
        const char *uploadpack;
@@@ -47,6 -46,7 +47,7 @@@ int remote_has_url(struct remote *remot
  struct refspec {
        unsigned force : 1;
        unsigned pattern : 1;
+       unsigned matching : 1;
  
        char *src;
        char *dst;
@@@ -54,8 -54,6 +55,8 @@@
  
  struct ref *alloc_ref(unsigned namelen);
  
 +struct ref *alloc_ref_from_str(const char* str);
 +
  struct ref *copy_ref_list(const struct ref *ref);
  
  int check_ref_type(const struct ref *ref, int flags);
@@@ -65,8 -63,6 +66,8 @@@
   */
  void free_refs(struct ref *ref);
  
 +int resolve_remote_symref(struct ref *ref, struct ref *list);
 +
  /*
   * Removes and frees any duplicate refs in the map.
   */
diff --combined t/t5516-fetch-push.sh
index 3af03d482727b69e4724d50eaf9ccc5fc8dceeee,53e47e1198763b878d629acb71df768ea479eb3c..c5c59335f36d0d8f6a26ee606fd605e74e2d1e33
@@@ -105,7 -105,7 +105,7 @@@ test_expect_success 'fetch with instead
        (
                TRASH=$(pwd)/ &&
                cd testrepo &&
 -              git config url.$TRASH.insteadOf trash/
 +              git config "url.$TRASH.insteadOf" trash/ &&
                git config remote.up.url trash/. &&
                git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
                git fetch up &&
@@@ -145,8 -145,8 +145,8 @@@ test_expect_success 'push with wildcard
  
  test_expect_success 'push with insteadOf' '
        mk_empty &&
 -      TRASH=$(pwd)/ &&
 -      git config url.$TRASH.insteadOf trash/ &&
 +      TRASH="$(pwd)/" &&
 +      git config "url./$TRASH/.insteadOf" trash/ &&
        git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
        (
                cd testrepo &&
@@@ -165,6 -165,47 +165,47 @@@ test_expect_success 'push with matchin
  
  '
  
+ test_expect_success 'push with matching heads on the command line' '
+       mk_test heads/master &&
+       git push testrepo : &&
+       check_push_result $the_commit heads/master
+ '
+ test_expect_success 'failed (non-fast-forward) push with matching heads' '
+       mk_test heads/master &&
+       git push testrepo : &&
+       git commit --amend -massaged &&
+       ! git push testrepo &&
+       check_push_result $the_commit heads/master &&
+       git reset --hard $the_commit
+ '
+ test_expect_success 'push --force with matching heads' '
+       mk_test heads/master &&
+       git push testrepo : &&
+       git commit --amend -massaged &&
+       git push --force testrepo &&
+       ! check_push_result $the_commit heads/master &&
+       git reset --hard $the_commit
+ '
+ test_expect_success 'push with matching heads and forced update' '
+       mk_test heads/master &&
+       git push testrepo : &&
+       git commit --amend -massaged &&
+       git push testrepo +: &&
+       ! check_push_result $the_commit heads/master &&
+       git reset --hard $the_commit
+ '
  test_expect_success 'push with no ambiguity (1)' '
  
        mk_test heads/master &&