Merge branch 'mk/maint-cg-push'
authorJunio C Hamano <gitster@pobox.com>
Thu, 13 Nov 2008 06:26:24 +0000 (22:26 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 13 Nov 2008 06:26:24 +0000 (22:26 -0800)
* mk/maint-cg-push:
git push: Interpret $GIT_DIR/branches in a Cogito compatible way

Conflicts:
t/t5516-fetch-push.sh

1  2 
remote.c
t/t5516-fetch-push.sh
diff --combined remote.c
index cbb3e484fe7803f68eae8507c8216ed951d0b54b,91f1b7cd81f5461b13c2553bf3dd3e4b5c76ab14..570e11286ea295e825a23b1d5ed40c9fbd02be57
+++ b/remote.c
@@@ -69,7 -69,7 +69,7 @@@ static const char *alias_url(const cha
        if (!longest)
                return url;
  
 -      ret = malloc(rewrite[longest_i]->baselen +
 +      ret = xmalloc(rewrite[longest_i]->baselen +
                     (strlen(url) - longest->len) + 1);
        strcpy(ret, rewrite[longest_i]->base);
        strcpy(ret + rewrite[longest_i]->baselen, url + longest->len);
@@@ -152,7 -152,7 +152,7 @@@ static struct branch *make_branch(cons
                ret->name = xstrndup(name, len);
        else
                ret->name = xstrdup(name);
 -      refname = malloc(strlen(name) + strlen("refs/heads/") + 1);
 +      refname = xmalloc(strlen(name) + strlen("refs/heads/") + 1);
        strcpy(refname, "refs/heads/");
        strcpy(refname + strlen("refs/heads/"), ret->name);
        ret->refname = refname;
@@@ -201,7 -201,6 +201,7 @@@ static void read_remotes_file(struct re
  
        if (!f)
                return;
 +      remote->origin = REMOTE_REMOTES;
        while (fgets(buffer, BUF_SIZE, f)) {
                int value_list;
                char *s, *p;
@@@ -246,7 -245,7 +246,7 @@@ static void read_branches_file(struct r
  {
        const char *slash = strchr(remote->name, '/');
        char *frag;
 -      struct strbuf branch;
 +      struct strbuf branch = STRBUF_INIT;
        int n = slash ? slash - remote->name : 1000;
        FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
        char *s, *p;
                s++;
        if (!*s)
                return;
 +      remote->origin = REMOTE_BRANCHES;
        p = s + strlen(s);
        while (isspace(p[-1]))
                *--p = 0;
         * #branch specified.  The "master" (or specified) branch is
         * fetched and stored in the local branch of the same name.
         */
 -      strbuf_init(&branch, 0);
        frag = strchr(p, '#');
        if (frag) {
                *(frag++) = '\0';
        }
        add_url_alias(remote, p);
        add_fetch_refspec(remote, strbuf_detach(&branch, 0));
+       /*
+        * Cogito compatible push: push current HEAD to remote #branch
+        * (master if missing)
+        */
+       strbuf_init(&branch, 0);
+       strbuf_addstr(&branch, "HEAD");
+       if (frag)
+               strbuf_addf(&branch, ":refs/heads/%s", frag);
+       else
+               strbuf_addstr(&branch, ":refs/heads/master");
+       add_push_refspec(remote, strbuf_detach(&branch, 0));
        remote->fetch_tags = 1; /* always auto-follow */
  }
  
@@@ -352,7 -362,6 +363,7 @@@ static int handle_config(const char *ke
        if (!subkey)
                return error("Config with no key for remote %s", name);
        remote = make_remote(name, subkey - name);
 +      remote->origin = REMOTE_CONFIG;
        if (!strcmp(subkey, ".mirror"))
                remote->mirror = git_config_bool(key, value);
        else if (!strcmp(subkey, ".skipdefaultupdate"))
@@@ -452,26 -461,6 +463,26 @@@ static int verify_refname(char *name, i
        return result;
  }
  
 +/*
 + * This function frees a refspec array.
 + * Warning: code paths should be checked to ensure that the src
 + *          and dst pointers are always freeable pointers as well
 + *          as the refspec pointer itself.
 + */
 +static void free_refspecs(struct refspec *refspec, int nr_refspec)
 +{
 +      int i;
 +
 +      if (!refspec)
 +              return;
 +
 +      for (i = 0; i < nr_refspec; i++) {
 +              free(refspec[i].src);
 +              free(refspec[i].dst);
 +      }
 +      free(refspec);
 +}
 +
  static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
  {
        int i;
  
   invalid:
        if (verify) {
 -              free(rs);
 +              /*
 +               * nr_refspec must be greater than zero and i must be valid
 +               * since it is only possible to reach this point from within
 +               * the for loop above.
 +               */
 +              free_refspecs(rs, i+1);
                return NULL;
        }
        die("Invalid refspec '%s'", refspec[i]);
@@@ -607,7 -591,7 +618,7 @@@ int valid_fetch_refspec(const char *fet
        struct refspec *refspec;
  
        refspec = parse_refspec_internal(1, fetch_refspec, 1, 1);
 -      free(refspec);
 +      free_refspecs(refspec, 1);
        return !!refspec;
  }
  
@@@ -616,7 -600,7 +627,7 @@@ struct refspec *parse_fetch_refspec(in
        return parse_refspec_internal(nr_refspec, refspec, 1, 0);
  }
  
 -struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
 +static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
  {
        return parse_refspec_internal(nr_refspec, refspec, 0, 0);
  }
@@@ -752,19 -736,18 +763,19 @@@ int remote_find_tracking(struct remote 
        return -1;
  }
  
 -struct ref *alloc_ref(unsigned namelen)
 +static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
 +              const char *name)
  {
 -      struct ref *ret = xmalloc(sizeof(struct ref) + namelen);
 -      memset(ret, 0, sizeof(struct ref) + namelen);
 -      return ret;
 +      size_t len = strlen(name);
 +      struct ref *ref = xcalloc(1, sizeof(struct ref) + prefixlen + len + 1);
 +      memcpy(ref->name, prefix, prefixlen);
 +      memcpy(ref->name + prefixlen, name, len);
 +      return ref;
  }
  
 -struct ref *alloc_ref_from_str(const char* str)
 +struct ref *alloc_ref(const char *name)
  {
 -      struct ref *ret = alloc_ref(strlen(str) + 1);
 -      strcpy(ret->name, str);
 -      return ret;
 +      return alloc_ref_with_prefix("", 0, name);
  }
  
  static struct ref *copy_ref(const struct ref *ref)
@@@ -787,7 -770,7 +798,7 @@@ struct ref *copy_ref_list(const struct 
        return ret;
  }
  
 -void free_ref(struct ref *ref)
 +static void free_ref(struct ref *ref)
  {
        if (!ref)
                return;
@@@ -875,20 -858,21 +886,20 @@@ static struct ref *try_explicit_object_
        struct ref *ref;
  
        if (!*name) {
 -              ref = alloc_ref(20);
 -              strcpy(ref->name, "(delete)");
 +              ref = alloc_ref("(delete)");
                hashclr(ref->new_sha1);
                return ref;
        }
        if (get_sha1(name, sha1))
                return NULL;
 -      ref = alloc_ref_from_str(name);
 +      ref = alloc_ref(name);
        hashcpy(ref->new_sha1, sha1);
        return ref;
  }
  
  static struct ref *make_linked_ref(const char *name, struct ref ***tail)
  {
 -      struct ref *ret = alloc_ref_from_str(name);
 +      struct ref *ret = alloc_ref(name);
        tail_link_ref(ret, tail);
        return ret;
  }
@@@ -1156,8 -1140,10 +1167,8 @@@ static struct ref *get_expanded_map(con
                        struct ref *cpy = copy_ref(ref);
                        match = ref->name + remote_prefix_len;
  
 -                      cpy->peer_ref = alloc_ref(local_prefix_len +
 -                                                strlen(match) + 1);
 -                      sprintf(cpy->peer_ref->name, "%s%s",
 -                              refspec->dst, match);
 +                      cpy->peer_ref = alloc_ref_with_prefix(refspec->dst,
 +                                      local_prefix_len, match);
                        if (refspec->force)
                                cpy->peer_ref->force = 1;
                        *tail = cpy;
@@@ -1190,18 -1176,25 +1201,18 @@@ struct ref *get_remote_ref(const struc
  
  static struct ref *get_local_ref(const char *name)
  {
 -      struct ref *ret;
        if (!name)
                return NULL;
  
 -      if (!prefixcmp(name, "refs/")) {
 -              return alloc_ref_from_str(name);
 -      }
 +      if (!prefixcmp(name, "refs/"))
 +              return alloc_ref(name);
  
        if (!prefixcmp(name, "heads/") ||
            !prefixcmp(name, "tags/") ||
 -          !prefixcmp(name, "remotes/")) {
 -              ret = alloc_ref(strlen(name) + 6);
 -              sprintf(ret->name, "refs/%s", name);
 -              return ret;
 -      }
 +          !prefixcmp(name, "remotes/"))
 +              return alloc_ref_with_prefix("refs/", 5, name);
  
 -      ret = alloc_ref(strlen(name) + 12);
 -      sprintf(ret->name, "refs/heads/%s", name);
 -      return ret;
 +      return alloc_ref_with_prefix("refs/heads/", 11, name);
  }
  
  int get_fetch_map(const struct ref *remote_refs,
diff --combined t/t5516-fetch-push.sh
index a6532cb29e311ec90430eea0349179e69d68e2fd,f9e878022a93657a8b8d81339670f4d910392755..4426df9226535c55eacff80217c4ab70f77639b6
@@@ -39,11 -39,6 +39,11 @@@ mk_test () 
        )
  }
  
 +mk_child() {
 +      rm -rf "$1" &&
 +      git clone testrepo "$1"
 +}
 +
  check_push_result () {
        (
                cd testrepo &&
@@@ -430,10 -425,13 +430,10 @@@ test_expect_success 'push with dry-run
  
  test_expect_success 'push updates local refs' '
  
 -      rm -rf parent child &&
 -      mkdir parent &&
 -      (cd parent && git init &&
 -              echo one >foo && git add foo && git commit -m one) &&
 -      git clone parent child &&
 +      mk_test heads/master &&
 +      mk_child child &&
        (cd child &&
 -              echo two >foo && git commit -a -m two &&
 +              git pull .. master &&
                git push &&
        test $(git rev-parse master) = $(git rev-parse remotes/origin/master))
  
  
  test_expect_success 'push updates up-to-date local refs' '
  
 -      rm -rf parent child &&
 -      mkdir parent &&
 -      (cd parent && git init &&
 -              echo one >foo && git add foo && git commit -m one) &&
 -      git clone parent child1 &&
 -      git clone parent child2 &&
 -      (cd child1 &&
 -              echo two >foo && git commit -a -m two &&
 -              git push) &&
 +      mk_test heads/master &&
 +      mk_child child1 &&
 +      mk_child child2 &&
 +      (cd child1 && git pull .. master && git push) &&
        (cd child2 &&
                git pull ../child1 master &&
                git push &&
  
  test_expect_success 'push preserves up-to-date packed refs' '
  
 -      rm -rf parent child &&
 -      mkdir parent &&
 -      (cd parent && git init &&
 -              echo one >foo && git add foo && git commit -m one) &&
 -      git clone parent child &&
 +      mk_test heads/master &&
 +      mk_child child &&
        (cd child &&
                git push &&
        ! test -f .git/refs/remotes/origin/master)
  
  test_expect_success 'push does not update local refs on failure' '
  
 -      rm -rf parent child &&
 -      mkdir parent &&
 -      (cd parent && git init &&
 -              echo one >foo && git add foo && git commit -m one &&
 -              echo exit 1 >.git/hooks/pre-receive &&
 -              chmod +x .git/hooks/pre-receive) &&
 -      git clone parent child &&
 +      mk_test heads/master &&
 +      mk_child child &&
 +      mkdir testrepo/.git/hooks &&
 +      echo exit 1 >testrepo/.git/hooks/pre-receive &&
 +      chmod +x testrepo/.git/hooks/pre-receive &&
        (cd child &&
 -              echo two >foo && git commit -a -m two &&
 +              git pull .. master
                test_must_fail git push &&
                test $(git rev-parse master) != \
                        $(git rev-parse remotes/origin/master))
  
  test_expect_success 'allow deleting an invalid remote ref' '
  
 -      pwd &&
 +      mk_test heads/master &&
        rm -f testrepo/.git/objects/??/* &&
        git push testrepo :refs/heads/master &&
        (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master)
  
  '
  
 +test_expect_success 'warn on push to HEAD of non-bare repository' '
 +      mk_test heads/master
 +      (cd testrepo &&
 +              git checkout master &&
 +              git config receive.denyCurrentBranch warn) &&
 +      git push testrepo master 2>stderr &&
 +      grep "warning.*this may cause confusion" stderr
 +'
 +
 +test_expect_success 'deny push to HEAD of non-bare repository' '
 +      mk_test heads/master
 +      (cd testrepo &&
 +              git checkout master &&
 +              git config receive.denyCurrentBranch true) &&
 +      test_must_fail git push testrepo master
 +'
 +
 +test_expect_success 'allow push to HEAD of bare repository (bare)' '
 +      mk_test heads/master
 +      (cd testrepo &&
 +              git checkout master &&
 +              git config receive.denyCurrentBranch true &&
 +              git config core.bare true) &&
 +      git push testrepo master 2>stderr &&
 +      ! grep "warning.*this may cause confusion" stderr
 +'
 +
 +test_expect_success 'allow push to HEAD of non-bare repository (config)' '
 +      mk_test heads/master
 +      (cd testrepo &&
 +              git checkout master &&
 +              git config receive.denyCurrentBranch false
 +      ) &&
 +      git push testrepo master 2>stderr &&
 +      ! grep "warning.*this may cause confusion" stderr
 +'
 +
+ test_expect_success 'fetch with branches' '
+       mk_empty &&
+       git branch second $the_first_commit &&
+       git checkout second &&
+       echo ".." > testrepo/.git/branches/branch1 &&
+       (cd testrepo &&
+               git fetch branch1 &&
+               r=$(git show-ref -s --verify refs/heads/branch1) &&
+               test "z$r" = "z$the_commit" &&
+               test 1 = $(git for-each-ref refs/heads | wc -l)
+       ) &&
+       git checkout master
+ '
+ test_expect_success 'fetch with branches containing #' '
+       mk_empty &&
+       echo "..#second" > testrepo/.git/branches/branch2 &&
+       (cd testrepo &&
+               git fetch branch2 &&
+               r=$(git show-ref -s --verify refs/heads/branch2) &&
+               test "z$r" = "z$the_first_commit" &&
+               test 1 = $(git for-each-ref refs/heads | wc -l)
+       ) &&
+       git checkout master
+ '
+ test_expect_success 'push with branches' '
+       mk_empty &&
+       git checkout second &&
+       echo "testrepo" > .git/branches/branch1 &&
+       git push branch1 &&
+       (cd testrepo &&
+               r=$(git show-ref -s --verify refs/heads/master) &&
+               test "z$r" = "z$the_first_commit" &&
+               test 1 = $(git for-each-ref refs/heads | wc -l)
+       )
+ '
+ test_expect_success 'push with branches containing #' '
+       mk_empty &&
+       echo "testrepo#branch3" > .git/branches/branch2 &&
+       git push branch2 &&
+       (cd testrepo &&
+               r=$(git show-ref -s --verify refs/heads/branch3) &&
+               test "z$r" = "z$the_first_commit" &&
+               test 1 = $(git for-each-ref refs/heads | wc -l)
+       ) &&
+       git checkout master
+ '
  test_done