Merge branch 'jc/legacy-loose-object'
authorJunio C Hamano <gitster@pobox.com>
Wed, 13 Jul 2011 21:31:34 +0000 (14:31 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Jul 2011 21:31:34 +0000 (14:31 -0700)
* jc/legacy-loose-object:
sha1_file.c: "legacy" is really the current format

1  2 
sha1_file.c
diff --combined sha1_file.c
index 8a85217996a80af300f544fa9a3d8d905b50f266,ac6a88ab0fcd39f9feb078c9af2243c1dbe55b94..bd6fb2f95dda17adcc56ae288af971a781cb7cdf
  #include "pack.h"
  #include "blob.h"
  #include "commit.h"
 +#include "run-command.h"
  #include "tag.h"
  #include "tree.h"
 +#include "tree-walk.h"
  #include "refs.h"
  #include "pack-revindex.h"
  #include "sha1-lookup.h"
  #endif
  #endif
  
 -#ifdef NO_C99_FORMAT
 -#define SZ_FMT "lu"
 -static unsigned long sz_fmt(size_t s) { return (unsigned long)s; }
 -#else
 -#define SZ_FMT "zu"
 -static size_t sz_fmt(size_t s) { return s; }
 -#endif
 +#define SZ_FMT PRIuMAX
 +static inline uintmax_t sz_fmt(size_t s) { return s; }
  
  const unsigned char null_sha1[20];
  
 +/*
 + * This is meant to hold a *small* number of objects that you would
 + * want read_sha1_file() to be able to return, but yet you do not want
 + * to write them into the object store (e.g. a browse-only
 + * application).
 + */
 +static struct cached_object {
 +      unsigned char sha1[20];
 +      enum object_type type;
 +      void *buf;
 +      unsigned long size;
 +} *cached_objects;
 +static int cached_object_nr, cached_object_alloc;
 +
 +static struct cached_object empty_tree = {
 +      EMPTY_TREE_SHA1_BIN_LITERAL,
 +      OBJ_TREE,
 +      "",
 +      0
 +};
 +
 +static struct cached_object *find_cached_object(const unsigned char *sha1)
 +{
 +      int i;
 +      struct cached_object *co = cached_objects;
 +
 +      for (i = 0; i < cached_object_nr; i++, co++) {
 +              if (!hashcmp(co->sha1, sha1))
 +                      return co;
 +      }
 +      if (!hashcmp(sha1, empty_tree.sha1))
 +              return &empty_tree;
 +      return NULL;
 +}
 +
 +int mkdir_in_gitdir(const char *path)
 +{
 +      if (mkdir(path, 0777)) {
 +              int saved_errno = errno;
 +              struct stat st;
 +              struct strbuf sb = STRBUF_INIT;
 +
 +              if (errno != EEXIST)
 +                      return -1;
 +              /*
 +               * Are we looking at a path in a symlinked worktree
 +               * whose original repository does not yet have it?
 +               * e.g. .git/rr-cache pointing at its original
 +               * repository in which the user hasn't performed any
 +               * conflict resolution yet?
 +               */
 +              if (lstat(path, &st) || !S_ISLNK(st.st_mode) ||
 +                  strbuf_readlink(&sb, path, st.st_size) ||
 +                  !is_absolute_path(sb.buf) ||
 +                  mkdir(sb.buf, 0777)) {
 +                      strbuf_release(&sb);
 +                      errno = saved_errno;
 +                      return -1;
 +              }
 +              strbuf_release(&sb);
 +      }
 +      return adjust_shared_perm(path);
 +}
 +
  int safe_create_leading_directories(char *path)
  {
        char *pos = path + offset_1st_component(path);
@@@ -226,7 -165,6 +226,7 @@@ struct alternate_object_database *alt_o
  static struct alternate_object_database **alt_odb_tail;
  
  static void read_info_alternates(const char * alternates, int depth);
 +static int git_open_noatime(const char *name);
  
  /*
   * Prepare alternate object database registry.
@@@ -360,7 -298,7 +360,7 @@@ static void read_info_alternates(const 
        int fd;
  
        sprintf(path, "%s/%s", relative_base, alt_file_name);
 -      fd = open(path, O_RDONLY);
 +      fd = git_open_noatime(path);
        if (fd < 0)
                return;
        if (fstat(fd, &st) || (st.st_size == 0)) {
@@@ -442,8 -380,6 +442,8 @@@ static unsigned int pack_used_ctr
  static unsigned int pack_mmap_calls;
  static unsigned int peak_pack_open_windows;
  static unsigned int pack_open_windows;
 +static unsigned int pack_open_fds;
 +static unsigned int pack_max_fds;
  static size_t peak_pack_mapped;
  static size_t pack_mapped;
  struct packed_git *packed_git;
@@@ -475,7 -411,7 +475,7 @@@ static int check_packed_git_idx(const c
        struct pack_idx_header *hdr;
        size_t idx_size;
        uint32_t version, nr, i, *index;
 -      int fd = open(path, O_RDONLY);
 +      int fd = git_open_noatime(path);
        struct stat st;
  
        if (fd < 0)
@@@ -621,10 -557,8 +621,10 @@@ static int unuse_one_window(struct pack
                        lru_l->next = lru_w->next;
                else {
                        lru_p->windows = lru_w->next;
 -                      if (!lru_p->windows && lru_p->pack_fd != keep_fd) {
 +                      if (!lru_p->windows && lru_p->pack_fd != -1
 +                              && lru_p->pack_fd != keep_fd) {
                                close(lru_p->pack_fd);
 +                              pack_open_fds--;
                                lru_p->pack_fd = -1;
                        }
                }
@@@ -642,21 -576,6 +642,21 @@@ void release_pack_memory(size_t need, i
                ; /* nothing */
  }
  
 +void *xmmap(void *start, size_t length,
 +      int prot, int flags, int fd, off_t offset)
 +{
 +      void *ret = mmap(start, length, prot, flags, fd, offset);
 +      if (ret == MAP_FAILED) {
 +              if (!length)
 +                      return NULL;
 +              release_pack_memory(length, fd);
 +              ret = mmap(start, length, prot, flags, fd, offset);
 +              if (ret == MAP_FAILED)
 +                      die_errno("Out of memory? mmap failed");
 +      }
 +      return ret;
 +}
 +
  void close_pack_windows(struct packed_git *p)
  {
        while (p->windows) {
@@@ -709,10 -628,8 +709,10 @@@ void free_pack_by_name(const char *pack
                if (strcmp(pack_name, p->pack_name) == 0) {
                        clear_delta_base_cache();
                        close_pack_windows(p);
 -                      if (p->pack_fd != -1)
 +                      if (p->pack_fd != -1) {
                                close(p->pack_fd);
 +                              pack_open_fds--;
 +                      }
                        close_pack_index(p);
                        free(p->bad_object_sha1);
                        *pp = p->next;
@@@ -738,29 -655,11 +738,29 @@@ static int open_packed_git_1(struct pac
        if (!p->index_data && open_pack_index(p))
                return error("packfile %s index unavailable", p->pack_name);
  
 -      p->pack_fd = open(p->pack_name, O_RDONLY);
 -      while (p->pack_fd < 0 && errno == EMFILE && unuse_one_window(p, -1))
 -              p->pack_fd = open(p->pack_name, O_RDONLY);
 +      if (!pack_max_fds) {
 +              struct rlimit lim;
 +              unsigned int max_fds;
 +
 +              if (getrlimit(RLIMIT_NOFILE, &lim))
 +                      die_errno("cannot get RLIMIT_NOFILE");
 +
 +              max_fds = lim.rlim_cur;
 +
 +              /* Save 3 for stdin/stdout/stderr, 22 for work */
 +              if (25 < max_fds)
 +                      pack_max_fds = max_fds - 25;
 +              else
 +                      pack_max_fds = 1;
 +      }
 +
 +      while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1))
 +              ; /* nothing */
 +
 +      p->pack_fd = git_open_noatime(p->pack_name);
        if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
                return -1;
 +      pack_open_fds++;
  
        /* If we created the struct before we had the pack we lack size. */
        if (!p->pack_size) {
@@@ -812,7 -711,6 +812,7 @@@ static int open_packed_git(struct packe
                return 0;
        if (p->pack_fd != -1) {
                close(p->pack_fd);
 +              pack_open_fds--;
                p->pack_fd = -1;
        }
        return -1;
@@@ -838,13 -736,14 +838,13 @@@ unsigned char *use_pack(struct packed_g
  {
        struct pack_window *win = *w_cursor;
  
 -      if (p->pack_fd == -1 && open_packed_git(p))
 -              die("packfile %s cannot be accessed", p->pack_name);
 -
        /* Since packfiles end in a hash of their content and it's
         * pointless to ask for an offset into the middle of that
         * hash, and the in_window function above wouldn't match
         * don't allow an offset too close to the end of the file.
         */
 +      if (!p->pack_size && p->pack_fd == -1 && open_packed_git(p))
 +              die("packfile %s cannot be accessed", p->pack_name);
        if (offset > (p->pack_size - 20))
                die("offset beyond end of packfile (truncated pack?)");
  
                if (!win) {
                        size_t window_align = packed_git_window_size / 2;
                        off_t len;
 +
 +                      if (p->pack_fd == -1 && open_packed_git(p))
 +                              die("packfile %s cannot be accessed", p->pack_name);
 +
                        win = xcalloc(1, sizeof(*win));
                        win->offset = (offset / window_align) * window_align;
                        len = p->pack_size - win->offset;
                                die("packfile %s cannot be mapped: %s",
                                        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;
 +                      }
                        pack_mmap_calls++;
                        pack_open_windows++;
                        if (pack_mapped > peak_pack_mapped)
@@@ -914,22 -803,11 +914,22 @@@ static struct packed_git *alloc_packed_
        return p;
  }
  
 +static void try_to_free_pack_memory(size_t size)
 +{
 +      release_pack_memory(size, -1);
 +}
 +
  struct packed_git *add_packed_git(const char *path, int path_len, int local)
  {
 +      static int have_set_try_to_free_routine;
        struct stat st;
        struct packed_git *p = alloc_packed_git(path_len + 2);
  
 +      if (!have_set_try_to_free_routine) {
 +              have_set_try_to_free_routine = 1;
 +              set_try_to_free_routine(try_to_free_pack_memory);
 +      }
 +
        /*
         * Make sure a corresponding .pack file exists and that
         * the index looks sane.
@@@ -979,9 -857,6 +979,9 @@@ struct packed_git *parse_pack_index(uns
  
  void install_packed_git(struct packed_git *pack)
  {
 +      if (pack->pack_fd != -1)
 +              pack_open_fds++;
 +
        pack->next = packed_git;
        packed_git = pack;
  }
@@@ -999,6 -874,8 +999,6 @@@ static void prepare_packed_git_one(cha
        sprintf(path, "%s/pack", objdir);
        len = strlen(path);
        dir = opendir(path);
 -      while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1))
 -              dir = opendir(path);
        if (!dir) {
                if (errno != ENOENT)
                        error("unable to open object pack directory: %s: %s",
@@@ -1126,7 -1003,7 +1126,7 @@@ static void mark_bad_packed_object(stru
        p->num_bad_objects++;
  }
  
 -static int has_packed_and_bad(const unsigned char *sha1)
 +static const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
  {
        struct packed_git *p;
        unsigned i;
        for (p = packed_git; p; p = p->next)
                for (i = 0; i < p->num_bad_objects; i++)
                        if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
 -                              return 1;
 -      return 0;
 +                              return p;
 +      return NULL;
  }
  
  int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
  static int git_open_noatime(const char *name)
  {
        static int sha1_file_open_flag = O_NOATIME;
 -      int fd = open(name, O_RDONLY | sha1_file_open_flag);
  
 -      /* Might the failure be due to O_NOATIME? */
 -      if (fd < 0 && errno != ENOENT && sha1_file_open_flag) {
 -              fd = open(name, O_RDONLY);
 +      for (;;) {
 +              int fd = open(name, O_RDONLY | sha1_file_open_flag);
                if (fd >= 0)
 +                      return fd;
 +
 +              /* Might the failure be due to O_NOATIME? */
 +              if (errno != ENOENT && sha1_file_open_flag) {
                        sha1_file_open_flag = 0;
 +                      continue;
 +              }
 +
 +              return -1;
        }
 -      return fd;
  }
  
  static int open_sha1_file(const unsigned char *sha1)
        return -1;
  }
  
 -static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 +void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
  {
        void *map;
        int fd;
        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)
 +int unpack_sha1_header(z_stream *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)
                /*
                 * The above condition must be (bytes <= size), not
                 * (bytes < size).  In other words, even though we
 -               * expect no more output and set avail_out to zer0,
 +               * expect no more output and set avail_out to zero,
                 * the input zlib stream may have bytes that express
                 * "this concludes the stream", and we *do* want to
                 * eat that input.
   * too permissive for what we want to check. So do an anal
   * object header parse by hand.
   */
 -static int parse_sha1_header(const char *hdr, unsigned long *sizep)
 +int parse_sha1_header(const char *hdr, unsigned long *sizep)
  {
        char type[10];
        int i;
@@@ -1481,7 -1357,7 +1485,7 @@@ static off_t get_delta_base(struct pack
  
  /* forward declaration for a mutually recursive function */
  static int packed_object_info(struct packed_git *p, off_t offset,
 -                            unsigned long *sizep);
 +                            unsigned long *sizep, int *rtype);
  
  static int packed_delta_info(struct packed_git *p,
                             struct pack_window **w_curs,
        base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
        if (!base_offset)
                return OBJ_BAD;
 -      type = packed_object_info(p, base_offset, NULL);
 +      type = packed_object_info(p, base_offset, NULL, NULL);
        if (type <= OBJ_NONE) {
                struct revindex_entry *revidx;
                const unsigned char *base_sha1;
        return type;
  }
  
 -static int unpack_object_header(struct packed_git *p,
 -                              struct pack_window **w_curs,
 -                              off_t *curpos,
 -                              unsigned long *sizep)
 +int unpack_object_header(struct packed_git *p,
 +                       struct pack_window **w_curs,
 +                       off_t *curpos,
 +                       unsigned long *sizep)
  {
        unsigned char *base;
        unsigned int left;
        enum object_type type;
  
        /* use_pack() assures us we have [base, base + 20) available
 -       * as a range that we can look at at.  (Its actually the hash
 +       * as a range that we can look at.  (Its actually the hash
         * size that is assured.)  With our object header encoding
         * the maximum deflated object size is 2^137, which is just
         * insane, so we know won't exceed what we have been given.
        return type;
  }
  
 -const char *packed_object_info_detail(struct packed_git *p,
 +int packed_object_info_detail(struct packed_git *p,
                                      off_t obj_offset,
                                      unsigned long *size,
                                      unsigned long *store_size,
                case OBJ_BLOB:
                case OBJ_TAG:
                        unuse_pack(&w_curs);
 -                      return typename(type);
 +                      return type;
                case OBJ_OFS_DELTA:
                        obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
                        if (!obj_offset)
  }
  
  static int packed_object_info(struct packed_git *p, off_t obj_offset,
 -                            unsigned long *sizep)
 +                            unsigned long *sizep, int *rtype)
  {
        struct pack_window *w_curs = NULL;
        unsigned long size;
        enum object_type type;
  
        type = unpack_object_header(p, &w_curs, &curpos, &size);
 +      if (rtype)
 +              *rtype = type; /* representation type */
  
        switch (type) {
        case OBJ_OFS_DELTA:
@@@ -1697,13 -1571,6 +1701,13 @@@ static unsigned long pack_entry_hash(st
        return hash % MAX_DELTA_CACHE;
  }
  
 +static int in_delta_base_cache(struct packed_git *p, off_t base_offset)
 +{
 +      unsigned long hash = pack_entry_hash(p, base_offset);
 +      struct delta_base_cache_entry *ent = delta_base_cache + hash;
 +      return (ent->data && ent->p == p && ent->base_offset == base_offset);
 +}
 +
  static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
        unsigned long *base_size, enum object_type *type, int keep_cache)
  {
@@@ -1994,27 -1861,6 +1998,27 @@@ off_t find_pack_entry_one(const unsigne
        return 0;
  }
  
 +static int is_pack_valid(struct packed_git *p)
 +{
 +      /* An already open pack is known to be valid. */
 +      if (p->pack_fd != -1)
 +              return 1;
 +
 +      /* If the pack has one window completely covering the
 +       * file size, the pack is known to be valid even if
 +       * the descriptor is not currently open.
 +       */
 +      if (p->windows) {
 +              struct pack_window *w = p->windows;
 +
 +              if (!w->offset && w->len == p->pack_size)
 +                      return 1;
 +      }
 +
 +      /* Force the pack to open to prove its valid. */
 +      return !open_packed_git(p);
 +}
 +
  static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
  {
        static struct packed_git *last_found = (void *)1;
                         * it may have been deleted since the index
                         * was loaded!
                         */
 -                      if (p->pack_fd == -1 && open_packed_git(p)) {
 +                      if (!is_pack_valid(p)) {
                                error("packfile %s cannot be accessed", p->pack_name);
                                goto next;
                        }
@@@ -2102,28 -1948,16 +2106,28 @@@ static int sha1_loose_object_info(cons
        return status;
  }
  
 -int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
 +/* returns enum object_type or negative */
 +int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
  {
 +      struct cached_object *co;
        struct pack_entry e;
 -      int status;
 +      int status, rtype;
 +
 +      co = find_cached_object(sha1);
 +      if (co) {
 +              if (oi->sizep)
 +                      *(oi->sizep) = co->size;
 +              oi->whence = OI_CACHED;
 +              return co->type;
 +      }
  
        if (!find_pack_entry(sha1, &e)) {
                /* Most likely it's a loose object. */
 -              status = sha1_loose_object_info(sha1, sizep);
 -              if (status >= 0)
 +              status = sha1_loose_object_info(sha1, oi->sizep);
 +              if (status >= 0) {
 +                      oi->whence = OI_LOOSE;
                        return status;
 +              }
  
                /* Not a loose object; someone else may have just packed it. */
                reprepare_packed_git();
                        return status;
        }
  
 -      status = packed_object_info(e.p, e.offset, sizep);
 +      status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
        if (status < 0) {
                mark_bad_packed_object(e.p, sha1);
 -              status = sha1_object_info(sha1, sizep);
 +              status = sha1_object_info_extended(sha1, oi);
 +      } else if (in_delta_base_cache(e.p, e.offset)) {
 +              oi->whence = OI_DBCACHED;
 +      } else {
 +              oi->whence = OI_PACKED;
 +              oi->u.packed.offset = e.offset;
 +              oi->u.packed.pack = e.p;
 +              oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
 +                                       rtype == OBJ_OFS_DELTA);
        }
  
        return status;
  }
  
 +int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
 +{
 +      struct object_info oi;
 +
 +      oi.sizep = sizep;
 +      return sha1_object_info_extended(sha1, &oi);
 +}
 +
  static void *read_packed_sha1(const unsigned char *sha1,
                              enum object_type *type, unsigned long *size)
  {
        return data;
  }
  
 -/*
 - * This is meant to hold a *small* number of objects that you would
 - * want read_sha1_file() to be able to return, but yet you do not want
 - * to write them into the object store (e.g. a browse-only
 - * application).
 - */
 -static struct cached_object {
 -      unsigned char sha1[20];
 -      enum object_type type;
 -      void *buf;
 -      unsigned long size;
 -} *cached_objects;
 -static int cached_object_nr, cached_object_alloc;
 -
 -static struct cached_object empty_tree = {
 -      EMPTY_TREE_SHA1_BIN,
 -      OBJ_TREE,
 -      "",
 -      0
 -};
 -
 -static struct cached_object *find_cached_object(const unsigned char *sha1)
 -{
 -      int i;
 -      struct cached_object *co = cached_objects;
 -
 -      for (i = 0; i < cached_object_nr; i++, co++) {
 -              if (!hashcmp(co->sha1, sha1))
 -                      return co;
 -      }
 -      if (!hashcmp(sha1, empty_tree.sha1))
 -              return &empty_tree;
 -      return NULL;
 -}
 -
  int pretend_sha1_file(void *buf, unsigned long len, enum object_type type,
                      unsigned char *sha1)
  {
@@@ -2230,46 -2083,27 +2234,46 @@@ static void *read_object(const unsigne
        return read_packed_sha1(sha1, type, size);
  }
  
 -void *read_sha1_file_repl(const unsigned char *sha1,
 -                        enum object_type *type,
 -                        unsigned long *size,
 -                        const unsigned char **replacement)
 +/*
 + * This function dies on corrupt objects; the callers who want to
 + * deal with them should arrange to call read_object() and give error
 + * messages themselves.
 + */
 +void *read_sha1_file_extended(const unsigned char *sha1,
 +                            enum object_type *type,
 +                            unsigned long *size,
 +                            unsigned flag)
  {
 -      const unsigned char *repl = lookup_replace_object(sha1);
 -      void *data = read_object(repl, type, size);
 +      void *data;
 +      char *path;
 +      const struct packed_git *p;
 +      const unsigned char *repl = (flag & READ_SHA1_FILE_REPLACE)
 +              ? lookup_replace_object(sha1) : sha1;
 +
 +      errno = 0;
 +      data = read_object(repl, type, size);
 +      if (data)
 +              return data;
 +
 +      if (errno && errno != ENOENT)
 +              die_errno("failed to read object %s", sha1_to_hex(sha1));
  
        /* die if we replaced an object with one that does not exist */
 -      if (!data && repl != sha1)
 +      if (repl != sha1)
                die("replacement %s not found for %s",
                    sha1_to_hex(repl), sha1_to_hex(sha1));
  
 -      /* legacy behavior is to die on corrupted objects */
 -      if (!data && (has_loose_object(repl) || has_packed_and_bad(repl)))
 -              die("object %s is corrupted", sha1_to_hex(repl));
 +      if (has_loose_object(repl)) {
 +              path = sha1_file_name(sha1);
 +              die("loose object %s (stored in %s) is corrupt",
 +                  sha1_to_hex(repl), path);
 +      }
  
 -      if (replacement)
 -              *replacement = repl;
 +      if ((p = has_packed_and_bad(repl)) != NULL)
 +              die("packed object %s (stored in %s) is corrupt",
 +                  sha1_to_hex(repl), p->pack_name);
  
 -      return data;
 +      return NULL;
  }
  
  void *read_object_with_reference(const unsigned char *sha1,
@@@ -2461,6 -2295,8 +2465,6 @@@ static int write_loose_object(const uns
  
        filename = sha1_file_name(sha1);
        fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
 -      while (fd < 0 && errno == EMFILE && unuse_one_window(packed_git, -1))
 -              fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
@@@ -2579,40 -2415,10 +2583,40 @@@ int has_sha1_file(const unsigned char *
        return has_loose_object(sha1);
  }
  
 +static void check_tree(const void *buf, size_t size)
 +{
 +      struct tree_desc desc;
 +      struct name_entry entry;
 +
 +      init_tree_desc(&desc, buf, size);
 +      while (tree_entry(&desc, &entry))
 +              /* do nothing
 +               * tree_entry() will die() on malformed entries */
 +              ;
 +}
 +
 +static void check_commit(const void *buf, size_t size)
 +{
 +      struct commit c;
 +      memset(&c, 0, sizeof(c));
 +      if (parse_commit_buffer(&c, buf, size))
 +              die("corrupt commit");
 +}
 +
 +static void check_tag(const void *buf, size_t size)
 +{
 +      struct tag t;
 +      memset(&t, 0, sizeof(t));
 +      if (parse_tag_buffer(&t, buf, size))
 +              die("corrupt tag");
 +}
 +
  static int index_mem(unsigned char *sha1, void *buf, size_t size,
 -                   int write_object, enum object_type type, const char *path)
 +                   enum object_type type,
 +                   const char *path, unsigned flags)
  {
        int ret, re_allocated = 0;
 +      int write_object = flags & HASH_WRITE_OBJECT;
  
        if (!type)
                type = OBJ_BLOB;
                        re_allocated = 1;
                }
        }
 +      if (flags & HASH_FORMAT_CHECK) {
 +              if (type == OBJ_TREE)
 +                      check_tree(buf, size);
 +              if (type == OBJ_COMMIT)
 +                      check_commit(buf, size);
 +              if (type == OBJ_TAG)
 +                      check_tag(buf, size);
 +      }
  
        if (write_object)
                ret = write_sha1_file(buf, size, typename(type), sha1);
        return ret;
  }
  
 +static int index_pipe(unsigned char *sha1, int fd, enum object_type type,
 +                    const char *path, unsigned flags)
 +{
 +      struct strbuf sbuf = STRBUF_INIT;
 +      int ret;
 +
 +      if (strbuf_read(&sbuf, fd, 4096) >= 0)
 +              ret = index_mem(sha1, sbuf.buf, sbuf.len, type, path, flags);
 +      else
 +              ret = -1;
 +      strbuf_release(&sbuf);
 +      return ret;
 +}
 +
  #define SMALL_FILE_SIZE (32*1024)
  
 -int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
 -           enum object_type type, const char *path)
 +static int index_core(unsigned char *sha1, int fd, size_t size,
 +                    enum object_type type, const char *path,
 +                    unsigned flags)
  {
        int ret;
 -      size_t size = xsize_t(st->st_size);
  
 -      if (!S_ISREG(st->st_mode)) {
 -              struct strbuf sbuf = STRBUF_INIT;
 -              if (strbuf_read(&sbuf, fd, 4096) >= 0)
 -                      ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
 -                                      type, path);
 -              else
 -                      ret = -1;
 -              strbuf_release(&sbuf);
 -      } else if (!size) {
 -              ret = index_mem(sha1, NULL, size, write_object, type, path);
 +      if (!size) {
 +              ret = index_mem(sha1, NULL, size, type, path, flags);
        } else if (size <= SMALL_FILE_SIZE) {
                char *buf = xmalloc(size);
                if (size == read_in_full(fd, buf, size))
 -                      ret = index_mem(sha1, buf, size, write_object, type,
 -                                      path);
 +                      ret = index_mem(sha1, buf, size, type, path, flags);
                else
                        ret = error("short read %s", strerror(errno));
                free(buf);
        } else {
                void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
 -              ret = index_mem(sha1, buf, size, write_object, type, path);
 +              ret = index_mem(sha1, buf, size, type, path, flags);
                munmap(buf, size);
        }
 +      return ret;
 +}
 +
 +/*
 + * This creates one packfile per large blob, because the caller
 + * immediately wants the result sha1, and fast-import can report the
 + * object name via marks mechanism only by closing the created
 + * packfile.
 + *
 + * This also bypasses the usual "convert-to-git" dance, and that is on
 + * purpose. We could write a streaming version of the converting
 + * functions and insert that before feeding the data to fast-import
 + * (or equivalent in-core API described above), but the primary
 + * motivation for trying to stream from the working tree file and to
 + * avoid mmaping it in core is to deal with large binary blobs, and
 + * by definition they do _not_ want to get any conversion.
 + */
 +static int index_stream(unsigned char *sha1, int fd, size_t size,
 +                      enum object_type type, const char *path,
 +                      unsigned flags)
 +{
 +      struct child_process fast_import;
 +      char export_marks[512];
 +      const char *argv[] = { "fast-import", "--quiet", export_marks, NULL };
 +      char tmpfile[512];
 +      char fast_import_cmd[512];
 +      char buf[512];
 +      int len, tmpfd;
 +
 +      strcpy(tmpfile, git_path("hashstream_XXXXXX"));
 +      tmpfd = git_mkstemp_mode(tmpfile, 0600);
 +      if (tmpfd < 0)
 +              die_errno("cannot create tempfile: %s", tmpfile);
 +      if (close(tmpfd))
 +              die_errno("cannot close tempfile: %s", tmpfile);
 +      sprintf(export_marks, "--export-marks=%s", tmpfile);
 +
 +      memset(&fast_import, 0, sizeof(fast_import));
 +      fast_import.in = -1;
 +      fast_import.argv = argv;
 +      fast_import.git_cmd = 1;
 +      if (start_command(&fast_import))
 +              die_errno("index-stream: git fast-import failed");
 +
 +      len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n",
 +                    (unsigned long) size);
 +      write_or_whine(fast_import.in, fast_import_cmd, len,
 +                     "index-stream: feeding fast-import");
 +      while (size) {
 +              char buf[10240];
 +              size_t sz = size < sizeof(buf) ? size : sizeof(buf);
 +              ssize_t actual;
 +
 +              actual = read_in_full(fd, buf, sz);
 +              if (actual < 0)
 +                      die_errno("index-stream: reading input");
 +              if (write_in_full(fast_import.in, buf, actual) != actual)
 +                      die_errno("index-stream: feeding fast-import");
 +              size -= actual;
 +      }
 +      if (close(fast_import.in))
 +              die_errno("index-stream: closing fast-import");
 +      if (finish_command(&fast_import))
 +              die_errno("index-stream: finishing fast-import");
 +
 +      tmpfd = open(tmpfile, O_RDONLY);
 +      if (tmpfd < 0)
 +              die_errno("index-stream: cannot open fast-import mark");
 +      len = read(tmpfd, buf, sizeof(buf));
 +      if (len < 0)
 +              die_errno("index-stream: reading fast-import mark");
 +      if (close(tmpfd) < 0)
 +              die_errno("index-stream: closing fast-import mark");
 +      if (unlink(tmpfile))
 +              die_errno("index-stream: unlinking fast-import mark");
 +      if (len != 44 ||
 +          memcmp(":1 ", buf, 3) ||
 +          get_sha1_hex(buf + 3, sha1))
 +              die_errno("index-stream: unexpected fast-import mark: <%s>", buf);
 +      return 0;
 +}
 +
 +int index_fd(unsigned char *sha1, int fd, struct stat *st,
 +           enum object_type type, const char *path, unsigned flags)
 +{
 +      int ret;
 +      size_t size = xsize_t(st->st_size);
 +
 +      if (!S_ISREG(st->st_mode))
 +              ret = index_pipe(sha1, fd, type, path, flags);
 +      else if (size <= big_file_threshold || type != OBJ_BLOB)
 +              ret = index_core(sha1, fd, size, type, path, flags);
 +      else
 +              ret = index_stream(sha1, fd, size, type, path, flags);
        close(fd);
        return ret;
  }
  
 -int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
 +int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags)
  {
        int fd;
        struct strbuf sb = STRBUF_INIT;
                if (fd < 0)
                        return error("open(\"%s\"): %s", path,
                                     strerror(errno));
 -              if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path) < 0)
 +              if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0)
                        return error("%s: failed to insert into database",
                                     path);
                break;
                        return error("readlink(\"%s\"): %s", path,
                                     errstr);
                }
 -              if (!write_object)
 +              if (!(flags & HASH_WRITE_OBJECT))
                        hash_sha1_file(sb.buf, sb.len, blob_type, sha1);
                else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1))
                        return error("%s: failed to insert into database",