Merge branch 'jk/allow-fetch-onelevel-refname'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Jan 2014 18:44:13 +0000 (10:44 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Jan 2014 18:44:14 +0000 (10:44 -0800)
"git clone" would fail to clone from a repository that has a ref
directly under "refs/", e.g. "refs/stash", because different
validation paths do different things on such a refname. Loosen the
client side's validation to allow such a ref.

* jk/allow-fetch-onelevel-refname:
fetch-pack: do not filter out one-level refs

1  2 
fetch-pack.c
t/t5510-fetch.sh
diff --combined fetch-pack.c
index d52de74c4b1f1a4cfcda756918d310e475c4c12c,4637eb13287ca253ef008b9df2848cacbf6f9dc6..90fdd49821a1d6d3e104124444e37ac4b9b03fa2
@@@ -9,11 -9,9 +9,11 @@@
  #include "fetch-pack.h"
  #include "remote.h"
  #include "run-command.h"
 +#include "connect.h"
  #include "transport.h"
  #include "version.h"
  #include "prio-queue.h"
 +#include "sha1-array.h"
  
  static int transfer_unpack_limit = -1;
  static int fetch_unpack_limit = -1;
@@@ -48,8 -46,9 +48,8 @@@ static void rev_list_push(struct commi
        if (!(commit->object.flags & mark)) {
                commit->object.flags |= mark;
  
 -              if (!(commit->object.parsed))
 -                      if (parse_commit(commit))
 -                              return;
 +              if (parse_commit(commit))
 +                      return;
  
                prio_queue_put(&rev_list, commit);
  
@@@ -128,7 -127,8 +128,7 @@@ static const unsigned char *get_rev(voi
                        return NULL;
  
                commit = prio_queue_get(&rev_list);
 -              if (!commit->object.parsed)
 -                      parse_commit(commit);
 +              parse_commit(commit);
                parents = commit->parents;
  
                commit->object.flags |= POPPED;
@@@ -175,9 -175,9 +175,9 @@@ static void consume_shallow_list(struc
                 */
                char *line;
                while ((line = packet_read_line(fd, NULL))) {
 -                      if (!prefixcmp(line, "shallow "))
 +                      if (starts_with(line, "shallow "))
                                continue;
 -                      if (!prefixcmp(line, "unshallow "))
 +                      if (starts_with(line, "unshallow "))
                                continue;
                        die("git fetch-pack: expected shallow list");
                }
@@@ -193,7 -193,7 +193,7 @@@ static enum ack_type get_ack(int fd, un
                die("git fetch-pack: expected ACK/NAK, got EOF");
        if (!strcmp(line, "NAK"))
                return NAK;
 -      if (!prefixcmp(line, "ACK ")) {
 +      if (starts_with(line, "ACK ")) {
                if (!get_sha1_hex(line+4, result_sha1)) {
                        if (len < 45)
                                return ACK;
@@@ -310,7 -310,7 +310,7 @@@ static int find_common(struct fetch_pac
        }
  
        if (is_repository_shallow())
 -              write_shallow_commits(&req_buf, 1);
 +              write_shallow_commits(&req_buf, 1, NULL);
        if (args->depth > 0)
                packet_buf_write(&req_buf, "deepen %d", args->depth);
        packet_buf_flush(&req_buf);
  
                send_request(args, fd[1], &req_buf);
                while ((line = packet_read_line(fd[0], NULL))) {
 -                      if (!prefixcmp(line, "shallow ")) {
 +                      if (starts_with(line, "shallow ")) {
                                if (get_sha1_hex(line + 8, sha1))
                                        die("invalid shallow line: %s", line);
                                register_shallow(sha1);
                                continue;
                        }
 -                      if (!prefixcmp(line, "unshallow ")) {
 +                      if (starts_with(line, "unshallow ")) {
                                if (get_sha1_hex(line + 10, sha1))
                                        die("invalid unshallow line: %s", line);
                                if (!lookup_object(sha1))
@@@ -506,7 -506,7 +506,7 @@@ static void filter_refs(struct fetch_pa
                next = ref->next;
  
                if (!memcmp(ref->name, "refs/", 5) &&
-                   check_refname_format(ref->name + 5, 0))
+                   check_refname_format(ref->name, 0))
                        ; /* trash */
                else {
                        while (i < nr_sought) {
                }
  
                if (!keep && args->fetch_all &&
 -                  (!args->depth || prefixcmp(ref->name, "refs/tags/")))
 +                  (!args->depth || !starts_with(ref->name, "refs/tags/")))
                        keep = 1;
  
                if (keep) {
@@@ -658,7 -658,7 +658,7 @@@ static int get_pack(struct fetch_pack_a
        const char *argv[22];
        char keep_arg[256];
        char hdr_arg[256];
 -      const char **av;
 +      const char **av, *cmd_name;
        int do_keep = args->keep_pack;
        struct child_process cmd;
        int ret;
        if (do_keep) {
                if (pack_lockfile)
                        cmd.out = -1;
 -              *av++ = "index-pack";
 +              *av++ = cmd_name = "index-pack";
                *av++ = "--stdin";
                if (!args->quiet && !args->no_progress)
                        *av++ = "-v";
                        *av++ = "--check-self-contained-and-connected";
        }
        else {
 -              *av++ = "unpack-objects";
 +              *av++ = cmd_name = "unpack-objects";
                if (args->quiet || args->no_progress)
                        *av++ = "-q";
                args->check_self_contained_and_connected = 0;
        cmd.in = demux.out;
        cmd.git_cmd = 1;
        if (start_command(&cmd))
 -              die("fetch-pack: unable to fork off %s", argv[0]);
 +              die("fetch-pack: unable to fork off %s", cmd_name);
        if (do_keep && pack_lockfile) {
                *pack_lockfile = index_pack_lockfile(cmd.out);
                close(cmd.out);
        }
  
 +      if (!use_sideband)
 +              /* Closed by start_command() */
 +              xd[0] = -1;
 +
        ret = finish_command(&cmd);
        if (!ret || (args->check_self_contained_and_connected && ret == 1))
                args->self_contained_and_connected =
                        args->check_self_contained_and_connected &&
                        ret == 0;
        else
 -              die("%s failed", argv[0]);
 +              die("%s failed", cmd_name);
        if (use_sideband && finish_async(&demux))
                die("error in sideband demultiplexer");
        return 0;
@@@ -773,7 -769,6 +773,7 @@@ static struct ref *do_fetch_pack(struc
                                 int fd[2],
                                 const struct ref *orig_ref,
                                 struct ref **sought, int nr_sought,
 +                               struct shallow_info *si,
                                 char **pack_lockfile)
  {
        struct ref *ref = copy_ref_list(orig_ref);
        if (args->stateless_rpc)
                packet_flush(fd[1]);
        if (args->depth > 0)
 -              setup_alternate_shallow(&shallow_lock, &alternate_shallow_file);
 +              setup_alternate_shallow(&shallow_lock, &alternate_shallow_file,
 +                                      NULL);
 +      else if (si->nr_ours || si->nr_theirs)
 +              alternate_shallow_file = setup_temporary_shallow(si->shallow);
        else
                alternate_shallow_file = NULL;
        if (get_pack(args, fd, pack_lockfile))
@@@ -927,121 -919,14 +927,121 @@@ static int remove_duplicates_in_refs(st
        return dst;
  }
  
 +static void update_shallow(struct fetch_pack_args *args,
 +                         struct ref **sought, int nr_sought,
 +                         struct shallow_info *si)
 +{
 +      struct sha1_array ref = SHA1_ARRAY_INIT;
 +      int *status;
 +      int i;
 +
 +      if (args->depth > 0 && alternate_shallow_file) {
 +              if (*alternate_shallow_file == '\0') { /* --unshallow */
 +                      unlink_or_warn(git_path("shallow"));
 +                      rollback_lock_file(&shallow_lock);
 +              } else
 +                      commit_lock_file(&shallow_lock);
 +              return;
 +      }
 +
 +      if (!si->shallow || !si->shallow->nr)
 +              return;
 +
 +      if (alternate_shallow_file) {
 +              /*
 +               * The temporary shallow file is only useful for
 +               * index-pack and unpack-objects because it may
 +               * contain more roots than we want. Delete it.
 +               */
 +              if (*alternate_shallow_file)
 +                      unlink(alternate_shallow_file);
 +              free((char *)alternate_shallow_file);
 +      }
 +
 +      if (args->cloning) {
 +              /*
 +               * remote is shallow, but this is a clone, there are
 +               * no objects in repo to worry about. Accept any
 +               * shallow points that exist in the pack (iow in repo
 +               * after get_pack() and reprepare_packed_git())
 +               */
 +              struct sha1_array extra = SHA1_ARRAY_INIT;
 +              unsigned char (*sha1)[20] = si->shallow->sha1;
 +              for (i = 0; i < si->shallow->nr; i++)
 +                      if (has_sha1_file(sha1[i]))
 +                              sha1_array_append(&extra, sha1[i]);
 +              if (extra.nr) {
 +                      setup_alternate_shallow(&shallow_lock,
 +                                              &alternate_shallow_file,
 +                                              &extra);
 +                      commit_lock_file(&shallow_lock);
 +              }
 +              sha1_array_clear(&extra);
 +              return;
 +      }
 +
 +      if (!si->nr_ours && !si->nr_theirs)
 +              return;
 +
 +      remove_nonexistent_theirs_shallow(si);
 +      if (!si->nr_ours && !si->nr_theirs)
 +              return;
 +      for (i = 0; i < nr_sought; i++)
 +              sha1_array_append(&ref, sought[i]->old_sha1);
 +      si->ref = &ref;
 +
 +      if (args->update_shallow) {
 +              /*
 +               * remote is also shallow, .git/shallow may be updated
 +               * so all refs can be accepted. Make sure we only add
 +               * shallow roots that are actually reachable from new
 +               * refs.
 +               */
 +              struct sha1_array extra = SHA1_ARRAY_INIT;
 +              unsigned char (*sha1)[20] = si->shallow->sha1;
 +              assign_shallow_commits_to_refs(si, NULL, NULL);
 +              if (!si->nr_ours && !si->nr_theirs) {
 +                      sha1_array_clear(&ref);
 +                      return;
 +              }
 +              for (i = 0; i < si->nr_ours; i++)
 +                      sha1_array_append(&extra, sha1[si->ours[i]]);
 +              for (i = 0; i < si->nr_theirs; i++)
 +                      sha1_array_append(&extra, sha1[si->theirs[i]]);
 +              setup_alternate_shallow(&shallow_lock,
 +                                      &alternate_shallow_file,
 +                                      &extra);
 +              commit_lock_file(&shallow_lock);
 +              sha1_array_clear(&extra);
 +              sha1_array_clear(&ref);
 +              return;
 +      }
 +
 +      /*
 +       * remote is also shallow, check what ref is safe to update
 +       * without updating .git/shallow
 +       */
 +      status = xcalloc(nr_sought, sizeof(*status));
 +      assign_shallow_commits_to_refs(si, NULL, status);
 +      if (si->nr_ours || si->nr_theirs) {
 +              for (i = 0; i < nr_sought; i++)
 +                      if (status[i])
 +                              sought[i]->status = REF_STATUS_REJECT_SHALLOW;
 +      }
 +      free(status);
 +      sha1_array_clear(&ref);
 +}
 +
  struct ref *fetch_pack(struct fetch_pack_args *args,
                       int fd[], struct child_process *conn,
                       const struct ref *ref,
                       const char *dest,
                       struct ref **sought, int nr_sought,
 +                     struct sha1_array *shallow,
                       char **pack_lockfile)
  {
        struct ref *ref_cpy;
 +      struct shallow_info si;
  
        fetch_pack_setup();
        if (nr_sought)
                packet_flush(fd[1]);
                die("no matching remote head");
        }
 -      ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
 -
 -      if (args->depth > 0 && alternate_shallow_file) {
 -              if (*alternate_shallow_file == '\0') { /* --unshallow */
 -                      unlink_or_warn(git_path("shallow"));
 -                      rollback_lock_file(&shallow_lock);
 -              } else
 -                      commit_lock_file(&shallow_lock);
 -      }
 -
 +      prepare_shallow_info(&si, shallow);
 +      ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
 +                              &si, pack_lockfile);
        reprepare_packed_git();
 +      update_shallow(args, sought, nr_sought, &si);
 +      clear_shallow_info(&si);
        return ref_cpy;
  }
diff --combined t/t5510-fetch.sh
index 12674ac0980fdafd60037bdff8009bb02a6da14a,d52ef7fe80683c455e9024c565938a5bda88b827..ab28594c62dd7cddc69d3afdb075b48c34091c45
@@@ -88,7 -88,7 +88,7 @@@ test_expect_success 'fetch --prune on i
        cd "$D" &&
        git clone . prune &&
        cd prune &&
 -      git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
 +      git update-ref refs/remotes/origin/extrabranch master &&
  
        git fetch --prune origin &&
        test_must_fail git rev-parse origin/extrabranch
@@@ -98,7 -98,7 +98,7 @@@ test_expect_success 'fetch --prune wit
        cd "$D" &&
        git clone . prune-branch &&
        cd prune-branch &&
 -      git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
 +      git update-ref refs/remotes/origin/extrabranch master &&
  
        git fetch --prune origin master &&
        git rev-parse origin/extrabranch
@@@ -113,45 -113,25 +113,45 @@@ test_expect_success 'fetch --prune wit
        git rev-parse origin/master
  '
  
 -test_expect_success 'fetch --prune --tags does not delete the remote-tracking branches' '
 +test_expect_success 'fetch --prune --tags prunes branches but not tags' '
        cd "$D" &&
        git clone . prune-tags &&
        cd prune-tags &&
 -      git fetch origin refs/heads/master:refs/tags/sometag &&
 +      git tag sometag master &&
 +      # Create what looks like a remote-tracking branch from an earlier
 +      # fetch that has since been deleted from the remote:
 +      git update-ref refs/remotes/origin/fake-remote master &&
  
        git fetch --prune --tags origin &&
        git rev-parse origin/master &&
 -      test_must_fail git rev-parse somebranch
 +      test_must_fail git rev-parse origin/fake-remote &&
 +      git rev-parse sometag
  '
  
 -test_expect_success 'fetch --prune --tags with branch does not delete other remote-tracking branches' '
 +test_expect_success 'fetch --prune --tags with branch does not prune other things' '
        cd "$D" &&
        git clone . prune-tags-branch &&
        cd prune-tags-branch &&
 -      git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
 +      git tag sometag master &&
 +      git update-ref refs/remotes/origin/extrabranch master &&
  
        git fetch --prune --tags origin master &&
 -      git rev-parse origin/extrabranch
 +      git rev-parse origin/extrabranch &&
 +      git rev-parse sometag
 +'
 +
 +test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' '
 +      cd "$D" &&
 +      git clone . prune-tags-refspec &&
 +      cd prune-tags-refspec &&
 +      git tag sometag master &&
 +      git update-ref refs/remotes/origin/foo/otherbranch master &&
 +      git update-ref refs/remotes/origin/extrabranch master &&
 +
 +      git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
 +      test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
 +      git rev-parse origin/extrabranch &&
 +      git rev-parse sometag
  '
  
  test_expect_success 'fetch tags when there is no tags' '
@@@ -517,88 -497,6 +517,88 @@@ test_expect_success "should be able to 
        )
  '
  
 +# configured prune tests
 +
 +set_config_tristate () {
 +      # var=$1 val=$2
 +      case "$2" in
 +      unset)  test_unconfig "$1" ;;
 +      *)      git config "$1" "$2" ;;
 +      esac
 +}
 +
 +test_configured_prune () {
 +      fetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4
 +
 +      test_expect_success "prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4" '
 +              # make sure a newbranch is there in . and also in one
 +              git branch -f newbranch &&
 +              (
 +                      cd one &&
 +                      test_unconfig fetch.prune &&
 +                      test_unconfig remote.origin.prune &&
 +                      git fetch &&
 +                      git rev-parse --verify refs/remotes/origin/newbranch
 +              )
 +
 +              # now remove it
 +              git branch -d newbranch &&
 +
 +              # then test
 +              (
 +                      cd one &&
 +                      set_config_tristate fetch.prune $fetch_prune &&
 +                      set_config_tristate remote.origin.prune $remote_origin_prune &&
 +
 +                      git fetch $cmdline &&
 +                      case "$expected" in
 +                      pruned)
 +                              test_must_fail git rev-parse --verify refs/remotes/origin/newbranch
 +                              ;;
 +                      kept)
 +                              git rev-parse --verify refs/remotes/origin/newbranch
 +                              ;;
 +                      esac
 +              )
 +      '
 +}
 +
 +test_configured_prune unset unset ""          kept
 +test_configured_prune unset unset "--no-prune"        kept
 +test_configured_prune unset unset "--prune"   pruned
 +
 +test_configured_prune false unset ""          kept
 +test_configured_prune false unset "--no-prune"        kept
 +test_configured_prune false unset "--prune"   pruned
 +
 +test_configured_prune true  unset ""          pruned
 +test_configured_prune true  unset "--prune"   pruned
 +test_configured_prune true  unset "--no-prune"        kept
 +
 +test_configured_prune unset false ""          kept
 +test_configured_prune unset false "--no-prune"        kept
 +test_configured_prune unset false "--prune"   pruned
 +
 +test_configured_prune false false ""          kept
 +test_configured_prune false false "--no-prune"        kept
 +test_configured_prune false false "--prune"   pruned
 +
 +test_configured_prune true  false ""          kept
 +test_configured_prune true  false "--prune"   pruned
 +test_configured_prune true  false "--no-prune"        kept
 +
 +test_configured_prune unset true  ""          pruned
 +test_configured_prune unset true  "--no-prune"        kept
 +test_configured_prune unset true  "--prune"   pruned
 +
 +test_configured_prune false true  ""          pruned
 +test_configured_prune false true  "--no-prune"        kept
 +test_configured_prune false true  "--prune"   pruned
 +
 +test_configured_prune true  true  ""          pruned
 +test_configured_prune true  true  "--prune"   pruned
 +test_configured_prune true  true  "--no-prune"        kept
 +
  test_expect_success 'all boundary commits are excluded' '
        test_commit base &&
        test_commit oneside &&
        test_bundle_object_count .git/objects/pack/pack-${pack##pack    }.pack 3
  '
  
 +test_expect_success 'fetch --prune prints the remotes url' '
 +      git branch goodbye &&
 +      git clone . only-prunes &&
 +      git branch -D goodbye &&
 +      (
 +              cd only-prunes &&
 +              git fetch --prune origin 2>&1 | head -n1 >../actual
 +      ) &&
 +      echo "From ${D}/." >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'branchname D/F conflict resolved by --prune' '
 +      git branch dir/file &&
 +      git clone . prune-df-conflict &&
 +      git branch -D dir/file &&
 +      git branch dir &&
 +      (
 +              cd prune-df-conflict &&
 +              git fetch --prune &&
 +              git rev-parse origin/dir >../actual
 +      ) &&
 +      git rev-parse dir >expect &&
 +      test_cmp expect actual
 +'
 +
+ test_expect_success 'fetching a one-level ref works' '
+       test_commit extra &&
+       git reset --hard HEAD^ &&
+       git update-ref refs/foo extra &&
+       git init one-level &&
+       (
+               cd one-level &&
+               git fetch .. HEAD refs/foo
+       )
+ '
  test_done