Merge branch 'js/clone-dissociate'
authorJunio C Hamano <gitster@pobox.com>
Thu, 15 Oct 2015 22:43:49 +0000 (15:43 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 15 Oct 2015 22:43:49 +0000 (15:43 -0700)
"git clone --dissociate" runs a big "git repack" process at the
end, and it helps to close file descriptors that are open on the
packs and their idx files before doing so on filesystems that
cannot remove a file that is still open.

* js/clone-dissociate:
clone --dissociate: avoid locking pack files
sha1_file.c: add a function to release all packs
sha1_file: consolidate code to close a pack's file descriptor
t5700: demonstrate a Windows file locking issue with `git clone --dissociate`

1  2 
builtin/clone.c
cache.h
sha1_file.c
t/t5700-clone-reference.sh
diff --combined builtin/clone.c
index 3e14491a340cdce70308b31cdf96612344644c15,6633d8703855377418237ac98e226dcfc28ee0da..9eaecd9a7cdb9ad6726626c96edd05d2d9305453
@@@ -99,140 -99,82 +99,140 @@@ static const char *argv_submodule[] = 
        "submodule", "update", "--init", "--recursive", NULL
  };
  
 -static char *get_repo_path(const char *repo, int *is_bundle)
 +static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
  {
        static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
        static char *bundle_suffix[] = { ".bundle", "" };
 +      size_t baselen = path->len;
        struct stat st;
        int i;
  
        for (i = 0; i < ARRAY_SIZE(suffix); i++) {
 -              const char *path;
 -              path = mkpath("%s%s", repo, suffix[i]);
 -              if (stat(path, &st))
 +              strbuf_setlen(path, baselen);
 +              strbuf_addstr(path, suffix[i]);
 +              if (stat(path->buf, &st))
                        continue;
 -              if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
 +              if (S_ISDIR(st.st_mode) && is_git_directory(path->buf)) {
                        *is_bundle = 0;
 -                      return xstrdup(absolute_path(path));
 +                      return path->buf;
                } 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);
 +                      const char *dst;
 +                      int len, fd = open(path->buf, 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) {
 +                      dst = read_gitfile(path->buf);
 +                      if (dst) {
                                *is_bundle = 0;
 -                              return xstrdup(absolute_path(path));
 +                              return dst;
                        }
                }
        }
  
        for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
 -              const char *path;
 -              path = mkpath("%s%s", repo, bundle_suffix[i]);
 -              if (!stat(path, &st) && S_ISREG(st.st_mode)) {
 +              strbuf_setlen(path, baselen);
 +              strbuf_addstr(path, bundle_suffix[i]);
 +              if (!stat(path->buf, &st) && S_ISREG(st.st_mode)) {
                        *is_bundle = 1;
 -                      return xstrdup(absolute_path(path));
 +                      return path->buf;
                }
        }
  
        return NULL;
  }
  
 +static char *get_repo_path(const char *repo, int *is_bundle)
 +{
 +      struct strbuf path = STRBUF_INIT;
 +      const char *raw;
 +      char *canon;
 +
 +      strbuf_addstr(&path, repo);
 +      raw = get_repo_path_1(&path, is_bundle);
 +      canon = raw ? xstrdup(absolute_path(raw)) : NULL;
 +      strbuf_release(&path);
 +      return canon;
 +}
 +
  static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
  {
 -      const char *end = repo + strlen(repo), *start;
 +      const char *end = repo + strlen(repo), *start, *ptr;
        size_t len;
        char *dir;
  
 +      /*
 +       * Skip scheme.
 +       */
 +      start = strstr(repo, "://");
 +      if (start == NULL)
 +              start = repo;
 +      else
 +              start += 3;
 +
 +      /*
 +       * Skip authentication data. The stripping does happen
 +       * greedily, such that we strip up to the last '@' inside
 +       * the host part.
 +       */
 +      for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
 +              if (*ptr == '@')
 +                      start = ptr + 1;
 +      }
 +
        /*
         * Strip trailing spaces, slashes and /.git
         */
 -      while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
 +      while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
                end--;
 -      if (end - repo > 5 && is_dir_sep(end[-5]) &&
 +      if (end - start > 5 && is_dir_sep(end[-5]) &&
            !strncmp(end - 4, ".git", 4)) {
                end -= 5;
 -              while (repo < end && is_dir_sep(end[-1]))
 +              while (start < end && is_dir_sep(end[-1]))
                        end--;
        }
  
        /*
 -       * Find last component, but be prepared that repo could have
 -       * the form  "remote.example.com:foo.git", i.e. no slash
 -       * in the directory part.
 +       * Strip trailing port number if we've got only a
 +       * hostname (that is, there is no dir separator but a
 +       * colon). This check is required such that we do not
 +       * strip URI's like '/foo/bar:2222.git', which should
 +       * result in a dir '2222' being guessed due to backwards
 +       * compatibility.
 +       */
 +      if (memchr(start, '/', end - start) == NULL
 +          && memchr(start, ':', end - start) != NULL) {
 +              ptr = end;
 +              while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':')
 +                      ptr--;
 +              if (start < ptr && ptr[-1] == ':')
 +                      end = ptr - 1;
 +      }
 +
 +      /*
 +       * Find last component. To remain backwards compatible we
 +       * also regard colons as path separators, such that
 +       * cloning a repository 'foo:bar.git' would result in a
 +       * directory 'bar' being guessed.
         */
 -      start = end;
 -      while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
 -              start--;
 +      ptr = end;
 +      while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':')
 +              ptr--;
 +      start = ptr;
  
        /*
         * Strip .{bundle,git}.
         */
 -      strip_suffix(start, is_bundle ? ".bundle" : ".git" , &len);
 +      len = end - start;
 +      strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
 +
 +      if (!len || (len == 1 && *start == '/'))
 +          die("No directory name could be guessed.\n"
 +              "Please specify a directory on the command line");
  
        if (is_bare)
                dir = xstrfmt("%.*s.git", (int)len, start);
