Code

Merge branch 'jc/maint-clone-alternates' into maint
authorJunio C Hamano <gitster@pobox.com>
Fri, 23 Sep 2011 21:27:33 +0000 (14:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 23 Sep 2011 21:27:33 +0000 (14:27 -0700)
* jc/maint-clone-alternates:
  clone: clone from a repository with relative alternates
  clone: allow more than one --reference

1  2 
builtin/clone.c
sha1_file.c
t/t5601-clone.sh

diff --combined builtin/clone.c
index ec57f3dbe4a629b502f7c9e60f89244ef446af4f,16b4fba71f8fed16ac67ee09b186e0295fc6b6da..5f20082d6d0688c7481d012fd9461c92437e38f2
@@@ -39,13 -39,23 +39,23 @@@ static const char * const builtin_clone
  
  static int option_no_checkout, option_bare, option_mirror;
  static int option_local, option_no_hardlinks, option_shared, option_recursive;
- static char *option_template, *option_reference, *option_depth;
+ static char *option_template, *option_depth;
  static char *option_origin = NULL;
  static char *option_branch = NULL;
  static const char *real_git_dir;
  static char *option_upload_pack = "git-upload-pack";
  static int option_verbosity;
  static int option_progress;
+ static struct string_list option_reference;
+ static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
+ {
+       struct string_list *option_reference = opt->value;
+       if (!arg)
+               return -1;
+       string_list_append(option_reference, arg);
+       return 0;
+ }
  
  static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
@@@ -71,8 -81,8 +81,8 @@@
                    "initialize submodules in the clone"),
        OPT_STRING(0, "template", &option_template, "template-directory",
                   "directory from which templates will be used"),
-       OPT_STRING(0, "reference", &option_reference, "repo",
-                  "reference repository"),
+       OPT_CALLBACK(0 , "reference", &option_reference, "repo",
+                    "reference repository", &opt_parse_reference),
        OPT_STRING('o', "origin", &option_origin, "branch",
                   "use <branch> instead of 'origin' to track upstream"),
        OPT_STRING('b', "branch", &option_branch, "branch",
@@@ -101,26 -111,9 +111,26 @@@ static char *get_repo_path(const char *
        for (i = 0; i < ARRAY_SIZE(suffix); i++) {
                const char *path;
                path = mkpath("%s%s", repo, suffix[i]);
 -              if (is_directory(path)) {
 +              if (stat(path, &st))
 +                      continue;
 +              if (S_ISDIR(st.st_mode)) {
                        *is_bundle = 0;
                        return xstrdup(absolute_path(path));
 +              } else if (S_ISREG(st.st_mode) && st.st_size > 8) {
 +                      /* Is it a "gitfile"? */
 +                      char signature[8];
 +                      int len, fd = open(path, O_RDONLY);
 +                      if (fd < 0)
 +                              continue;
 +                      len = read_in_full(fd, signature, 8);
 +                      close(fd);
 +                      if (len != 8 || strncmp(signature, "gitdir: ", 8))
 +                              continue;
 +                      path = read_gitfile(path);
 +                      if (path) {
 +                              *is_bundle = 0;
 +                              return xstrdup(absolute_path(path));
 +                      }
                }
        }
  
@@@ -214,39 -207,80 +224,80 @@@ static void strip_trailing_slashes(cha
        *end = '\0';
  }
  
- static void setup_reference(const char *repo)
+ static int add_one_reference(struct string_list_item *item, void *cb_data)
  {
-       const char *ref_git;
-       char *ref_git_copy;
+       char *ref_git;
+       struct strbuf alternate = STRBUF_INIT;
        struct remote *remote;
        struct transport *transport;
        const struct ref *extra;
  
-       ref_git = real_path(option_reference);
-       if (is_directory(mkpath("%s/.git/objects", ref_git)))
-               ref_git = mkpath("%s/.git", ref_git);
-       else if (!is_directory(mkpath("%s/objects", ref_git)))
+       /* Beware: real_path() and mkpath() return static buffer */
+       ref_git = xstrdup(real_path(item->string));
+       if (is_directory(mkpath("%s/.git/objects", ref_git))) {
+               char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git));
+               free(ref_git);
+               ref_git = ref_git_git;
+       } else if (!is_directory(mkpath("%s/objects", ref_git)))
                die(_("reference repository '%s' is not a local directory."),
-                   option_reference);
-       ref_git_copy = xstrdup(ref_git);
+                   item->string);
  
-       add_to_alternates_file(ref_git_copy);
+       strbuf_addf(&alternate, "%s/objects", ref_git);
+       add_to_alternates_file(alternate.buf);
+       strbuf_release(&alternate);
  
-       remote = remote_get(ref_git_copy);
-       transport = transport_get(remote, ref_git_copy);
+       remote = remote_get(ref_git);
+       transport = transport_get(remote, ref_git);
        for (extra = transport_get_remote_refs(transport); extra;
             extra = extra->next)
                add_extra_ref(extra->name, extra->old_sha1, 0);
  
        transport_disconnect(transport);
+       free(ref_git);
+       return 0;
+ }
  
