Merge branch 'jk/diff-submodule-diff-inline'
authorJunio C Hamano <gitster@pobox.com>
Mon, 12 Sep 2016 22:34:31 +0000 (15:34 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 12 Sep 2016 22:34:31 +0000 (15:34 -0700)
The "git diff --submodule={short,log}" mechanism has been enhanced
to allow "--submodule=diff" to show the patch between the submodule
commits bound to the superproject.

* jk/diff-submodule-diff-inline:
diff: teach diff to display submodule difference with an inline diff
submodule: refactor show_submodule_summary with helper function
submodule: convert show_submodule_summary to use struct object_id *
allow do_submodule_path to work even if submodule isn't checked out
diff: prepare for additional submodule formats
graph: add support for --line-prefix on all graph-aware output
diff.c: remove output_prefix_length field
cache: add empty_tree_oid object and helper function

1  2 
cache.h
diff.c
diff.h
path.c
sha1_file.c
diff --combined cache.h
index 6738050132e8cc1b24ccbd84286b3a2fcd6ed609,4d62df5cce1cfb4f5442de31ac99ac8085259530..aac38a6b99541951b9bf79b73ebd8853344e82c9
+++ b/cache.h
@@@ -819,8 -819,8 +819,8 @@@ extern void strbuf_git_common_path(stru
        __attribute__((format (printf, 2, 3)));
  extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
- extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
-                                     const char *fmt, ...)
+ extern int 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)));
@@@ -953,22 -953,39 +953,39 @@@ static inline void oidclr(struct object
  #define EMPTY_TREE_SHA1_BIN_LITERAL \
         "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
         "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
- #define EMPTY_TREE_SHA1_BIN \
       ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
+ extern const struct object_id empty_tree_oid;
#define EMPTY_TREE_SHA1_BIN (empty_tree_oid.hash)
  
  #define EMPTY_BLOB_SHA1_HEX \
        "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
  #define EMPTY_BLOB_SHA1_BIN_LITERAL \
        "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
        "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
- #define EMPTY_BLOB_SHA1_BIN \
-       ((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL)
+ extern const struct object_id empty_blob_oid;
+ #define EMPTY_BLOB_SHA1_BIN (empty_blob_oid.hash)
  
  static inline int is_empty_blob_sha1(const unsigned char *sha1)
  {
        return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
  }
  
+ static inline int is_empty_blob_oid(const struct object_id *oid)
+ {
+       return !hashcmp(oid->hash, EMPTY_BLOB_SHA1_BIN);
+ }
+ static inline int is_empty_tree_sha1(const unsigned char *sha1)
+ {
+       return !hashcmp(sha1, EMPTY_TREE_SHA1_BIN);
+ }
+ static inline int is_empty_tree_oid(const struct object_id *oid)
+ {
+       return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
+ }
  int git_mkstemp(char *path, size_t n, const char *template);
  
  /* set default permissions by passing mode arguments to open(2) */
@@@ -1344,7 -1361,6 +1361,7 @@@ extern struct alternate_object_databas
  } *alt_odb_list;
  extern void prepare_alt_odb(void);
  extern void read_info_alternates(const char * relative_base, int depth);
 +extern char *compute_alternate_path(const char *path, struct strbuf *err);
  extern void add_to_alternates_file(const char *reference);
  typedef int alt_odb_fn(struct alternate_object_database *, void *);
  extern int foreach_alt_odb(alt_odb_fn, void*);
@@@ -1757,6 -1773,7 +1774,6 @@@ extern int copy_file(const char *dst, c
  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_pipe(int fd, const void *buf, size_t count, const char *msg);
  extern void fsync_or_die(int fd, const char *);
  
  extern ssize_t read_in_full(int fd, void *buf, size_t count);
diff --combined diff.c
index 534c12e28ea8b077c9fa305a634f7a73cf30589d,b002fdfb81800339f2e9982e291ae2184bd0f670..b38d95eb249c8dabc906cf426a7fd33abd37c4f7
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -18,6 -18,7 +18,7 @@@
  #include "ll-merge.h"
  #include "string-list.h"
  #include "argv-array.h"
+ #include "graph.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -131,9 -132,11 +132,11 @@@ static int parse_dirstat_params(struct 
  static int parse_submodule_params(struct diff_options *options, const char *value)
  {
        if (!strcmp(value, "log"))
-               DIFF_OPT_SET(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (!strcmp(value, "short"))
-               DIFF_OPT_CLR(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_SHORT;
+       else if (!strcmp(value, "diff"))
+               options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
        else
                return -1;
        return 0;
@@@ -1625,7 -1628,7 +1628,7 @@@ static void show_stats(struct diffstat_
         */
  
        if (options->stat_width == -1)
-               width = term_columns() - options->output_prefix_length;
+               width = term_columns() - strlen(line_prefix);
        else
                width = options->stat_width ? options->stat_width : 80;
        number_width = decimal_width(max_change) > number_width ?
@@@ -2299,17 -2302,37 +2302,37 @@@ static void builtin_diff(const char *na
        struct strbuf header = STRBUF_INIT;
        const char *line_prefix = diff_line_prefix(o);
  
-       if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
-                       (!one->mode || S_ISGITLINK(one->mode)) &&
-                       (!two->mode || S_ISGITLINK(two->mode))) {
+       diff_set_mnemonic_prefix(o, "a/", "b/");
+       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+               a_prefix = o->b_prefix;
+               b_prefix = o->a_prefix;
+       } else {
+               a_prefix = o->a_prefix;
+               b_prefix = o->b_prefix;
+       }
+       if (o->submodule_format == DIFF_SUBMODULE_LOG &&
+           (!one->mode || S_ISGITLINK(one->mode)) &&
+           (!two->mode || S_ISGITLINK(two->mode))) {
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one->path ? one->path : two->path,
                                line_prefix,
-                               one->oid.hash, two->oid.hash,
+                               &one->oid, &two->oid,
                                two->dirty_submodule,
                                meta, del, add, reset);
                return;
+       } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
+                  (!one->mode || S_ISGITLINK(one->mode)) &&
+                  (!two->mode || S_ISGITLINK(two->mode))) {
+               const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
+               const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
+               show_submodule_inline_diff(o->file, one->path ? one->path : two->path,
+                               line_prefix,
+                               &one->oid, &two->oid,
+                               two->dirty_submodule,
+                               meta, del, add, reset, o);
+               return;
        }
  
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
                textconv_two = get_textconv(two);
        }
  
-       diff_set_mnemonic_prefix(o, "a/", "b/");
-       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
-               a_prefix = o->b_prefix;
-               b_prefix = o->a_prefix;
-       } else {
-               a_prefix = o->a_prefix;
-               b_prefix = o->b_prefix;
-       }
        /* Never use a non-valid filename anywhere if at all possible */
        name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
        name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
@@@ -3915,7 -3929,7 +3929,7 @@@ int diff_opt_parse(struct diff_options 
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(options, arg);
        } else if (!strcmp(arg, "--submodule"))
-               DIFF_OPT_SET(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (skip_prefix(arg, "--submodule=", &arg))
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
                options->a_prefix = optarg;
                return argcount;
        }
+       else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) {
+               options->line_prefix = optarg;
+               options->line_prefix_length = strlen(options->line_prefix);
+               graph_setup_line_prefix(options);
+               return argcount;
+       }
        else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
                options->b_prefix = optarg;
                return argcount;
@@@ -4462,7 -4482,7 +4482,7 @@@ static void patch_id_consume(void *priv
  }
  
  /* returns 0 upon success, and writes result into sha1 */
 -static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
 +static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
  
                diff_fill_sha1_info(p->one);
                diff_fill_sha1_info(p->two);
 -              if (fill_mmfile(&mf1, p->one) < 0 ||
 -                              fill_mmfile(&mf2, p->two) < 0)
 -                      return error("unable to read files to diff");
  
                len1 = remove_space(p->one->path, strlen(p->one->path));
                len2 = remove_space(p->two->path, strlen(p->two->path));
                                        len2, p->two->path);
                git_SHA1_Update(&ctx, buffer, len1);
  
 +              if (diff_header_only)
 +                      continue;
 +
 +              if (fill_mmfile(&mf1, p->one) < 0 ||
 +                  fill_mmfile(&mf2, p->two) < 0)
 +                      return error("unable to read files to diff");
 +
                if (diff_filespec_is_binary(p->one) ||
                    diff_filespec_is_binary(p->two)) {
                        git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid),
        return 0;
  }
  
 -int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
 +int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
 -      int result = diff_get_patch_id(options, sha1);
 +      int result = diff_get_patch_id(options, sha1, diff_header_only);
  
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
diff --combined diff.h
index 7883729edf10ae2888ebc672f62f6015dee21550,2d884f1d08cc1b900d8e7e17c18b68d51041c0a6..ec76a90522ea88354882666a5a6d336dfa8963fd
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -83,7 -83,6 +83,6 @@@ typedef struct strbuf *(*diff_prefix_fn
  #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
  #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
  #define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
- #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
  #define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)
  #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
  #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
@@@ -110,11 -109,19 +109,19 @@@ enum diff_words_type 
        DIFF_WORDS_COLOR
  };
  