@@@ -294,14 -236,9 +294,14 @@@ static int add_one_reference(struct str
                char *ref_git_git = mkpathdup("%s/.git", ref_git);
                free(ref_git);
                ref_git = ref_git_git;
 -      } else if (!is_directory(mkpath("%s/objects", ref_git)))
 +      } else if (!is_directory(mkpath("%s/objects", ref_git))) {
 +              struct strbuf sb = STRBUF_INIT;
 +              if (get_common_dir(&sb, ref_git))
 +                      die(_("reference repository '%s' as a linked checkout is not supported yet."),
 +                          item->string);
                die(_("reference repository '%s' is not a local repository."),
                    item->string);
 +      }
  
        if (!access(mkpath("%s/shallow", ref_git), F_OK))
                die(_("reference repository '%s' is shallow"), item->string);
@@@ -340,17 -277,16 +340,17 @@@ static void copy_alternates(struct strb
        struct strbuf line = STRBUF_INIT;
  
        while (strbuf_getline(&line, in, '\n') != EOF) {
 -              char *abs_path, abs_buf[PATH_MAX];
 +              char *abs_path;
                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);
 +              abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
 +              normalize_path_copy(abs_path, abs_path);
 +              add_to_alternates_file(abs_path);
 +              free(abs_path);
        }
        strbuf_release(&line);
        fclose(in);
@@@ -429,10 -365,8 +429,10 @@@ static void clone_local(const char *src
        } 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);
 +              get_common_dir(&src, src_repo);
 +              get_common_dir(&dest, dest_repo);
 +              strbuf_addstr(&src, "/objects");
 +              strbuf_addstr(&dest, "/objects");
                copy_or_link_directory(&src, &dest, src_repo, src.len);
                strbuf_release(&src);
                strbuf_release(&dest);
@@@ -549,26 -483,16 +549,26 @@@ static void write_remote_refs(const str
  {
        const struct ref *r;
  
 -      lock_packed_refs(LOCK_DIE_ON_ERROR);
 +      struct ref_transaction *t;
 +      struct strbuf err = STRBUF_INIT;
 +
 +      t = ref_transaction_begin(&err);
 +      if (!t)
 +              die("%s", err.buf);
  
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
 -              add_packed_ref(r->peer_ref->name, r->old_sha1);
 +              if (ref_transaction_create(t, r->peer_ref->name, r->old_sha1,
 +                                         0, NULL, &err))
 +                      die("%s", err.buf);
        }
  
 -      if (commit_packed_refs())
 -              die_errno("unable to overwrite old ref-pack file");
 +      if (initial_ref_transaction_commit(t, &err))
 +              die("%s", err.buf);
 +
 +      strbuf_release(&err);
 +      ref_transaction_free(t);
  }
  
  static void write_followtags(const struct ref *refs, const char *msg)