-       free(ref_git_copy);
+ static void setup_reference(void)
+ {
+       for_each_string_list(&option_reference, add_one_reference, NULL);
  }
  
- static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
+ static void copy_alternates(struct strbuf *src, struct strbuf *dst,
+                           const char *src_repo)
+ {
+       /*
+        * Read from the source objects/info/alternates file
+        * and copy the entries to corresponding file in the
+        * destination repository with add_to_alternates_file().
+        * Both src and dst have "$path/objects/info/alternates".
+        *
+        * Instead of copying bit-for-bit from the original,
+        * we need to append to existing one so that the already
+        * created entry via "clone -s" is not lost, and also
+        * to turn entries with paths relative to the original
+        * absolute, so that they can be used in the new repository.
+        */
+       FILE *in = fopen(src->buf, "r");
+       struct strbuf line = STRBUF_INIT;
+       while (strbuf_getline(&line, in, '\n') != EOF) {
+               char *abs_path, abs_buf[PATH_MAX];
+               if (!line.len || line.buf[0] == '#')
+                       continue;
+               if (is_absolute_path(line.buf)) {
+                       add_to_alternates_file(line.buf);
+                       continue;
+               }
+               abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
+               normalize_path_copy(abs_buf, abs_path);
+               add_to_alternates_file(abs_buf);
+       }
+       strbuf_release(&line);
+       fclose(in);
+ }
+ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
+                                  const char *src_repo, int src_baselen)
  {
        struct dirent *de;
        struct stat buf;
                }
                if (S_ISDIR(buf.st_mode)) {
                        if (de->d_name[0] != '.')
-                               copy_or_link_directory(src, dest);
+                               copy_or_link_directory(src, dest,
+                                                      src_repo, src_baselen);
+                       continue;
+               }
+               /* Files that cannot be copied bit-for-bit... */
+               if (!strcmp(src->buf + src_baselen, "/info/alternates")) {
+                       copy_alternates(src, dest, src_repo);
                        continue;
                }
  