+ enum diff_submodule_format {
+       DIFF_SUBMODULE_SHORT = 0,
+       DIFF_SUBMODULE_LOG,
+       DIFF_SUBMODULE_INLINE_DIFF
+ };
  struct diff_options {
        const char *orderfile;
        const char *pickaxe;
        const char *single_follow;
        const char *a_prefix, *b_prefix;
+       const char *line_prefix;
+       size_t line_prefix_length;
        unsigned flags;
        unsigned touched_flags;
  
        int stat_count;
        const char *word_regex;
        enum diff_words_type word_diff;
+       enum diff_submodule_format submodule_format;
  
        /* this is set by diffcore for DIFF_FORMAT_PATCH */
        int found_changes;
        diff_format_fn_t format_callback;
        void *format_callback_data;
        diff_prefix_fn_t output_prefix;
-       int output_prefix_length;
        void *output_prefix_data;
  
        int diff_path_counter;
@@@ -342,7 -349,7 +349,7 @@@ extern int run_diff_files(struct rev_in
  extern int run_diff_index(struct rev_info *revs, int cached);
  
  extern int do_diff_cache(const unsigned char *, struct diff_options *);
 -extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
 +extern int diff_flush_patch_id(struct diff_options *, unsigned char *, int);
  
  extern int diff_result_code(struct diff_options *, int);
  
diff --combined path.c
index fe3c4d96c6d82b2c8f4e1553f1a52e410c8c02d3,ba60c9849ef7dcc849b2014fb049e3589c6b32e3..a8e72955f6f28f6e27e7faeee144f4f3ed492c83
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -6,6 -6,7 +6,7 @@@
  #include "string-list.h"
  #include "dir.h"
  #include "worktree.h"
+ #include "submodule-config.h"
  
  static int get_st_mode_bits(const char *path, int *mode)
  {
@@@ -380,8 -381,6 +381,8 @@@ static void adjust_git_path(struct strb
                              get_index_file(), strlen(get_index_file()));
        else if (git_db_env && dir_prefix(base, "objects"))
                replace_dir(buf, git_dir_len + 7, get_object_directory());
 +      else if (git_hooks_path && dir_prefix(base, "hooks"))
 +              replace_dir(buf, git_dir_len + 5, git_hooks_path);
        else if (git_common_dir_env)
                update_common_dir(buf, git_dir_len, NULL);
  }
@@@ -468,12 -467,16 +469,16 @@@ const char *worktree_git_path(const str
        return pathname->buf;
  }
  
- static void do_submodule_path(struct strbuf *buf, const char *path,
-                             const char *fmt, va_list args)
+ /* Returns 0 on success, negative on failure. */
+ #define SUBMODULE_PATH_ERR_NOT_CONFIGURED -1
+ static int do_submodule_path(struct strbuf *buf, const char *path,
+                            const char *fmt, va_list args)
  {
        const char *git_dir;
        struct strbuf git_submodule_common_dir = STRBUF_INIT;
        struct strbuf git_submodule_dir = STRBUF_INIT;
+       const struct submodule *sub;
+       int err = 0;
  
        strbuf_addstr(buf, path);
        strbuf_complete(buf, '/');
                strbuf_reset(buf);
                strbuf_addstr(buf, git_dir);
        }
+       if (!is_git_directory(buf->buf)) {
+               gitmodules_config();
+               sub = submodule_from_path(null_sha1, path);
+               if (!sub) {
+                       err = SUBMODULE_PATH_ERR_NOT_CONFIGURED;
+                       goto cleanup;
+               }
+               strbuf_reset(buf);
+               strbuf_git_path(buf, "%s/%s", "modules", sub->name);
+       }
        strbuf_addch(buf, '/');
        strbuf_addbuf(&git_submodule_dir, buf);
  
  
        strbuf_cleanup_path(buf);
  
+ cleanup:
        strbuf_release(&git_submodule_dir);
        strbuf_release(&git_submodule_common_dir);
+       return err;
  }
  
  char *git_pathdup_submodule(const char *path, const char *fmt, ...)
  {
+       int err;
        va_list args;
        struct strbuf buf = STRBUF_INIT;
        va_start(args, fmt);
-       do_submodule_path(&buf, path, fmt, args);
+       err = do_submodule_path(&buf, path, fmt, args);
        va_end(args);
+       if (err) {
+               strbuf_release(&buf);
+               return NULL;
+       }
        return strbuf_detach(&buf, NULL);
  }
  
void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
-                              const char *fmt, ...)
int strbuf_git_path_submodule(struct strbuf *buf, const char *path,
+                             const char *fmt, ...)
  {
+       int err;
        va_list args;
        va_start(args, fmt);
-       do_submodule_path(buf, path, fmt, args);
+       err = do_submodule_path(buf, path, fmt, args);
        va_end(args);
+       return err;
  }
  
  static void do_git_common_path(struct strbuf *buf,
diff --combined sha1_file.c
index 6f38dc65487bb4e639766bb6a189297b356021d7,5b8553d69e06ff37a37573a5ef1a040b90de812f..472ccb2ff96d4bcf57cba4832dc2ca7631b309eb
@@@ -24,7 -24,6 +24,7 @@@
  #include "streaming.h"
  #include "dir.h"
  #include "mru.h"
 +#include "list.h"
  
  #ifndef O_NOATIME
  #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@@ -39,6 -38,12 +39,12 @@@ static inline uintmax_t sz_fmt(size_t s
  
  const unsigned char null_sha1[20];
  const struct object_id null_oid;
+ const struct object_id empty_tree_oid = {
+       EMPTY_TREE_SHA1_BIN_LITERAL
+ };
+ const struct object_id empty_blob_oid = {
+       EMPTY_BLOB_SHA1_BIN_LITERAL
+ };
  
  /*
   * This is meant to hold a *small* number of objects that you would
@@@ -419,82 -424,6 +425,82 @@@ void add_to_alternates_file(const char 
        free(alts);
  }
  
 +/*
 + * Compute the exact path an alternate is at and returns it. In case of
 + * error NULL is returned and the human readable error is added to `err`
 + * `path` may be relative and should point to $GITDIR.
 + * `err` must not be null.
 + */
 +char *compute_alternate_path(const char *path, struct strbuf *err)
 +{
 +      char *ref_git = NULL;
 +      const char *repo, *ref_git_s;
 +      int seen_error = 0;
 +
 +      ref_git_s = real_path_if_valid(path);
 +      if (!ref_git_s) {
 +              seen_error = 1;
 +              strbuf_addf(err, _("path '%s' does not exist"), path);
 +              goto out;
 +      } else
 +              /*
 +               * Beware: read_gitfile(), real_path() and mkpath()
 +               * return static buffer
 +               */
 +              ref_git = xstrdup(ref_git_s);
 +
 +      repo = read_gitfile(ref_git);
 +      if (!repo)
 +              repo = read_gitfile(mkpath("%s/.git", ref_git));
 +      if (repo) {
 +              free(ref_git);
 +              ref_git = xstrdup(repo);
 +      }
 +
 +      if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) {
 +              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))) {
 +              struct strbuf sb = STRBUF_INIT;
 +              seen_error = 1;
 +              if (get_common_dir(&sb, ref_git)) {
 +                      strbuf_addf(err,
 +                                  _("reference repository '%s' as a linked "
 +                                    "checkout is not supported yet."),
 +                                  path);
 +                      goto out;
 +              }
 +
 +              strbuf_addf(err, _("reference repository '%s' is not a "
 +                                      "local repository."), path);
 +              goto out;
 +      }
 +
 +      if (!access(mkpath("%s/shallow", ref_git), F_OK)) {
 +              strbuf_addf(err, _("reference repository '%s' is shallow"),
 +                          path);
 +              seen_error = 1;
 +              goto out;
 +      }
 +
 +      if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) {
 +              strbuf_addf(err,
 +                          _("reference repository '%s' is grafted"),
 +                          path);
 +              seen_error = 1;
 +              goto out;
 +      }
 +
 +out:
 +      if (seen_error) {
 +              free(ref_git);
 +              ref_git = NULL;
 +      }
 +
 +      return ref_git;
 +}
 +
  int foreach_alt_odb(alt_odb_fn fn, void *cb)
  {
        struct alternate_object_database *ent;
@@@ -1769,7 -1698,7 +1775,7 @@@ static int parse_sha1_header_extended(c
                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'
 +       * we're obtaining the type using '--allow-unknown-type'
         * option.
         */
        if ((flags & LOOKUP_UNKNOWN_OBJECT) && (type < 0))
@@@ -2150,142 -2079,136 +2156,142 @@@ static void *unpack_compressed_entry(st
        return buffer;
  }
  
 -#define MAX_DELTA_CACHE (256)
 -
 +static struct hashmap delta_base_cache;
  static size_t delta_base_cached;
  
 -static struct delta_base_cache_lru_list {
 -      struct delta_base_cache_lru_list *prev;
 -      struct delta_base_cache_lru_list *next;
 -} delta_base_cache_lru = { &delta_base_cache_lru, &delta_base_cache_lru };
 +static LIST_HEAD(delta_base_cache_lru);
  
 -static struct delta_base_cache_entry {
 -      struct delta_base_cache_lru_list lru;
 -      void *data;
 +struct delta_base_cache_key {
        struct packed_git *p;
        off_t base_offset;
 +};
 +
 +struct delta_base_cache_entry {
 +      struct hashmap hash;
 +      struct delta_base_cache_key key;
 +      struct list_head lru;
 +      void *data;
        unsigned long size;
        enum object_type type;
 -} delta_base_cache[MAX_DELTA_CACHE];
 +};
  
 -static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
 +static unsigned int pack_entry_hash(struct packed_git *p, off_t base_offset)
  {
 -      unsigned long hash;
 +      unsigned int hash;
  
 -      hash = (unsigned long)(intptr_t)p + (unsigned long)base_offset;
 +      hash = (unsigned int)(intptr_t)p + (unsigned int)base_offset;
        hash += (hash >> 8) + (hash >> 16);
 -      return hash % MAX_DELTA_CACHE;
 +      return hash;
  }
  
  static struct delta_base_cache_entry *
  get_delta_base_cache_entry(struct packed_git *p, off_t base_offset)
  {
 -      unsigned long hash = pack_entry_hash(p, base_offset);
 -      return delta_base_cache + hash;
 +      struct hashmap_entry entry;
 +      struct delta_base_cache_key key;
 +
 +      if (!delta_base_cache.cmpfn)
 +              return NULL;
 +
 +      hashmap_entry_init(&entry, pack_entry_hash(p, base_offset));
 +      key.p = p;
 +      key.base_offset = base_offset;
 +      return hashmap_get(&delta_base_cache, &entry, &key);
 +}
 +
 +static int delta_base_cache_key_eq(const struct delta_base_cache_key *a,
 +                                 const struct delta_base_cache_key *b)
 +{
 +      return a->p == b->p && a->base_offset == b->base_offset;
  }
  
 -static int eq_delta_base_cache_entry(struct delta_base_cache_entry *ent,
 -                                   struct packed_git *p, off_t base_offset)
 +static int delta_base_cache_hash_cmp(const void *va, const void *vb,
 +                                   const void *vkey)
  {
 -      return (ent->data && ent->p == p && ent->base_offset == base_offset);
 +      const struct delta_base_cache_entry *a = va, *b = vb;
 +      const struct delta_base_cache_key *key = vkey;
 +      if (key)
 +              return !delta_base_cache_key_eq(&a->key, key);
 +      else
 +              return !delta_base_cache_key_eq(&a->key, &b->key);
  }
  
  static int in_delta_base_cache(struct packed_git *p, off_t base_offset)
  {
 -      struct delta_base_cache_entry *ent;
 -      ent = get_delta_base_cache_entry(p, base_offset);
 -      return eq_delta_base_cache_entry(ent, p, base_offset);
 +      return !!get_delta_base_cache_entry(p, base_offset);
  }
  
 -static void clear_delta_base_cache_entry(struct delta_base_cache_entry *ent)
 +/*
 + * Remove the entry from the cache, but do _not_ free the associated
 + * entry data. The caller takes ownership of the "data" buffer, and
 + * should copy out any fields it wants before detaching.
 + */
 +static void detach_delta_base_cache_entry(struct delta_base_cache_entry *ent)
  {
 -      ent->data = NULL;
 -      ent->lru.next->prev = ent->lru.prev;
 -      ent->lru.prev->next = ent->lru.next;
 +      hashmap_remove(&delta_base_cache, ent, &ent->key);
 +      list_del(&ent->lru);
        delta_base_cached -= ent->size;
 +      free(ent);
  }
  
  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)
 +      unsigned long *base_size, enum object_type *type)
  {
        struct delta_base_cache_entry *ent;
 -      void *ret;
  
        ent = get_delta_base_cache_entry(p, base_offset);
 -
 -      if (!eq_delta_base_cache_entry(ent, p, base_offset))
 +      if (!ent)
                return unpack_entry(p, base_offset, type, base_size);
  
 -      ret = ent->data;
 -
 -      if (!keep_cache)
 -              clear_delta_base_cache_entry(ent);
 -      else
 -              ret = xmemdupz(ent->data, ent->size);
        *type = ent->type;
        *base_size = ent->size;
 -      return ret;
 +      return xmemdupz(ent->data, ent->size);
  }
  
  static inline void release_delta_base_cache(struct delta_base_cache_entry *ent)
  {
 -      if (ent->data) {
 -              free(ent->data);
 -              ent->data = NULL;
 -              ent->lru.next->prev = ent->lru.prev;
 -              ent->lru.prev->next = ent->lru.next;
 -              delta_base_cached -= ent->size;
 -      }
 +      free(ent->data);
 +      detach_delta_base_cache_entry(ent);
  }
  
  void clear_delta_base_cache(void)
  {
 -      unsigned long p;
 -      for (p = 0; p < MAX_DELTA_CACHE; p++)
 -              release_delta_base_cache(&delta_base_cache[p]);
 +      struct hashmap_iter iter;
 +      struct delta_base_cache_entry *entry;
 +      for (entry = hashmap_iter_first(&delta_base_cache, &iter);
 +           entry;
 +           entry = hashmap_iter_next(&iter)) {
 +              release_delta_base_cache(entry);
 +      }
  }
  
  static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
        void *base, unsigned long base_size, enum object_type type)
  {
 -      unsigned long hash = pack_entry_hash(p, base_offset);
 -      struct delta_base_cache_entry *ent = delta_base_cache + hash;
 -      struct delta_base_cache_lru_list *lru;
 +      struct delta_base_cache_entry *ent = xmalloc(sizeof(*ent));
 +      struct list_head *lru;
  
 -      release_delta_base_cache(ent);
        delta_base_cached += base_size;
  
 -      for (lru = delta_base_cache_lru.next;
 -           delta_base_cached > delta_base_cache_limit
 -           && lru != &delta_base_cache_lru;
 -           lru = lru->next) {
 -              struct delta_base_cache_entry *f = (void *)lru;
 -              if (f->type == OBJ_BLOB)
 -                      release_delta_base_cache(f);
 -      }
 -      for (lru = delta_base_cache_lru.next;
 -           delta_base_cached > delta_base_cache_limit
 -           && lru != &delta_base_cache_lru;
 -           lru = lru->next) {
 -              struct delta_base_cache_entry *f = (void *)lru;
 +      list_for_each(lru, &delta_base_cache_lru) {
 +              struct delta_base_cache_entry *f =
 +                      list_entry(lru, struct delta_base_cache_entry, lru);
 +              if (delta_base_cached <= delta_base_cache_limit)
 +                      break;
                release_delta_base_cache(f);
        }
  
 -      ent->p = p;
 -      ent->base_offset = base_offset;
 +      ent->key.p = p;
 +      ent->key.base_offset = base_offset;
        ent->type = type;
        ent->data = base;
        ent->size = base_size;
 -      ent->lru.next = &delta_base_cache_lru;
 -      ent->lru.prev = delta_base_cache_lru.prev;
 -      delta_base_cache_lru.prev->next = &ent->lru;
 -      delta_base_cache_lru.prev = &ent->lru;
 +      list_add_tail(&ent->lru, &delta_base_cache_lru);
 +
 +      if (!delta_base_cache.cmpfn)
 +              hashmap_init(&delta_base_cache, delta_base_cache_hash_cmp, 0);
 +      hashmap_entry_init(ent, pack_entry_hash(p, base_offset));
 +      hashmap_add(&delta_base_cache, ent);
  }
  
  static void *read_object(const unsigned char *sha1, enum object_type *type,
@@@ -2329,11 -2252,11 +2335,11 @@@ void *unpack_entry(struct packed_git *p
                struct delta_base_cache_entry *ent;
  
                ent = get_delta_base_cache_entry(p, curpos);
 -              if (eq_delta_base_cache_entry(ent, p, curpos)) {
 +              if (ent) {
                        type = ent->type;
                        data = ent->data;
                        size = ent->size;
 -                      clear_delta_base_cache_entry(ent);
 +                      detach_delta_base_cache_entry(ent);
                        base_from_cache = 1;
                        break;
                }
@@@ -2838,7 -2761,7 +2844,7 @@@ static void *read_packed_sha1(const uns
  
        if (!find_pack_entry(sha1, &e))
                return NULL;
 -      data = cache_or_unpack_entry(e.p, e.offset, size, type, 1);
 +      data = cache_or_unpack_entry(e.p, e.offset, size, type);
        if (!data) {
                /*
                 * We're probably in deep shit, but let's try to fetch