@@@ -1071,8 -995,10 +1071,10 @@@ int cmd_clone(int argc, const char **ar
        transport_unlock_pack(transport);
        transport_disconnect(transport);
  
-       if (option_dissociate)
+       if (option_dissociate) {
+               close_all_packs();
                dissociate_from_references();
+       }
  
        junk_mode = JUNK_LEAVE_REPO;
        err = checkout();
diff --combined cache.h
index 274137ae161dcb3bc4c702fadde179c5c59a8f92,c3a2bb8afff32b029d429b052b1c901dc4776811..44c02600008ff5b2858a0b212b84cd917739cd64
+++ b/cache.h
@@@ -43,14 -43,6 +43,14 @@@ int git_deflate_end_gently(git_zstream 
  int git_deflate(git_zstream *, int flush);
  unsigned long git_deflate_bound(git_zstream *, unsigned long);
  
 +/* The length in bytes and in hex digits of an object name (SHA-1 value). */
 +#define GIT_SHA1_RAWSZ 20
 +#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
 +
 +struct object_id {
 +      unsigned char hash[GIT_SHA1_RAWSZ];
 +};
 +
  #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
  #define DTYPE(de)     ((de)->d_type)
  #else
@@@ -297,11 -289,8 +297,11 @@@ static inline unsigned int canon_mode(u
  #define RESOLVE_UNDO_CHANGED  (1 << 4)
  #define CACHE_TREE_CHANGED    (1 << 5)
  #define SPLIT_INDEX_ORDERED   (1 << 6)
 +#define UNTRACKED_CHANGED     (1 << 7)
  
  struct split_index;
 +struct untracked_cache;
 +
  struct index_state {
        struct cache_entry **cache;
        unsigned int version;
        struct hashmap name_hash;
        struct hashmap dir_hash;
        unsigned char sha1[20];
 +      struct untracked_cache *untracked;
  };
  
  extern struct index_state the_index;
@@@ -382,7 -370,6 +382,7 @@@ static inline enum object_type object_t
  
  /* Double-check local_repo_env below if you add to this list. */
  #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 +#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
  #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
  #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
  #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
  #define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
 +#define GIT_REPLACE_REF_BASE_ENVIRONMENT "GIT_REPLACE_REF_BASE"
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
@@@ -437,28 -423,15 +437,28 @@@ extern int is_inside_git_dir(void)
  extern char *git_work_tree_cfg;
  extern int is_inside_work_tree(void);
  extern const char *get_git_dir(void);
 +extern const char *get_git_common_dir(void);
  extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
  extern int set_git_dir(const char *path);
 +extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
 +extern int get_common_dir(struct strbuf *sb, const char *gitdir);
  extern const char *get_git_namespace(void);
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
 -extern const char *read_gitfile(const char *path);
 +
 +#define READ_GITFILE_ERR_STAT_FAILED 1
 +#define READ_GITFILE_ERR_NOT_A_FILE 2
 +#define READ_GITFILE_ERR_OPEN_FAILED 3
 +#define READ_GITFILE_ERR_READ_FAILED 4
 +#define READ_GITFILE_ERR_INVALID_FORMAT 5
 +#define READ_GITFILE_ERR_NO_PATH 6
 +#define READ_GITFILE_ERR_NOT_A_REPO 7
 +#define READ_GITFILE_ERR_TOO_LARGE 8
 +extern const char *read_gitfile_gently(const char *path, int *return_error_code);
 +#define read_gitfile(path) read_gitfile_gently((path), NULL)
  extern const char *resolve_gitdir(const char *suspect);
  extern void set_git_work_tree(const char *tree);
  
@@@ -579,8 -552,6 +579,8 @@@ extern void fill_stat_data(struct stat_
   * INODE_CHANGED, and DATA_CHANGED.
   */
  extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +extern int match_stat_data_racy(const struct index_state *istate,
 +                              const struct stat_data *sd, struct stat *st);
  
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
@@@ -597,6 -568,8 +597,6 @@@ extern void update_index_if_able(struc
  extern int hold_locked_index(struct lock_file *, int);
  extern void set_alternate_index_output(const char *);
  
 -extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);
 -
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
@@@ -632,7 -605,6 +632,7 @@@ extern unsigned long pack_size_limit_cf
   * been sought but there were none.
   */
  extern int check_replace_refs;
 +extern char *git_replace_ref_base;
  
  extern int fsync_object_files;
  extern int core_preload_index;
@@@ -640,7 -612,6 +640,7 @@@ extern int core_apply_sparse_checkout
  extern int precomposed_unicode;
  extern int protect_hfs;
  extern int protect_ntfs;
 +extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
  
  /*
   * Include broken refs in all ref iterations, which will
@@@ -709,59 -680,21 +709,59 @@@ extern int check_repository_format(void
  #define DATA_CHANGED    0x0020
  #define TYPE_CHANGED    0x0040
  
 +/*
 + * Return a statically allocated filename, either generically (mkpath), in
 + * the repository directory (git_path), or in a submodule's repository
 + * directory (git_path_submodule). In all cases, note that the result
 + * may be overwritten by another call to _any_ of the functions. Consider
 + * using the safer "dup" or "strbuf" formats below (in some cases, the
 + * unsafe versions have already been removed).
 + */
 +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +
  extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 -extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
 +extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 +      __attribute__((format (printf, 2, 3)));
 +extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
 +                                    const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
  extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
  extern char *mkpathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
 -
 -/* Return a statically allocated filename matching the sha1 signature */
 -extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern char *git_path_submodule(const char *path, const char *fmt, ...)
 +extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
  
 +extern void report_linked_checkout_garbage(void);
 +
 +/*
 + * You can define a static memoized git path like:
 + *
 + *    static GIT_PATH_FUNC(git_path_foo, "FOO");
 + *
 + * or use one of the global ones below.
 + */
 +#define GIT_PATH_FUNC(func, filename) \
 +      const char *func(void) \
 +      { \
 +              static char *ret; \
 +              if (!ret) \
 +                      ret = git_pathdup(filename); \
 +              return ret; \
 +      }
 +
 +const char *git_path_cherry_pick_head(void);
 +const char *git_path_revert_head(void);
 +const char *git_path_squash_msg(void);
 +const char *git_path_merge_msg(void);
 +const char *git_path_merge_rr(void);
 +const char *git_path_merge_mode(void);
 +const char *git_path_merge_head(void);
 +const char *git_path_fetch_head(void);
 +const char *git_path_shallow(void);
 +
  /*
   * Return the name of the file in the local object database that would
   * be used to store a loose object with the specified sha1.  The
@@@ -785,13 -718,13 +785,13 @@@ extern char *sha1_pack_name(const unsig
  extern char *sha1_pack_index_name(const unsigned char *sha1);
  
  extern const char *find_unique_abbrev(const unsigned char *sha1, int);
 -extern const unsigned char null_sha1[20];
 +extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
  {
        int i;
  
 -      for (i = 0; i < 20; i++, sha1++, sha2++) {
 +      for (i = 0; i < GIT_SHA1_RAWSZ; i++, sha1++, sha2++) {
                if (*sha1 != *sha2)
                        return *sha1 - *sha2;
        }
        return 0;
  }
  
 +static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
 +{
 +      return hashcmp(oid1->hash, oid2->hash);
 +}
 +
  static inline int is_null_sha1(const unsigned char *sha1)
  {
        return !hashcmp(sha1, null_sha1);
  }
  
 +static inline int is_null_oid(const struct object_id *oid)
 +{
 +      return !hashcmp(oid->hash, null_sha1);
 +}
 +
  static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
  {
 -      memcpy(sha_dst, sha_src, 20);
 +      memcpy(sha_dst, sha_src, GIT_SHA1_RAWSZ);
 +}
 +
 +static inline void oidcpy(struct object_id *dst, const struct object_id *src)
 +{
 +      hashcpy(dst->hash, src->hash);
  }
 +
  static inline void hashclr(unsigned char *hash)
  {
 -      memset(hash, 0, 20);
 +      memset(hash, 0, GIT_SHA1_RAWSZ);
  }
  
 +static inline void oidclr(struct object_id *oid)
 +{
 +      hashclr(oid->hash);
 +}
 +
 +
  #define EMPTY_TREE_SHA1_HEX \
        "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
  #define EMPTY_TREE_SHA1_BIN_LITERAL \
@@@ -933,7 -844,6 +933,7 @@@ extern char *xdg_config_home(const cha
  
  /* object replacement */
  #define LOOKUP_REPLACE_OBJECT 1
 +#define LOOKUP_UNKNOWN_OBJECT 2
  extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
  static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
  {
@@@ -983,7 -893,7 +983,7 @@@ extern int do_check_packed_object_crc
  
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
 -extern int move_temp_to_file(const char *tmpfile, const char *filename);
 +extern int finalize_object_file(const char *tmpfile, const char *filename);
  
  extern int has_sha1_pack(const unsigned char *sha1);
  
@@@ -1028,21 -938,15 +1028,21 @@@ struct object_context 
        unsigned char tree[20];
        char path[PATH_MAX];
        unsigned mode;
 +      /*
 +       * symlink_path is only used by get_tree_entry_follow_symlinks,
 +       * and only for symlinks that point outside the repository.
 +       */
 +      struct strbuf symlink_path;
  };
  
 -#define GET_SHA1_QUIETLY        01
 -#define GET_SHA1_COMMIT         02
 -#define GET_SHA1_COMMITTISH     04
 -#define GET_SHA1_TREE          010
 -#define GET_SHA1_TREEISH       020
 -#define GET_SHA1_BLOB        040
 -#define GET_SHA1_ONLY_TO_DIE 04000
 +#define GET_SHA1_QUIETLY           01
 +#define GET_SHA1_COMMIT            02
 +#define GET_SHA1_COMMITTISH        04
 +#define GET_SHA1_TREE             010
 +#define GET_SHA1_TREEISH          020
 +#define GET_SHA1_BLOB             040
 +#define GET_SHA1_FOLLOW_SYMLINKS 0100
 +#define GET_SHA1_ONLY_TO_DIE    04000
  
  extern int get_sha1(const char *str, unsigned char *sha1);
  extern int get_sha1_commit(const char *str, unsigned char *sha1);
@@@ -1064,14 -968,78 +1064,14 @@@ extern int for_each_abbrev(const char *
   * null-terminated string.
   */
  extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 +extern int get_oid_hex(const char *hex, struct object_id *sha1);
  
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
 -extern int read_ref_full(const char *refname, int resolve_flags,
 -                       unsigned char *sha1, int *flags);
 -extern int read_ref(const char *refname, unsigned char *sha1);
 +extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
  
 -/*
 - * Resolve a reference, recursively following symbolic refererences.
 - *
 - * Store the referred-to object's name in sha1 and return the name of
 - * the non-symbolic reference that ultimately pointed at it.  The
 - * return value, if not NULL, is a pointer into either a static buffer
 - * or the input ref.
 - *
 - * If the reference cannot be resolved to an object, the behavior
 - * depends on the RESOLVE_REF_READING flag:
 - *
 - * - If RESOLVE_REF_READING is set, return NULL.
 - *
 - * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
 - *   the last reference name in the chain, which will either be a non-symbolic
 - *   reference or an undefined reference.  If this is a prelude to
 - *   "writing" to the ref, the return value is the name of the ref
 - *   that will actually be created or changed.
 - *
 - * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
 - * level of symbolic reference.  The value stored in sha1 for a symbolic
 - * reference will always be null_sha1 in this case, and the return
 - * value is the reference that the symref refers to directly.
 - *
 - * If flags is non-NULL, set the value that it points to the
 - * combination of REF_ISPACKED (if the reference was found among the
 - * packed references), REF_ISSYMREF (if the initial reference was a
 - * symbolic reference), REF_BAD_NAME (if the reference name is ill
 - * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN
 - * (if the ref is malformed or has a bad name). See refs.h for more detail
 - * on each flag.
 - *
 - * If ref is not a properly-formatted, normalized reference, return
 - * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
 - * give up and return NULL.
 - *
 - * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
 - * name is invalid according to git-check-ref-format(1).  If the name
 - * is bad then the value stored in sha1 will be null_sha1 and the two
 - * flags REF_ISBROKEN and REF_BAD_NAME will be set.
 - *
 - * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
 - * directory and do not consist of all caps and underscores cannot be
 - * resolved. The function returns NULL for such ref names.
 - * Caps and underscores refers to the special refs, such as HEAD,
 - * FETCH_HEAD and friends, that all live outside of the refs/ directory.
 - */
 -#define RESOLVE_REF_READING 0x01
 -#define RESOLVE_REF_NO_RECURSE 0x02
 -#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
 -extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -
 -extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 -extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
 -/*
 - * Return true iff abbrev_name is a possible abbreviation for
 - * full_name according to the rules defined by ref_rev_parse_rules in
 - * refs.c.
 - */
 -extern int refname_match(const char *abbrev_name, const char *full_name);
 -
 -extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
@@@ -1087,30 -1055,18 +1087,30 @@@ extern void *read_object_with_reference
  extern struct object *peel_to_type(const char *name, int namelen,
                                   struct object *o, enum object_type);
  
 -enum date_mode {
 -      DATE_NORMAL = 0,
 -      DATE_RELATIVE,
 -      DATE_SHORT,
 -      DATE_LOCAL,
 -      DATE_ISO8601,
 -      DATE_ISO8601_STRICT,
 -      DATE_RFC2822,
 -      DATE_RAW
 +struct date_mode {
 +      enum date_mode_type {
 +              DATE_NORMAL = 0,
 +              DATE_RELATIVE,
 +              DATE_SHORT,
 +              DATE_ISO8601,
 +              DATE_ISO8601_STRICT,
 +              DATE_RFC2822,
 +              DATE_STRFTIME,
 +              DATE_RAW
 +      } type;
 +      const char *strftime_fmt;
 +      int local;
  };
  
 -const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 +/*
 + * Convenience helper for passing a constant type, like:
 + *
 + *   show_date(t, tz, DATE_MODE(NORMAL));
 + */
 +#define DATE_MODE(t) date_mode_from_type(DATE_##t)
 +struct date_mode *date_mode_from_type(enum date_mode_type type);
 +
 +const char *show_date(unsigned long time, int timezone, const struct date_mode *mode);
  void show_date_relative(unsigned long time, int tz, const struct timeval *now,
                        struct strbuf *timebuf);
  int parse_date(const char *date, struct strbuf *out);
@@@ -1120,7 -1076,7 +1120,7 @@@ void datestamp(struct strbuf *out)
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
  unsigned long approxidate_relative(const char *date, const struct timeval *now);
 -enum date_mode parse_date_format(const char *format);
 +void parse_date_format(const char *format, struct date_mode *mode);
  int date_overflows(unsigned long date);
  
  #define IDENT_STRICT         1
@@@ -1157,8 -1113,7 +1157,8 @@@ extern int split_ident_line(struct iden
   * the ident_split. It will also sanity-check the values and produce
   * a well-known sentinel date if they appear bogus.
   */
 -const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
 +const char *show_ident_date(const struct ident_split *id,
 +                          const struct date_mode *mode);
  
  /*
   * Compare split idents for equality or strict ordering. Note that we
@@@ -1276,6 -1231,7 +1276,7 @@@ extern void close_pack_index(struct pac
  
  extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
  extern void close_pack_windows(struct packed_git *);
+ extern void close_all_packs(void);
  extern void unuse_pack(struct pack_window **);
  extern void free_pack_by_name(const char *);
  extern void clear_delta_base_cache(void);
@@@ -1368,7 -1324,6 +1369,7 @@@ struct object_info 
        unsigned long *sizep;
        unsigned long *disk_sizep;
        unsigned char *delta_base_sha1;
 +      struct strbuf *typename;
  
        /* Response */
        enum {
@@@ -1430,7 -1385,6 +1431,7 @@@ extern int git_config_with_options(conf
                                   int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
 +extern int git_parse_maybe_bool(const char *);
  extern int git_config_int(const char *, const char *);
  extern int64_t git_config_int64(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
@@@ -1442,7 -1396,6 +1443,7 @@@ extern int git_config_pathname(const ch
  extern int git_config_set_in_file(const char *, const char *, const char *);
  extern int git_config_set(const char *, const char *);
  extern int git_config_parse_key(const char *, char **, int *);
 +extern int git_config_key_is_valid(const char *key);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
@@@ -1559,13 -1512,9 +1560,13 @@@ extern const char *git_mailmap_blob
  extern void maybe_flush_or_die(FILE *, const char *);
  __attribute__((format (printf, 2, 3)))
  extern void fprintf_or_die(FILE *, const char *fmt, ...);
 +
 +#define COPY_READ_ERROR (-2)
 +#define COPY_WRITE_ERROR (-3)
  extern int copy_fd(int ifd, int ofd);
  extern int copy_file(const char *dst, const char *src, int mode);
  extern int copy_file_with_time(const char *dst, const char *src, int mode);
 +
  extern void write_or_die(int fd, const void *buf, size_t count);
  extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
  extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
@@@ -1580,9 -1529,6 +1581,9 @@@ static inline ssize_t write_str_in_full
        return write_in_full(fd, str, strlen(str));
  }
  
 +extern int write_file(const char *path, const char *fmt, ...);
 +extern int write_file_gently(const char *path, const char *fmt, ...);
 +
  /* pager.c */
  extern void setup_pager(void);
  extern const char *pager_program;
@@@ -1705,6 -1651,5 +1706,6 @@@ int stat_validity_check(struct stat_val
  void stat_validity_update(struct stat_validity *sv, int fd);
  
  int versioncmp(const char *s1, const char *s2);
 +void sleep_millisec(int millisec);
  
  #endif /* CACHE_H */
diff --combined sha1_file.c
index d295a3225a1e083708812f99746773d0ec8fdff4,d215e0c9bf0fdf0a7a28233ddccd87ba0c51fd68..ca699d7beb067bff83ea15f0a0e724916f33549b
@@@ -401,46 -401,13 +401,46 @@@ void read_info_alternates(const char * 
  void add_to_alternates_file(const char *reference)
  {
        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\n", reference);
 -      write_or_die(fd, alt, strlen(alt));
 -      if (commit_lock_file(lock))
 -              die("could not close alternates file");
 -      if (alt_odb_tail)
 -              link_alt_odb_entries(alt, strlen(alt), '\n', NULL, 0);
 +      char *alts = git_pathdup("objects/info/alternates");
 +      FILE *in, *out;
 +
 +      hold_lock_file_for_update(lock, alts, LOCK_DIE_ON_ERROR);
 +      out = fdopen_lock_file(lock, "w");
 +      if (!out)
 +              die_errno("unable to fdopen alternates lockfile");
 +
 +      in = fopen(alts, "r");
 +      if (in) {
 +              struct strbuf line = STRBUF_INIT;
 +              int found = 0;
 +
 +              while (strbuf_getline(&line, in, '\n') != EOF) {
 +                      if (!strcmp(reference, line.buf)) {
 +                              found = 1;
 +                              break;
 +                      }
 +                      fprintf_or_die(out, "%s\n", line.buf);
 +              }
 +
 +              strbuf_release(&line);
 +              fclose(in);
 +
 +              if (found) {
 +                      rollback_lock_file(lock);
 +                      lock = NULL;
 +              }
 +      }
 +      else if (errno != ENOENT)
 +              die_errno("unable to read alternates file");
 +
 +      if (lock) {
 +              fprintf_or_die(out, "%s\n", reference);
 +              if (commit_lock_file(lock))
 +                      die_errno("unable to move new alternates file into place");
 +              if (alt_odb_tail)
 +                      link_alt_odb_entries(reference, strlen(reference), '\n', NULL, 0);
 +      }
 +      free(alts);
  }
  
  int foreach_alt_odb(alt_odb_fn fn, void *cb)
@@@ -786,6 -753,37 +786,37 @@@ void close_pack_windows(struct packed_g
        }
  }
  
+ static int close_pack_fd(struct packed_git *p)
+ {
+       if (p->pack_fd < 0)
+               return 0;
+       close(p->pack_fd);
+       pack_open_fds--;
+       p->pack_fd = -1;
+       return 1;
+ }
+ static void close_pack(struct packed_git *p)
+ {
+       close_pack_windows(p);
+       close_pack_fd(p);
+       close_pack_index(p);
+ }
+ void close_all_packs(void)
+ {
+       struct packed_git *p;
+       for (p = packed_git; p; p = p->next)
+               if (p->do_not_close)
+                       die("BUG! Want to close pack marked 'do-not-close'");
+               else
+                       close_pack(p);
+ }
  /*
   * The LRU pack is the one with the oldest MRU window, preferring packs
   * with no used windows, or the oldest mtime if it has no windows allocated.
@@@ -853,12 -851,8 +884,8 @@@ static int close_one_pack(void
                find_lru_pack(p, &lru_p, &mru_w, &accept_windows_inuse);
        }
  
-       if (lru_p) {
-               close(lru_p->pack_fd);
-               pack_open_fds--;
-               lru_p->pack_fd = -1;
-               return 1;
-       }
+       if (lru_p)
+               return close_pack_fd(lru_p);
  
        return 0;
  }
@@@ -898,12 -892,7 +925,7 @@@ void free_pack_by_name(const char *pack
                p = *pp;
                if (strcmp(pack_name, p->pack_name) == 0) {
                        clear_delta_base_cache();
-                       close_pack_windows(p);
-                       if (p->pack_fd != -1) {
-                               close(p->pack_fd);
-                               pack_open_fds--;
-                       }
-                       close_pack_index(p);
+                       close_pack(p);
                        free(p->bad_object_sha1);
                        *pp = p->next;
                        if (last_found_pack == p)
@@@ -1037,11 -1026,7 +1059,7 @@@ static int open_packed_git(struct packe
  {
        if (!open_packed_git_1(p))
                return 0;
-       if (p->pack_fd != -1) {
-               close(p->pack_fd);
-               pack_open_fds--;
-               p->pack_fd = -1;
-       }
+       close_pack_fd(p);
        return -1;
  }
  
@@@ -1107,11 -1092,8 +1125,8 @@@ unsigned char *use_pack(struct packed_g
                                        p->pack_name,
                                        strerror(errno));
                        if (!win->offset && win->len == p->pack_size
-                               && !p->do_not_close) {
-                               close(p->pack_fd);
-                               pack_open_fds--;
-                               p->pack_fd = -1;
-                       }
+                               && !p->do_not_close)
+                               close_pack_fd(p);
                        pack_mmap_calls++;
                        pack_open_windows++;
                        if (pack_mapped > peak_pack_mapped)
@@@ -1491,10 -1473,7 +1506,10 @@@ int git_open_noatime(const char *name
        static int sha1_file_open_flag = O_NOATIME;
  
        for (;;) {
 -              int fd = open(name, O_RDONLY | sha1_file_open_flag);
 +              int fd;
 +
 +              errno = 0;
 +              fd = open(name, O_RDONLY | sha1_file_open_flag);
                if (fd >= 0)
                        return fd;
  
@@@ -1612,40 -1591,6 +1627,40 @@@ int unpack_sha1_header(git_zstream *str
        return git_inflate(stream, 0);
  }
  
 +static int unpack_sha1_header_to_strbuf(git_zstream *stream, unsigned char *map,
 +                                      unsigned long mapsize, void *buffer,
 +                                      unsigned long bufsiz, struct strbuf *header)
 +{
 +      int status;
 +
 +      status = unpack_sha1_header(stream, map, mapsize, buffer, bufsiz);
 +
 +      /*
 +       * Check if entire header is unpacked in the first iteration.
 +       */
 +      if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
 +              return 0;
 +
 +      /*
 +       * buffer[0..bufsiz] was not large enough.  Copy the partial
 +       * result out to header, and then append the result of further
 +       * reading the stream.
 +       */
 +      strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer);
 +      stream->next_out = buffer;
 +      stream->avail_out = bufsiz;
 +
 +      do {
 +              status = git_inflate(stream, 0);
 +              strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer);
 +              if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
 +                      return 0;
 +              stream->next_out = buffer;
 +              stream->avail_out = bufsiz;
 +      } while (status != Z_STREAM_END);
 +      return -1;
 +}
 +
  static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
  {
        int bytes = strlen(buffer) + 1;
   * too permissive for what we want to check. So do an anal
   * object header parse by hand.
   */
 -int parse_sha1_header(const char *hdr, unsigned long *sizep)
 +static int parse_sha1_header_extended(const char *hdr, struct object_info *oi,
 +                             unsigned int flags)
  {
 -      char type[10];
 -      int i;
 +      const char *type_buf = hdr;
        unsigned long size;
 +      int type, type_len = 0;
  
        /*
 -       * The type can be at most ten bytes (including the
 -       * terminating '\0' that we add), and is followed by
 +       * The type can be of any size but is followed by
         * a space.
         */
 -      i = 0;
        for (;;) {
                char c = *hdr++;
                if (c == ' ')
                        break;
 -              type[i++] = c;
 -              if (i >= sizeof(type))
 -                      return -1;
 +              type_len++;
        }
 -      type[i] = 0;
 +
 +      type = type_from_string_gently(type_buf, type_len, 1);
 +      if (oi->typename)
 +              strbuf_add(oi->typename, type_buf, type_len);
 +      /*
 +       * Set type to 0 if its an unknown object and
 +       * we're obtaining the type using '--allow-unkown-type'
 +       * option.
 +       */
 +      if ((flags & LOOKUP_UNKNOWN_OBJECT) && (type < 0))
 +              type = 0;
 +      else if (type < 0)
 +              die("invalid object type");
 +      if (oi->typep)
 +              *oi->typep = type;
  
        /*
         * The length must follow immediately, and be in canonical
                        size = size * 10 + c;
                }
        }
 -      *sizep = size;
 +
 +      if (oi->sizep)
 +              *oi->sizep = size;
  
        /*
         * The length must be followed by a zero byte
         */
 -      return *hdr ? -1 : type_from_string(type);
 +      return *hdr ? -1 : type;
 +}
 +
 +int parse_sha1_header(const char *hdr, unsigned long *sizep)
 +{
 +      struct object_info oi;
 +
 +      oi.sizep = sizep;
 +      oi.typename = NULL;
 +      oi.typep = NULL;
 +      return parse_sha1_header_extended(hdr, &oi, LOOKUP_REPLACE_OBJECT);
  }
  
  static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
@@@ -2627,15 -2549,13 +2642,15 @@@ struct packed_git *find_sha1_pack(cons
  }
  
  static int sha1_loose_object_info(const unsigned char *sha1,
 -                                struct object_info *oi)
 +                                struct object_info *oi,
 +                                int flags)
  {
 -      int status;
 -      unsigned long mapsize, size;
 +      int status = 0;
 +      unsigned long mapsize;
        void *map;
        git_zstream stream;
        char hdr[32];
 +      struct strbuf hdrbuf = STRBUF_INIT;
  
        if (oi->delta_base_sha1)
                hashclr(oi->delta_base_sha1);
         * return value implicitly indicates whether the
         * object even exists.
         */
 -      if (!oi->typep && !oi->sizep) {
 +      if (!oi->typep && !oi->typename && !oi->sizep) {
                struct stat st;
                if (stat_sha1_file(sha1, &st) < 0)
                        return -1;
                return -1;
        if (oi->disk_sizep)
                *oi->disk_sizep = mapsize;
 -      if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
 +      if ((flags & LOOKUP_UNKNOWN_OBJECT)) {
 +              if (unpack_sha1_header_to_strbuf(&stream, map, mapsize, hdr, sizeof(hdr), &hdrbuf) < 0)
 +                      status = error("unable to unpack %s header with --allow-unknown-type",
 +                                     sha1_to_hex(sha1));
 +      } else if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
                status = error("unable to unpack %s header",
                               sha1_to_hex(sha1));
 -      else if ((status = parse_sha1_header(hdr, &size)) < 0)
 +      if (status < 0)
 +              ; /* Do nothing */
 +      else if (hdrbuf.len) {
 +              if ((status = parse_sha1_header_extended(hdrbuf.buf, oi, flags)) < 0)
 +                      status = error("unable to parse %s header with --allow-unknown-type",
 +                                     sha1_to_hex(sha1));
 +      } else if ((status = parse_sha1_header_extended(hdr, oi, flags)) < 0)
                status = error("unable to parse %s header", sha1_to_hex(sha1));
 -      else if (oi->sizep)
 -              *oi->sizep = size;
        git_inflate_end(&stream);
        munmap(map, mapsize);
 -      if (oi->typep)
 +      if (status && oi->typep)
                *oi->typep = status;
 +      strbuf_release(&hdrbuf);
        return 0;
  }
  
@@@ -2690,7 -2601,6 +2705,7 @@@ int sha1_object_info_extended(const uns
        struct cached_object *co;
        struct pack_entry e;
        int rtype;
 +      enum object_type real_type;
        const unsigned char *real = lookup_replace_object_extended(sha1, flags);
  
        co = find_cached_object(real);
                        *(oi->disk_sizep) = 0;
                if (oi->delta_base_sha1)
                        hashclr(oi->delta_base_sha1);
 +              if (oi->typename)
 +                      strbuf_addstr(oi->typename, typename(co->type));
                oi->whence = OI_CACHED;
                return 0;
        }
  
        if (!find_pack_entry(real, &e)) {
                /* Most likely it's a loose object. */
 -              if (!sha1_loose_object_info(real, oi)) {
 +              if (!sha1_loose_object_info(real, oi, flags)) {
                        oi->whence = OI_LOOSE;
                        return 0;
                }
                        return -1;
        }
  
 +      /*
 +       * packed_object_info() does not follow the delta chain to
 +       * find out the real type, unless it is given oi->typep.
 +       */
 +      if (oi->typename && !oi->typep)
 +              oi->typep = &real_type;
 +
        rtype = packed_object_info(e.p, e.offset, oi);
        if (rtype < 0) {
                mark_bad_packed_object(e.p, real);
 +              if (oi->typep == &real_type)
 +                      oi->typep = NULL;
                return sha1_object_info_extended(real, oi, 0);
        } else if (in_delta_base_cache(e.p, e.offset)) {
                oi->whence = OI_DBCACHED;
                oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
                                         rtype == OBJ_OFS_DELTA);
        }
 +      if (oi->typename)
 +              strbuf_addstr(oi->typename, typename(*oi->typep));
 +      if (oi->typep == &real_type)
 +              oi->typep = NULL;
  
        return 0;
  }
@@@ -2941,8 -2836,11 +2956,8 @@@ static void write_sha1_file_prepare(con
  
  /*
   * Move the just written object into its final resting place.
 - * NEEDSWORK: this should be renamed to finalize_temp_file() as
 - * "moving" is only a part of what it does, when no patch between
 - * master to pu changes the call sites of this function.
   */
 -int move_temp_to_file(const char *tmpfile, const char *filename)
 +int finalize_object_file(const char *tmpfile, const char *filename)
  {
        int ret = 0;
  
@@@ -3115,7 -3013,7 +3130,7 @@@ static int write_loose_object(const uns
                                tmp_file, strerror(errno));
        }
  
 -      return move_temp_to_file(tmp_file, filename);
 +      return finalize_object_file(tmp_file, filename);
  }
  
  static int freshen_loose_object(const unsigned char *sha1)
@@@ -3613,19 -3511,14 +3628,19 @@@ int for_each_packed_object(each_packed_
  {
        struct packed_git *p;
        int r = 0;
 +      int pack_errors = 0;
  
        prepare_packed_git();
        for (p = packed_git; p; p = p->next) {
                if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
                        continue;
 +              if (open_pack_index(p)) {
 +                      pack_errors = 1;
 +                      continue;
 +              }
                r = for_each_object_in_pack(p, cb, data);
                if (r)
                        break;
        }
 -      return r;
 +      return r ? r : pack_errors;
  }
index ef1779f5cae5642c65c83a9e18b55488005ccf12,057da59ae0fdcbfb3cebebf6f2a3daab37052f3f..2250ef4fe245bda17328d46844a4caf49fbde8d9
@@@ -10,51 -10,49 +10,51 @@@ base_dir=`pwd
  
  U=$base_dir/UPLOAD_LOG
  
 -test_expect_success 'preparing first repository' \
 -'test_create_repo A && cd A &&
 -echo first > file1 &&
 -git add file1 &&
 -git commit -m initial'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'preparing second repository' \
 -'git clone A B && cd B &&
 -echo second > file2 &&
 -git add file2 &&
 -git commit -m addition &&
 -git repack -a -d &&
 -git prune'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'cloning with reference (-l -s)' \
 -'git clone -l -s --reference B A C'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'existence of info/alternates' \
 -'test_line_count = 2 C/.git/objects/info/alternates'
 -
 -cd "$base_dir"
 +# create a commit in repo $1 with name $2
 +commit_in () {
 +      (
 +              cd "$1" &&
 +              echo "$2" >"$2" &&
 +              git add "$2" &&
 +              git commit -m "$2"
 +      )
 +}
 +
 +# check that there are $2 loose objects in repo $1
 +test_objcount () {
 +      echo "$2" >expect &&
 +      git -C "$1" count-objects >actual.raw &&
 +      cut -d' ' -f1 <actual.raw >actual &&
 +      test_cmp expect actual
 +}
 +
 +test_expect_success 'preparing first repository' '
 +      test_create_repo A &&
 +      commit_in A file1
 +'
  
 -test_expect_success 'pulling from reference' \
 -'cd C &&
 -git pull ../B master'
 +test_expect_success 'preparing second repository' '
 +      git clone A B &&
 +      commit_in B file2 &&
 +      git -C B repack -ad &&
 +      git -C B prune
 +'
  
 -cd "$base_dir"
 +test_expect_success 'cloning with reference (-l -s)' '
 +      git clone -l -s --reference B A C
 +'
  
 -test_expect_success 'that reference gets used' \
 -'cd C &&
 -echo "0 objects, 0 kilobytes" > expected &&
 -git count-objects > current &&
 -test_cmp expected current'
 +test_expect_success 'existence of info/alternates' '
 +      test_line_count = 2 C/.git/objects/info/alternates
 +'
  
 -cd "$base_dir"
 +test_expect_success 'pulling from reference' '
 +      git -C C pull ../B master
 +'
  
 -rm -f "$U.D"
 +test_expect_success 'that reference gets used' '
 +      test_objcount C 0
 +'
  
  test_expect_success 'cloning with reference (no -l -s)' '
        GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D
@@@ -65,69 -63,95 +65,69 @@@ test_expect_success 'fetched no objects
        ! grep " want" "$U.D"
  '
  
 -cd "$base_dir"
 -
 -test_expect_success 'existence of info/alternates' \
 -'test_line_count = 1 D/.git/objects/info/alternates'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'pulling from reference' \
 -'cd D && git pull ../B master'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'that reference gets used' \
 -'cd D && echo "0 objects, 0 kilobytes" > expected &&
 -git count-objects > current &&
 -test_cmp expected current'
 -
 -cd "$base_dir"
 +test_expect_success 'existence of info/alternates' '
 +      test_line_count = 1 D/.git/objects/info/alternates
 +'
  
 -test_expect_success 'updating origin' \
 -'cd A &&
 -echo third > file3 &&
 -git add file3 &&
 -git commit -m update &&
 -git repack -a -d &&
 -git prune'
 +test_expect_success 'pulling from reference' '
 +      git -C D pull ../B master
 +'
  
 -cd "$base_dir"
 +test_expect_success 'that reference gets used' '
 +      test_objcount D 0
 +'
  
 -test_expect_success 'pulling changes from origin' \
 -'cd C &&
 -git pull origin'
 +test_expect_success 'updating origin' '
 +      commit_in A file3 &&
 +      git -C A repack -ad &&
 +      git -C A prune
 +'
  
 -cd "$base_dir"
 +test_expect_success 'pulling changes from origin' '
 +      git -C C pull origin
 +'
  
  # the 2 local objects are commit and tree from the merge
 -test_expect_success 'that alternate to origin gets used' \
 -'cd C &&
 -echo "2 objects" > expected &&
 -git count-objects | cut -d, -f1 > current &&
 -test_cmp expected current'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'pulling changes from origin' \
 -'cd D &&
 -git pull origin'
 +test_expect_success 'that alternate to origin gets used' '
 +      test_objcount C 2
 +'
  
 -cd "$base_dir"
 +test_expect_success 'pulling changes from origin' '
 +      git -C D pull origin
 +'
  
  # the 5 local objects are expected; file3 blob, commit in A to add it
  # and its tree, and 2 are our tree and the merge commit.
 -test_expect_success 'check objects expected to exist locally' \
 -'cd D &&
 -echo "5 objects" > expected &&
 -git count-objects | cut -d, -f1 > current &&
 -test_cmp expected current'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'preparing alternate repository #1' \
 -'test_create_repo F && cd F &&
 -echo first > file1 &&
 -git add file1 &&
 -git commit -m initial'
 -
 -cd "$base_dir"
 -
 -test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' \
 -'git clone F G && cd F &&
 -echo second > file2 &&
 -git add file2 &&
 -git commit -m addition'
 +test_expect_success 'check objects expected to exist locally' '
 +      test_objcount D 5
 +'
  
 -cd "$base_dir"
 +test_expect_success 'preparing alternate repository #1' '
 +      test_create_repo F &&
 +      commit_in F file1
 +'
  
 -test_expect_success 'cloning alternate repo #1, using #2 as reference' \
 -'git clone --reference G F H'
 +test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' '
 +      git clone F G &&
 +      commit_in F file2
 +'
  
 -cd "$base_dir"
 +test_expect_success 'cloning alternate repo #1, using #2 as reference' '
 +      git clone --reference G F H
 +'
  
 -test_expect_success 'cloning with reference being subset of source (-l -s)' \
 -'git clone -l -s --reference A B E'
 +test_expect_success 'cloning with reference being subset of source (-l -s)' '
 +      git clone -l -s --reference A B E
 +'
  
 -cd "$base_dir"
 +test_expect_success 'cloning with multiple references drops duplicates' '
 +      git clone -s --reference B --reference A --reference B A dups &&
 +      test_line_count = 2 dups/.git/objects/info/alternates
 +'
  
  test_expect_success 'clone with reference from a tagged repository' '
        (
 -              cd A && git tag -a -m 'tagged' HEAD
 +              cd A && git tag -a -m tagged HEAD
        ) &&
        git clone --reference=A A I
  '
@@@ -144,6 -168,8 +144,6 @@@ test_expect_success 'prepare branched r
        )
  '
  
 -rm -f "$U.K"
 -
  test_expect_success 'fetch with incomplete alternates' '
        git init K &&
        echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
@@@ -188,5 -214,26 +188,26 @@@ test_expect_success 'clone and dissocia
        test_must_fail git -C R fsck &&
        git -C S fsck
  '
+ test_expect_success 'clone, dissociate from partial reference and repack' '
+       rm -fr P Q R &&
+       git init P &&
+       (
+               cd P &&
+               test_commit one &&
+               git repack &&
+               test_commit two &&
+               git repack
+       ) &&
+       git clone --bare P Q &&
+       (
+               cd P &&
+               git checkout -b second &&
+               test_commit three &&
+               git repack
+       ) &&
+       git clone --bare --dissociate --reference=P Q R &&
+       ls R/objects/pack/*.pack >packs.txt &&
+       test_line_count = 1 packs.txt
+ '
  
  test_done