@@@ -305,17 -346,20 +363,20 @@@ static const struct ref *clone_local(co
                                     const char *dest_repo)
  {
        const struct ref *ret;
-       struct strbuf src = STRBUF_INIT;
-       struct strbuf dest = STRBUF_INIT;
        struct remote *remote;
        struct transport *transport;
  
-       if (option_shared)
-               add_to_alternates_file(src_repo);
-       else {
+       if (option_shared) {
+               struct strbuf alt = STRBUF_INIT;
+               strbuf_addf(&alt, "%s/objects", src_repo);
+               add_to_alternates_file(alt.buf);
+               strbuf_release(&alt);
+       } else {
+               struct strbuf src = STRBUF_INIT;
+               struct strbuf dest = STRBUF_INIT;
                strbuf_addf(&src, "%s/objects", src_repo);
                strbuf_addf(&dest, "%s/objects", dest_repo);
-               copy_or_link_directory(&src, &dest);
+               copy_or_link_directory(&src, &dest, src_repo, src.len);
                strbuf_release(&src);
                strbuf_release(&dest);
        }
@@@ -538,8 -582,8 +599,8 @@@ int cmd_clone(int argc, const char **ar
        git_config_set(key.buf, repo);
        strbuf_reset(&key);
  
-       if (option_reference)
-               setup_reference(git_dir);
+       if (option_reference.nr)
+               setup_reference();
  
        fetch_pattern = value.buf;
        refspec = parse_fetch_refspec(1, &fetch_pattern);
diff --combined sha1_file.c
index 92e87ee2254b6e7a675ad2bdf5111ef7a9b8b5ec,f7c3408de61ce406874d9dea69133563c04c3ed8..32268d11d0c929624729b01f143845b13d7a569c
@@@ -380,7 -380,7 +380,7 @@@ void add_to_alternates_file(const char 
  {
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
        int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
-       char *alt = mkpath("%s/objects\n", reference);
+       char *alt = mkpath("%s\n", reference);
        write_or_die(fd, alt, strlen(alt));
        if (commit_lock_file(lock))
                die("could not close alternates file");
@@@ -834,7 -834,7 +834,7 @@@ static int in_window(struct pack_windo
  unsigned char *use_pack(struct packed_git *p,
                struct pack_window **w_cursor,
                off_t offset,
 -              unsigned int *left)
 +              unsigned long *left)
  {
        struct pack_window *win = *w_cursor;
  
@@@ -1205,29 -1205,20 +1205,29 @@@ static void *map_sha1_file(const unsign
        return map;
  }
  
 -static int legacy_loose_object(unsigned char *map)
 +/*
 + * There used to be a second loose object header format which
 + * was meant to mimic the in-pack format, allowing for direct
 + * copy of the object data.  This format turned up not to be
 + * really worth it and we no longer write loose objects in that
 + * format.
 + */
 +static int experimental_loose_object(unsigned char *map)
  {
        unsigned int word;
  
        /*
         * Is it a zlib-compressed buffer? If so, the first byte
         * must be 0x78 (15-bit window size, deflated), and the
 -       * first 16-bit word is evenly divisible by 31
 +       * first 16-bit word is evenly divisible by 31. If so,
 +       * we are looking at the official format, not the experimental
 +       * one.
         */
        word = (map[0] << 8) + map[1];
        if (map[0] == 0x78 && !(word % 31))
 -              return 1;
 -      else
                return 0;
 +      else
 +              return 1;
  }
  
  unsigned long unpack_object_header_buffer(const unsigned char *buf,
        return used;
  }
  
 -static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
 +static int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
  {
        unsigned long size, used;
        static const char valid_loose_object_type[8] = {
        stream->next_out = buffer;
        stream->avail_out = bufsiz;
  
 -      if (legacy_loose_object(map)) {
 -              git_inflate_init(stream);
 -              return git_inflate(stream, 0);
 -      }
 -
 +      if (experimental_loose_object(map)) {
 +              /*
 +               * The old experimental format we no longer produce;
 +               * we can still read it.
 +               */
 +              used = unpack_object_header_buffer(map, mapsize, &type, &size);
 +              if (!used || !valid_loose_object_type[type])
 +                      return -1;
 +              map += used;
 +              mapsize -= used;
  
 -      /*
 -       * There used to be a second loose object header format which
 -       * was meant to mimic the in-pack format, allowing for direct
 -       * copy of the object data.  This format turned up not to be
 -       * really worth it and we don't write it any longer.  But we
 -       * can still read it.
 -       */
 -      used = unpack_object_header_buffer(map, mapsize, &type, &size);
 -      if (!used || !valid_loose_object_type[type])
 -              return -1;
 -      map += used;
 -      mapsize -= used;
 +              /* Set up the stream for the rest.. */
 +              stream->next_in = map;
 +              stream->avail_in = mapsize;
 +              git_inflate_init(stream);
  
 -      /* Set up the stream for the rest.. */
 -      stream->next_in = map;
 -      stream->avail_in = mapsize;
 +              /* And generate the fake traditional header */
 +              stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
 +                                               typename(type), size);
 +              return 0;
 +      }
        git_inflate_init(stream);
 -
 -      /* And generate the fake traditional header */
 -      stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
 -                                       typename(type), size);
 -      return 0;
 +      return git_inflate(stream, 0);
  }
  
 -static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
 +static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
  {
        int bytes = strlen(buffer) + 1;
        unsigned char *buf = xmallocz(size);
@@@ -1395,7 -1391,7 +1395,7 @@@ static int parse_sha1_header(const cha
  static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
  {
        int ret;
 -      z_stream stream;
 +      git_zstream stream;
        char hdr[8192];
  
        ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
@@@ -1411,7 -1407,7 +1411,7 @@@ unsigned long get_size_from_delta(struc
  {
        const unsigned char *data;
        unsigned char delta_head[20], *in;
 -      z_stream stream;
 +      git_zstream stream;
        int st;
  
        memset(&stream, 0, sizeof(stream));
@@@ -1533,7 -1529,7 +1533,7 @@@ static int unpack_object_header(struct 
                                unsigned long *sizep)
  {
        unsigned char *base;
 -      unsigned int left;
 +      unsigned long left;
        unsigned long used;
        enum object_type type;
  
@@@ -1646,7 -1642,7 +1646,7 @@@ static void *unpack_compressed_entry(st
                                    unsigned long size)
  {
        int st;
 -      z_stream stream;
 +      git_zstream stream;
        unsigned char *buffer, *in;
  
        buffer = xmallocz(size);
@@@ -2079,7 -2075,7 +2079,7 @@@ static int sha1_loose_object_info(cons
        int status;
        unsigned long mapsize, size;
        void *map;
 -      z_stream stream;
 +      git_zstream stream;
        char hdr[32];
  
        map = map_sha1_file(sha1, &mapsize);
@@@ -2428,7 -2424,7 +2428,7 @@@ static int write_loose_object(const uns
  {
        int fd, ret;
        unsigned char compressed[4096];
 -      z_stream stream;
 +      git_zstream stream;
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
        char *filename;
  
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
 -      deflateInit(&stream, zlib_compression_level);
 +      git_deflate_init(&stream, zlib_compression_level);
        stream.next_out = compressed;
        stream.avail_out = sizeof(compressed);
        git_SHA1_Init(&c);
        /* First header.. */
        stream.next_in = (unsigned char *)hdr;
        stream.avail_in = hdrlen;
 -      while (deflate(&stream, 0) == Z_OK)
 -              /* nothing */;
 +      while (git_deflate(&stream, 0) == Z_OK)
 +              ; /* nothing */
        git_SHA1_Update(&c, hdr, hdrlen);
  
        /* Then the data itself.. */
        stream.avail_in = len;
        do {
                unsigned char *in0 = stream.next_in;
 -              ret = deflate(&stream, Z_FINISH);
 +              ret = git_deflate(&stream, Z_FINISH);
                git_SHA1_Update(&c, in0, stream.next_in - in0);
                if (write_buffer(fd, compressed, stream.next_out - compressed) < 0)
                        die("unable to write sha1 file");
  
        if (ret != Z_STREAM_END)
                die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret);
 -      ret = deflateEnd(&stream);
 +      ret = git_deflate_end_gently(&stream);
        if (ret != Z_OK)
                die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret);
        git_SHA1_Final(parano_sha1, &c);
diff --combined t/t5601-clone.sh
index 501bd3fb6cc9c8795a0ea9570512668abaaa2693,d87214cfbf95d44baa9683b6b05a78743ee78bfb..e8103144bb026afb12f5b058b9ec399b70abebbd
@@@ -202,13 -202,32 +202,36 @@@ test_expect_success 'clone separate git
        test_cmp expected dst/.git
  '
  
 +test_expect_success 'clone from .git file' '
 +      git clone dst/.git dst2
 +'
 +
  test_expect_success 'clone separate gitdir where target already exists' '
        rm -rf dst &&
        test_must_fail git clone --separate-git-dir realgitdir src dst
  '
  
+ test_expect_success 'clone --reference from original' '
+       git clone --shared --bare src src-1 &&
+       git clone --bare src src-2 &&
+       git clone --reference=src-2 --bare src-1 target-8 &&
+       grep /src-2/ target-8/objects/info/alternates
+ '
+ test_expect_success 'clone with more than one --reference' '
+       git clone --bare src src-3 &&
+       git clone --bare src src-4 &&
+       git clone --reference=src-3 --reference=src-4 src target-9 &&
+       grep /src-3/ target-9/.git/objects/info/alternates &&
+       grep /src-4/ target-9/.git/objects/info/alternates
+ '
+ test_expect_success 'clone from original with relative alternate' '
+       mkdir nest &&
+       git clone --bare src nest/src-5 &&
+       echo ../../../src/.git/objects >nest/src-5/objects/info/alternates &&
+       git clone --bare nest/src-5 target-10 &&
+       grep /src/\\.git/objects target-10/objects/info/alternates
+ '
  test_done