{"thread":{"id":"20630","subject":"[PATCH 2/6 (v4)] basic revision cache system, no integration or features","startedAt":"2009-08-17T12:31:23Z","lastAt":"2009-10-19T20:28:30Z","messageCount":9,"participants":["Nick Edelen","Junio C Hamano","Sverre Rabbelier"],"isPatch":true,"patchVersion":4,"patchTotal":6},"messages":[{"id":"120873","messageId":"op.uys3qlsjtdk399@sirnot.private","threadId":"20630","inReplyTo":null,"subject":"[PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Nick Edelen","fromEmail":"sirnot@gmail.com","sentAt":"2009-08-17T12:31:23Z","receivedAt":"2009-08-17T12:31:23Z","isPatch":true,"sender":{"key":"sirnot@gmail.com","avatar":null},"body":"Second in the revision cache series, this particular patch provides:\n - minimal API: caching only commit topo data\n - minimal porcelain: add and walk cache slices\n - appropriate tests\n\nSigned-off-by: Nick Edelen <sirnot@gmail.com>\n\n---\n Makefile                  |    2 +\n builtin-rev-cache.c       |  206 ++++++++\n builtin.h                 |    1 +\n commit.c                  |    2 +\n git.c                     |    1 +\n rev-cache.c               | 1169 +++++++++++++++++++++++++++++++++++++++++++++\n rev-cache.h               |  107 ++++\n revision.c                |    2 +-\n revision.h                |   26 +-\n t/t6015-rev-cache-list.sh |  104 ++++\n 10 files changed, 1618 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex e6df8ec..1ceb514 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -537,6 +537,7 @@ LIB_OBJS += reflog-walk.o\n LIB_OBJS += refs.o\n LIB_OBJS += remote.o\n LIB_OBJS += rerere.o\n+LIB_OBJS += rev-cache.o\n LIB_OBJS += revision.o\n LIB_OBJS += run-command.o\n LIB_OBJS += server-info.o\n@@ -627,6 +628,7 @@ BUILTIN_OBJS += builtin-reflog.o\n BUILTIN_OBJS += builtin-remote.o\n BUILTIN_OBJS += builtin-rerere.o\n BUILTIN_OBJS += builtin-reset.o\n+BUILTIN_OBJS += builtin-rev-cache.o\n BUILTIN_OBJS += builtin-rev-list.o\n BUILTIN_OBJS += builtin-rev-parse.o\n BUILTIN_OBJS += builtin-revert.o\ndiff --git a/builtin-rev-cache.c b/builtin-rev-cache.c\nnew file mode 100644\nindex 0000000..c27e0f3\n--- /dev/null\n+++ b/builtin-rev-cache.c\n@@ -0,0 +1,206 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+\n+/* porcelain for rev-cache.c */\n+static int handle_add(int argc, const char *argv[]) /* args beyond this command */\n+{\n+\tstruct rev_info revs;\n+\tstruct rev_cache_info rci;\n+\tchar dostdin = 0;\n+\tunsigned int flags = 0;\n+\tint i, retval;\n+\tunsigned char cache_sha1[20];\n+\tstruct commit_list *starts = 0, *ends = 0;\n+\tstruct commit *commit;\n+\n+\tinit_revisions(&revs, 0);\n+\tinit_rev_cache_info(&rci);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--stdin\"))\n+\t\t\tdostdin = 1;\n+\t\telse if (!strcmp(argv[i], \"--fresh\"))\n+\t\t\tstarts_from_slices(&revs, UNINTERESTING);\n+\t\telse if (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--legs\"))\n+\t\t\trci.legs = 1;\n+\t\telse if (!strcmp(argv[i], \"--no-objects\"))\n+\t\t\trci.objects = 0;\n+\t\telse if (!strcmp(argv[i], \"--all\")) {\n+\t\t\tconst char *args[2];\n+\t\t\tint argn = 0;\n+\n+\t\t\targs[argn++] = \"rev-list\";\n+\t\t\targs[argn++] = \"--all\";\n+\t\t\tsetup_revisions(argn, args, &revs, 0);\n+\t\t} else\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\tif (dostdin) {\n+\t\tchar line[1000];\n+\n+\t\tflags = 0;\n+\t\twhile (fgets(line, sizeof(line), stdin)) {\n+\t\t\tint len = strlen(line);\n+\t\t\twhile (len && (line[len - 1] == '\\n' || line[len - 1] == '\\r'))\n+\t\t\t\tline[--len] = 0;\n+\n+\t\t\tif (!len)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (!strcmp(line, \"--not\"))\n+\t\t\t\tflags ^= UNINTERESTING;\n+\t\t\telse\n+\t\t\t\thandle_revision_arg(line, &revs, flags, 1);\n+\t\t}\n+\t}\n+\n+\tretval = make_cache_slice(&rci, &revs, &starts, &ends, cache_sha1);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tprintf(\"%s\\n\", sha1_to_hex(cache_sha1));\n+\n+\tfprintf(stderr, \"endpoints:\\n\");\n+\twhile ((commit = pop_commit(&starts)))\n+\t\tfprintf(stderr, \"S %s\\n\", sha1_to_hex(commit->object.sha1));\n+\twhile ((commit = pop_commit(&ends)))\n+\t\tfprintf(stderr, \"E %s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\treturn 0;\n+}\n+\n+static int handle_walk(int argc, const char *argv[])\n+{\n+\tstruct commit *commit;\n+\tstruct rev_info revs;\n+\tstruct commit_list *queue, *work, **qp;\n+\tunsigned char *sha1p, *sha1pt;\n+\tunsigned long date = 0;\n+\tunsigned int flags = 0;\n+\tint retval, slop = 5, i;\n+\n+\tinit_revisions(&revs, 0);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--objects\"))\n+\t\t\trevs.tree_objects = revs.blob_objects = 1;\n+\t\telse\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\twork = 0;\n+\tsha1p = 0;\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tcommit = lookup_commit(revs.pending.objects[i].item->sha1);\n+\n+\t\tsha1pt = get_cache_slice(commit);\n+\t\tif (!sha1pt)\n+\t\t\tdie(\"%s: not in a cache slice\", sha1_to_hex(commit->object.sha1));\n+\n+\t\tif (!i)\n+\t\t\tsha1p = sha1pt;\n+\t\telse if (sha1p != sha1pt)\n+\t\t\tdie(\"walking porcelain is /per/ cache slice; commits cannot be spread out amoung several\");\n+\n+\t\tinsert_by_date(commit, &work);\n+\t}\n+\n+\tif (!sha1p)\n+\t\tdie(\"nothing to traverse!\");\n+\n+\tqueue = 0;\n+\tqp = &queue;\n+\tcommit = pop_commit(&work);\n+\tretval = traverse_cache_slice(&revs, sha1p, commit, &date, &slop, &qp, &work);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tfprintf(stderr, \"queue:\\n\");\n+\twhile ((commit = pop_commit(&queue)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"work:\\n\");\n+\twhile ((commit = pop_commit(&work)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"pending:\\n\");\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tstruct object *obj = revs.pending.objects[i].item;\n+\n+\t\t/* unfortunately, despite our careful generation, object duplication *is* a possibility...\n+\t\t * (eg. same object introduced into two different branches) */\n+\t\tif (obj->flags & SEEN)\n+\t\t\tcontinue;\n+\n+\t\tprintf(\"%s\\n\", sha1_to_hex(revs.pending.objects[i].item->sha1));\n+\t\tobj->flags |= SEEN;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int handle_help(void)\n+{\n+\tchar *usage = \"\\\n+usage:\\n\\\n+git-rev-cache COMMAND [options] [<commit-id>...]\\n\\\n+commands:\\n\\\n+  add    - add revisions to the cache.  reads commit ids from stdin, \\n\\\n+           formatted as: START START ... --not END END ...\\n\\\n+           options:\\n\\\n+            --all             use all branch heads as starts\\n\\\n+            --fresh           exclude everything already in a cache slice\\n\\\n+            --stdin           also read commit ids from stdin (same form as cmd)\\n\\\n+            --legs            ensure branch is entirely self-contained\\n\\\n+            --no-objects      don't add non-commit objects to slice\\n\\\n+  walk   - walk a cache slice based on set of commits; formatted as add\\n\\\n+           options:\\n\\\n+           --objects          include non-commit objects in traversals\\n\\\n+  fuse   - coalesce cache slices into a single cache.\\n\\\n+           options:\\n\\\n+            --all             include all objects in repository\\n\\\n+            --no-objects      don't add non-commit objects to slice\\n\\\n+            --ignore-size[=N] ignore slices of size >= N; defaults to ~5MB\\n\\\n+  index  - regnerate the cache index.\";\n+\n+\tputs(usage);\n+\n+\treturn 0;\n+}\n+\n+int cmd_rev_cache(int argc, const char *argv[], const char *prefix)\n+{\n+\tconst char *arg;\n+\tint r;\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\tif (argc > 1)\n+\t\targ = argv[1];\n+\telse\n+\t\targ = \"\";\n+\n+\targc -= 2;\n+\targv += 2;\n+\tif (!strcmp(arg, \"add\"))\n+\t\tr = handle_add(argc, argv);\n+\telse if (!strcmp(arg, \"walk\"))\n+\t\tr = handle_walk(argc, argv);\n+\telse\n+\t\treturn handle_help();\n+\n+\tfprintf(stderr, \"final return value: %d\\n\", r);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 20427d2..00ecc9c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -87,6 +87,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\n extern int cmd_rerere(int argc, const char **argv, const char *prefix);\n extern int cmd_reset(int argc, const char **argv, const char *prefix);\n+extern int cmd_rev_cache(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_list(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n extern int cmd_revert(int argc, const char **argv, const char *prefix);\ndiff --git a/commit.c b/commit.c\nindex e2bcbe8..682e7a7 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -252,6 +252,8 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)\n \titem->tree = lookup_tree(parent);\n \tbufptr += 46; /* \"tree \" + \"hex sha1\" + \"\\n\" */\n \tpptr = &item->parents;\n+\twhile (pop_commit(pptr))\n+\t\t; /* clear anything from cache */\n \n \tgraft = lookup_commit_graft(item->object.sha1);\n \twhile (bufptr + 48 < tail && !memcmp(bufptr, \"parent \", 7)) {\ndiff --git a/git.c b/git.c\nindex 4588a8b..7105c74 100644\n--- a/git.c\n+++ b/git.c\n@@ -342,6 +342,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"repo-config\", cmd_config },\n \t\t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t\t{ \"reset\", cmd_reset, RUN_SETUP },\n+\t\t{ \"rev-cache\", cmd_rev_cache, RUN_SETUP },\n \t\t{ \"rev-list\", cmd_rev_list, RUN_SETUP },\n \t\t{ \"rev-parse\", cmd_rev_parse },\n \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/rev-cache.c b/rev-cache.c\nnew file mode 100644\nindex 0000000..a01db87\n--- /dev/null\n+++ b/rev-cache.c\n@@ -0,0 +1,1169 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+#include \"run-command.h\"\n+\n+/* list resembles pack index format */\n+static uint32_t fanout[0xff + 2];\n+\n+static unsigned char *idx_map;\n+static int idx_size;\n+static struct rc_index_header idx_head;\n+static unsigned char *idx_caches;\n+static char no_idx;\n+\n+static struct strbuf *acc_buffer;\n+\n+#define SLOP\t\t\t5\n+\n+/* initialization */\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst)\n+{\n+\tstatic struct rc_index_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->is_start = !!(src->flags & 0x80);\n+\tdst->cache_index = src->flags & 0x7f;\n+\tdst->pos = ntohl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst)\n+{\n+\tstatic struct rc_index_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\thashcpy(dst->sha1, src->sha1);\n+\tdst->flags = (unsigned char)src->is_start << 7 | (unsigned char)src->cache_index;\n+\tdst->pos = htonl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst)\n+{\n+\tstatic struct rc_object_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->type = src->flags >> 5;\n+\tdst->is_end = !!(src->flags & 0x10);\n+\tdst->is_start = !!(src->flags & 0x08);\n+\tdst->uninteresting = !!(src->flags & 0x04);\n+\tdst->include = !!(src->flags & 0x02);\n+\tdst->flag = !!(src->flags & 0x01);\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->size_size = src->sizes >> 5;\n+\tdst->padding = src->sizes & 0x1f;\n+\n+\tdst->date = ntohl(src->date);\n+\tdst->path = ntohs(src->path);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst)\n+{\n+\tstatic struct rc_object_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->flags  = (unsigned char)src->type << 5;\n+\tdst->flags |= (unsigned char)src->is_end << 4;\n+\tdst->flags |= (unsigned char)src->is_start << 3;\n+\tdst->flags |= (unsigned char)src->uninteresting << 2;\n+\tdst->flags |= (unsigned char)src->include << 1;\n+\tdst->flags |= (unsigned char)src->flag;\n+\n+\thashcpy(dst->sha1, src->sha1);\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->sizes  = (unsigned char)src->size_size << 5;\n+\tdst->sizes |= (unsigned char)src->padding;\n+\n+\tdst->date = htonl(src->date);\n+\tdst->path = htons(src->path);\n+\n+\treturn dst;\n+}\n+\n+static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)\n+{\n+\tstruct rc_index_header whead;\n+\tint i, index = sizeof(struct rc_index_header);\n+\n+\tmemcpy(&whead, map, sizeof(struct rc_index_header));\n+\tif (memcmp(whead.signature, \"REVINDEX\", 8) || whead.version != SUPPORTED_REVINDEX_VERSION)\n+\t\treturn -1;\n+\n+\tmemcpy(head->signature, \"REVINDEX\", 8);\n+\thead->version = whead.version;\n+\thead->ofs_objects = ntohl(whead.ofs_objects);\n+\thead->object_nr = ntohl(whead.object_nr);\n+\thead->cache_nr = whead.cache_nr;\n+\thead->max_date = ntohl(whead.max_date);\n+\n+\tif (len < index + head->cache_nr * 20 + 0x100 * sizeof(uint32_t))\n+\t\treturn -2;\n+\n+\t*caches = xmalloc(head->cache_nr * 20);\n+\tmemcpy(*caches, map + index, head->cache_nr * 20);\n+\tindex += head->cache_nr * 20;\n+\n+\tmemcpy(fanout, map + index, 0x100 * sizeof(uint32_t));\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = ntohl(fanout[i]);\n+\tfanout[0x100] = len;\n+\n+\treturn 0;\n+}\n+\n+/* added in init_index */\n+static void cleanup_cache_slices(void)\n+{\n+\tif (idx_map) {\n+\t\tfree(idx_caches);\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+}\n+\n+static int init_index(void)\n+{\n+\tint fd;\n+\tstruct stat fi;\n+\n+\tfd = open(git_path(\"rev-cache/index\"), O_RDONLY);\n+\tif (fd == -1 || fstat(fd, &fi))\n+\t\tgoto end;\n+\tif (fi.st_size < sizeof(struct rc_index_header))\n+\t\tgoto end;\n+\n+\tidx_size = fi.st_size;\n+\tidx_map = xmmap(0, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tif (idx_map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_index_head(idx_map, fi.st_size, &idx_head, fanout, &idx_caches))\n+\t\tgoto end;\n+\n+\tatexit(cleanup_cache_slices);\n+\n+\treturn 0;\n+\n+end:\n+\tidx_map = 0;\n+\tno_idx = 1;\n+\treturn -1;\n+}\n+\n+/* this assumes index is already loaded */\n+static struct rc_index_entry_ondisk *search_index_1(unsigned char *sha1)\n+{\n+\tint start, end, starti, endi, i, len, r;\n+\tstruct rc_index_entry_ondisk *ie;\n+\n+\tif (!idx_map)\n+\t\treturn 0;\n+\n+\t/* binary search */\n+\tstart = fanout[(int)sha1[0]];\n+\tend = fanout[(int)sha1[0] + 1];\n+\tlen = (end - start) / sizeof(struct rc_index_entry_ondisk);\n+\tif (!len || len * sizeof(struct rc_index_entry_ondisk) != end - start)\n+\t\treturn 0;\n+\n+\tstarti = 0;\n+\tendi = len - 1;\n+\tfor (;;) {\n+\t\ti = (endi + starti) / 2;\n+\t\tie = (struct rc_index_entry_ondisk *)(idx_map + start + i * sizeof(struct rc_index_entry_ondisk));\n+\t\tr = hashcmp(sha1, ie->sha1);\n+\n+\t\tif (r) {\n+\t\t\tif (starti + 1 == endi) {\n+\t\t\t\tstarti++;\n+\t\t\t\tcontinue;\n+\t\t\t} else if (starti == endi)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (r > 0)\n+\t\t\t\tstarti = i;\n+\t\t\telse /* r < 0 */\n+\t\t\t\tendi = i;\n+\t\t} else\n+\t\t\treturn ie;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static struct rc_index_entry *search_index(unsigned char *sha1)\n+{\n+\tstruct rc_index_entry_ondisk *ied = search_index_1(sha1);\n+\n+\tif (ied)\n+\t\treturn from_disked_rc_index_entry(ied, 0);\n+\n+\treturn 0;\n+}\n+\n+unsigned char *get_cache_slice(struct commit *commit)\n+{\n+\tstruct rc_index_entry *ie;\n+\n+\tif (!idx_map) {\n+\t\tif (no_idx)\n+\t\t\treturn 0;\n+\t\tinit_index();\n+\t}\n+\n+\tif (commit->date > idx_head.max_date)\n+\t\treturn 0;\n+\n+\tie = search_index(commit->object.sha1);\n+\tif (ie && ie->cache_index < idx_head.cache_nr)\n+\t\treturn idx_caches + ie->cache_index * 20;\n+\n+\treturn 0;\n+}\n+\n+\n+/* traversal */\n+\n+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)\n+{\n+\tstruct rc_index_entry *iep;\n+\tstruct rc_object_entry *oep;\n+\tstruct commit_list *prev, *wp, **wpp;\n+\tint retval;\n+\n+\tiep = search_index(commit->object.sha1), 0;\n+\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t/* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,\n+\t * but we might as well initialize it while we're at it */\n+\toep->include = 1;\n+\toep->uninteresting = !!(commit->object.flags & UNINTERESTING);\n+\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\tretval = iep->pos;\n+\n+\t/* include any others in the work array */\n+\tprev = 0;\n+\twpp = work;\n+\twp = *work;\n+\twhile (wp) {\n+\t\tstruct object *obj = &wp->item->object;\n+\t\tstruct commit *co;\n+\n+\t\t/* is this in our cache slice? */\n+\t\tiep = search_index(obj->sha1);\n+\t\tif (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {\n+\t\t\tprev = wp;\n+\t\t\twp = wp->next;\n+\t\t\twpp = &wp;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (iep->pos < retval)\n+\t\t\tretval = iep->pos;\n+\t\n+\t\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t\t/* mark this for later */\n+\t\toep->include = 1;\n+\t\toep->uninteresting = !!(obj->flags & UNINTERESTING);\n+\t\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\n+\t\t/* remove from work list */\n+\t\tco = pop_commit(wpp);\n+\t\twp = *wpp;\n+\t\tif (prev)\n+\t\t\tprev->next = wp;\n+\t}\n+\n+\treturn retval;\n+}\n+\n+#define IPATH\t\t\t\t0x40\n+#define UPATH\t\t\t\t0x80\n+\n+#define GET_COUNT(x)\t\t((x) & 0x3f)\n+#define SET_COUNT(x, s)\t\t((x) = ((x) & ~0x3f) | ((s) & 0x3f))\n+\n+static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *map,\n+\tstruct rev_info *revs, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tstruct commit_list *insert_cache = 0;\n+\tstruct commit **last_objects, *co;\n+\tint i, total_path_nr = head->path_nr, retval = -1;\n+\tchar consume_children = 0;\n+\tunsigned char *paths;\n+\n+\ti = setup_traversal(head, map, commit, work);\n+\tif (i < 0)\n+\t\treturn -1;\n+\n+\tpaths = xcalloc(total_path_nr, sizeof(uint16_t));\n+\tlast_objects = xcalloc(total_path_nr, sizeof(struct commit *));\n+\n+\t/* i already set */\n+\twhile (i < head->size) {\n+\t\tstruct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tint path = entry->path;\n+\t\tstruct object *obj;\n+\t\tint index = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);\n+\n+\t\t/* add extra objects if necessary */\n+\t\tif (entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tconsume_children = 0;\n+\n+\t\tif (path >= total_path_nr)\n+\t\t\tgoto end;\n+\n+\t\t/* in one of our branches?\n+\t\t * uninteresting trumps interesting */\n+\t\tif (entry->include)\n+\t\t\tpaths[path] |= entry->uninteresting ? UPATH : IPATH;\n+\t\telse if (!paths[path])\n+\t\t\tcontinue;\n+\n+\t\t/* date stuff */\n+\t\tif (revs->max_age != -1 && entry->date < revs->max_age)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\t/* lookup object */\n+\t\tco = lookup_commit(entry->sha1);\n+\t\tobj = &co->object;\n+\n+\t\tif (obj->flags & UNINTERESTING)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\tif ((paths[path] & IPATH) && (paths[path] & UPATH)) {\n+\t\t\tpaths[path] = UPATH;\n+\n+\t\t\t/* mark edge */\n+\t\t\tif (last_objects[path]) {\n+\t\t\t\tparse_commit(last_objects[path]);\n+\n+\t\t\t\tlast_objects[path]->object.flags &= ~FACE_VALUE;\n+\t\t\t\tlast_objects[path] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* now we gotta re-assess the whole interesting thing... */\n+\t\tentry->uninteresting = !!(paths[path] & UPATH);\n+\n+\t\t/* first close paths */\n+\t\tif (entry->split_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE(entry->merge_nr);\n+\n+\t\t\tfor (j = 0; j < entry->split_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\t/* boundary commit? */\n+\t\t\t\tif ((paths[p] & IPATH) && entry->uninteresting) {\n+\t\t\t\t\tif (last_objects[p]) {\n+\t\t\t\t\t\tparse_commit(last_objects[p]);\n+\n+\t\t\t\t\t\tlast_objects[p]->object.flags &= ~FACE_VALUE;\n+\t\t\t\t\t\tlast_objects[p] = 0;\n+\t\t\t\t\t}\n+\t\t\t\t} else if (last_objects[p] && !last_objects[p]->object.parsed)\n+\t\t\t\t\tcommit_list_insert(co, &last_objects[p]->parents);\n+\n+\t\t\t\t/* can't close a merge path until all are parents have been encountered */\n+\t\t\t\tif (GET_COUNT(paths[p])) {\n+\t\t\t\t\tSET_COUNT(paths[p], GET_COUNT(paths[p]) - 1);\n+\n+\t\t\t\t\tif (GET_COUNT(paths[p]))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tpaths[p] = 0;\n+\t\t\t\tlast_objects[p] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* make topo relations */\n+\t\tif (last_objects[path] && !last_objects[path]->object.parsed)\n+\t\t\tcommit_list_insert(co, &last_objects[path]->parents);\n+\n+\t\t/* initialize commit */\n+\t\tif (!entry->is_end) {\n+\t\t\tco->date = entry->date;\n+\t\t\tobj->flags |= ADDED | FACE_VALUE;\n+\t\t} else\n+\t\t\tparse_commit(co);\n+\n+\t\tobj->flags |= SEEN;\n+\n+\t\tif (entry->uninteresting)\n+\t\t\tobj->flags |= UNINTERESTING;\n+\n+\t\t/* we need to know what the edges are */\n+\t\tlast_objects[path] = co;\n+\n+\t\t/* add to list */\n+\t\tif (!(obj->flags & UNINTERESTING) || revs->show_all) {\n+\t\t\tif (entry->is_end)\n+\t\t\t\tinsert_by_date_cached(co, work, insert_cache, &insert_cache);\n+\t\t\telse\n+\t\t\t\t*queue = &commit_list_insert(co, *queue)->next;\n+\n+\t\t\t/* add children to list as well */\n+\t\t\tif (obj->flags & UNINTERESTING)\n+\t\t\t\tconsume_children = 0;\n+\t\t\telse\n+\t\t\t\tconsume_children = 1;\n+\t\t}\n+\n+\t\t/* open parents */\n+\t\tif (entry->merge_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk);\n+\t\t\tchar flag = entry->uninteresting ? UPATH : IPATH;\n+\n+\t\t\tfor (j = 0; j < entry->merge_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\tif (paths[p] & flag)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tpaths[p] |= flag;\n+\t\t\t}\n+\n+\t\t\t/* make sure we don't use this path before all our parents have had their say */\n+\t\t\tSET_COUNT(paths[path], entry->merge_nr);\n+\t\t}\n+\n+\t}\n+\n+\tretval = 0;\n+\n+end:\n+\tfree(paths);\n+\tfree(last_objects);\n+\n+\treturn retval;\n+}\n+\n+static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)\n+{\n+\tint t;\n+\n+\tmemcpy(head, map, sizeof(struct rc_slice_header));\n+\thead->ofs_objects = ntohl(head->ofs_objects);\n+\thead->object_nr = ntohl(head->object_nr);\n+\thead->size = ntohl(head->size);\n+\thead->path_nr = ntohs(head->path_nr);\n+\n+\tif (memcmp(head->signature, \"REVCACHE\", 8))\n+\t\treturn -1;\n+\tif (head->version != SUPPORTED_REVCACHE_VERSION)\n+\t\treturn -2;\n+\tif (hashcmp(head->sha1, cache_sha1))\n+\t\treturn -3;\n+\tt = sizeof(struct rc_slice_header);\n+\tif (t != head->ofs_objects || t >= len)\n+\t\treturn -4;\n+\n+\thead->size = len;\n+\n+\treturn 0;\n+}\n+\n+int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tint fd = -1, retval = -3;\n+\tstruct stat fi;\n+\tstruct rc_slice_header head;\n+\tstruct rev_cache_info *rci;\n+\tunsigned char *map = MAP_FAILED;\n+\n+\t/* the index should've been loaded already to find cache_sha1, but it's good\n+\t * to be absolutely sure... */\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn -1;\n+\n+\t/* load options */\n+\trci = &revs->rev_cache_info;\n+\n+\tmemset(&head, 0, sizeof(struct rc_slice_header));\n+\n+\tfd = open(git_path(\"rev-cache/%s\", sha1_to_hex(cache_sha1)), O_RDONLY);\n+\tif (fd == -1)\n+\t\tgoto end;\n+\tif (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))\n+\t\tgoto end;\n+\n+\tmap = xmmap(0, fi.st_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))\n+\t\tgoto end;\n+\n+\tretval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);\n+\n+end:\n+\tif (map != MAP_FAILED)\n+\t\tmunmap(map, fi.st_size);\n+\tif (fd != -1)\n+\t\tclose(fd);\n+\n+\treturn retval;\n+}\n+\n+\n+\n+/* generation */\n+\n+struct path_track {\n+\tstruct commit *commit;\n+\tint path; /* for keeping track of children */\n+\n+\tstruct path_track *next, *prev;\n+};\n+\n+static unsigned char *paths;\n+static int path_nr = 1, path_sz;\n+\n+static struct path_track *path_track;\n+static struct path_track *path_track_alloc;\n+\n+#define PATH_IN_USE\t\t\t0x80 /* biggest bit we can get as a char */\n+\n+static int get_new_path(void)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i < path_nr; i++)\n+\t\tif (!paths[i])\n+\t\t\tbreak;\n+\n+\tif (i == path_nr) {\n+\t\tif (path_nr >= path_sz) {\n+\t\t\tpath_sz += 50;\n+\t\t\tpaths = xrealloc(paths, path_sz);\n+\t\t\tmemset(paths + path_sz - 50, 0, 50);\n+\t\t}\n+\t\tpath_nr++;\n+\t}\n+\n+\tpaths[i] = PATH_IN_USE;\n+\treturn i;\n+}\n+\n+static void remove_path_track(struct path_track **ppt, char total_free)\n+{\n+\tstruct path_track *t = *ppt;\n+\n+\tif (t->next)\n+\t\tt->next->prev = t->prev;\n+\tif (t->prev)\n+\t\tt->prev->next = t->next;\n+\n+\tt = t->next;\n+\n+\tif (total_free)\n+\t\tfree(*ppt);\n+\telse {\n+\t\t(*ppt)->next = path_track_alloc;\n+\t\tpath_track_alloc = *ppt;\n+\t}\n+\n+\t*ppt = t;\n+}\n+\n+static struct path_track *make_path_track(struct path_track **head, struct commit *commit)\n+{\n+\tstruct path_track *pt;\n+\n+\tif (path_track_alloc) {\n+\t\tpt = path_track_alloc;\n+\t\tpath_track_alloc = pt->next;\n+\t} else\n+\t\tpt = xmalloc(sizeof(struct path_track));\n+\n+\tmemset(pt, 0, sizeof(struct path_track));\n+\tpt->commit = commit;\n+\n+\tpt->next = *head;\n+\tif (*head)\n+\t\t(*head)->prev = pt;\n+\t*head = pt;\n+\n+\treturn pt;\n+}\n+\n+static void add_path_to_track(struct commit *commit, int path)\n+{\n+\tmake_path_track(&path_track, commit);\n+\tpath_track->path = path;\n+}\n+\n+static void handle_paths(struct commit *commit, struct rc_object_entry *object, struct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tint child_nr, parent_nr, open_parent_nr, this_path;\n+\tstruct commit_list *list;\n+\tstruct commit *first_parent;\n+\tstruct path_track **ppt, *pt;\n+\n+\t/* we can only re-use a closed path once all it's children have been encountered,\n+\t * as we need to keep track of commit boundaries */\n+\tppt = &path_track;\n+\tpt = *ppt;\n+\tchild_nr = 0;\n+\twhile (pt) {\n+\t\tif (pt->commit == commit) {\n+\t\t\tuint16_t write_path;\n+\n+\t\t\tif (paths[pt->path] != PATH_IN_USE)\n+\t\t\t\tpaths[pt->path]--;\n+\n+\t\t\t/* make sure we can handle this */\n+\t\t\tchild_nr++;\n+\t\t\tif (child_nr > 0x7f)\n+\t\t\t\tdie(\"%s: too many branches!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t\t\t/* add to split list */\n+\t\t\tobject->split_nr++;\n+\t\t\twrite_path = htons((uint16_t)pt->path);\n+\t\t\tstrbuf_add(split_str, &write_path, sizeof(uint16_t));\n+\n+\t\t\tremove_path_track(ppt, 0);\n+\t\t\tpt = *ppt;\n+\t\t} else {\n+\t\t\tpt = pt->next;\n+\t\t\tppt = &pt;\n+\t\t}\n+\t}\n+\n+\t/* initialize our self! */\n+\tif (!commit->indegree) {\n+\t\tcommit->indegree = get_new_path();\n+\t\tobject->is_start = 1;\n+\t}\n+\n+\tthis_path = commit->indegree;\n+\tpaths[this_path] = PATH_IN_USE;\n+\tobject->path = this_path;\n+\n+\t/* count interesting parents */\n+\tparent_nr = open_parent_nr = 0;\n+\tfirst_parent = 0;\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tif (list->item->object.flags & UNINTERESTING) {\n+\t\t\tobject->is_end = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tparent_nr++;\n+\t\tif (!list->item->indegree)\n+\t\t\topen_parent_nr++;\n+\t\tif (!first_parent)\n+\t\t\tfirst_parent = list->item;\n+\t}\n+\n+\tif (!parent_nr)\n+\t\treturn;\n+\n+\tif (parent_nr == 1 && open_parent_nr == 1) {\n+\t\tfirst_parent->indegree = this_path;\n+\t\treturn;\n+\t}\n+\n+\t/* bail out on obscene parent/child #s */\n+\tif (parent_nr > 0x7f)\n+\t\tdie(\"%s: too many parents in merge!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t/* make merge list */\n+\tobject->merge_nr = parent_nr;\n+\tpaths[this_path] = parent_nr;\n+\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tstruct commit *p = list->item;\n+\t\tuint16_t write_path;\n+\n+\t\tif (p->object.flags & UNINTERESTING)\n+\t\t\tcontinue;\n+\n+\t\t/* unfortunately due to boundary tracking we can't re-use merge paths\n+\t\t * (unable to guarantee last parent path = this -> last won't always be able to\n+\t\t * set this as a boundary object */\n+\t\tif (!p->indegree)\n+\t\t\tp->indegree = get_new_path();\n+\n+\t\twrite_path = htons((uint16_t)p->indegree);\n+\t\tstrbuf_add(merge_str, &write_path, sizeof(uint16_t));\n+\n+\t\t/* make sure path is properly ended */\n+\t\tadd_path_to_track(p, this_path);\n+\t}\n+\n+}\n+\n+\n+static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,\n+\tstruct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tstruct rc_object_entry object;\n+\n+\tif (!nothisone) {\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.sha1 = (unsigned char *)sha1;\n+\t\tobject.type = type;\n+\n+\t\tif (merge_str)\n+\t\t\tobject.merge_nr = merge_str->len / sizeof(uint16_t);\n+\t\tif (split_str)\n+\t\t\tobject.split_nr = split_str->len / sizeof(uint16_t);\n+\n+\t\tnothisone = &object;\n+\t}\n+\n+\tstrbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));\n+\n+\tif (merge_str && merge_str->len)\n+\t\tstrbuf_add(acc_buffer, merge_str->buf, merge_str->len);\n+\tif (split_str && split_str->len)\n+\t\tstrbuf_add(acc_buffer, split_str->buf, split_str->len);\n+\n+}\n+\n+static void init_revcache_directory(void)\n+{\n+\tstruct stat fi;\n+\n+\tif (stat(git_path(\"rev-cache\"), &fi) || !S_ISDIR(fi.st_mode))\n+\t\tif (mkdir(git_path(\"rev-cache\"), 0666))\n+\t\t\tdie(\"can't make rev-cache directory\");\n+\n+}\n+\n+void init_rev_cache_info(struct rev_cache_info *rci)\n+{\n+\trci->objects = 1;\n+\trci->legs = 0;\n+\trci->make_index = 1;\n+\n+\trci->add_to_pending = 1;\n+\n+\trci->ignore_size = 0;\n+}\n+\n+void maybe_fill_with_defaults(struct rev_cache_info *rci)\n+{\n+\tstatic struct rev_cache_info def_rci;\n+\n+\tif (rci)\n+\t\treturn;\n+\n+\tinit_rev_cache_info(&def_rci);\n+\trci = &def_rci;\n+}\n+\n+int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1)\n+{\n+\tstruct rev_info therevs;\n+\tstruct strbuf buffer, startlist, endlist;\n+\tstruct rc_slice_header head;\n+\tstruct commit *commit;\n+\tunsigned char sha1[20];\n+\tstruct strbuf merge_paths, split_paths;\n+\tint object_nr, total_sz, fd;\n+\tchar file[PATH_MAX], *newfile;\n+\tstruct rev_cache_info *trci;\n+\tgit_SHA_CTX ctx;\n+\n+\tmaybe_fill_with_defaults(rci);\n+\n+\tinit_revcache_directory();\n+\tstrcpy(file, git_path(\"rev-cache/XXXXXX\"));\n+\tfd = xmkstemp(file);\n+\n+\tstrbuf_init(&buffer, 0);\n+\tstrbuf_init(&startlist, 0);\n+\tstrbuf_init(&endlist, 0);\n+\tstrbuf_init(&merge_paths, 0);\n+\tstrbuf_init(&split_paths, 0);\n+\tacc_buffer = &buffer;\n+\n+\tif (!revs) {\n+\t\trevs = &therevs;\n+\t\tinit_revisions(revs, 0);\n+\n+\t\t/* we're gonna assume no one else has already traversed this... */\n+\t\twhile ((commit = pop_commit(starts)))\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\n+\t\twhile ((commit = pop_commit(ends))) {\n+\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\t\t}\n+\t}\n+\n+\t/* write head placeholder */\n+\tmemset(&head, 0, sizeof(head));\n+\thead.ofs_objects = htonl(sizeof(head));\n+\txwrite(fd, &head, sizeof(head));\n+\n+\t/* init revisions! */\n+\trevs->tree_objects = 1;\n+\trevs->blob_objects = 1;\n+\trevs->topo_order = 1;\n+\trevs->lifo = 1;\n+\n+\t/* re-use info from other caches if possible */\n+\ttrci = &revs->rev_cache_info;\n+\tinit_rev_cache_info(trci);\n+\ttrci->add_to_pending = 0;\n+\n+\tsetup_revisions(0, 0, revs, 0);\n+\tif (prepare_revision_walk(revs))\n+\t\tdie(\"died preparing revision walk\");\n+\n+\tobject_nr = total_sz = 0;\n+\twhile ((commit = get_revision(revs)) != 0) {\n+\t\tstruct rc_object_entry object;\n+\n+\t\tstrbuf_setlen(&merge_paths, 0);\n+\t\tstrbuf_setlen(&split_paths, 0);\n+\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.type = OBJ_COMMIT;\n+\t\tobject.date = commit->date;\n+\t\tobject.sha1 = commit->object.sha1;\n+\n+\t\thandle_paths(commit, &object, &merge_paths, &split_paths);\n+\n+\t\tif (object.is_end) {\n+\t\t\tstrbuf_add(&endlist, object.sha1, 20);\n+\t\t\tif (ends)\n+\t\t\t\tcommit_list_insert(commit, ends);\n+\t\t}\n+\t\t/* the two *aren't* mutually exclusive */\n+\t\tif (object.is_start) {\n+\t\t\tstrbuf_add(&startlist, object.sha1, 20);\n+\t\t\tif (starts)\n+\t\t\t\tcommit_list_insert(commit, starts);\n+\t\t}\n+\n+\t\tcommit->indegree = 0;\n+\n+\t\tadd_object_entry(0, 0, &object, &merge_paths, &split_paths);\n+\t\tobject_nr++;\n+\n+\t\t/* print every ~1MB or so */\n+\t\tif (buffer.len > 1000000) {\n+\t\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\t\ttotal_sz += buffer.len;\n+\n+\t\t\tstrbuf_setlen(&buffer, 0);\n+\t\t}\n+\t}\n+\n+\tif (buffer.len) {\n+\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\ttotal_sz += buffer.len;\n+\t}\n+\n+\t/* go ahead a free some stuff... */\n+\tstrbuf_release(&buffer);\n+\tstrbuf_release(&merge_paths);\n+\tstrbuf_release(&split_paths);\n+\tif (path_sz)\n+\t\tfree(paths);\n+\twhile (path_track_alloc)\n+\t\tremove_path_track(&path_track_alloc, 1);\n+\n+\t/* the meaning of the hash name is more or less irrelevant, it's the uniqueness that matters */\n+\tstrbuf_add(&endlist, startlist.buf, startlist.len);\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, endlist.buf, endlist.len);\n+\tgit_SHA1_Final(sha1, &ctx);\n+\n+\t/* now actually initialize header */\n+\tstrcpy(head.signature, \"REVCACHE\");\n+\thead.version = SUPPORTED_REVCACHE_VERSION;\n+\n+\thead.object_nr = htonl(object_nr);\n+\thead.size = htonl(ntohl(head.ofs_objects) + total_sz);\n+\thead.path_nr = htons(path_nr);\n+\thashcpy(head.sha1, sha1);\n+\n+\t/* some info! */\n+\tfprintf(stderr, \"objects: %d\\n\", object_nr);\n+\tfprintf(stderr, \"paths: %d\\n\", path_nr);\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\txwrite(fd, &head, sizeof(head));\n+\n+\tif (rci->make_index && make_cache_index(rci, sha1, fd, ntohl(head.size)) < 0)\n+\t\tdie(\"can't update index\");\n+\n+\tclose(fd);\n+\n+\tnewfile = git_path(\"rev-cache/%s\", sha1_to_hex(sha1));\n+\tif (rename(file, newfile))\n+\t\tdie(\"can't move temp file\");\n+\n+\t/* let our caller know what we've just made */\n+\tif (cache_sha1)\n+\t\thashcpy(cache_sha1, sha1);\n+\n+\tstrbuf_release(&endlist);\n+\tstrbuf_release(&startlist);\n+\n+\treturn 0;\n+}\n+\n+\n+static int index_sort_hash(const void *a, const void *b)\n+{\n+\treturn hashcmp(((struct rc_index_entry_ondisk *)a)->sha1, ((struct rc_index_entry_ondisk *)b)->sha1);\n+}\n+\n+static int write_cache_index(struct strbuf *body)\n+{\n+\tstruct rc_index_header whead;\n+\tstruct lock_file *lk;\n+\tint fd, i;\n+\n+\t/* clear index map if loaded */\n+\tif (idx_map) {\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+\tlk = xcalloc(sizeof(struct lock_file), 1);\n+\tfd = hold_lock_file_for_update(lk, git_path(\"rev-cache/index\"), 0);\n+\tif (fd < 0) {\n+\t\tfree(lk);\n+\t\treturn -1;\n+\t}\n+\n+\t/* endianness yay! */\n+\tmemset(&whead, 0, sizeof(whead));\n+\tmemcpy(whead.signature, \"REVINDEX\", 8);\n+\twhead.version = idx_head.version;\n+\twhead.ofs_objects = htonl(idx_head.ofs_objects);\n+\twhead.object_nr = htonl(idx_head.object_nr);\n+\twhead.cache_nr = idx_head.cache_nr;\n+\twhead.max_date = htonl(idx_head.max_date);\n+\n+\twrite(fd, &whead, sizeof(struct rc_index_header));\n+\twrite_in_full(fd, idx_caches, idx_head.cache_nr * 20);\n+\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = htonl(fanout[i]);\n+\twrite_in_full(fd, fanout, 0x100 * sizeof(uint32_t));\n+\n+\twrite_in_full(fd, body->buf, body->len);\n+\n+\tif (commit_lock_file(lk) < 0)\n+\t\treturn -2;\n+\n+\t/* lk freed by lockfile.c */\n+\n+\treturn 0;\n+}\n+\n+int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size)\n+{\n+\tstruct strbuf buffer;\n+\tint i, cache_index, cur;\n+\tunsigned char *map;\n+\tunsigned long max_date;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\tmap = xmmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\treturn -1;\n+\n+\tstrbuf_init(&buffer, 0);\n+\tif (idx_map) {\n+\t\tstrbuf_add(&buffer, idx_map + fanout[0], fanout[0x100] - fanout[0]);\n+\t} else {\n+\t\t/* not an update */\n+\t\tmemset(&idx_head, 0, sizeof(struct rc_index_header));\n+\t\tidx_caches = 0;\n+\n+\t\tstrcpy(idx_head.signature, \"REVINDEX\");\n+\t\tidx_head.version = SUPPORTED_REVINDEX_VERSION;\n+\t\tidx_head.ofs_objects = sizeof(struct rc_index_header) + 0x100 * sizeof(uint32_t);\n+\t}\n+\n+\t/* are we remaking a slice? */\n+\tfor (i = 0; i < idx_head.cache_nr; i++)\n+\t\tif (!hashcmp(idx_caches + i * 20, cache_sha1))\n+\t\t\tbreak;\n+\n+\tif (i == idx_head.cache_nr) {\n+\t\tcache_index = idx_head.cache_nr++;\n+\t\tidx_head.ofs_objects += 20;\n+\n+\t\tidx_caches = xrealloc(idx_caches, idx_head.cache_nr * 20);\n+\t\thashcpy(idx_caches + cache_index * 20, cache_sha1);\n+\t} else\n+\t\tcache_index = i;\n+\n+\ti = sizeof(struct rc_slice_header); /* offset */\n+\tmax_date = idx_head.max_date;\n+\twhile (i < size) {\n+\t\tstruct rc_index_entry index_entry, *entry;\n+\t\tstruct rc_index_entry_ondisk *disked_entry;\n+\t\tstruct rc_object_entry *object_entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tunsigned long date;\n+\t\tint off, pos = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(object_entry);\n+\n+\t\tif (object_entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\n+\t\t/* don't include ends; otherwise we'll find ourselves in loops */\n+\t\tif (object_entry->is_end)\n+\t\t\tcontinue;\n+\n+\t\t/* handle index duplication\n+\t\t * -> keep old copy unless new one is a start -- based on expected usage, older ones will be more\n+\t\t * likely to lead to greater slice traversals than new ones */\n+\t\tdate = object_entry->date;\n+\t\tif (date > idx_head.max_date) {\n+\t\t\tdisked_entry = 0;\n+\t\t\tif (date > max_date)\n+\t\t\t\tmax_date = date;\n+\t\t} else\n+\t\t\tdisked_entry = search_index_1(object_entry->sha1);\n+\n+\t\tif (disked_entry && !object_entry->is_start)\n+\t\t\tcontinue;\n+\t\telse if (disked_entry) {\n+\t\t\t/* mmm, pointer arithmetic... tasty */  /* (entry - idx_map = offset, so cast is valid) */\n+\t\t\toff = (unsigned int)((unsigned char *)disked_entry - idx_map) - fanout[0];\n+\t\t\tdisked_entry = (struct rc_index_entry_ondisk *)(buffer.buf + off);\n+\t\t\tentry = from_disked_rc_index_entry(disked_entry, 0);\n+\t\t} else\n+\t\t\tentry = &index_entry;\n+\n+\t\tmemset(entry, 0, sizeof(index_entry));\n+\t\tentry->sha1 = object_entry->sha1;\n+\t\tentry->is_start = object_entry->is_start;\n+\t\tentry->cache_index = cache_index;\n+\t\tentry->pos = pos;\n+\n+\t\tif (entry == &index_entry) {\n+\t\t\tstrbuf_add(&buffer, to_disked_rc_index_entry(entry, 0), sizeof(struct rc_index_entry_ondisk));\n+\t\t\tidx_head.object_nr++;\n+\t\t} else\n+\t\t\tto_disked_rc_index_entry(entry, disked_entry);\n+\n+\t}\n+\n+\tidx_head.max_date = max_date;\n+\tqsort(buffer.buf, buffer.len / sizeof(struct rc_index_entry_ondisk), sizeof(struct rc_index_entry_ondisk), index_sort_hash);\n+\n+\t/* generate fanout */\n+\tcur = 0x00;\n+\tfor (i = 0; i < buffer.len; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry_ondisk *entry = (struct rc_index_entry_ondisk *)(buffer.buf + i);\n+\n+\t\twhile (cur <= entry->sha1[0])\n+\t\t\tfanout[cur++] = i + idx_head.ofs_objects;\n+\t}\n+\n+\twhile (cur <= 0xff)\n+\t\tfanout[cur++] = idx_head.ofs_objects + buffer.len;\n+\n+\t/* BOOM! */\n+\tif (write_cache_index(&buffer))\n+\t\treturn -1;\n+\n+\tmunmap(map, size);\n+\tstrbuf_release(&buffer);\n+\n+\t/* idx_map is unloaded without cleanup_cache_slices(), so regardless of previous index existence\n+\t * we can still free this up */\n+\tfree(idx_caches);\n+\n+\treturn 0;\n+}\n+\n+\n+/* add start-commits from each cache slice (uninterestingness will be propogated) */\n+void starts_from_slices(struct rev_info *revs, unsigned int flags)\n+{\n+\tstruct commit *commit;\n+\tint i;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn;\n+\n+\tfor (i = idx_head.ofs_objects; i < idx_size; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry *entry = RC_OBTAIN_INDEX_ENTRY(idx_map + i);\n+\n+\t\tif (!entry->is_start)\n+\t\t\tcontinue;\n+\n+\t\tcommit = lookup_commit(entry->sha1);\n+\t\tif (!commit)\n+\t\t\tcontinue;\n+\n+\t\tcommit->object.flags |= flags;\n+\t\tadd_pending_object(revs, &commit->object, 0);\n+\t}\n+\n+}\ndiff --git a/rev-cache.h b/rev-cache.h\nnew file mode 100644\nindex 0000000..a471fbf\n--- /dev/null\n+++ b/rev-cache.h\n@@ -0,0 +1,107 @@\n+#ifndef REV_CACHE_H\n+#define REV_CACHE_H\n+\n+#define SUPPORTED_REVCACHE_VERSION \t\t1\n+#define SUPPORTED_REVINDEX_VERSION\t\t1\n+\n+#define RC_PATH_SIZE(x)\t(sizeof(uint16_t) * (x))\n+\n+#define RC_OBTAIN_OBJECT_ENTRY(p)\t\t\tfrom_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)\n+#define RC_OBTAIN_INDEX_ENTRY(p)\t\t\tfrom_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)\n+\n+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e)\t\t(sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)\n+\n+/* single index maps objects to cache files */\n+struct rc_index_header {\n+\tchar signature[8]; /* REVINDEX */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tunsigned char cache_nr;\n+\n+\tuint32_t max_date;\n+};\n+\n+struct rc_index_entry_ondisk {\n+\tunsigned char sha1[20];\n+\tunsigned char flags;\n+\tuint32_t pos;\n+};\n+\n+struct rc_index_entry {\n+\tunsigned char *sha1;\n+\tunsigned is_start : 1;\n+\tunsigned cache_index : 7;\n+\tuint32_t pos;\n+};\n+\n+\n+/* structure for actual cache file */\n+struct rc_slice_header {\n+\tchar signature[8]; /* REVCACHE */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tuint16_t path_nr;\n+\tuint32_t size;\n+\n+\tunsigned char sha1[20];\n+};\n+\n+struct rc_object_entry_ondisk {\n+\tunsigned char flags;\n+\tunsigned char sha1[20];\n+\n+\tunsigned char merge_nr;\n+\tunsigned char split_nr;\n+\tunsigned char sizes;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+};\n+\n+struct rc_object_entry {\n+\tunsigned type : 3;\n+\tunsigned is_end : 1;\n+\tunsigned is_start : 1;\n+\tunsigned uninteresting : 1;\n+\tunsigned include : 1;\n+\tunsigned flag : 1; /* unused */\n+\tunsigned char *sha1; /* 20 byte */\n+\n+\tunsigned char merge_nr; /* : 7 */\n+\tunsigned char split_nr; /* : 7 */\n+\tunsigned size_size : 3;\n+\tunsigned padding : 5;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+\n+\t/* merge paths */\n+\t/* split paths */\n+\t/* size */\n+};\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst);\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst);\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);\n+\n+extern unsigned char *get_cache_slice(struct commit *commit);\n+extern int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work);\n+\n+extern void init_rev_cache_info(struct rev_cache_info *rci);\n+extern int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1);\n+extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size);\n+\n+extern void starts_from_slices(struct rev_info *revs, unsigned int flags);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 9f5dac5..485bf72 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -432,7 +432,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \tcommit->object.flags |= TREESAME;\n }\n \n-static void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+void insert_by_date_cached(struct commit *p, struct commit_list **head,\n \t\t    struct commit_list *cached_base, struct commit_list **cache)\n {\n \tstruct commit_list *new_entry;\ndiff --git a/revision.h b/revision.h\nindex fb74492..e767a43 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -13,11 +13,25 @@\n #define CHILD_SHOWN\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define FACE_VALUE\t(1u<<9)\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n \n struct rev_info;\n struct log_info;\n \n+struct rev_cache_info {\n+\t/* generation flags */\n+\tunsigned objects : 1,\n+\t\tlegs : 1,\n+\t\tmake_index : 1;\n+\n+\t/* traversal flags */\n+\tunsigned add_to_pending : 1;\n+\n+\t/* fuse options */\n+\tunsigned int ignore_size;\n+};\n+\n struct rev_info {\n \t/* Starting list */\n \tstruct commit_list *commits;\n@@ -73,6 +87,10 @@ struct rev_info {\n \t\t\tdense_combined_merges:1,\n \t\t\talways_show_header:1;\n \n+\t/* rev-cache flags */\n+\tunsigned int for_pack:1,\n+\t\tdont_cache_me:1;\n+\n \t/* Format info */\n \tunsigned int\tshown_one:1,\n \t\t\tshow_merge:1,\n@@ -116,6 +134,9 @@ struct rev_info {\n \tstruct reflog_walk_info *reflog_info;\n \tstruct decoration children;\n \tstruct decoration merge_simplification;\n+\n+\t/* caching info, used ONLY by traverse_cache_slice */\n+\tstruct rev_cache_info rev_cache_info;\n };\n \n #define REV_TREE_SAME\t\t0\n@@ -167,4 +188,7 @@ enum commit_action {\n \n extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);\n \n+extern void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+\t\t    struct commit_list *cached_base, struct commit_list **cache);\n+\n #endif\ndiff --git a/t/t6015-rev-cache-list.sh b/t/t6015-rev-cache-list.sh\nnew file mode 100755\nindex 0000000..e7474fd\n--- /dev/null\n+++ b/t/t6015-rev-cache-list.sh\n@@ -0,0 +1,104 @@\n+#!/bin/sh\n+\n+test_description='git rev-cache tests'\n+. ./test-lib.sh\n+\n+test_cmp_sorted() {\n+\tgrep -io \"[a-f0-9]*\" $1 | sort >.tmpfile1 && \n+\tgrep -io \"[a-f0-9]*\" $2 | sort >.tmpfile2 && \n+\ttest_cmp .tmpfile1 .tmpfile2\n+}\n+\n+# we want a totally wacked out branch structure...\n+# we need branching and merging of sizes up through 3, tree \n+# addition/deletion, and enough branching to exercise path \n+# reuse\n+test_expect_success 'init repo' '\n+\techo bla >file && \n+\tgit add . && \n+\tgit commit -m \"bla\" && \n+\n+\tgit branch b1 && \n+\tgit checkout b1 && \n+\techo blu >file2 && \n+\tmkdir d1 && \n+\techo bang >d1/filed1 && \n+\tgit add . && \n+\tgit commit -m \"blu\" && \n+\n+\tgit checkout master && \n+\tgit branch b2 && \n+\tgit checkout b2 && \n+\techo kaplaa >>file && \n+\tgit commit -a -m \"kaplaa\" && \n+\n+\tgit checkout master && \n+\tmkdir smoke && \n+\techo omg >smoke/bong && \n+\tgit add . && \n+\tgit commit -m \"omg\" && \n+\n+\tgit branch b4 && \n+\tgit checkout b4 && \n+\techo shazam >file8 && \n+\tgit add . && \n+\tgit commit -m \"shazam\" && \n+\tgit merge -m \"merge b2\" b2 && \n+\n+\techo bam >smoke/pipe && \n+\tgit add .\n+\tgit commit -m \"bam\" && \n+\n+\tgit checkout master && \n+\techo pow >file7 && \n+\tgit add . && \n+\tgit commit -m \"pow\" && \n+\tgit merge -m \"merge b4\" b4 && \n+\n+\tgit checkout b1 && \n+\techo stuff >d1/filed1 && \n+\tgit commit -a -m \"stuff\" && \n+\n+\tgit branch b11 && \n+\tgit checkout b11 && \n+\techo wazzup >file3 &&\n+\tgit add file3 && \n+\tgit commit -m \"wazzup\" && \n+\n+\tgit checkout b1 && \n+\tmkdir d1/d2 && \n+\techo lol >d1/d2/filed2 && \n+\tgit add . && \n+\tgit commit -m \"lol\" && \n+\n+\tgit checkout master && \n+\tgit merge -m \"triple merge\" b1 b11 && \n+\tgit rm -r d1 &&  \n+\tgit commit -a -m \"oh noes\"\n+'\n+\n+git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited\n+git-rev-list HEAD >proper_commit_list\n+\n+test_expect_success 'make cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err && \n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+test_expect_success 'remake cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err && \n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+#check core mechanics and rev-list hook for commits\n+test_expect_success 'test rev-caches walker directly (limited)' '\n+\tgit-rev-cache walk HEAD --not HEAD~3 >list && \n+\ttest_cmp_sorted list proper_commit_list_limited\n+'\n+\n+test_expect_success 'test rev-caches walker directly (unlimited)' '\n+\tgit-rev-cache walk HEAD >list && \n+\ttest_cmp_sorted list proper_commit_list\n+'\n+\n+test_done\n-- \ntg: (64d5fe0..) t/revcache/basic (depends on: master)\n"},{"id":"121085","messageId":"op.uyuwkovjtdk399@sirnot.private","threadId":"20630","inReplyTo":"op.uys3qlsjtdk399@sirnot.private","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Nick Edelen","fromEmail":"sirnot@gmail.com","sentAt":"2009-08-18T11:51:50Z","receivedAt":"2009-08-18T11:51:50Z","isPatch":true,"sender":{"key":"sirnot@gmail.com","avatar":null},"body":"Second in the revision cache series, this particular patch provides:\n - minimal API: caching only commit topo data\n - minimal porcelain: add and walk cache slices\n - appropriate tests\n\nSigned-off-by: Nick Edelen <sirnot@gmail.com>\n\n---\n Makefile                  |    2 +\n builtin-rev-cache.c       |  207 ++++++++\n builtin.h                 |    1 +\n commit.c                  |    2 +\n git.c                     |    1 +\n rev-cache.c               | 1171 +++++++++++++++++++++++++++++++++++++++++++++\n rev-cache.h               |  107 ++++\n revision.c                |    2 +-\n revision.h                |   26 +-\n t/t6015-rev-cache-list.sh |  104 ++++\n 10 files changed, 1621 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex e6df8ec..1ceb514 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -537,6 +537,7 @@ LIB_OBJS += reflog-walk.o\n LIB_OBJS += refs.o\n LIB_OBJS += remote.o\n LIB_OBJS += rerere.o\n+LIB_OBJS += rev-cache.o\n LIB_OBJS += revision.o\n LIB_OBJS += run-command.o\n LIB_OBJS += server-info.o\n@@ -627,6 +628,7 @@ BUILTIN_OBJS += builtin-reflog.o\n BUILTIN_OBJS += builtin-remote.o\n BUILTIN_OBJS += builtin-rerere.o\n BUILTIN_OBJS += builtin-reset.o\n+BUILTIN_OBJS += builtin-rev-cache.o\n BUILTIN_OBJS += builtin-rev-list.o\n BUILTIN_OBJS += builtin-rev-parse.o\n BUILTIN_OBJS += builtin-revert.o\ndiff --git a/builtin-rev-cache.c b/builtin-rev-cache.c\nnew file mode 100644\nindex 0000000..6eb7065\n--- /dev/null\n+++ b/builtin-rev-cache.c\n@@ -0,0 +1,207 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+\n+/* porcelain for rev-cache.c */\n+static int handle_add(int argc, const char *argv[]) /* args beyond this command */\n+{\n+\tstruct rev_info revs;\n+\tstruct rev_cache_info rci;\n+\tchar dostdin = 0;\n+\tunsigned int flags = 0;\n+\tint i, retval;\n+\tunsigned char cache_sha1[20];\n+\tstruct commit_list *starts = 0, *ends = 0;\n+\tstruct commit *commit;\n+\n+\tinit_revisions(&revs, 0);\n+\tinit_rev_cache_info(&rci);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--stdin\"))\n+\t\t\tdostdin = 1;\n+\t\telse if (!strcmp(argv[i], \"--fresh\") || !strcmp(argv[i], \"--incremental\"))\n+\t\t\tstarts_from_slices(&revs, UNINTERESTING);\n+\t\telse if (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--legs\"))\n+\t\t\trci.legs = 1;\n+\t\telse if (!strcmp(argv[i], \"--no-objects\"))\n+\t\t\trci.objects = 0;\n+\t\telse if (!strcmp(argv[i], \"--all\")) {\n+\t\t\tconst char *args[2];\n+\t\t\tint argn = 0;\n+\n+\t\t\targs[argn++] = \"rev-list\";\n+\t\t\targs[argn++] = \"--all\";\n+\t\t\tsetup_revisions(argn, args, &revs, 0);\n+\t\t} else\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\tif (dostdin) {\n+\t\tchar line[1000];\n+\n+\t\tflags = 0;\n+\t\twhile (fgets(line, sizeof(line), stdin)) {\n+\t\t\tint len = strlen(line);\n+\t\t\twhile (len && (line[len - 1] == '\\n' || line[len - 1] == '\\r'))\n+\t\t\t\tline[--len] = 0;\n+\n+\t\t\tif (!len)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (!strcmp(line, \"--not\"))\n+\t\t\t\tflags ^= UNINTERESTING;\n+\t\t\telse\n+\t\t\t\thandle_revision_arg(line, &revs, flags, 1);\n+\t\t}\n+\t}\n+\n+\tretval = make_cache_slice(&rci, &revs, &starts, &ends, cache_sha1);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tprintf(\"%s\\n\", sha1_to_hex(cache_sha1));\n+\n+\tfprintf(stderr, \"endpoints:\\n\");\n+\twhile ((commit = pop_commit(&starts)))\n+\t\tfprintf(stderr, \"S %s\\n\", sha1_to_hex(commit->object.sha1));\n+\twhile ((commit = pop_commit(&ends)))\n+\t\tfprintf(stderr, \"E %s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\treturn 0;\n+}\n+\n+static int handle_walk(int argc, const char *argv[])\n+{\n+\tstruct commit *commit;\n+\tstruct rev_info revs;\n+\tstruct commit_list *queue, *work, **qp;\n+\tunsigned char *sha1p, *sha1pt;\n+\tunsigned long date = 0;\n+\tunsigned int flags = 0;\n+\tint retval, slop = 5, i;\n+\n+\tinit_revisions(&revs, 0);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--objects\"))\n+\t\t\trevs.tree_objects = revs.blob_objects = 1;\n+\t\telse\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\twork = 0;\n+\tsha1p = 0;\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tcommit = lookup_commit(revs.pending.objects[i].item->sha1);\n+\n+\t\tsha1pt = get_cache_slice(commit);\n+\t\tif (!sha1pt)\n+\t\t\tdie(\"%s: not in a cache slice\", sha1_to_hex(commit->object.sha1));\n+\n+\t\tif (!i)\n+\t\t\tsha1p = sha1pt;\n+\t\telse if (sha1p != sha1pt)\n+\t\t\tdie(\"walking porcelain is /per/ cache slice; commits cannot be spread out amoung several\");\n+\n+\t\tinsert_by_date(commit, &work);\n+\t}\n+\n+\tif (!sha1p)\n+\t\tdie(\"nothing to traverse!\");\n+\n+\tqueue = 0;\n+\tqp = &queue;\n+\tcommit = pop_commit(&work);\n+\tretval = traverse_cache_slice(&revs, sha1p, commit, &date, &slop, &qp, &work);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tfprintf(stderr, \"queue:\\n\");\n+\twhile ((commit = pop_commit(&queue)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"work:\\n\");\n+\twhile ((commit = pop_commit(&work)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"pending:\\n\");\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tstruct object *obj = revs.pending.objects[i].item;\n+\n+\t\t/* unfortunately, despite our careful generation, object duplication *is* a possibility...\n+\t\t * (eg. same object introduced into two different branches) */\n+\t\tif (obj->flags & SEEN)\n+\t\t\tcontinue;\n+\n+\t\tprintf(\"%s\\n\", sha1_to_hex(revs.pending.objects[i].item->sha1));\n+\t\tobj->flags |= SEEN;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int handle_help(void)\n+{\n+\tchar *usage = \"\\\n+usage:\\n\\\n+git-rev-cache COMMAND [options] [<commit-id>...]\\n\\\n+commands:\\n\\\n+  add    - add revisions to the cache.  reads commit ids from stdin, \\n\\\n+           formatted as: START START ... --not END END ...\\n\\\n+           options:\\n\\\n+            --all                  use all branch heads as starts\\n\\\n+            --fresh/--incremental  exclude everything already in a cache slice\\n\\\n+            --stdin                also read commit ids from stdin (same form\\n\\\n+                                   as cmd)\\n\\\n+            --legs                 ensure branch is entirely self-contained\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+  walk   - walk a cache slice based on set of commits; formatted as add\\n\\\n+           options:\\n\\\n+           --objects               include non-commit objects in traversals\\n\\\n+  fuse   - coalesce cache slices into a single cache.\\n\\\n+           options:\\n\\\n+            --all                  include all objects in repository\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+            --ignore-size[=N]      ignore slices of size >= N; defaults to ~5MB\\n\\\n+  index  - regnerate the cache index.\";\n+\n+\tputs(usage);\n+\n+\treturn 0;\n+}\n+\n+int cmd_rev_cache(int argc, const char *argv[], const char *prefix)\n+{\n+\tconst char *arg;\n+\tint r;\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\tif (argc > 1)\n+\t\targ = argv[1];\n+\telse\n+\t\targ = \"\";\n+\n+\targc -= 2;\n+\targv += 2;\n+\tif (!strcmp(arg, \"add\"))\n+\t\tr = handle_add(argc, argv);\n+\telse if (!strcmp(arg, \"walk\"))\n+\t\tr = handle_walk(argc, argv);\n+\telse\n+\t\treturn handle_help();\n+\n+\tfprintf(stderr, \"final return value: %d\\n\", r);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 20427d2..00ecc9c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -87,6 +87,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\n extern int cmd_rerere(int argc, const char **argv, const char *prefix);\n extern int cmd_reset(int argc, const char **argv, const char *prefix);\n+extern int cmd_rev_cache(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_list(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n extern int cmd_revert(int argc, const char **argv, const char *prefix);\ndiff --git a/commit.c b/commit.c\nindex e2bcbe8..682e7a7 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -252,6 +252,8 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)\n \titem->tree = lookup_tree(parent);\n \tbufptr += 46; /* \"tree \" + \"hex sha1\" + \"\\n\" */\n \tpptr = &item->parents;\n+\twhile (pop_commit(pptr))\n+\t\t; /* clear anything from cache */\n \n \tgraft = lookup_commit_graft(item->object.sha1);\n \twhile (bufptr + 48 < tail && !memcmp(bufptr, \"parent \", 7)) {\ndiff --git a/git.c b/git.c\nindex 4588a8b..7105c74 100644\n--- a/git.c\n+++ b/git.c\n@@ -342,6 +342,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"repo-config\", cmd_config },\n \t\t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t\t{ \"reset\", cmd_reset, RUN_SETUP },\n+\t\t{ \"rev-cache\", cmd_rev_cache, RUN_SETUP },\n \t\t{ \"rev-list\", cmd_rev_list, RUN_SETUP },\n \t\t{ \"rev-parse\", cmd_rev_parse },\n \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/rev-cache.c b/rev-cache.c\nnew file mode 100644\nindex 0000000..8951cdf\n--- /dev/null\n+++ b/rev-cache.c\n@@ -0,0 +1,1171 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+#include \"run-command.h\"\n+\n+/* list resembles pack index format */\n+static uint32_t fanout[0xff + 2];\n+\n+static unsigned char *idx_map;\n+static int idx_size;\n+static struct rc_index_header idx_head;\n+static unsigned char *idx_caches;\n+static char no_idx;\n+\n+static struct strbuf *acc_buffer;\n+\n+#define SLOP\t\t\t5\n+\n+/* initialization */\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst)\n+{\n+\tstatic struct rc_index_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->is_start = !!(src->flags & 0x80);\n+\tdst->cache_index = src->flags & 0x7f;\n+\tdst->pos = ntohl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst)\n+{\n+\tstatic struct rc_index_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->flags = (unsigned char)src->is_start << 7 | (unsigned char)src->cache_index;\n+\tdst->pos = htonl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst)\n+{\n+\tstatic struct rc_object_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->type = src->flags >> 5;\n+\tdst->is_end = !!(src->flags & 0x10);\n+\tdst->is_start = !!(src->flags & 0x08);\n+\tdst->uninteresting = !!(src->flags & 0x04);\n+\tdst->include = !!(src->flags & 0x02);\n+\tdst->flag = !!(src->flags & 0x01);\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->size_size = src->sizes >> 5;\n+\tdst->padding = src->sizes & 0x1f;\n+\n+\tdst->date = ntohl(src->date);\n+\tdst->path = ntohs(src->path);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst)\n+{\n+\tstatic struct rc_object_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->flags  = (unsigned char)src->type << 5;\n+\tdst->flags |= (unsigned char)src->is_end << 4;\n+\tdst->flags |= (unsigned char)src->is_start << 3;\n+\tdst->flags |= (unsigned char)src->uninteresting << 2;\n+\tdst->flags |= (unsigned char)src->include << 1;\n+\tdst->flags |= (unsigned char)src->flag;\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->sizes  = (unsigned char)src->size_size << 5;\n+\tdst->sizes |= (unsigned char)src->padding;\n+\n+\tdst->date = htonl(src->date);\n+\tdst->path = htons(src->path);\n+\n+\treturn dst;\n+}\n+\n+static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)\n+{\n+\tstruct rc_index_header whead;\n+\tint i, index = sizeof(struct rc_index_header);\n+\n+\tmemcpy(&whead, map, sizeof(struct rc_index_header));\n+\tif (memcmp(whead.signature, \"REVINDEX\", 8) || whead.version != SUPPORTED_REVINDEX_VERSION)\n+\t\treturn -1;\n+\n+\tmemcpy(head->signature, \"REVINDEX\", 8);\n+\thead->version = whead.version;\n+\thead->ofs_objects = ntohl(whead.ofs_objects);\n+\thead->object_nr = ntohl(whead.object_nr);\n+\thead->cache_nr = whead.cache_nr;\n+\thead->max_date = ntohl(whead.max_date);\n+\n+\tif (len < index + head->cache_nr * 20 + 0x100 * sizeof(uint32_t))\n+\t\treturn -2;\n+\n+\t*caches = xmalloc(head->cache_nr * 20);\n+\tmemcpy(*caches, map + index, head->cache_nr * 20);\n+\tindex += head->cache_nr * 20;\n+\n+\tmemcpy(fanout, map + index, 0x100 * sizeof(uint32_t));\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = ntohl(fanout[i]);\n+\tfanout[0x100] = len;\n+\n+\treturn 0;\n+}\n+\n+/* added in init_index */\n+static void cleanup_cache_slices(void)\n+{\n+\tif (idx_map) {\n+\t\tfree(idx_caches);\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+}\n+\n+static int init_index(void)\n+{\n+\tint fd;\n+\tstruct stat fi;\n+\n+\tfd = open(git_path(\"rev-cache/index\"), O_RDONLY);\n+\tif (fd == -1 || fstat(fd, &fi))\n+\t\tgoto end;\n+\tif (fi.st_size < sizeof(struct rc_index_header))\n+\t\tgoto end;\n+\n+\tidx_size = fi.st_size;\n+\tidx_map = xmmap(0, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tif (idx_map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_index_head(idx_map, fi.st_size, &idx_head, fanout, &idx_caches))\n+\t\tgoto end;\n+\n+\tatexit(cleanup_cache_slices);\n+\n+\treturn 0;\n+\n+end:\n+\tidx_map = 0;\n+\tno_idx = 1;\n+\treturn -1;\n+}\n+\n+/* this assumes index is already loaded */\n+static struct rc_index_entry_ondisk *search_index_1(unsigned char *sha1)\n+{\n+\tint start, end, starti, endi, i, len, r;\n+\tstruct rc_index_entry_ondisk *ie;\n+\n+\tif (!idx_map)\n+\t\treturn 0;\n+\n+\t/* binary search */\n+\tstart = fanout[(int)sha1[0]];\n+\tend = fanout[(int)sha1[0] + 1];\n+\tlen = (end - start) / sizeof(struct rc_index_entry_ondisk);\n+\tif (!len || len * sizeof(struct rc_index_entry_ondisk) != end - start)\n+\t\treturn 0;\n+\n+\tstarti = 0;\n+\tendi = len - 1;\n+\tfor (;;) {\n+\t\ti = (endi + starti) / 2;\n+\t\tie = (struct rc_index_entry_ondisk *)(idx_map + start + i * sizeof(struct rc_index_entry_ondisk));\n+\t\tr = hashcmp(sha1, ie->sha1);\n+\n+\t\tif (r) {\n+\t\t\tif (starti + 1 == endi) {\n+\t\t\t\tstarti++;\n+\t\t\t\tcontinue;\n+\t\t\t} else if (starti == endi)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (r > 0)\n+\t\t\t\tstarti = i;\n+\t\t\telse /* r < 0 */\n+\t\t\t\tendi = i;\n+\t\t} else\n+\t\t\treturn ie;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static struct rc_index_entry *search_index(unsigned char *sha1)\n+{\n+\tstruct rc_index_entry_ondisk *ied = search_index_1(sha1);\n+\n+\tif (ied)\n+\t\treturn from_disked_rc_index_entry(ied, 0);\n+\n+\treturn 0;\n+}\n+\n+unsigned char *get_cache_slice(struct commit *commit)\n+{\n+\tstruct rc_index_entry *ie;\n+\n+\tif (!idx_map) {\n+\t\tif (no_idx)\n+\t\t\treturn 0;\n+\t\tinit_index();\n+\t}\n+\n+\tif (commit->date > idx_head.max_date)\n+\t\treturn 0;\n+\n+\tie = search_index(commit->object.sha1);\n+\tif (ie && ie->cache_index < idx_head.cache_nr)\n+\t\treturn idx_caches + ie->cache_index * 20;\n+\n+\treturn 0;\n+}\n+\n+\n+/* traversal */\n+\n+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)\n+{\n+\tstruct rc_index_entry *iep;\n+\tstruct rc_object_entry *oep;\n+\tstruct commit_list *prev, *wp, **wpp;\n+\tint retval;\n+\n+\tiep = search_index(commit->object.sha1), 0;\n+\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t/* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,\n+\t * but we might as well initialize it while we're at it */\n+\toep->include = 1;\n+\toep->uninteresting = !!(commit->object.flags & UNINTERESTING);\n+\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\tretval = iep->pos;\n+\n+\t/* include any others in the work array */\n+\tprev = 0;\n+\twpp = work;\n+\twp = *work;\n+\twhile (wp) {\n+\t\tstruct object *obj = &wp->item->object;\n+\t\tstruct commit *co;\n+\n+\t\t/* is this in our cache slice? */\n+\t\tiep = search_index(obj->sha1);\n+\t\tif (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {\n+\t\t\tprev = wp;\n+\t\t\twp = wp->next;\n+\t\t\twpp = &wp;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (iep->pos < retval)\n+\t\t\tretval = iep->pos;\n+\n+\t\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t\t/* mark this for later */\n+\t\toep->include = 1;\n+\t\toep->uninteresting = !!(obj->flags & UNINTERESTING);\n+\t\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\n+\t\t/* remove from work list */\n+\t\tco = pop_commit(wpp);\n+\t\twp = *wpp;\n+\t\tif (prev)\n+\t\t\tprev->next = wp;\n+\t}\n+\n+\treturn retval;\n+}\n+\n+#define IPATH\t\t\t\t0x40\n+#define UPATH\t\t\t\t0x80\n+\n+#define GET_COUNT(x)\t\t((x) & 0x3f)\n+#define SET_COUNT(x, s)\t\t((x) = ((x) & ~0x3f) | ((s) & 0x3f))\n+\n+static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *map,\n+\tstruct rev_info *revs, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tstruct commit_list *insert_cache = 0;\n+\tstruct commit **last_objects, *co;\n+\tint i, total_path_nr = head->path_nr, retval = -1;\n+\tchar consume_children = 0;\n+\tunsigned char *paths;\n+\n+\ti = setup_traversal(head, map, commit, work);\n+\tif (i < 0)\n+\t\treturn -1;\n+\n+\tpaths = xcalloc(total_path_nr, sizeof(uint16_t));\n+\tlast_objects = xcalloc(total_path_nr, sizeof(struct commit *));\n+\n+\t/* i already set */\n+\twhile (i < head->size) {\n+\t\tstruct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tint path = entry->path;\n+\t\tstruct object *obj;\n+\t\tint index = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);\n+\n+\t\t/* add extra objects if necessary */\n+\t\tif (entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tconsume_children = 0;\n+\n+\t\tif (path >= total_path_nr)\n+\t\t\tgoto end;\n+\n+\t\t/* in one of our branches?\n+\t\t * uninteresting trumps interesting */\n+\t\tif (entry->include)\n+\t\t\tpaths[path] |= entry->uninteresting ? UPATH : IPATH;\n+\t\telse if (!paths[path])\n+\t\t\tcontinue;\n+\n+\t\t/* date stuff */\n+\t\tif (revs->max_age != -1 && entry->date < revs->max_age)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\t/* lookup object */\n+\t\tco = lookup_commit(entry->sha1);\n+\t\tobj = &co->object;\n+\n+\t\tif (obj->flags & UNINTERESTING)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\tif ((paths[path] & IPATH) && (paths[path] & UPATH)) {\n+\t\t\tpaths[path] = UPATH;\n+\n+\t\t\t/* mark edge */\n+\t\t\tif (last_objects[path]) {\n+\t\t\t\tparse_commit(last_objects[path]);\n+\n+\t\t\t\tlast_objects[path]->object.flags &= ~FACE_VALUE;\n+\t\t\t\tlast_objects[path] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* now we gotta re-assess the whole interesting thing... */\n+\t\tentry->uninteresting = !!(paths[path] & UPATH);\n+\n+\t\t/* first close paths */\n+\t\tif (entry->split_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE(entry->merge_nr);\n+\n+\t\t\tfor (j = 0; j < entry->split_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\t/* boundary commit? */\n+\t\t\t\tif ((paths[p] & IPATH) && entry->uninteresting) {\n+\t\t\t\t\tif (last_objects[p]) {\n+\t\t\t\t\t\tparse_commit(last_objects[p]);\n+\n+\t\t\t\t\t\tlast_objects[p]->object.flags &= ~FACE_VALUE;\n+\t\t\t\t\t\tlast_objects[p] = 0;\n+\t\t\t\t\t}\n+\t\t\t\t} else if (last_objects[p] && !last_objects[p]->object.parsed)\n+\t\t\t\t\tcommit_list_insert(co, &last_objects[p]->parents);\n+\n+\t\t\t\t/* can't close a merge path until all are parents have been encountered */\n+\t\t\t\tif (GET_COUNT(paths[p])) {\n+\t\t\t\t\tSET_COUNT(paths[p], GET_COUNT(paths[p]) - 1);\n+\n+\t\t\t\t\tif (GET_COUNT(paths[p]))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tpaths[p] = 0;\n+\t\t\t\tlast_objects[p] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* make topo relations */\n+\t\tif (last_objects[path] && !last_objects[path]->object.parsed)\n+\t\t\tcommit_list_insert(co, &last_objects[path]->parents);\n+\n+\t\t/* initialize commit */\n+\t\tif (!entry->is_end) {\n+\t\t\tco->date = entry->date;\n+\t\t\tobj->flags |= ADDED | FACE_VALUE;\n+\t\t} else\n+\t\t\tparse_commit(co);\n+\n+\t\tobj->flags |= SEEN;\n+\n+\t\tif (entry->uninteresting)\n+\t\t\tobj->flags |= UNINTERESTING;\n+\n+\t\t/* we need to know what the edges are */\n+\t\tlast_objects[path] = co;\n+\n+\t\t/* add to list */\n+\t\tif (!(obj->flags & UNINTERESTING) || revs->show_all) {\n+\t\t\tif (entry->is_end)\n+\t\t\t\tinsert_by_date_cached(co, work, insert_cache, &insert_cache);\n+\t\t\telse\n+\t\t\t\t*queue = &commit_list_insert(co, *queue)->next;\n+\n+\t\t\t/* add children to list as well */\n+\t\t\tif (obj->flags & UNINTERESTING)\n+\t\t\t\tconsume_children = 0;\n+\t\t\telse\n+\t\t\t\tconsume_children = 1;\n+\t\t}\n+\n+\t\t/* open parents */\n+\t\tif (entry->merge_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk);\n+\t\t\tchar flag = entry->uninteresting ? UPATH : IPATH;\n+\n+\t\t\tfor (j = 0; j < entry->merge_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\tif (paths[p] & flag)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tpaths[p] |= flag;\n+\t\t\t}\n+\n+\t\t\t/* make sure we don't use this path before all our parents have had their say */\n+\t\t\tSET_COUNT(paths[path], entry->merge_nr);\n+\t\t}\n+\n+\t}\n+\n+\tretval = 0;\n+\n+end:\n+\tfree(paths);\n+\tfree(last_objects);\n+\n+\treturn retval;\n+}\n+\n+static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)\n+{\n+\tint t;\n+\n+\tmemcpy(head, map, sizeof(struct rc_slice_header));\n+\thead->ofs_objects = ntohl(head->ofs_objects);\n+\thead->object_nr = ntohl(head->object_nr);\n+\thead->size = ntohl(head->size);\n+\thead->path_nr = ntohs(head->path_nr);\n+\n+\tif (memcmp(head->signature, \"REVCACHE\", 8))\n+\t\treturn -1;\n+\tif (head->version != SUPPORTED_REVCACHE_VERSION)\n+\t\treturn -2;\n+\tif (hashcmp(head->sha1, cache_sha1))\n+\t\treturn -3;\n+\tt = sizeof(struct rc_slice_header);\n+\tif (t != head->ofs_objects || t >= len)\n+\t\treturn -4;\n+\n+\thead->size = len;\n+\n+\treturn 0;\n+}\n+\n+int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tint fd = -1, retval = -3;\n+\tstruct stat fi;\n+\tstruct rc_slice_header head;\n+\tstruct rev_cache_info *rci;\n+\tunsigned char *map = MAP_FAILED;\n+\n+\t/* the index should've been loaded already to find cache_sha1, but it's good\n+\t * to be absolutely sure... */\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn -1;\n+\n+\t/* load options */\n+\trci = &revs->rev_cache_info;\n+\n+\tmemset(&head, 0, sizeof(struct rc_slice_header));\n+\n+\tfd = open(git_path(\"rev-cache/%s\", sha1_to_hex(cache_sha1)), O_RDONLY);\n+\tif (fd == -1)\n+\t\tgoto end;\n+\tif (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))\n+\t\tgoto end;\n+\n+\tmap = xmmap(0, fi.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))\n+\t\tgoto end;\n+\n+\tretval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);\n+\n+end:\n+\tif (map != MAP_FAILED)\n+\t\tmunmap(map, fi.st_size);\n+\tif (fd != -1)\n+\t\tclose(fd);\n+\n+\treturn retval;\n+}\n+\n+\n+\n+/* generation */\n+\n+struct path_track {\n+\tstruct commit *commit;\n+\tint path; /* for keeping track of children */\n+\n+\tstruct path_track *next, *prev;\n+};\n+\n+static unsigned char *paths;\n+static int path_nr = 1, path_sz;\n+\n+static struct path_track *path_track;\n+static struct path_track *path_track_alloc;\n+\n+#define PATH_IN_USE\t\t\t0x80 /* biggest bit we can get as a char */\n+\n+static int get_new_path(void)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i < path_nr; i++)\n+\t\tif (!paths[i])\n+\t\t\tbreak;\n+\n+\tif (i == path_nr) {\n+\t\tif (path_nr >= path_sz) {\n+\t\t\tpath_sz += 50;\n+\t\t\tpaths = xrealloc(paths, path_sz);\n+\t\t\tmemset(paths + path_sz - 50, 0, 50);\n+\t\t}\n+\t\tpath_nr++;\n+\t}\n+\n+\tpaths[i] = PATH_IN_USE;\n+\treturn i;\n+}\n+\n+static void remove_path_track(struct path_track **ppt, char total_free)\n+{\n+\tstruct path_track *t = *ppt;\n+\n+\tif (t->next)\n+\t\tt->next->prev = t->prev;\n+\tif (t->prev)\n+\t\tt->prev->next = t->next;\n+\n+\tt = t->next;\n+\n+\tif (total_free)\n+\t\tfree(*ppt);\n+\telse {\n+\t\t(*ppt)->next = path_track_alloc;\n+\t\tpath_track_alloc = *ppt;\n+\t}\n+\n+\t*ppt = t;\n+}\n+\n+static struct path_track *make_path_track(struct path_track **head, struct commit *commit)\n+{\n+\tstruct path_track *pt;\n+\n+\tif (path_track_alloc) {\n+\t\tpt = path_track_alloc;\n+\t\tpath_track_alloc = pt->next;\n+\t} else\n+\t\tpt = xmalloc(sizeof(struct path_track));\n+\n+\tmemset(pt, 0, sizeof(struct path_track));\n+\tpt->commit = commit;\n+\n+\tpt->next = *head;\n+\tif (*head)\n+\t\t(*head)->prev = pt;\n+\t*head = pt;\n+\n+\treturn pt;\n+}\n+\n+static void add_path_to_track(struct commit *commit, int path)\n+{\n+\tmake_path_track(&path_track, commit);\n+\tpath_track->path = path;\n+}\n+\n+static void handle_paths(struct commit *commit, struct rc_object_entry *object, struct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tint child_nr, parent_nr, open_parent_nr, this_path;\n+\tstruct commit_list *list;\n+\tstruct commit *first_parent;\n+\tstruct path_track **ppt, *pt;\n+\n+\t/* we can only re-use a closed path once all it's children have been encountered,\n+\t * as we need to keep track of commit boundaries */\n+\tppt = &path_track;\n+\tpt = *ppt;\n+\tchild_nr = 0;\n+\twhile (pt) {\n+\t\tif (pt->commit == commit) {\n+\t\t\tuint16_t write_path;\n+\n+\t\t\tif (paths[pt->path] != PATH_IN_USE)\n+\t\t\t\tpaths[pt->path]--;\n+\n+\t\t\t/* make sure we can handle this */\n+\t\t\tchild_nr++;\n+\t\t\tif (child_nr > 0x7f)\n+\t\t\t\tdie(\"%s: too many branches!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t\t\t/* add to split list */\n+\t\t\tobject->split_nr++;\n+\t\t\twrite_path = htons((uint16_t)pt->path);\n+\t\t\tstrbuf_add(split_str, &write_path, sizeof(uint16_t));\n+\n+\t\t\tremove_path_track(ppt, 0);\n+\t\t\tpt = *ppt;\n+\t\t} else {\n+\t\t\tpt = pt->next;\n+\t\t\tppt = &pt;\n+\t\t}\n+\t}\n+\n+\t/* initialize our self! */\n+\tif (!commit->indegree) {\n+\t\tcommit->indegree = get_new_path();\n+\t\tobject->is_start = 1;\n+\t}\n+\n+\tthis_path = commit->indegree;\n+\tpaths[this_path] = PATH_IN_USE;\n+\tobject->path = this_path;\n+\n+\t/* count interesting parents */\n+\tparent_nr = open_parent_nr = 0;\n+\tfirst_parent = 0;\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tif (list->item->object.flags & UNINTERESTING) {\n+\t\t\tobject->is_end = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tparent_nr++;\n+\t\tif (!list->item->indegree)\n+\t\t\topen_parent_nr++;\n+\t\tif (!first_parent)\n+\t\t\tfirst_parent = list->item;\n+\t}\n+\n+\tif (!parent_nr)\n+\t\treturn;\n+\n+\tif (parent_nr == 1 && open_parent_nr == 1) {\n+\t\tfirst_parent->indegree = this_path;\n+\t\treturn;\n+\t}\n+\n+\t/* bail out on obscene parent/child #s */\n+\tif (parent_nr > 0x7f)\n+\t\tdie(\"%s: too many parents in merge!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t/* make merge list */\n+\tobject->merge_nr = parent_nr;\n+\tpaths[this_path] = parent_nr;\n+\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tstruct commit *p = list->item;\n+\t\tuint16_t write_path;\n+\n+\t\tif (p->object.flags & UNINTERESTING)\n+\t\t\tcontinue;\n+\n+\t\t/* unfortunately due to boundary tracking we can't re-use merge paths\n+\t\t * (unable to guarantee last parent path = this -> last won't always be able to\n+\t\t * set this as a boundary object */\n+\t\tif (!p->indegree)\n+\t\t\tp->indegree = get_new_path();\n+\n+\t\twrite_path = htons((uint16_t)p->indegree);\n+\t\tstrbuf_add(merge_str, &write_path, sizeof(uint16_t));\n+\n+\t\t/* make sure path is properly ended */\n+\t\tadd_path_to_track(p, this_path);\n+\t}\n+\n+}\n+\n+\n+static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,\n+\tstruct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tstruct rc_object_entry object;\n+\n+\tif (!nothisone) {\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.sha1 = (unsigned char *)sha1;\n+\t\tobject.type = type;\n+\n+\t\tif (merge_str)\n+\t\t\tobject.merge_nr = merge_str->len / sizeof(uint16_t);\n+\t\tif (split_str)\n+\t\t\tobject.split_nr = split_str->len / sizeof(uint16_t);\n+\n+\t\tnothisone = &object;\n+\t}\n+\n+\tstrbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));\n+\n+\tif (merge_str && merge_str->len)\n+\t\tstrbuf_add(acc_buffer, merge_str->buf, merge_str->len);\n+\tif (split_str && split_str->len)\n+\t\tstrbuf_add(acc_buffer, split_str->buf, split_str->len);\n+\n+}\n+\n+static void init_revcache_directory(void)\n+{\n+\tstruct stat fi;\n+\n+\tif (stat(git_path(\"rev-cache\"), &fi) || !S_ISDIR(fi.st_mode))\n+\t\tif (mkdir(git_path(\"rev-cache\"), 0777))\n+\t\t\tdie(\"can't make rev-cache directory\");\n+\n+}\n+\n+void init_rev_cache_info(struct rev_cache_info *rci)\n+{\n+\trci->objects = 1;\n+\trci->legs = 0;\n+\trci->make_index = 1;\n+\n+\trci->add_to_pending = 1;\n+\n+\trci->ignore_size = 0;\n+}\n+\n+void maybe_fill_with_defaults(struct rev_cache_info *rci)\n+{\n+\tstatic struct rev_cache_info def_rci;\n+\n+\tif (rci)\n+\t\treturn;\n+\n+\tinit_rev_cache_info(&def_rci);\n+\trci = &def_rci;\n+}\n+\n+int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1)\n+{\n+\tstruct rev_info therevs;\n+\tstruct strbuf buffer, startlist, endlist;\n+\tstruct rc_slice_header head;\n+\tstruct commit *commit;\n+\tunsigned char sha1[20];\n+\tstruct strbuf merge_paths, split_paths;\n+\tint object_nr, total_sz, fd;\n+\tchar file[PATH_MAX], *newfile;\n+\tstruct rev_cache_info *trci;\n+\tgit_SHA_CTX ctx;\n+\n+\tmaybe_fill_with_defaults(rci);\n+\n+\tinit_revcache_directory();\n+\tstrcpy(file, git_path(\"rev-cache/XXXXXX\"));\n+\tfd = xmkstemp(file);\n+\n+\tstrbuf_init(&buffer, 0);\n+\tstrbuf_init(&startlist, 0);\n+\tstrbuf_init(&endlist, 0);\n+\tstrbuf_init(&merge_paths, 0);\n+\tstrbuf_init(&split_paths, 0);\n+\tacc_buffer = &buffer;\n+\n+\tif (!revs) {\n+\t\trevs = &therevs;\n+\t\tinit_revisions(revs, 0);\n+\n+\t\t/* we're gonna assume no one else has already traversed this... */\n+\t\twhile ((commit = pop_commit(starts)))\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\n+\t\twhile ((commit = pop_commit(ends))) {\n+\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\t\t}\n+\t}\n+\n+\t/* write head placeholder */\n+\tmemset(&head, 0, sizeof(head));\n+\thead.ofs_objects = htonl(sizeof(head));\n+\txwrite(fd, &head, sizeof(head));\n+\n+\t/* init revisions! */\n+\trevs->tree_objects = 1;\n+\trevs->blob_objects = 1;\n+\trevs->topo_order = 1;\n+\trevs->lifo = 1;\n+\n+\t/* re-use info from other caches if possible */\n+\ttrci = &revs->rev_cache_info;\n+\tinit_rev_cache_info(trci);\n+\ttrci->add_to_pending = 0;\n+\n+\tsetup_revisions(0, 0, revs, 0);\n+\tif (prepare_revision_walk(revs))\n+\t\tdie(\"died preparing revision walk\");\n+\n+\tobject_nr = total_sz = 0;\n+\twhile ((commit = get_revision(revs)) != 0) {\n+\t\tstruct rc_object_entry object;\n+\n+\t\tstrbuf_setlen(&merge_paths, 0);\n+\t\tstrbuf_setlen(&split_paths, 0);\n+\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.type = OBJ_COMMIT;\n+\t\tobject.date = commit->date;\n+\t\tobject.sha1 = commit->object.sha1;\n+\n+\t\thandle_paths(commit, &object, &merge_paths, &split_paths);\n+\n+\t\tif (object.is_end) {\n+\t\t\tstrbuf_add(&endlist, object.sha1, 20);\n+\t\t\tif (ends)\n+\t\t\t\tcommit_list_insert(commit, ends);\n+\t\t}\n+\t\t/* the two *aren't* mutually exclusive */\n+\t\tif (object.is_start) {\n+\t\t\tstrbuf_add(&startlist, object.sha1, 20);\n+\t\t\tif (starts)\n+\t\t\t\tcommit_list_insert(commit, starts);\n+\t\t}\n+\n+\t\tcommit->indegree = 0;\n+\n+\t\tadd_object_entry(0, 0, &object, &merge_paths, &split_paths);\n+\t\tobject_nr++;\n+\n+\t\t/* print every ~1MB or so */\n+\t\tif (buffer.len > 1000000) {\n+\t\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\t\ttotal_sz += buffer.len;\n+\n+\t\t\tstrbuf_setlen(&buffer, 0);\n+\t\t}\n+\t}\n+\n+\tif (buffer.len) {\n+\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\ttotal_sz += buffer.len;\n+\t}\n+\n+\t/* go ahead a free some stuff... */\n+\tstrbuf_release(&buffer);\n+\tstrbuf_release(&merge_paths);\n+\tstrbuf_release(&split_paths);\n+\tif (path_sz)\n+\t\tfree(paths);\n+\twhile (path_track_alloc)\n+\t\tremove_path_track(&path_track_alloc, 1);\n+\n+\t/* the meaning of the hash name is more or less irrelevant, it's the uniqueness that matters */\n+\tstrbuf_add(&endlist, startlist.buf, startlist.len);\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, endlist.buf, endlist.len);\n+\tgit_SHA1_Final(sha1, &ctx);\n+\n+\t/* now actually initialize header */\n+\tstrcpy(head.signature, \"REVCACHE\");\n+\thead.version = SUPPORTED_REVCACHE_VERSION;\n+\n+\thead.object_nr = htonl(object_nr);\n+\thead.size = htonl(ntohl(head.ofs_objects) + total_sz);\n+\thead.path_nr = htons(path_nr);\n+\thashcpy(head.sha1, sha1);\n+\n+\t/* some info! */\n+\tfprintf(stderr, \"objects: %d\\n\", object_nr);\n+\tfprintf(stderr, \"paths: %d\\n\", path_nr);\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\txwrite(fd, &head, sizeof(head));\n+\n+\tif (rci->make_index && make_cache_index(rci, sha1, fd, ntohl(head.size)) < 0)\n+\t\tdie(\"can't update index\");\n+\n+\tclose(fd);\n+\n+\tnewfile = git_path(\"rev-cache/%s\", sha1_to_hex(sha1));\n+\tif (rename(file, newfile))\n+\t\tdie(\"can't move temp file\");\n+\n+\t/* let our caller know what we've just made */\n+\tif (cache_sha1)\n+\t\thashcpy(cache_sha1, sha1);\n+\n+\tstrbuf_release(&endlist);\n+\tstrbuf_release(&startlist);\n+\n+\treturn 0;\n+}\n+\n+\n+static int index_sort_hash(const void *a, const void *b)\n+{\n+\treturn hashcmp(((struct rc_index_entry_ondisk *)a)->sha1, ((struct rc_index_entry_ondisk *)b)->sha1);\n+}\n+\n+static int write_cache_index(struct strbuf *body)\n+{\n+\tstruct rc_index_header whead;\n+\tstruct lock_file *lk;\n+\tint fd, i;\n+\n+\t/* clear index map if loaded */\n+\tif (idx_map) {\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+\tlk = xcalloc(sizeof(struct lock_file), 1);\n+\tfd = hold_lock_file_for_update(lk, git_path(\"rev-cache/index\"), 0);\n+\tif (fd < 0) {\n+\t\tfree(lk);\n+\t\treturn -1;\n+\t}\n+\n+\t/* endianness yay! */\n+\tmemset(&whead, 0, sizeof(whead));\n+\tmemcpy(whead.signature, \"REVINDEX\", 8);\n+\twhead.version = idx_head.version;\n+\twhead.ofs_objects = htonl(idx_head.ofs_objects);\n+\twhead.object_nr = htonl(idx_head.object_nr);\n+\twhead.cache_nr = idx_head.cache_nr;\n+\twhead.max_date = htonl(idx_head.max_date);\n+\n+\twrite(fd, &whead, sizeof(struct rc_index_header));\n+\twrite_in_full(fd, idx_caches, idx_head.cache_nr * 20);\n+\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = htonl(fanout[i]);\n+\twrite_in_full(fd, fanout, 0x100 * sizeof(uint32_t));\n+\n+\twrite_in_full(fd, body->buf, body->len);\n+\n+\tif (commit_lock_file(lk) < 0)\n+\t\treturn -2;\n+\n+\t/* lk freed by lockfile.c */\n+\n+\treturn 0;\n+}\n+\n+int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size)\n+{\n+\tstruct strbuf buffer;\n+\tint i, cache_index, cur;\n+\tunsigned char *map;\n+\tunsigned long max_date;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\tmap = xmmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\treturn -1;\n+\n+\tstrbuf_init(&buffer, 0);\n+\tif (idx_map) {\n+\t\tstrbuf_add(&buffer, idx_map + fanout[0], fanout[0x100] - fanout[0]);\n+\t} else {\n+\t\t/* not an update */\n+\t\tmemset(&idx_head, 0, sizeof(struct rc_index_header));\n+\t\tidx_caches = 0;\n+\n+\t\tstrcpy(idx_head.signature, \"REVINDEX\");\n+\t\tidx_head.version = SUPPORTED_REVINDEX_VERSION;\n+\t\tidx_head.ofs_objects = sizeof(struct rc_index_header) + 0x100 * sizeof(uint32_t);\n+\t}\n+\n+\t/* are we remaking a slice? */\n+\tfor (i = 0; i < idx_head.cache_nr; i++)\n+\t\tif (!hashcmp(idx_caches + i * 20, cache_sha1))\n+\t\t\tbreak;\n+\n+\tif (i == idx_head.cache_nr) {\n+\t\tcache_index = idx_head.cache_nr++;\n+\t\tidx_head.ofs_objects += 20;\n+\n+\t\tidx_caches = xrealloc(idx_caches, idx_head.cache_nr * 20);\n+\t\thashcpy(idx_caches + cache_index * 20, cache_sha1);\n+\t} else\n+\t\tcache_index = i;\n+\n+\ti = sizeof(struct rc_slice_header); /* offset */\n+\tmax_date = idx_head.max_date;\n+\twhile (i < size) {\n+\t\tstruct rc_index_entry index_entry, *entry;\n+\t\tstruct rc_index_entry_ondisk *disked_entry;\n+\t\tstruct rc_object_entry *object_entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tunsigned long date;\n+\t\tint off, pos = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(object_entry);\n+\n+\t\tif (object_entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\n+\t\t/* don't include ends; otherwise we'll find ourselves in loops */\n+\t\tif (object_entry->is_end)\n+\t\t\tcontinue;\n+\n+\t\t/* handle index duplication\n+\t\t * -> keep old copy unless new one is a start -- based on expected usage, older ones will be more\n+\t\t * likely to lead to greater slice traversals than new ones */\n+\t\tdate = object_entry->date;\n+\t\tif (date > idx_head.max_date) {\n+\t\t\tdisked_entry = 0;\n+\t\t\tif (date > max_date)\n+\t\t\t\tmax_date = date;\n+\t\t} else\n+\t\t\tdisked_entry = search_index_1(object_entry->sha1);\n+\n+\t\tif (disked_entry && !object_entry->is_start)\n+\t\t\tcontinue;\n+\t\telse if (disked_entry) {\n+\t\t\t/* mmm, pointer arithmetic... tasty */  /* (entry - idx_map = offset, so cast is valid) */\n+\t\t\toff = (unsigned int)((unsigned char *)disked_entry - idx_map) - fanout[0];\n+\t\t\tdisked_entry = (struct rc_index_entry_ondisk *)(buffer.buf + off);\n+\t\t\tentry = from_disked_rc_index_entry(disked_entry, 0);\n+\t\t} else\n+\t\t\tentry = &index_entry;\n+\n+\t\tmemset(entry, 0, sizeof(index_entry));\n+\t\tentry->sha1 = object_entry->sha1;\n+\t\tentry->is_start = object_entry->is_start;\n+\t\tentry->cache_index = cache_index;\n+\t\tentry->pos = pos;\n+\n+\t\tif (entry == &index_entry) {\n+\t\t\tstrbuf_add(&buffer, to_disked_rc_index_entry(entry, 0), sizeof(struct rc_index_entry_ondisk));\n+\t\t\tidx_head.object_nr++;\n+\t\t} else\n+\t\t\tto_disked_rc_index_entry(entry, disked_entry);\n+\n+\t}\n+\n+\tidx_head.max_date = max_date;\n+\tqsort(buffer.buf, buffer.len / sizeof(struct rc_index_entry_ondisk), sizeof(struct rc_index_entry_ondisk), index_sort_hash);\n+\n+\t/* generate fanout */\n+\tcur = 0x00;\n+\tfor (i = 0; i < buffer.len; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry_ondisk *entry = (struct rc_index_entry_ondisk *)(buffer.buf + i);\n+\n+\t\twhile (cur <= entry->sha1[0])\n+\t\t\tfanout[cur++] = i + idx_head.ofs_objects;\n+\t}\n+\n+\twhile (cur <= 0xff)\n+\t\tfanout[cur++] = idx_head.ofs_objects + buffer.len;\n+\n+\t/* BOOM! */\n+\tif (write_cache_index(&buffer))\n+\t\treturn -1;\n+\n+\tmunmap(map, size);\n+\tstrbuf_release(&buffer);\n+\n+\t/* idx_map is unloaded without cleanup_cache_slices(), so regardless of previous index existence\n+\t * we can still free this up */\n+\tfree(idx_caches);\n+\n+\treturn 0;\n+}\n+\n+\n+/* add start-commits from each cache slice (uninterestingness will be propogated) */\n+void starts_from_slices(struct rev_info *revs, unsigned int flags)\n+{\n+\tstruct commit *commit;\n+\tint i;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn;\n+\n+\tfor (i = idx_head.ofs_objects; i < idx_size; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry *entry = RC_OBTAIN_INDEX_ENTRY(idx_map + i);\n+\n+\t\tif (!entry->is_start)\n+\t\t\tcontinue;\n+\n+\t\tcommit = lookup_commit(entry->sha1);\n+\t\tif (!commit)\n+\t\t\tcontinue;\n+\n+\t\tcommit->object.flags |= flags;\n+\t\tadd_pending_object(revs, &commit->object, 0);\n+\t}\n+\n+}\ndiff --git a/rev-cache.h b/rev-cache.h\nnew file mode 100644\nindex 0000000..a471fbf\n--- /dev/null\n+++ b/rev-cache.h\n@@ -0,0 +1,107 @@\n+#ifndef REV_CACHE_H\n+#define REV_CACHE_H\n+\n+#define SUPPORTED_REVCACHE_VERSION \t\t1\n+#define SUPPORTED_REVINDEX_VERSION\t\t1\n+\n+#define RC_PATH_SIZE(x)\t(sizeof(uint16_t) * (x))\n+\n+#define RC_OBTAIN_OBJECT_ENTRY(p)\t\t\tfrom_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)\n+#define RC_OBTAIN_INDEX_ENTRY(p)\t\t\tfrom_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)\n+\n+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e)\t\t(sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)\n+\n+/* single index maps objects to cache files */\n+struct rc_index_header {\n+\tchar signature[8]; /* REVINDEX */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tunsigned char cache_nr;\n+\n+\tuint32_t max_date;\n+};\n+\n+struct rc_index_entry_ondisk {\n+\tunsigned char sha1[20];\n+\tunsigned char flags;\n+\tuint32_t pos;\n+};\n+\n+struct rc_index_entry {\n+\tunsigned char *sha1;\n+\tunsigned is_start : 1;\n+\tunsigned cache_index : 7;\n+\tuint32_t pos;\n+};\n+\n+\n+/* structure for actual cache file */\n+struct rc_slice_header {\n+\tchar signature[8]; /* REVCACHE */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tuint16_t path_nr;\n+\tuint32_t size;\n+\n+\tunsigned char sha1[20];\n+};\n+\n+struct rc_object_entry_ondisk {\n+\tunsigned char flags;\n+\tunsigned char sha1[20];\n+\n+\tunsigned char merge_nr;\n+\tunsigned char split_nr;\n+\tunsigned char sizes;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+};\n+\n+struct rc_object_entry {\n+\tunsigned type : 3;\n+\tunsigned is_end : 1;\n+\tunsigned is_start : 1;\n+\tunsigned uninteresting : 1;\n+\tunsigned include : 1;\n+\tunsigned flag : 1; /* unused */\n+\tunsigned char *sha1; /* 20 byte */\n+\n+\tunsigned char merge_nr; /* : 7 */\n+\tunsigned char split_nr; /* : 7 */\n+\tunsigned size_size : 3;\n+\tunsigned padding : 5;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+\n+\t/* merge paths */\n+\t/* split paths */\n+\t/* size */\n+};\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst);\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst);\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);\n+\n+extern unsigned char *get_cache_slice(struct commit *commit);\n+extern int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work);\n+\n+extern void init_rev_cache_info(struct rev_cache_info *rci);\n+extern int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1);\n+extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size);\n+\n+extern void starts_from_slices(struct rev_info *revs, unsigned int flags);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 9f5dac5..485bf72 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -432,7 +432,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \tcommit->object.flags |= TREESAME;\n }\n \n-static void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+void insert_by_date_cached(struct commit *p, struct commit_list **head,\n \t\t    struct commit_list *cached_base, struct commit_list **cache)\n {\n \tstruct commit_list *new_entry;\ndiff --git a/revision.h b/revision.h\nindex fb74492..e767a43 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -13,11 +13,25 @@\n #define CHILD_SHOWN\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define FACE_VALUE\t(1u<<9)\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n \n struct rev_info;\n struct log_info;\n \n+struct rev_cache_info {\n+\t/* generation flags */\n+\tunsigned objects : 1,\n+\t\tlegs : 1,\n+\t\tmake_index : 1;\n+\n+\t/* traversal flags */\n+\tunsigned add_to_pending : 1;\n+\n+\t/* fuse options */\n+\tunsigned int ignore_size;\n+};\n+\n struct rev_info {\n \t/* Starting list */\n \tstruct commit_list *commits;\n@@ -73,6 +87,10 @@ struct rev_info {\n \t\t\tdense_combined_merges:1,\n \t\t\talways_show_header:1;\n \n+\t/* rev-cache flags */\n+\tunsigned int for_pack:1,\n+\t\tdont_cache_me:1;\n+\n \t/* Format info */\n \tunsigned int\tshown_one:1,\n \t\t\tshow_merge:1,\n@@ -116,6 +134,9 @@ struct rev_info {\n \tstruct reflog_walk_info *reflog_info;\n \tstruct decoration children;\n \tstruct decoration merge_simplification;\n+\n+\t/* caching info, used ONLY by traverse_cache_slice */\n+\tstruct rev_cache_info rev_cache_info;\n };\n \n #define REV_TREE_SAME\t\t0\n@@ -167,4 +188,7 @@ enum commit_action {\n \n extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);\n \n+extern void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+\t\t    struct commit_list *cached_base, struct commit_list **cache);\n+\n #endif\ndiff --git a/t/t6015-rev-cache-list.sh b/t/t6015-rev-cache-list.sh\nnew file mode 100755\nindex 0000000..95e00a8\n--- /dev/null\n+++ b/t/t6015-rev-cache-list.sh\n@@ -0,0 +1,104 @@\n+#!/bin/sh\n+\n+test_description='git rev-cache tests'\n+. ./test-lib.sh\n+\n+test_cmp_sorted() {\n+\tgrep -io \"[a-f0-9]*\" $1 | sort >.tmpfile1 &&\n+\tgrep -io \"[a-f0-9]*\" $2 | sort >.tmpfile2 &&\n+\ttest_cmp .tmpfile1 .tmpfile2\n+}\n+\n+# we want a totally wacked out branch structure...\n+# we need branching and merging of sizes up through 3, tree\n+# addition/deletion, and enough branching to exercise path\n+# reuse\n+test_expect_success 'init repo' '\n+\techo bla >file &&\n+\tgit add . &&\n+\tgit commit -m \"bla\" &&\n+\n+\tgit branch b1 &&\n+\tgit checkout b1 &&\n+\techo blu >file2 &&\n+\tmkdir d1 &&\n+\techo bang >d1/filed1 &&\n+\tgit add . &&\n+\tgit commit -m \"blu\" &&\n+\n+\tgit checkout master &&\n+\tgit branch b2 &&\n+\tgit checkout b2 &&\n+\techo kaplaa >>file &&\n+\tgit commit -a -m \"kaplaa\" &&\n+\n+\tgit checkout master &&\n+\tmkdir smoke &&\n+\techo omg >smoke/bong &&\n+\tgit add . &&\n+\tgit commit -m \"omg\" &&\n+\n+\tgit branch b4 &&\n+\tgit checkout b4 &&\n+\techo shazam >file8 &&\n+\tgit add . &&\n+\tgit commit -m \"shazam\" &&\n+\tgit merge -m \"merge b2\" b2 &&\n+\n+\techo bam >smoke/pipe &&\n+\tgit add .\n+\tgit commit -m \"bam\" &&\n+\n+\tgit checkout master &&\n+\techo pow >file7 &&\n+\tgit add . &&\n+\tgit commit -m \"pow\" &&\n+\tgit merge -m \"merge b4\" b4 &&\n+\n+\tgit checkout b1 &&\n+\techo stuff >d1/filed1 &&\n+\tgit commit -a -m \"stuff\" &&\n+\n+\tgit branch b11 &&\n+\tgit checkout b11 &&\n+\techo wazzup >file3 &&\n+\tgit add file3 &&\n+\tgit commit -m \"wazzup\" &&\n+\n+\tgit checkout b1 &&\n+\tmkdir d1/d2 &&\n+\techo lol >d1/d2/filed2 &&\n+\tgit add . &&\n+\tgit commit -m \"lol\" &&\n+\n+\tgit checkout master &&\n+\tgit merge -m \"triple merge\" b1 b11 &&\n+\tgit rm -r d1 && \n+\tgit commit -a -m \"oh noes\"\n+'\n+\n+git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited\n+git-rev-list HEAD >proper_commit_list\n+\n+test_expect_success 'make cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+test_expect_success 'remake cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+#check core mechanics and rev-list hook for commits\n+test_expect_success 'test rev-caches walker directly (limited)' '\n+\tgit-rev-cache walk HEAD --not HEAD~3 >list &&\n+\ttest_cmp_sorted list proper_commit_list_limited\n+'\n+\n+test_expect_success 'test rev-caches walker directly (unlimited)' '\n+\tgit-rev-cache walk HEAD >list &&\n+\ttest_cmp_sorted list proper_commit_list\n+'\n+\n+test_done\n-- \ntg: (64d5fe0..) t/revcache/basic (depends on: master)\n"},{"id":"121380","messageId":"op.uyzwxz0ptdk399@sirnot","threadId":"20630","inReplyTo":"op.uyuwkovjtdk399@sirnot.private","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Nick Edelen","fromEmail":"sirnot@gmail.com","sentAt":"2009-08-21T04:47:49Z","receivedAt":"2009-08-21T04:47:49Z","isPatch":true,"sender":{"key":"sirnot@gmail.com","avatar":null},"body":"Second in the revision cache series, this particular patch provides:\n - minimal API: caching only commit topo data\n - minimal porcelain: add and walk cache slices\n - appropriate tests\n\nSigned-off-by: Nick Edelen <sirnot@gmail.com>\n\n---\nfixed a whitespace error.\n\n Makefile                  |    2 +\n builtin-rev-cache.c       |  207 ++++++++\n builtin.h                 |    1 +\n commit.c                  |    2 +\n git.c                     |    1 +\n rev-cache.c               | 1171 +++++++++++++++++++++++++++++++++++++++++++++\n rev-cache.h               |  107 ++++\n revision.c                |    2 +-\n revision.h                |   26 +-\n t/t6015-rev-cache-list.sh |  104 ++++\n 10 files changed, 1621 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex e6df8ec..1ceb514 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -537,6 +537,7 @@ LIB_OBJS += reflog-walk.o\n LIB_OBJS += refs.o\n LIB_OBJS += remote.o\n LIB_OBJS += rerere.o\n+LIB_OBJS += rev-cache.o\n LIB_OBJS += revision.o\n LIB_OBJS += run-command.o\n LIB_OBJS += server-info.o\n@@ -627,6 +628,7 @@ BUILTIN_OBJS += builtin-reflog.o\n BUILTIN_OBJS += builtin-remote.o\n BUILTIN_OBJS += builtin-rerere.o\n BUILTIN_OBJS += builtin-reset.o\n+BUILTIN_OBJS += builtin-rev-cache.o\n BUILTIN_OBJS += builtin-rev-list.o\n BUILTIN_OBJS += builtin-rev-parse.o\n BUILTIN_OBJS += builtin-revert.o\ndiff --git a/builtin-rev-cache.c b/builtin-rev-cache.c\nnew file mode 100644\nindex 0000000..6eb7065\n--- /dev/null\n+++ b/builtin-rev-cache.c\n@@ -0,0 +1,207 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+\n+/* porcelain for rev-cache.c */\n+static int handle_add(int argc, const char *argv[]) /* args beyond this command */\n+{\n+\tstruct rev_info revs;\n+\tstruct rev_cache_info rci;\n+\tchar dostdin = 0;\n+\tunsigned int flags = 0;\n+\tint i, retval;\n+\tunsigned char cache_sha1[20];\n+\tstruct commit_list *starts = 0, *ends = 0;\n+\tstruct commit *commit;\n+\n+\tinit_revisions(&revs, 0);\n+\tinit_rev_cache_info(&rci);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--stdin\"))\n+\t\t\tdostdin = 1;\n+\t\telse if (!strcmp(argv[i], \"--fresh\") || !strcmp(argv[i], \"--incremental\"))\n+\t\t\tstarts_from_slices(&revs, UNINTERESTING);\n+\t\telse if (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--legs\"))\n+\t\t\trci.legs = 1;\n+\t\telse if (!strcmp(argv[i], \"--no-objects\"))\n+\t\t\trci.objects = 0;\n+\t\telse if (!strcmp(argv[i], \"--all\")) {\n+\t\t\tconst char *args[2];\n+\t\t\tint argn = 0;\n+\n+\t\t\targs[argn++] = \"rev-list\";\n+\t\t\targs[argn++] = \"--all\";\n+\t\t\tsetup_revisions(argn, args, &revs, 0);\n+\t\t} else\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\tif (dostdin) {\n+\t\tchar line[1000];\n+\n+\t\tflags = 0;\n+\t\twhile (fgets(line, sizeof(line), stdin)) {\n+\t\t\tint len = strlen(line);\n+\t\t\twhile (len && (line[len - 1] == '\\n' || line[len - 1] == '\\r'))\n+\t\t\t\tline[--len] = 0;\n+\n+\t\t\tif (!len)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (!strcmp(line, \"--not\"))\n+\t\t\t\tflags ^= UNINTERESTING;\n+\t\t\telse\n+\t\t\t\thandle_revision_arg(line, &revs, flags, 1);\n+\t\t}\n+\t}\n+\n+\tretval = make_cache_slice(&rci, &revs, &starts, &ends, cache_sha1);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tprintf(\"%s\\n\", sha1_to_hex(cache_sha1));\n+\n+\tfprintf(stderr, \"endpoints:\\n\");\n+\twhile ((commit = pop_commit(&starts)))\n+\t\tfprintf(stderr, \"S %s\\n\", sha1_to_hex(commit->object.sha1));\n+\twhile ((commit = pop_commit(&ends)))\n+\t\tfprintf(stderr, \"E %s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\treturn 0;\n+}\n+\n+static int handle_walk(int argc, const char *argv[])\n+{\n+\tstruct commit *commit;\n+\tstruct rev_info revs;\n+\tstruct commit_list *queue, *work, **qp;\n+\tunsigned char *sha1p, *sha1pt;\n+\tunsigned long date = 0;\n+\tunsigned int flags = 0;\n+\tint retval, slop = 5, i;\n+\n+\tinit_revisions(&revs, 0);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--objects\"))\n+\t\t\trevs.tree_objects = revs.blob_objects = 1;\n+\t\telse\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\twork = 0;\n+\tsha1p = 0;\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tcommit = lookup_commit(revs.pending.objects[i].item->sha1);\n+\n+\t\tsha1pt = get_cache_slice(commit);\n+\t\tif (!sha1pt)\n+\t\t\tdie(\"%s: not in a cache slice\", sha1_to_hex(commit->object.sha1));\n+\n+\t\tif (!i)\n+\t\t\tsha1p = sha1pt;\n+\t\telse if (sha1p != sha1pt)\n+\t\t\tdie(\"walking porcelain is /per/ cache slice; commits cannot be spread out amoung several\");\n+\n+\t\tinsert_by_date(commit, &work);\n+\t}\n+\n+\tif (!sha1p)\n+\t\tdie(\"nothing to traverse!\");\n+\n+\tqueue = 0;\n+\tqp = &queue;\n+\tcommit = pop_commit(&work);\n+\tretval = traverse_cache_slice(&revs, sha1p, commit, &date, &slop, &qp, &work);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tfprintf(stderr, \"queue:\\n\");\n+\twhile ((commit = pop_commit(&queue)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"work:\\n\");\n+\twhile ((commit = pop_commit(&work)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"pending:\\n\");\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tstruct object *obj = revs.pending.objects[i].item;\n+\n+\t\t/* unfortunately, despite our careful generation, object duplication *is* a possibility...\n+\t\t * (eg. same object introduced into two different branches) */\n+\t\tif (obj->flags & SEEN)\n+\t\t\tcontinue;\n+\n+\t\tprintf(\"%s\\n\", sha1_to_hex(revs.pending.objects[i].item->sha1));\n+\t\tobj->flags |= SEEN;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int handle_help(void)\n+{\n+\tchar *usage = \"\\\n+usage:\\n\\\n+git-rev-cache COMMAND [options] [<commit-id>...]\\n\\\n+commands:\\n\\\n+  add    - add revisions to the cache.  reads commit ids from stdin, \\n\\\n+           formatted as: START START ... --not END END ...\\n\\\n+           options:\\n\\\n+            --all                  use all branch heads as starts\\n\\\n+            --fresh/--incremental  exclude everything already in a cache slice\\n\\\n+            --stdin                also read commit ids from stdin (same form\\n\\\n+                                   as cmd)\\n\\\n+            --legs                 ensure branch is entirely self-contained\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+  walk   - walk a cache slice based on set of commits; formatted as add\\n\\\n+           options:\\n\\\n+           --objects               include non-commit objects in traversals\\n\\\n+  fuse   - coalesce cache slices into a single cache.\\n\\\n+           options:\\n\\\n+            --all                  include all objects in repository\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+            --ignore-size[=N]      ignore slices of size >= N; defaults to ~5MB\\n\\\n+  index  - regnerate the cache index.\";\n+\n+\tputs(usage);\n+\n+\treturn 0;\n+}\n+\n+int cmd_rev_cache(int argc, const char *argv[], const char *prefix)\n+{\n+\tconst char *arg;\n+\tint r;\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\tif (argc > 1)\n+\t\targ = argv[1];\n+\telse\n+\t\targ = \"\";\n+\n+\targc -= 2;\n+\targv += 2;\n+\tif (!strcmp(arg, \"add\"))\n+\t\tr = handle_add(argc, argv);\n+\telse if (!strcmp(arg, \"walk\"))\n+\t\tr = handle_walk(argc, argv);\n+\telse\n+\t\treturn handle_help();\n+\n+\tfprintf(stderr, \"final return value: %d\\n\", r);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 20427d2..00ecc9c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -87,6 +87,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\n extern int cmd_rerere(int argc, const char **argv, const char *prefix);\n extern int cmd_reset(int argc, const char **argv, const char *prefix);\n+extern int cmd_rev_cache(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_list(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n extern int cmd_revert(int argc, const char **argv, const char *prefix);\ndiff --git a/commit.c b/commit.c\nindex e2bcbe8..682e7a7 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -252,6 +252,8 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)\n \titem->tree = lookup_tree(parent);\n \tbufptr += 46; /* \"tree \" + \"hex sha1\" + \"\\n\" */\n \tpptr = &item->parents;\n+\twhile (pop_commit(pptr))\n+\t\t; /* clear anything from cache */\n \n \tgraft = lookup_commit_graft(item->object.sha1);\n \twhile (bufptr + 48 < tail && !memcmp(bufptr, \"parent \", 7)) {\ndiff --git a/git.c b/git.c\nindex 4588a8b..7105c74 100644\n--- a/git.c\n+++ b/git.c\n@@ -342,6 +342,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"repo-config\", cmd_config },\n \t\t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t\t{ \"reset\", cmd_reset, RUN_SETUP },\n+\t\t{ \"rev-cache\", cmd_rev_cache, RUN_SETUP },\n \t\t{ \"rev-list\", cmd_rev_list, RUN_SETUP },\n \t\t{ \"rev-parse\", cmd_rev_parse },\n \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/rev-cache.c b/rev-cache.c\nnew file mode 100644\nindex 0000000..8951cdf\n--- /dev/null\n+++ b/rev-cache.c\n@@ -0,0 +1,1171 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+#include \"run-command.h\"\n+\n+/* list resembles pack index format */\n+static uint32_t fanout[0xff + 2];\n+\n+static unsigned char *idx_map;\n+static int idx_size;\n+static struct rc_index_header idx_head;\n+static unsigned char *idx_caches;\n+static char no_idx;\n+\n+static struct strbuf *acc_buffer;\n+\n+#define SLOP\t\t\t5\n+\n+/* initialization */\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst)\n+{\n+\tstatic struct rc_index_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->is_start = !!(src->flags & 0x80);\n+\tdst->cache_index = src->flags & 0x7f;\n+\tdst->pos = ntohl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst)\n+{\n+\tstatic struct rc_index_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->flags = (unsigned char)src->is_start << 7 | (unsigned char)src->cache_index;\n+\tdst->pos = htonl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst)\n+{\n+\tstatic struct rc_object_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->type = src->flags >> 5;\n+\tdst->is_end = !!(src->flags & 0x10);\n+\tdst->is_start = !!(src->flags & 0x08);\n+\tdst->uninteresting = !!(src->flags & 0x04);\n+\tdst->include = !!(src->flags & 0x02);\n+\tdst->flag = !!(src->flags & 0x01);\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->size_size = src->sizes >> 5;\n+\tdst->padding = src->sizes & 0x1f;\n+\n+\tdst->date = ntohl(src->date);\n+\tdst->path = ntohs(src->path);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst)\n+{\n+\tstatic struct rc_object_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->flags  = (unsigned char)src->type << 5;\n+\tdst->flags |= (unsigned char)src->is_end << 4;\n+\tdst->flags |= (unsigned char)src->is_start << 3;\n+\tdst->flags |= (unsigned char)src->uninteresting << 2;\n+\tdst->flags |= (unsigned char)src->include << 1;\n+\tdst->flags |= (unsigned char)src->flag;\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->sizes  = (unsigned char)src->size_size << 5;\n+\tdst->sizes |= (unsigned char)src->padding;\n+\n+\tdst->date = htonl(src->date);\n+\tdst->path = htons(src->path);\n+\n+\treturn dst;\n+}\n+\n+static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)\n+{\n+\tstruct rc_index_header whead;\n+\tint i, index = sizeof(struct rc_index_header);\n+\n+\tmemcpy(&whead, map, sizeof(struct rc_index_header));\n+\tif (memcmp(whead.signature, \"REVINDEX\", 8) || whead.version != SUPPORTED_REVINDEX_VERSION)\n+\t\treturn -1;\n+\n+\tmemcpy(head->signature, \"REVINDEX\", 8);\n+\thead->version = whead.version;\n+\thead->ofs_objects = ntohl(whead.ofs_objects);\n+\thead->object_nr = ntohl(whead.object_nr);\n+\thead->cache_nr = whead.cache_nr;\n+\thead->max_date = ntohl(whead.max_date);\n+\n+\tif (len < index + head->cache_nr * 20 + 0x100 * sizeof(uint32_t))\n+\t\treturn -2;\n+\n+\t*caches = xmalloc(head->cache_nr * 20);\n+\tmemcpy(*caches, map + index, head->cache_nr * 20);\n+\tindex += head->cache_nr * 20;\n+\n+\tmemcpy(fanout, map + index, 0x100 * sizeof(uint32_t));\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = ntohl(fanout[i]);\n+\tfanout[0x100] = len;\n+\n+\treturn 0;\n+}\n+\n+/* added in init_index */\n+static void cleanup_cache_slices(void)\n+{\n+\tif (idx_map) {\n+\t\tfree(idx_caches);\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+}\n+\n+static int init_index(void)\n+{\n+\tint fd;\n+\tstruct stat fi;\n+\n+\tfd = open(git_path(\"rev-cache/index\"), O_RDONLY);\n+\tif (fd == -1 || fstat(fd, &fi))\n+\t\tgoto end;\n+\tif (fi.st_size < sizeof(struct rc_index_header))\n+\t\tgoto end;\n+\n+\tidx_size = fi.st_size;\n+\tidx_map = xmmap(0, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tif (idx_map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_index_head(idx_map, fi.st_size, &idx_head, fanout, &idx_caches))\n+\t\tgoto end;\n+\n+\tatexit(cleanup_cache_slices);\n+\n+\treturn 0;\n+\n+end:\n+\tidx_map = 0;\n+\tno_idx = 1;\n+\treturn -1;\n+}\n+\n+/* this assumes index is already loaded */\n+static struct rc_index_entry_ondisk *search_index_1(unsigned char *sha1)\n+{\n+\tint start, end, starti, endi, i, len, r;\n+\tstruct rc_index_entry_ondisk *ie;\n+\n+\tif (!idx_map)\n+\t\treturn 0;\n+\n+\t/* binary search */\n+\tstart = fanout[(int)sha1[0]];\n+\tend = fanout[(int)sha1[0] + 1];\n+\tlen = (end - start) / sizeof(struct rc_index_entry_ondisk);\n+\tif (!len || len * sizeof(struct rc_index_entry_ondisk) != end - start)\n+\t\treturn 0;\n+\n+\tstarti = 0;\n+\tendi = len - 1;\n+\tfor (;;) {\n+\t\ti = (endi + starti) / 2;\n+\t\tie = (struct rc_index_entry_ondisk *)(idx_map + start + i * sizeof(struct rc_index_entry_ondisk));\n+\t\tr = hashcmp(sha1, ie->sha1);\n+\n+\t\tif (r) {\n+\t\t\tif (starti + 1 == endi) {\n+\t\t\t\tstarti++;\n+\t\t\t\tcontinue;\n+\t\t\t} else if (starti == endi)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (r > 0)\n+\t\t\t\tstarti = i;\n+\t\t\telse /* r < 0 */\n+\t\t\t\tendi = i;\n+\t\t} else\n+\t\t\treturn ie;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static struct rc_index_entry *search_index(unsigned char *sha1)\n+{\n+\tstruct rc_index_entry_ondisk *ied = search_index_1(sha1);\n+\n+\tif (ied)\n+\t\treturn from_disked_rc_index_entry(ied, 0);\n+\n+\treturn 0;\n+}\n+\n+unsigned char *get_cache_slice(struct commit *commit)\n+{\n+\tstruct rc_index_entry *ie;\n+\n+\tif (!idx_map) {\n+\t\tif (no_idx)\n+\t\t\treturn 0;\n+\t\tinit_index();\n+\t}\n+\n+\tif (commit->date > idx_head.max_date)\n+\t\treturn 0;\n+\n+\tie = search_index(commit->object.sha1);\n+\tif (ie && ie->cache_index < idx_head.cache_nr)\n+\t\treturn idx_caches + ie->cache_index * 20;\n+\n+\treturn 0;\n+}\n+\n+\n+/* traversal */\n+\n+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)\n+{\n+\tstruct rc_index_entry *iep;\n+\tstruct rc_object_entry *oep;\n+\tstruct commit_list *prev, *wp, **wpp;\n+\tint retval;\n+\n+\tiep = search_index(commit->object.sha1), 0;\n+\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t/* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,\n+\t * but we might as well initialize it while we're at it */\n+\toep->include = 1;\n+\toep->uninteresting = !!(commit->object.flags & UNINTERESTING);\n+\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\tretval = iep->pos;\n+\n+\t/* include any others in the work array */\n+\tprev = 0;\n+\twpp = work;\n+\twp = *work;\n+\twhile (wp) {\n+\t\tstruct object *obj = &wp->item->object;\n+\t\tstruct commit *co;\n+\n+\t\t/* is this in our cache slice? */\n+\t\tiep = search_index(obj->sha1);\n+\t\tif (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {\n+\t\t\tprev = wp;\n+\t\t\twp = wp->next;\n+\t\t\twpp = &wp;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (iep->pos < retval)\n+\t\t\tretval = iep->pos;\n+\n+\t\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t\t/* mark this for later */\n+\t\toep->include = 1;\n+\t\toep->uninteresting = !!(obj->flags & UNINTERESTING);\n+\t\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\n+\t\t/* remove from work list */\n+\t\tco = pop_commit(wpp);\n+\t\twp = *wpp;\n+\t\tif (prev)\n+\t\t\tprev->next = wp;\n+\t}\n+\n+\treturn retval;\n+}\n+\n+#define IPATH\t\t\t\t0x40\n+#define UPATH\t\t\t\t0x80\n+\n+#define GET_COUNT(x)\t\t((x) & 0x3f)\n+#define SET_COUNT(x, s)\t\t((x) = ((x) & ~0x3f) | ((s) & 0x3f))\n+\n+static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *map,\n+\tstruct rev_info *revs, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tstruct commit_list *insert_cache = 0;\n+\tstruct commit **last_objects, *co;\n+\tint i, total_path_nr = head->path_nr, retval = -1;\n+\tchar consume_children = 0;\n+\tunsigned char *paths;\n+\n+\ti = setup_traversal(head, map, commit, work);\n+\tif (i < 0)\n+\t\treturn -1;\n+\n+\tpaths = xcalloc(total_path_nr, sizeof(uint16_t));\n+\tlast_objects = xcalloc(total_path_nr, sizeof(struct commit *));\n+\n+\t/* i already set */\n+\twhile (i < head->size) {\n+\t\tstruct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tint path = entry->path;\n+\t\tstruct object *obj;\n+\t\tint index = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);\n+\n+\t\t/* add extra objects if necessary */\n+\t\tif (entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tconsume_children = 0;\n+\n+\t\tif (path >= total_path_nr)\n+\t\t\tgoto end;\n+\n+\t\t/* in one of our branches?\n+\t\t * uninteresting trumps interesting */\n+\t\tif (entry->include)\n+\t\t\tpaths[path] |= entry->uninteresting ? UPATH : IPATH;\n+\t\telse if (!paths[path])\n+\t\t\tcontinue;\n+\n+\t\t/* date stuff */\n+\t\tif (revs->max_age != -1 && entry->date < revs->max_age)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\t/* lookup object */\n+\t\tco = lookup_commit(entry->sha1);\n+\t\tobj = &co->object;\n+\n+\t\tif (obj->flags & UNINTERESTING)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\tif ((paths[path] & IPATH) && (paths[path] & UPATH)) {\n+\t\t\tpaths[path] = UPATH;\n+\n+\t\t\t/* mark edge */\n+\t\t\tif (last_objects[path]) {\n+\t\t\t\tparse_commit(last_objects[path]);\n+\n+\t\t\t\tlast_objects[path]->object.flags &= ~FACE_VALUE;\n+\t\t\t\tlast_objects[path] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* now we gotta re-assess the whole interesting thing... */\n+\t\tentry->uninteresting = !!(paths[path] & UPATH);\n+\n+\t\t/* first close paths */\n+\t\tif (entry->split_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE(entry->merge_nr);\n+\n+\t\t\tfor (j = 0; j < entry->split_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\t/* boundary commit? */\n+\t\t\t\tif ((paths[p] & IPATH) && entry->uninteresting) {\n+\t\t\t\t\tif (last_objects[p]) {\n+\t\t\t\t\t\tparse_commit(last_objects[p]);\n+\n+\t\t\t\t\t\tlast_objects[p]->object.flags &= ~FACE_VALUE;\n+\t\t\t\t\t\tlast_objects[p] = 0;\n+\t\t\t\t\t}\n+\t\t\t\t} else if (last_objects[p] && !last_objects[p]->object.parsed)\n+\t\t\t\t\tcommit_list_insert(co, &last_objects[p]->parents);\n+\n+\t\t\t\t/* can't close a merge path until all are parents have been encountered */\n+\t\t\t\tif (GET_COUNT(paths[p])) {\n+\t\t\t\t\tSET_COUNT(paths[p], GET_COUNT(paths[p]) - 1);\n+\n+\t\t\t\t\tif (GET_COUNT(paths[p]))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tpaths[p] = 0;\n+\t\t\t\tlast_objects[p] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* make topo relations */\n+\t\tif (last_objects[path] && !last_objects[path]->object.parsed)\n+\t\t\tcommit_list_insert(co, &last_objects[path]->parents);\n+\n+\t\t/* initialize commit */\n+\t\tif (!entry->is_end) {\n+\t\t\tco->date = entry->date;\n+\t\t\tobj->flags |= ADDED | FACE_VALUE;\n+\t\t} else\n+\t\t\tparse_commit(co);\n+\n+\t\tobj->flags |= SEEN;\n+\n+\t\tif (entry->uninteresting)\n+\t\t\tobj->flags |= UNINTERESTING;\n+\n+\t\t/* we need to know what the edges are */\n+\t\tlast_objects[path] = co;\n+\n+\t\t/* add to list */\n+\t\tif (!(obj->flags & UNINTERESTING) || revs->show_all) {\n+\t\t\tif (entry->is_end)\n+\t\t\t\tinsert_by_date_cached(co, work, insert_cache, &insert_cache);\n+\t\t\telse\n+\t\t\t\t*queue = &commit_list_insert(co, *queue)->next;\n+\n+\t\t\t/* add children to list as well */\n+\t\t\tif (obj->flags & UNINTERESTING)\n+\t\t\t\tconsume_children = 0;\n+\t\t\telse\n+\t\t\t\tconsume_children = 1;\n+\t\t}\n+\n+\t\t/* open parents */\n+\t\tif (entry->merge_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk);\n+\t\t\tchar flag = entry->uninteresting ? UPATH : IPATH;\n+\n+\t\t\tfor (j = 0; j < entry->merge_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\tif (paths[p] & flag)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tpaths[p] |= flag;\n+\t\t\t}\n+\n+\t\t\t/* make sure we don't use this path before all our parents have had their say */\n+\t\t\tSET_COUNT(paths[path], entry->merge_nr);\n+\t\t}\n+\n+\t}\n+\n+\tretval = 0;\n+\n+end:\n+\tfree(paths);\n+\tfree(last_objects);\n+\n+\treturn retval;\n+}\n+\n+static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)\n+{\n+\tint t;\n+\n+\tmemcpy(head, map, sizeof(struct rc_slice_header));\n+\thead->ofs_objects = ntohl(head->ofs_objects);\n+\thead->object_nr = ntohl(head->object_nr);\n+\thead->size = ntohl(head->size);\n+\thead->path_nr = ntohs(head->path_nr);\n+\n+\tif (memcmp(head->signature, \"REVCACHE\", 8))\n+\t\treturn -1;\n+\tif (head->version != SUPPORTED_REVCACHE_VERSION)\n+\t\treturn -2;\n+\tif (hashcmp(head->sha1, cache_sha1))\n+\t\treturn -3;\n+\tt = sizeof(struct rc_slice_header);\n+\tif (t != head->ofs_objects || t >= len)\n+\t\treturn -4;\n+\n+\thead->size = len;\n+\n+\treturn 0;\n+}\n+\n+int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tint fd = -1, retval = -3;\n+\tstruct stat fi;\n+\tstruct rc_slice_header head;\n+\tstruct rev_cache_info *rci;\n+\tunsigned char *map = MAP_FAILED;\n+\n+\t/* the index should've been loaded already to find cache_sha1, but it's good\n+\t * to be absolutely sure... */\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn -1;\n+\n+\t/* load options */\n+\trci = &revs->rev_cache_info;\n+\n+\tmemset(&head, 0, sizeof(struct rc_slice_header));\n+\n+\tfd = open(git_path(\"rev-cache/%s\", sha1_to_hex(cache_sha1)), O_RDONLY);\n+\tif (fd == -1)\n+\t\tgoto end;\n+\tif (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))\n+\t\tgoto end;\n+\n+\tmap = xmmap(0, fi.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))\n+\t\tgoto end;\n+\n+\tretval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);\n+\n+end:\n+\tif (map != MAP_FAILED)\n+\t\tmunmap(map, fi.st_size);\n+\tif (fd != -1)\n+\t\tclose(fd);\n+\n+\treturn retval;\n+}\n+\n+\n+\n+/* generation */\n+\n+struct path_track {\n+\tstruct commit *commit;\n+\tint path; /* for keeping track of children */\n+\n+\tstruct path_track *next, *prev;\n+};\n+\n+static unsigned char *paths;\n+static int path_nr = 1, path_sz;\n+\n+static struct path_track *path_track;\n+static struct path_track *path_track_alloc;\n+\n+#define PATH_IN_USE\t\t\t0x80 /* biggest bit we can get as a char */\n+\n+static int get_new_path(void)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i < path_nr; i++)\n+\t\tif (!paths[i])\n+\t\t\tbreak;\n+\n+\tif (i == path_nr) {\n+\t\tif (path_nr >= path_sz) {\n+\t\t\tpath_sz += 50;\n+\t\t\tpaths = xrealloc(paths, path_sz);\n+\t\t\tmemset(paths + path_sz - 50, 0, 50);\n+\t\t}\n+\t\tpath_nr++;\n+\t}\n+\n+\tpaths[i] = PATH_IN_USE;\n+\treturn i;\n+}\n+\n+static void remove_path_track(struct path_track **ppt, char total_free)\n+{\n+\tstruct path_track *t = *ppt;\n+\n+\tif (t->next)\n+\t\tt->next->prev = t->prev;\n+\tif (t->prev)\n+\t\tt->prev->next = t->next;\n+\n+\tt = t->next;\n+\n+\tif (total_free)\n+\t\tfree(*ppt);\n+\telse {\n+\t\t(*ppt)->next = path_track_alloc;\n+\t\tpath_track_alloc = *ppt;\n+\t}\n+\n+\t*ppt = t;\n+}\n+\n+static struct path_track *make_path_track(struct path_track **head, struct commit *commit)\n+{\n+\tstruct path_track *pt;\n+\n+\tif (path_track_alloc) {\n+\t\tpt = path_track_alloc;\n+\t\tpath_track_alloc = pt->next;\n+\t} else\n+\t\tpt = xmalloc(sizeof(struct path_track));\n+\n+\tmemset(pt, 0, sizeof(struct path_track));\n+\tpt->commit = commit;\n+\n+\tpt->next = *head;\n+\tif (*head)\n+\t\t(*head)->prev = pt;\n+\t*head = pt;\n+\n+\treturn pt;\n+}\n+\n+static void add_path_to_track(struct commit *commit, int path)\n+{\n+\tmake_path_track(&path_track, commit);\n+\tpath_track->path = path;\n+}\n+\n+static void handle_paths(struct commit *commit, struct rc_object_entry *object, struct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tint child_nr, parent_nr, open_parent_nr, this_path;\n+\tstruct commit_list *list;\n+\tstruct commit *first_parent;\n+\tstruct path_track **ppt, *pt;\n+\n+\t/* we can only re-use a closed path once all it's children have been encountered,\n+\t * as we need to keep track of commit boundaries */\n+\tppt = &path_track;\n+\tpt = *ppt;\n+\tchild_nr = 0;\n+\twhile (pt) {\n+\t\tif (pt->commit == commit) {\n+\t\t\tuint16_t write_path;\n+\n+\t\t\tif (paths[pt->path] != PATH_IN_USE)\n+\t\t\t\tpaths[pt->path]--;\n+\n+\t\t\t/* make sure we can handle this */\n+\t\t\tchild_nr++;\n+\t\t\tif (child_nr > 0x7f)\n+\t\t\t\tdie(\"%s: too many branches!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t\t\t/* add to split list */\n+\t\t\tobject->split_nr++;\n+\t\t\twrite_path = htons((uint16_t)pt->path);\n+\t\t\tstrbuf_add(split_str, &write_path, sizeof(uint16_t));\n+\n+\t\t\tremove_path_track(ppt, 0);\n+\t\t\tpt = *ppt;\n+\t\t} else {\n+\t\t\tpt = pt->next;\n+\t\t\tppt = &pt;\n+\t\t}\n+\t}\n+\n+\t/* initialize our self! */\n+\tif (!commit->indegree) {\n+\t\tcommit->indegree = get_new_path();\n+\t\tobject->is_start = 1;\n+\t}\n+\n+\tthis_path = commit->indegree;\n+\tpaths[this_path] = PATH_IN_USE;\n+\tobject->path = this_path;\n+\n+\t/* count interesting parents */\n+\tparent_nr = open_parent_nr = 0;\n+\tfirst_parent = 0;\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tif (list->item->object.flags & UNINTERESTING) {\n+\t\t\tobject->is_end = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tparent_nr++;\n+\t\tif (!list->item->indegree)\n+\t\t\topen_parent_nr++;\n+\t\tif (!first_parent)\n+\t\t\tfirst_parent = list->item;\n+\t}\n+\n+\tif (!parent_nr)\n+\t\treturn;\n+\n+\tif (parent_nr == 1 && open_parent_nr == 1) {\n+\t\tfirst_parent->indegree = this_path;\n+\t\treturn;\n+\t}\n+\n+\t/* bail out on obscene parent/child #s */\n+\tif (parent_nr > 0x7f)\n+\t\tdie(\"%s: too many parents in merge!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t/* make merge list */\n+\tobject->merge_nr = parent_nr;\n+\tpaths[this_path] = parent_nr;\n+\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tstruct commit *p = list->item;\n+\t\tuint16_t write_path;\n+\n+\t\tif (p->object.flags & UNINTERESTING)\n+\t\t\tcontinue;\n+\n+\t\t/* unfortunately due to boundary tracking we can't re-use merge paths\n+\t\t * (unable to guarantee last parent path = this -> last won't always be able to\n+\t\t * set this as a boundary object */\n+\t\tif (!p->indegree)\n+\t\t\tp->indegree = get_new_path();\n+\n+\t\twrite_path = htons((uint16_t)p->indegree);\n+\t\tstrbuf_add(merge_str, &write_path, sizeof(uint16_t));\n+\n+\t\t/* make sure path is properly ended */\n+\t\tadd_path_to_track(p, this_path);\n+\t}\n+\n+}\n+\n+\n+static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,\n+\tstruct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tstruct rc_object_entry object;\n+\n+\tif (!nothisone) {\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.sha1 = (unsigned char *)sha1;\n+\t\tobject.type = type;\n+\n+\t\tif (merge_str)\n+\t\t\tobject.merge_nr = merge_str->len / sizeof(uint16_t);\n+\t\tif (split_str)\n+\t\t\tobject.split_nr = split_str->len / sizeof(uint16_t);\n+\n+\t\tnothisone = &object;\n+\t}\n+\n+\tstrbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));\n+\n+\tif (merge_str && merge_str->len)\n+\t\tstrbuf_add(acc_buffer, merge_str->buf, merge_str->len);\n+\tif (split_str && split_str->len)\n+\t\tstrbuf_add(acc_buffer, split_str->buf, split_str->len);\n+\n+}\n+\n+static void init_revcache_directory(void)\n+{\n+\tstruct stat fi;\n+\n+\tif (stat(git_path(\"rev-cache\"), &fi) || !S_ISDIR(fi.st_mode))\n+\t\tif (mkdir(git_path(\"rev-cache\"), 0777))\n+\t\t\tdie(\"can't make rev-cache directory\");\n+\n+}\n+\n+void init_rev_cache_info(struct rev_cache_info *rci)\n+{\n+\trci->objects = 1;\n+\trci->legs = 0;\n+\trci->make_index = 1;\n+\n+\trci->add_to_pending = 1;\n+\n+\trci->ignore_size = 0;\n+}\n+\n+void maybe_fill_with_defaults(struct rev_cache_info *rci)\n+{\n+\tstatic struct rev_cache_info def_rci;\n+\n+\tif (rci)\n+\t\treturn;\n+\n+\tinit_rev_cache_info(&def_rci);\n+\trci = &def_rci;\n+}\n+\n+int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1)\n+{\n+\tstruct rev_info therevs;\n+\tstruct strbuf buffer, startlist, endlist;\n+\tstruct rc_slice_header head;\n+\tstruct commit *commit;\n+\tunsigned char sha1[20];\n+\tstruct strbuf merge_paths, split_paths;\n+\tint object_nr, total_sz, fd;\n+\tchar file[PATH_MAX], *newfile;\n+\tstruct rev_cache_info *trci;\n+\tgit_SHA_CTX ctx;\n+\n+\tmaybe_fill_with_defaults(rci);\n+\n+\tinit_revcache_directory();\n+\tstrcpy(file, git_path(\"rev-cache/XXXXXX\"));\n+\tfd = xmkstemp(file);\n+\n+\tstrbuf_init(&buffer, 0);\n+\tstrbuf_init(&startlist, 0);\n+\tstrbuf_init(&endlist, 0);\n+\tstrbuf_init(&merge_paths, 0);\n+\tstrbuf_init(&split_paths, 0);\n+\tacc_buffer = &buffer;\n+\n+\tif (!revs) {\n+\t\trevs = &therevs;\n+\t\tinit_revisions(revs, 0);\n+\n+\t\t/* we're gonna assume no one else has already traversed this... */\n+\t\twhile ((commit = pop_commit(starts)))\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\n+\t\twhile ((commit = pop_commit(ends))) {\n+\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\t\t}\n+\t}\n+\n+\t/* write head placeholder */\n+\tmemset(&head, 0, sizeof(head));\n+\thead.ofs_objects = htonl(sizeof(head));\n+\txwrite(fd, &head, sizeof(head));\n+\n+\t/* init revisions! */\n+\trevs->tree_objects = 1;\n+\trevs->blob_objects = 1;\n+\trevs->topo_order = 1;\n+\trevs->lifo = 1;\n+\n+\t/* re-use info from other caches if possible */\n+\ttrci = &revs->rev_cache_info;\n+\tinit_rev_cache_info(trci);\n+\ttrci->add_to_pending = 0;\n+\n+\tsetup_revisions(0, 0, revs, 0);\n+\tif (prepare_revision_walk(revs))\n+\t\tdie(\"died preparing revision walk\");\n+\n+\tobject_nr = total_sz = 0;\n+\twhile ((commit = get_revision(revs)) != 0) {\n+\t\tstruct rc_object_entry object;\n+\n+\t\tstrbuf_setlen(&merge_paths, 0);\n+\t\tstrbuf_setlen(&split_paths, 0);\n+\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.type = OBJ_COMMIT;\n+\t\tobject.date = commit->date;\n+\t\tobject.sha1 = commit->object.sha1;\n+\n+\t\thandle_paths(commit, &object, &merge_paths, &split_paths);\n+\n+\t\tif (object.is_end) {\n+\t\t\tstrbuf_add(&endlist, object.sha1, 20);\n+\t\t\tif (ends)\n+\t\t\t\tcommit_list_insert(commit, ends);\n+\t\t}\n+\t\t/* the two *aren't* mutually exclusive */\n+\t\tif (object.is_start) {\n+\t\t\tstrbuf_add(&startlist, object.sha1, 20);\n+\t\t\tif (starts)\n+\t\t\t\tcommit_list_insert(commit, starts);\n+\t\t}\n+\n+\t\tcommit->indegree = 0;\n+\n+\t\tadd_object_entry(0, 0, &object, &merge_paths, &split_paths);\n+\t\tobject_nr++;\n+\n+\t\t/* print every ~1MB or so */\n+\t\tif (buffer.len > 1000000) {\n+\t\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\t\ttotal_sz += buffer.len;\n+\n+\t\t\tstrbuf_setlen(&buffer, 0);\n+\t\t}\n+\t}\n+\n+\tif (buffer.len) {\n+\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\ttotal_sz += buffer.len;\n+\t}\n+\n+\t/* go ahead a free some stuff... */\n+\tstrbuf_release(&buffer);\n+\tstrbuf_release(&merge_paths);\n+\tstrbuf_release(&split_paths);\n+\tif (path_sz)\n+\t\tfree(paths);\n+\twhile (path_track_alloc)\n+\t\tremove_path_track(&path_track_alloc, 1);\n+\n+\t/* the meaning of the hash name is more or less irrelevant, it's the uniqueness that matters */\n+\tstrbuf_add(&endlist, startlist.buf, startlist.len);\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, endlist.buf, endlist.len);\n+\tgit_SHA1_Final(sha1, &ctx);\n+\n+\t/* now actually initialize header */\n+\tstrcpy(head.signature, \"REVCACHE\");\n+\thead.version = SUPPORTED_REVCACHE_VERSION;\n+\n+\thead.object_nr = htonl(object_nr);\n+\thead.size = htonl(ntohl(head.ofs_objects) + total_sz);\n+\thead.path_nr = htons(path_nr);\n+\thashcpy(head.sha1, sha1);\n+\n+\t/* some info! */\n+\tfprintf(stderr, \"objects: %d\\n\", object_nr);\n+\tfprintf(stderr, \"paths: %d\\n\", path_nr);\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\txwrite(fd, &head, sizeof(head));\n+\n+\tif (rci->make_index && make_cache_index(rci, sha1, fd, ntohl(head.size)) < 0)\n+\t\tdie(\"can't update index\");\n+\n+\tclose(fd);\n+\n+\tnewfile = git_path(\"rev-cache/%s\", sha1_to_hex(sha1));\n+\tif (rename(file, newfile))\n+\t\tdie(\"can't move temp file\");\n+\n+\t/* let our caller know what we've just made */\n+\tif (cache_sha1)\n+\t\thashcpy(cache_sha1, sha1);\n+\n+\tstrbuf_release(&endlist);\n+\tstrbuf_release(&startlist);\n+\n+\treturn 0;\n+}\n+\n+\n+static int index_sort_hash(const void *a, const void *b)\n+{\n+\treturn hashcmp(((struct rc_index_entry_ondisk *)a)->sha1, ((struct rc_index_entry_ondisk *)b)->sha1);\n+}\n+\n+static int write_cache_index(struct strbuf *body)\n+{\n+\tstruct rc_index_header whead;\n+\tstruct lock_file *lk;\n+\tint fd, i;\n+\n+\t/* clear index map if loaded */\n+\tif (idx_map) {\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+\tlk = xcalloc(sizeof(struct lock_file), 1);\n+\tfd = hold_lock_file_for_update(lk, git_path(\"rev-cache/index\"), 0);\n+\tif (fd < 0) {\n+\t\tfree(lk);\n+\t\treturn -1;\n+\t}\n+\n+\t/* endianness yay! */\n+\tmemset(&whead, 0, sizeof(whead));\n+\tmemcpy(whead.signature, \"REVINDEX\", 8);\n+\twhead.version = idx_head.version;\n+\twhead.ofs_objects = htonl(idx_head.ofs_objects);\n+\twhead.object_nr = htonl(idx_head.object_nr);\n+\twhead.cache_nr = idx_head.cache_nr;\n+\twhead.max_date = htonl(idx_head.max_date);\n+\n+\twrite(fd, &whead, sizeof(struct rc_index_header));\n+\twrite_in_full(fd, idx_caches, idx_head.cache_nr * 20);\n+\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = htonl(fanout[i]);\n+\twrite_in_full(fd, fanout, 0x100 * sizeof(uint32_t));\n+\n+\twrite_in_full(fd, body->buf, body->len);\n+\n+\tif (commit_lock_file(lk) < 0)\n+\t\treturn -2;\n+\n+\t/* lk freed by lockfile.c */\n+\n+\treturn 0;\n+}\n+\n+int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size)\n+{\n+\tstruct strbuf buffer;\n+\tint i, cache_index, cur;\n+\tunsigned char *map;\n+\tunsigned long max_date;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\tmap = xmmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\treturn -1;\n+\n+\tstrbuf_init(&buffer, 0);\n+\tif (idx_map) {\n+\t\tstrbuf_add(&buffer, idx_map + fanout[0], fanout[0x100] - fanout[0]);\n+\t} else {\n+\t\t/* not an update */\n+\t\tmemset(&idx_head, 0, sizeof(struct rc_index_header));\n+\t\tidx_caches = 0;\n+\n+\t\tstrcpy(idx_head.signature, \"REVINDEX\");\n+\t\tidx_head.version = SUPPORTED_REVINDEX_VERSION;\n+\t\tidx_head.ofs_objects = sizeof(struct rc_index_header) + 0x100 * sizeof(uint32_t);\n+\t}\n+\n+\t/* are we remaking a slice? */\n+\tfor (i = 0; i < idx_head.cache_nr; i++)\n+\t\tif (!hashcmp(idx_caches + i * 20, cache_sha1))\n+\t\t\tbreak;\n+\n+\tif (i == idx_head.cache_nr) {\n+\t\tcache_index = idx_head.cache_nr++;\n+\t\tidx_head.ofs_objects += 20;\n+\n+\t\tidx_caches = xrealloc(idx_caches, idx_head.cache_nr * 20);\n+\t\thashcpy(idx_caches + cache_index * 20, cache_sha1);\n+\t} else\n+\t\tcache_index = i;\n+\n+\ti = sizeof(struct rc_slice_header); /* offset */\n+\tmax_date = idx_head.max_date;\n+\twhile (i < size) {\n+\t\tstruct rc_index_entry index_entry, *entry;\n+\t\tstruct rc_index_entry_ondisk *disked_entry;\n+\t\tstruct rc_object_entry *object_entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tunsigned long date;\n+\t\tint off, pos = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(object_entry);\n+\n+\t\tif (object_entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\n+\t\t/* don't include ends; otherwise we'll find ourselves in loops */\n+\t\tif (object_entry->is_end)\n+\t\t\tcontinue;\n+\n+\t\t/* handle index duplication\n+\t\t * -> keep old copy unless new one is a start -- based on expected usage, older ones will be more\n+\t\t * likely to lead to greater slice traversals than new ones */\n+\t\tdate = object_entry->date;\n+\t\tif (date > idx_head.max_date) {\n+\t\t\tdisked_entry = 0;\n+\t\t\tif (date > max_date)\n+\t\t\t\tmax_date = date;\n+\t\t} else\n+\t\t\tdisked_entry = search_index_1(object_entry->sha1);\n+\n+\t\tif (disked_entry && !object_entry->is_start)\n+\t\t\tcontinue;\n+\t\telse if (disked_entry) {\n+\t\t\t/* mmm, pointer arithmetic... tasty */  /* (entry - idx_map = offset, so cast is valid) */\n+\t\t\toff = (unsigned int)((unsigned char *)disked_entry - idx_map) - fanout[0];\n+\t\t\tdisked_entry = (struct rc_index_entry_ondisk *)(buffer.buf + off);\n+\t\t\tentry = from_disked_rc_index_entry(disked_entry, 0);\n+\t\t} else\n+\t\t\tentry = &index_entry;\n+\n+\t\tmemset(entry, 0, sizeof(index_entry));\n+\t\tentry->sha1 = object_entry->sha1;\n+\t\tentry->is_start = object_entry->is_start;\n+\t\tentry->cache_index = cache_index;\n+\t\tentry->pos = pos;\n+\n+\t\tif (entry == &index_entry) {\n+\t\t\tstrbuf_add(&buffer, to_disked_rc_index_entry(entry, 0), sizeof(struct rc_index_entry_ondisk));\n+\t\t\tidx_head.object_nr++;\n+\t\t} else\n+\t\t\tto_disked_rc_index_entry(entry, disked_entry);\n+\n+\t}\n+\n+\tidx_head.max_date = max_date;\n+\tqsort(buffer.buf, buffer.len / sizeof(struct rc_index_entry_ondisk), sizeof(struct rc_index_entry_ondisk), index_sort_hash);\n+\n+\t/* generate fanout */\n+\tcur = 0x00;\n+\tfor (i = 0; i < buffer.len; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry_ondisk *entry = (struct rc_index_entry_ondisk *)(buffer.buf + i);\n+\n+\t\twhile (cur <= entry->sha1[0])\n+\t\t\tfanout[cur++] = i + idx_head.ofs_objects;\n+\t}\n+\n+\twhile (cur <= 0xff)\n+\t\tfanout[cur++] = idx_head.ofs_objects + buffer.len;\n+\n+\t/* BOOM! */\n+\tif (write_cache_index(&buffer))\n+\t\treturn -1;\n+\n+\tmunmap(map, size);\n+\tstrbuf_release(&buffer);\n+\n+\t/* idx_map is unloaded without cleanup_cache_slices(), so regardless of previous index existence\n+\t * we can still free this up */\n+\tfree(idx_caches);\n+\n+\treturn 0;\n+}\n+\n+\n+/* add start-commits from each cache slice (uninterestingness will be propogated) */\n+void starts_from_slices(struct rev_info *revs, unsigned int flags)\n+{\n+\tstruct commit *commit;\n+\tint i;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn;\n+\n+\tfor (i = idx_head.ofs_objects; i < idx_size; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry *entry = RC_OBTAIN_INDEX_ENTRY(idx_map + i);\n+\n+\t\tif (!entry->is_start)\n+\t\t\tcontinue;\n+\n+\t\tcommit = lookup_commit(entry->sha1);\n+\t\tif (!commit)\n+\t\t\tcontinue;\n+\n+\t\tcommit->object.flags |= flags;\n+\t\tadd_pending_object(revs, &commit->object, 0);\n+\t}\n+\n+}\ndiff --git a/rev-cache.h b/rev-cache.h\nnew file mode 100644\nindex 0000000..a471fbf\n--- /dev/null\n+++ b/rev-cache.h\n@@ -0,0 +1,107 @@\n+#ifndef REV_CACHE_H\n+#define REV_CACHE_H\n+\n+#define SUPPORTED_REVCACHE_VERSION \t\t1\n+#define SUPPORTED_REVINDEX_VERSION\t\t1\n+\n+#define RC_PATH_SIZE(x)\t(sizeof(uint16_t) * (x))\n+\n+#define RC_OBTAIN_OBJECT_ENTRY(p)\t\t\tfrom_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)\n+#define RC_OBTAIN_INDEX_ENTRY(p)\t\t\tfrom_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)\n+\n+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e)\t\t(sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)\n+\n+/* single index maps objects to cache files */\n+struct rc_index_header {\n+\tchar signature[8]; /* REVINDEX */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tunsigned char cache_nr;\n+\n+\tuint32_t max_date;\n+};\n+\n+struct rc_index_entry_ondisk {\n+\tunsigned char sha1[20];\n+\tunsigned char flags;\n+\tuint32_t pos;\n+};\n+\n+struct rc_index_entry {\n+\tunsigned char *sha1;\n+\tunsigned is_start : 1;\n+\tunsigned cache_index : 7;\n+\tuint32_t pos;\n+};\n+\n+\n+/* structure for actual cache file */\n+struct rc_slice_header {\n+\tchar signature[8]; /* REVCACHE */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tuint16_t path_nr;\n+\tuint32_t size;\n+\n+\tunsigned char sha1[20];\n+};\n+\n+struct rc_object_entry_ondisk {\n+\tunsigned char flags;\n+\tunsigned char sha1[20];\n+\n+\tunsigned char merge_nr;\n+\tunsigned char split_nr;\n+\tunsigned char sizes;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+};\n+\n+struct rc_object_entry {\n+\tunsigned type : 3;\n+\tunsigned is_end : 1;\n+\tunsigned is_start : 1;\n+\tunsigned uninteresting : 1;\n+\tunsigned include : 1;\n+\tunsigned flag : 1; /* unused */\n+\tunsigned char *sha1; /* 20 byte */\n+\n+\tunsigned char merge_nr; /* : 7 */\n+\tunsigned char split_nr; /* : 7 */\n+\tunsigned size_size : 3;\n+\tunsigned padding : 5;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+\n+\t/* merge paths */\n+\t/* split paths */\n+\t/* size */\n+};\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst);\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst);\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);\n+\n+extern unsigned char *get_cache_slice(struct commit *commit);\n+extern int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work);\n+\n+extern void init_rev_cache_info(struct rev_cache_info *rci);\n+extern int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1);\n+extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size);\n+\n+extern void starts_from_slices(struct rev_info *revs, unsigned int flags);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 9f5dac5..485bf72 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -432,7 +432,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \tcommit->object.flags |= TREESAME;\n }\n \n-static void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+void insert_by_date_cached(struct commit *p, struct commit_list **head,\n \t\t    struct commit_list *cached_base, struct commit_list **cache)\n {\n \tstruct commit_list *new_entry;\ndiff --git a/revision.h b/revision.h\nindex fb74492..e767a43 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -13,11 +13,25 @@\n #define CHILD_SHOWN\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define FACE_VALUE\t(1u<<9)\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n \n struct rev_info;\n struct log_info;\n \n+struct rev_cache_info {\n+\t/* generation flags */\n+\tunsigned objects : 1,\n+\t\tlegs : 1,\n+\t\tmake_index : 1;\n+\n+\t/* traversal flags */\n+\tunsigned add_to_pending : 1;\n+\n+\t/* fuse options */\n+\tunsigned int ignore_size;\n+};\n+\n struct rev_info {\n \t/* Starting list */\n \tstruct commit_list *commits;\n@@ -73,6 +87,10 @@ struct rev_info {\n \t\t\tdense_combined_merges:1,\n \t\t\talways_show_header:1;\n \n+\t/* rev-cache flags */\n+\tunsigned int for_pack:1,\n+\t\tdont_cache_me:1;\n+\n \t/* Format info */\n \tunsigned int\tshown_one:1,\n \t\t\tshow_merge:1,\n@@ -116,6 +134,9 @@ struct rev_info {\n \tstruct reflog_walk_info *reflog_info;\n \tstruct decoration children;\n \tstruct decoration merge_simplification;\n+\n+\t/* caching info, used ONLY by traverse_cache_slice */\n+\tstruct rev_cache_info rev_cache_info;\n };\n \n #define REV_TREE_SAME\t\t0\n@@ -167,4 +188,7 @@ enum commit_action {\n \n extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);\n \n+extern void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+\t\t    struct commit_list *cached_base, struct commit_list **cache);\n+\n #endif\ndiff --git a/t/t6015-rev-cache-list.sh b/t/t6015-rev-cache-list.sh\nnew file mode 100755\nindex 0000000..fc29311\n--- /dev/null\n+++ b/t/t6015-rev-cache-list.sh\n@@ -0,0 +1,104 @@\n+#!/bin/sh\n+\n+test_description='git rev-cache tests'\n+. ./test-lib.sh\n+\n+test_cmp_sorted() {\n+\tgrep -io \"[a-f0-9]*\" $1 | sort >.tmpfile1 &&\n+\tgrep -io \"[a-f0-9]*\" $2 | sort >.tmpfile2 &&\n+\ttest_cmp .tmpfile1 .tmpfile2\n+}\n+\n+# we want a totally wacked out branch structure...\n+# we need branching and merging of sizes up through 3, tree\n+# addition/deletion, and enough branching to exercise path\n+# reuse\n+test_expect_success 'init repo' '\n+\techo bla >file &&\n+\tgit add . &&\n+\tgit commit -m \"bla\" &&\n+\n+\tgit branch b1 &&\n+\tgit checkout b1 &&\n+\techo blu >file2 &&\n+\tmkdir d1 &&\n+\techo bang >d1/filed1 &&\n+\tgit add . &&\n+\tgit commit -m \"blu\" &&\n+\n+\tgit checkout master &&\n+\tgit branch b2 &&\n+\tgit checkout b2 &&\n+\techo kaplaa >>file &&\n+\tgit commit -a -m \"kaplaa\" &&\n+\n+\tgit checkout master &&\n+\tmkdir smoke &&\n+\techo omg >smoke/bong &&\n+\tgit add . &&\n+\tgit commit -m \"omg\" &&\n+\n+\tgit branch b4 &&\n+\tgit checkout b4 &&\n+\techo shazam >file8 &&\n+\tgit add . &&\n+\tgit commit -m \"shazam\" &&\n+\tgit merge -m \"merge b2\" b2 &&\n+\n+\techo bam >smoke/pipe &&\n+\tgit add .\n+\tgit commit -m \"bam\" &&\n+\n+\tgit checkout master &&\n+\techo pow >file7 &&\n+\tgit add . &&\n+\tgit commit -m \"pow\" &&\n+\tgit merge -m \"merge b4\" b4 &&\n+\n+\tgit checkout b1 &&\n+\techo stuff >d1/filed1 &&\n+\tgit commit -a -m \"stuff\" &&\n+\n+\tgit branch b11 &&\n+\tgit checkout b11 &&\n+\techo wazzup >file3 &&\n+\tgit add file3 &&\n+\tgit commit -m \"wazzup\" &&\n+\n+\tgit checkout b1 &&\n+\tmkdir d1/d2 &&\n+\techo lol >d1/d2/filed2 &&\n+\tgit add . &&\n+\tgit commit -m \"lol\" &&\n+\n+\tgit checkout master &&\n+\tgit merge -m \"triple merge\" b1 b11 &&\n+\tgit rm -r d1 &&\n+\tgit commit -a -m \"oh noes\"\n+'\n+\n+git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited\n+git-rev-list HEAD >proper_commit_list\n+\n+test_expect_success 'make cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+test_expect_success 'remake cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+#check core mechanics and rev-list hook for commits\n+test_expect_success 'test rev-caches walker directly (limited)' '\n+\tgit-rev-cache walk HEAD --not HEAD~3 >list &&\n+\ttest_cmp_sorted list proper_commit_list_limited\n+'\n+\n+test_expect_success 'test rev-caches walker directly (unlimited)' '\n+\tgit-rev-cache walk HEAD >list &&\n+\ttest_cmp_sorted list proper_commit_list\n+'\n+\n+test_done\n-- \ntg: (64d5fe0..) t/revcache/basic (depends on: master)\n"},{"id":"122633","messageId":"op.uzv4b9s0tdk399@sirnot.private","threadId":"20630","inReplyTo":"op.uyzwxz0ptdk399@sirnot","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Nick Edelen","fromEmail":"sirnot@gmail.com","sentAt":"2009-09-07T14:10:47Z","receivedAt":"2009-09-07T14:10:47Z","isPatch":true,"sender":{"key":"sirnot@gmail.com","avatar":null},"body":"Second in the revision cache series, this particular patch provides:\n - minimal API: caching only commit topo data\n - minimal porcelain: add and walk cache slices\n - appropriate tests\n\nSigned-off-by: Nick Edelen <sirnot@gmail.com>\n\n---\nRenamed test file to t6017-... to avoid conflict with newly-introduced\nt6015-rev-list-show-all-parents.\n\n Makefile                  |    2 +\n builtin-rev-cache.c       |  207 ++++++++\n builtin.h                 |    1 +\n commit.c                  |    2 +\n git.c                     |    1 +\n rev-cache.c               | 1171 +++++++++++++++++++++++++++++++++++++++++++++\n rev-cache.h               |  107 ++++\n revision.c                |    2 +-\n revision.h                |   26 +-\n t/t6017-rev-cache-list.sh |  112 +++++\n 10 files changed, 1629 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex a614347..242e2af 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -529,6 +529,7 @@ LIB_OBJS += refs.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace_object.o\n LIB_OBJS += rerere.o\n+LIB_OBJS += rev-cache.o\n LIB_OBJS += revision.o\n LIB_OBJS += run-command.o\n LIB_OBJS += server-info.o\n@@ -620,6 +621,7 @@ BUILTIN_OBJS += builtin-remote.o\n BUILTIN_OBJS += builtin-replace.o\n BUILTIN_OBJS += builtin-rerere.o\n BUILTIN_OBJS += builtin-reset.o\n+BUILTIN_OBJS += builtin-rev-cache.o\n BUILTIN_OBJS += builtin-rev-list.o\n BUILTIN_OBJS += builtin-rev-parse.o\n BUILTIN_OBJS += builtin-revert.o\ndiff --git a/builtin-rev-cache.c b/builtin-rev-cache.c\nnew file mode 100644\nindex 0000000..6eb7065\n--- /dev/null\n+++ b/builtin-rev-cache.c\n@@ -0,0 +1,207 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+\n+/* porcelain for rev-cache.c */\n+static int handle_add(int argc, const char *argv[]) /* args beyond this command */\n+{\n+\tstruct rev_info revs;\n+\tstruct rev_cache_info rci;\n+\tchar dostdin = 0;\n+\tunsigned int flags = 0;\n+\tint i, retval;\n+\tunsigned char cache_sha1[20];\n+\tstruct commit_list *starts = 0, *ends = 0;\n+\tstruct commit *commit;\n+\n+\tinit_revisions(&revs, 0);\n+\tinit_rev_cache_info(&rci);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--stdin\"))\n+\t\t\tdostdin = 1;\n+\t\telse if (!strcmp(argv[i], \"--fresh\") || !strcmp(argv[i], \"--incremental\"))\n+\t\t\tstarts_from_slices(&revs, UNINTERESTING);\n+\t\telse if (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--legs\"))\n+\t\t\trci.legs = 1;\n+\t\telse if (!strcmp(argv[i], \"--no-objects\"))\n+\t\t\trci.objects = 0;\n+\t\telse if (!strcmp(argv[i], \"--all\")) {\n+\t\t\tconst char *args[2];\n+\t\t\tint argn = 0;\n+\n+\t\t\targs[argn++] = \"rev-list\";\n+\t\t\targs[argn++] = \"--all\";\n+\t\t\tsetup_revisions(argn, args, &revs, 0);\n+\t\t} else\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\tif (dostdin) {\n+\t\tchar line[1000];\n+\n+\t\tflags = 0;\n+\t\twhile (fgets(line, sizeof(line), stdin)) {\n+\t\t\tint len = strlen(line);\n+\t\t\twhile (len && (line[len - 1] == '\\n' || line[len - 1] == '\\r'))\n+\t\t\t\tline[--len] = 0;\n+\n+\t\t\tif (!len)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (!strcmp(line, \"--not\"))\n+\t\t\t\tflags ^= UNINTERESTING;\n+\t\t\telse\n+\t\t\t\thandle_revision_arg(line, &revs, flags, 1);\n+\t\t}\n+\t}\n+\n+\tretval = make_cache_slice(&rci, &revs, &starts, &ends, cache_sha1);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tprintf(\"%s\\n\", sha1_to_hex(cache_sha1));\n+\n+\tfprintf(stderr, \"endpoints:\\n\");\n+\twhile ((commit = pop_commit(&starts)))\n+\t\tfprintf(stderr, \"S %s\\n\", sha1_to_hex(commit->object.sha1));\n+\twhile ((commit = pop_commit(&ends)))\n+\t\tfprintf(stderr, \"E %s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\treturn 0;\n+}\n+\n+static int handle_walk(int argc, const char *argv[])\n+{\n+\tstruct commit *commit;\n+\tstruct rev_info revs;\n+\tstruct commit_list *queue, *work, **qp;\n+\tunsigned char *sha1p, *sha1pt;\n+\tunsigned long date = 0;\n+\tunsigned int flags = 0;\n+\tint retval, slop = 5, i;\n+\n+\tinit_revisions(&revs, 0);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--objects\"))\n+\t\t\trevs.tree_objects = revs.blob_objects = 1;\n+\t\telse\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\twork = 0;\n+\tsha1p = 0;\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tcommit = lookup_commit(revs.pending.objects[i].item->sha1);\n+\n+\t\tsha1pt = get_cache_slice(commit);\n+\t\tif (!sha1pt)\n+\t\t\tdie(\"%s: not in a cache slice\", sha1_to_hex(commit->object.sha1));\n+\n+\t\tif (!i)\n+\t\t\tsha1p = sha1pt;\n+\t\telse if (sha1p != sha1pt)\n+\t\t\tdie(\"walking porcelain is /per/ cache slice; commits cannot be spread out amoung several\");\n+\n+\t\tinsert_by_date(commit, &work);\n+\t}\n+\n+\tif (!sha1p)\n+\t\tdie(\"nothing to traverse!\");\n+\n+\tqueue = 0;\n+\tqp = &queue;\n+\tcommit = pop_commit(&work);\n+\tretval = traverse_cache_slice(&revs, sha1p, commit, &date, &slop, &qp, &work);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tfprintf(stderr, \"queue:\\n\");\n+\twhile ((commit = pop_commit(&queue)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"work:\\n\");\n+\twhile ((commit = pop_commit(&work)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"pending:\\n\");\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tstruct object *obj = revs.pending.objects[i].item;\n+\n+\t\t/* unfortunately, despite our careful generation, object duplication *is* a possibility...\n+\t\t * (eg. same object introduced into two different branches) */\n+\t\tif (obj->flags & SEEN)\n+\t\t\tcontinue;\n+\n+\t\tprintf(\"%s\\n\", sha1_to_hex(revs.pending.objects[i].item->sha1));\n+\t\tobj->flags |= SEEN;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int handle_help(void)\n+{\n+\tchar *usage = \"\\\n+usage:\\n\\\n+git-rev-cache COMMAND [options] [<commit-id>...]\\n\\\n+commands:\\n\\\n+  add    - add revisions to the cache.  reads commit ids from stdin, \\n\\\n+           formatted as: START START ... --not END END ...\\n\\\n+           options:\\n\\\n+            --all                  use all branch heads as starts\\n\\\n+            --fresh/--incremental  exclude everything already in a cache slice\\n\\\n+            --stdin                also read commit ids from stdin (same form\\n\\\n+                                   as cmd)\\n\\\n+            --legs                 ensure branch is entirely self-contained\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+  walk   - walk a cache slice based on set of commits; formatted as add\\n\\\n+           options:\\n\\\n+           --objects               include non-commit objects in traversals\\n\\\n+  fuse   - coalesce cache slices into a single cache.\\n\\\n+           options:\\n\\\n+            --all                  include all objects in repository\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+            --ignore-size[=N]      ignore slices of size >= N; defaults to ~5MB\\n\\\n+  index  - regnerate the cache index.\";\n+\n+\tputs(usage);\n+\n+\treturn 0;\n+}\n+\n+int cmd_rev_cache(int argc, const char *argv[], const char *prefix)\n+{\n+\tconst char *arg;\n+\tint r;\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\tif (argc > 1)\n+\t\targ = argv[1];\n+\telse\n+\t\targ = \"\";\n+\n+\targc -= 2;\n+\targv += 2;\n+\tif (!strcmp(arg, \"add\"))\n+\t\tr = handle_add(argc, argv);\n+\telse if (!strcmp(arg, \"walk\"))\n+\t\tr = handle_walk(argc, argv);\n+\telse\n+\t\treturn handle_help();\n+\n+\tfprintf(stderr, \"final return value: %d\\n\", r);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex a2174dc..2f89feb 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -86,6 +86,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\n extern int cmd_rerere(int argc, const char **argv, const char *prefix);\n extern int cmd_reset(int argc, const char **argv, const char *prefix);\n+extern int cmd_rev_cache(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_list(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n extern int cmd_revert(int argc, const char **argv, const char *prefix);\ndiff --git a/commit.c b/commit.c\nindex a6c6f70..b7485c4 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -252,6 +252,8 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)\n \titem->tree = lookup_tree(parent);\n \tbufptr += 46; /* \"tree \" + \"hex sha1\" + \"\\n\" */\n \tpptr = &item->parents;\n+\twhile (pop_commit(pptr))\n+\t\t; /* clear anything from cache */\n \n \tgraft = lookup_commit_graft(item->object.sha1);\n \twhile (bufptr + 48 < tail && !memcmp(bufptr, \"parent \", 7)) {\ndiff --git a/git.c b/git.c\nindex 0b22595..3cfde66 100644\n--- a/git.c\n+++ b/git.c\n@@ -343,6 +343,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"repo-config\", cmd_config },\n \t\t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t\t{ \"reset\", cmd_reset, RUN_SETUP },\n+\t\t{ \"rev-cache\", cmd_rev_cache, RUN_SETUP },\n \t\t{ \"rev-list\", cmd_rev_list, RUN_SETUP },\n \t\t{ \"rev-parse\", cmd_rev_parse },\n \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/rev-cache.c b/rev-cache.c\nnew file mode 100644\nindex 0000000..8951cdf\n--- /dev/null\n+++ b/rev-cache.c\n@@ -0,0 +1,1171 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+#include \"run-command.h\"\n+\n+/* list resembles pack index format */\n+static uint32_t fanout[0xff + 2];\n+\n+static unsigned char *idx_map;\n+static int idx_size;\n+static struct rc_index_header idx_head;\n+static unsigned char *idx_caches;\n+static char no_idx;\n+\n+static struct strbuf *acc_buffer;\n+\n+#define SLOP\t\t\t5\n+\n+/* initialization */\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst)\n+{\n+\tstatic struct rc_index_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->is_start = !!(src->flags & 0x80);\n+\tdst->cache_index = src->flags & 0x7f;\n+\tdst->pos = ntohl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst)\n+{\n+\tstatic struct rc_index_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->flags = (unsigned char)src->is_start << 7 | (unsigned char)src->cache_index;\n+\tdst->pos = htonl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst)\n+{\n+\tstatic struct rc_object_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->type = src->flags >> 5;\n+\tdst->is_end = !!(src->flags & 0x10);\n+\tdst->is_start = !!(src->flags & 0x08);\n+\tdst->uninteresting = !!(src->flags & 0x04);\n+\tdst->include = !!(src->flags & 0x02);\n+\tdst->flag = !!(src->flags & 0x01);\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->size_size = src->sizes >> 5;\n+\tdst->padding = src->sizes & 0x1f;\n+\n+\tdst->date = ntohl(src->date);\n+\tdst->path = ntohs(src->path);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst)\n+{\n+\tstatic struct rc_object_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->flags  = (unsigned char)src->type << 5;\n+\tdst->flags |= (unsigned char)src->is_end << 4;\n+\tdst->flags |= (unsigned char)src->is_start << 3;\n+\tdst->flags |= (unsigned char)src->uninteresting << 2;\n+\tdst->flags |= (unsigned char)src->include << 1;\n+\tdst->flags |= (unsigned char)src->flag;\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->sizes  = (unsigned char)src->size_size << 5;\n+\tdst->sizes |= (unsigned char)src->padding;\n+\n+\tdst->date = htonl(src->date);\n+\tdst->path = htons(src->path);\n+\n+\treturn dst;\n+}\n+\n+static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)\n+{\n+\tstruct rc_index_header whead;\n+\tint i, index = sizeof(struct rc_index_header);\n+\n+\tmemcpy(&whead, map, sizeof(struct rc_index_header));\n+\tif (memcmp(whead.signature, \"REVINDEX\", 8) || whead.version != SUPPORTED_REVINDEX_VERSION)\n+\t\treturn -1;\n+\n+\tmemcpy(head->signature, \"REVINDEX\", 8);\n+\thead->version = whead.version;\n+\thead->ofs_objects = ntohl(whead.ofs_objects);\n+\thead->object_nr = ntohl(whead.object_nr);\n+\thead->cache_nr = whead.cache_nr;\n+\thead->max_date = ntohl(whead.max_date);\n+\n+\tif (len < index + head->cache_nr * 20 + 0x100 * sizeof(uint32_t))\n+\t\treturn -2;\n+\n+\t*caches = xmalloc(head->cache_nr * 20);\n+\tmemcpy(*caches, map + index, head->cache_nr * 20);\n+\tindex += head->cache_nr * 20;\n+\n+\tmemcpy(fanout, map + index, 0x100 * sizeof(uint32_t));\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = ntohl(fanout[i]);\n+\tfanout[0x100] = len;\n+\n+\treturn 0;\n+}\n+\n+/* added in init_index */\n+static void cleanup_cache_slices(void)\n+{\n+\tif (idx_map) {\n+\t\tfree(idx_caches);\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+}\n+\n+static int init_index(void)\n+{\n+\tint fd;\n+\tstruct stat fi;\n+\n+\tfd = open(git_path(\"rev-cache/index\"), O_RDONLY);\n+\tif (fd == -1 || fstat(fd, &fi))\n+\t\tgoto end;\n+\tif (fi.st_size < sizeof(struct rc_index_header))\n+\t\tgoto end;\n+\n+\tidx_size = fi.st_size;\n+\tidx_map = xmmap(0, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tif (idx_map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_index_head(idx_map, fi.st_size, &idx_head, fanout, &idx_caches))\n+\t\tgoto end;\n+\n+\tatexit(cleanup_cache_slices);\n+\n+\treturn 0;\n+\n+end:\n+\tidx_map = 0;\n+\tno_idx = 1;\n+\treturn -1;\n+}\n+\n+/* this assumes index is already loaded */\n+static struct rc_index_entry_ondisk *search_index_1(unsigned char *sha1)\n+{\n+\tint start, end, starti, endi, i, len, r;\n+\tstruct rc_index_entry_ondisk *ie;\n+\n+\tif (!idx_map)\n+\t\treturn 0;\n+\n+\t/* binary search */\n+\tstart = fanout[(int)sha1[0]];\n+\tend = fanout[(int)sha1[0] + 1];\n+\tlen = (end - start) / sizeof(struct rc_index_entry_ondisk);\n+\tif (!len || len * sizeof(struct rc_index_entry_ondisk) != end - start)\n+\t\treturn 0;\n+\n+\tstarti = 0;\n+\tendi = len - 1;\n+\tfor (;;) {\n+\t\ti = (endi + starti) / 2;\n+\t\tie = (struct rc_index_entry_ondisk *)(idx_map + start + i * sizeof(struct rc_index_entry_ondisk));\n+\t\tr = hashcmp(sha1, ie->sha1);\n+\n+\t\tif (r) {\n+\t\t\tif (starti + 1 == endi) {\n+\t\t\t\tstarti++;\n+\t\t\t\tcontinue;\n+\t\t\t} else if (starti == endi)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (r > 0)\n+\t\t\t\tstarti = i;\n+\t\t\telse /* r < 0 */\n+\t\t\t\tendi = i;\n+\t\t} else\n+\t\t\treturn ie;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static struct rc_index_entry *search_index(unsigned char *sha1)\n+{\n+\tstruct rc_index_entry_ondisk *ied = search_index_1(sha1);\n+\n+\tif (ied)\n+\t\treturn from_disked_rc_index_entry(ied, 0);\n+\n+\treturn 0;\n+}\n+\n+unsigned char *get_cache_slice(struct commit *commit)\n+{\n+\tstruct rc_index_entry *ie;\n+\n+\tif (!idx_map) {\n+\t\tif (no_idx)\n+\t\t\treturn 0;\n+\t\tinit_index();\n+\t}\n+\n+\tif (commit->date > idx_head.max_date)\n+\t\treturn 0;\n+\n+\tie = search_index(commit->object.sha1);\n+\tif (ie && ie->cache_index < idx_head.cache_nr)\n+\t\treturn idx_caches + ie->cache_index * 20;\n+\n+\treturn 0;\n+}\n+\n+\n+/* traversal */\n+\n+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)\n+{\n+\tstruct rc_index_entry *iep;\n+\tstruct rc_object_entry *oep;\n+\tstruct commit_list *prev, *wp, **wpp;\n+\tint retval;\n+\n+\tiep = search_index(commit->object.sha1), 0;\n+\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t/* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,\n+\t * but we might as well initialize it while we're at it */\n+\toep->include = 1;\n+\toep->uninteresting = !!(commit->object.flags & UNINTERESTING);\n+\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\tretval = iep->pos;\n+\n+\t/* include any others in the work array */\n+\tprev = 0;\n+\twpp = work;\n+\twp = *work;\n+\twhile (wp) {\n+\t\tstruct object *obj = &wp->item->object;\n+\t\tstruct commit *co;\n+\n+\t\t/* is this in our cache slice? */\n+\t\tiep = search_index(obj->sha1);\n+\t\tif (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {\n+\t\t\tprev = wp;\n+\t\t\twp = wp->next;\n+\t\t\twpp = &wp;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (iep->pos < retval)\n+\t\t\tretval = iep->pos;\n+\n+\t\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t\t/* mark this for later */\n+\t\toep->include = 1;\n+\t\toep->uninteresting = !!(obj->flags & UNINTERESTING);\n+\t\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\n+\t\t/* remove from work list */\n+\t\tco = pop_commit(wpp);\n+\t\twp = *wpp;\n+\t\tif (prev)\n+\t\t\tprev->next = wp;\n+\t}\n+\n+\treturn retval;\n+}\n+\n+#define IPATH\t\t\t\t0x40\n+#define UPATH\t\t\t\t0x80\n+\n+#define GET_COUNT(x)\t\t((x) & 0x3f)\n+#define SET_COUNT(x, s)\t\t((x) = ((x) & ~0x3f) | ((s) & 0x3f))\n+\n+static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *map,\n+\tstruct rev_info *revs, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tstruct commit_list *insert_cache = 0;\n+\tstruct commit **last_objects, *co;\n+\tint i, total_path_nr = head->path_nr, retval = -1;\n+\tchar consume_children = 0;\n+\tunsigned char *paths;\n+\n+\ti = setup_traversal(head, map, commit, work);\n+\tif (i < 0)\n+\t\treturn -1;\n+\n+\tpaths = xcalloc(total_path_nr, sizeof(uint16_t));\n+\tlast_objects = xcalloc(total_path_nr, sizeof(struct commit *));\n+\n+\t/* i already set */\n+\twhile (i < head->size) {\n+\t\tstruct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tint path = entry->path;\n+\t\tstruct object *obj;\n+\t\tint index = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);\n+\n+\t\t/* add extra objects if necessary */\n+\t\tif (entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tconsume_children = 0;\n+\n+\t\tif (path >= total_path_nr)\n+\t\t\tgoto end;\n+\n+\t\t/* in one of our branches?\n+\t\t * uninteresting trumps interesting */\n+\t\tif (entry->include)\n+\t\t\tpaths[path] |= entry->uninteresting ? UPATH : IPATH;\n+\t\telse if (!paths[path])\n+\t\t\tcontinue;\n+\n+\t\t/* date stuff */\n+\t\tif (revs->max_age != -1 && entry->date < revs->max_age)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\t/* lookup object */\n+\t\tco = lookup_commit(entry->sha1);\n+\t\tobj = &co->object;\n+\n+\t\tif (obj->flags & UNINTERESTING)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\tif ((paths[path] & IPATH) && (paths[path] & UPATH)) {\n+\t\t\tpaths[path] = UPATH;\n+\n+\t\t\t/* mark edge */\n+\t\t\tif (last_objects[path]) {\n+\t\t\t\tparse_commit(last_objects[path]);\n+\n+\t\t\t\tlast_objects[path]->object.flags &= ~FACE_VALUE;\n+\t\t\t\tlast_objects[path] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* now we gotta re-assess the whole interesting thing... */\n+\t\tentry->uninteresting = !!(paths[path] & UPATH);\n+\n+\t\t/* first close paths */\n+\t\tif (entry->split_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE(entry->merge_nr);\n+\n+\t\t\tfor (j = 0; j < entry->split_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\t/* boundary commit? */\n+\t\t\t\tif ((paths[p] & IPATH) && entry->uninteresting) {\n+\t\t\t\t\tif (last_objects[p]) {\n+\t\t\t\t\t\tparse_commit(last_objects[p]);\n+\n+\t\t\t\t\t\tlast_objects[p]->object.flags &= ~FACE_VALUE;\n+\t\t\t\t\t\tlast_objects[p] = 0;\n+\t\t\t\t\t}\n+\t\t\t\t} else if (last_objects[p] && !last_objects[p]->object.parsed)\n+\t\t\t\t\tcommit_list_insert(co, &last_objects[p]->parents);\n+\n+\t\t\t\t/* can't close a merge path until all are parents have been encountered */\n+\t\t\t\tif (GET_COUNT(paths[p])) {\n+\t\t\t\t\tSET_COUNT(paths[p], GET_COUNT(paths[p]) - 1);\n+\n+\t\t\t\t\tif (GET_COUNT(paths[p]))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tpaths[p] = 0;\n+\t\t\t\tlast_objects[p] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* make topo relations */\n+\t\tif (last_objects[path] && !last_objects[path]->object.parsed)\n+\t\t\tcommit_list_insert(co, &last_objects[path]->parents);\n+\n+\t\t/* initialize commit */\n+\t\tif (!entry->is_end) {\n+\t\t\tco->date = entry->date;\n+\t\t\tobj->flags |= ADDED | FACE_VALUE;\n+\t\t} else\n+\t\t\tparse_commit(co);\n+\n+\t\tobj->flags |= SEEN;\n+\n+\t\tif (entry->uninteresting)\n+\t\t\tobj->flags |= UNINTERESTING;\n+\n+\t\t/* we need to know what the edges are */\n+\t\tlast_objects[path] = co;\n+\n+\t\t/* add to list */\n+\t\tif (!(obj->flags & UNINTERESTING) || revs->show_all) {\n+\t\t\tif (entry->is_end)\n+\t\t\t\tinsert_by_date_cached(co, work, insert_cache, &insert_cache);\n+\t\t\telse\n+\t\t\t\t*queue = &commit_list_insert(co, *queue)->next;\n+\n+\t\t\t/* add children to list as well */\n+\t\t\tif (obj->flags & UNINTERESTING)\n+\t\t\t\tconsume_children = 0;\n+\t\t\telse\n+\t\t\t\tconsume_children = 1;\n+\t\t}\n+\n+\t\t/* open parents */\n+\t\tif (entry->merge_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk);\n+\t\t\tchar flag = entry->uninteresting ? UPATH : IPATH;\n+\n+\t\t\tfor (j = 0; j < entry->merge_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\tif (paths[p] & flag)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tpaths[p] |= flag;\n+\t\t\t}\n+\n+\t\t\t/* make sure we don't use this path before all our parents have had their say */\n+\t\t\tSET_COUNT(paths[path], entry->merge_nr);\n+\t\t}\n+\n+\t}\n+\n+\tretval = 0;\n+\n+end:\n+\tfree(paths);\n+\tfree(last_objects);\n+\n+\treturn retval;\n+}\n+\n+static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)\n+{\n+\tint t;\n+\n+\tmemcpy(head, map, sizeof(struct rc_slice_header));\n+\thead->ofs_objects = ntohl(head->ofs_objects);\n+\thead->object_nr = ntohl(head->object_nr);\n+\thead->size = ntohl(head->size);\n+\thead->path_nr = ntohs(head->path_nr);\n+\n+\tif (memcmp(head->signature, \"REVCACHE\", 8))\n+\t\treturn -1;\n+\tif (head->version != SUPPORTED_REVCACHE_VERSION)\n+\t\treturn -2;\n+\tif (hashcmp(head->sha1, cache_sha1))\n+\t\treturn -3;\n+\tt = sizeof(struct rc_slice_header);\n+\tif (t != head->ofs_objects || t >= len)\n+\t\treturn -4;\n+\n+\thead->size = len;\n+\n+\treturn 0;\n+}\n+\n+int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tint fd = -1, retval = -3;\n+\tstruct stat fi;\n+\tstruct rc_slice_header head;\n+\tstruct rev_cache_info *rci;\n+\tunsigned char *map = MAP_FAILED;\n+\n+\t/* the index should've been loaded already to find cache_sha1, but it's good\n+\t * to be absolutely sure... */\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn -1;\n+\n+\t/* load options */\n+\trci = &revs->rev_cache_info;\n+\n+\tmemset(&head, 0, sizeof(struct rc_slice_header));\n+\n+\tfd = open(git_path(\"rev-cache/%s\", sha1_to_hex(cache_sha1)), O_RDONLY);\n+\tif (fd == -1)\n+\t\tgoto end;\n+\tif (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))\n+\t\tgoto end;\n+\n+\tmap = xmmap(0, fi.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))\n+\t\tgoto end;\n+\n+\tretval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);\n+\n+end:\n+\tif (map != MAP_FAILED)\n+\t\tmunmap(map, fi.st_size);\n+\tif (fd != -1)\n+\t\tclose(fd);\n+\n+\treturn retval;\n+}\n+\n+\n+\n+/* generation */\n+\n+struct path_track {\n+\tstruct commit *commit;\n+\tint path; /* for keeping track of children */\n+\n+\tstruct path_track *next, *prev;\n+};\n+\n+static unsigned char *paths;\n+static int path_nr = 1, path_sz;\n+\n+static struct path_track *path_track;\n+static struct path_track *path_track_alloc;\n+\n+#define PATH_IN_USE\t\t\t0x80 /* biggest bit we can get as a char */\n+\n+static int get_new_path(void)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i < path_nr; i++)\n+\t\tif (!paths[i])\n+\t\t\tbreak;\n+\n+\tif (i == path_nr) {\n+\t\tif (path_nr >= path_sz) {\n+\t\t\tpath_sz += 50;\n+\t\t\tpaths = xrealloc(paths, path_sz);\n+\t\t\tmemset(paths + path_sz - 50, 0, 50);\n+\t\t}\n+\t\tpath_nr++;\n+\t}\n+\n+\tpaths[i] = PATH_IN_USE;\n+\treturn i;\n+}\n+\n+static void remove_path_track(struct path_track **ppt, char total_free)\n+{\n+\tstruct path_track *t = *ppt;\n+\n+\tif (t->next)\n+\t\tt->next->prev = t->prev;\n+\tif (t->prev)\n+\t\tt->prev->next = t->next;\n+\n+\tt = t->next;\n+\n+\tif (total_free)\n+\t\tfree(*ppt);\n+\telse {\n+\t\t(*ppt)->next = path_track_alloc;\n+\t\tpath_track_alloc = *ppt;\n+\t}\n+\n+\t*ppt = t;\n+}\n+\n+static struct path_track *make_path_track(struct path_track **head, struct commit *commit)\n+{\n+\tstruct path_track *pt;\n+\n+\tif (path_track_alloc) {\n+\t\tpt = path_track_alloc;\n+\t\tpath_track_alloc = pt->next;\n+\t} else\n+\t\tpt = xmalloc(sizeof(struct path_track));\n+\n+\tmemset(pt, 0, sizeof(struct path_track));\n+\tpt->commit = commit;\n+\n+\tpt->next = *head;\n+\tif (*head)\n+\t\t(*head)->prev = pt;\n+\t*head = pt;\n+\n+\treturn pt;\n+}\n+\n+static void add_path_to_track(struct commit *commit, int path)\n+{\n+\tmake_path_track(&path_track, commit);\n+\tpath_track->path = path;\n+}\n+\n+static void handle_paths(struct commit *commit, struct rc_object_entry *object, struct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tint child_nr, parent_nr, open_parent_nr, this_path;\n+\tstruct commit_list *list;\n+\tstruct commit *first_parent;\n+\tstruct path_track **ppt, *pt;\n+\n+\t/* we can only re-use a closed path once all it's children have been encountered,\n+\t * as we need to keep track of commit boundaries */\n+\tppt = &path_track;\n+\tpt = *ppt;\n+\tchild_nr = 0;\n+\twhile (pt) {\n+\t\tif (pt->commit == commit) {\n+\t\t\tuint16_t write_path;\n+\n+\t\t\tif (paths[pt->path] != PATH_IN_USE)\n+\t\t\t\tpaths[pt->path]--;\n+\n+\t\t\t/* make sure we can handle this */\n+\t\t\tchild_nr++;\n+\t\t\tif (child_nr > 0x7f)\n+\t\t\t\tdie(\"%s: too many branches!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t\t\t/* add to split list */\n+\t\t\tobject->split_nr++;\n+\t\t\twrite_path = htons((uint16_t)pt->path);\n+\t\t\tstrbuf_add(split_str, &write_path, sizeof(uint16_t));\n+\n+\t\t\tremove_path_track(ppt, 0);\n+\t\t\tpt = *ppt;\n+\t\t} else {\n+\t\t\tpt = pt->next;\n+\t\t\tppt = &pt;\n+\t\t}\n+\t}\n+\n+\t/* initialize our self! */\n+\tif (!commit->indegree) {\n+\t\tcommit->indegree = get_new_path();\n+\t\tobject->is_start = 1;\n+\t}\n+\n+\tthis_path = commit->indegree;\n+\tpaths[this_path] = PATH_IN_USE;\n+\tobject->path = this_path;\n+\n+\t/* count interesting parents */\n+\tparent_nr = open_parent_nr = 0;\n+\tfirst_parent = 0;\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tif (list->item->object.flags & UNINTERESTING) {\n+\t\t\tobject->is_end = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tparent_nr++;\n+\t\tif (!list->item->indegree)\n+\t\t\topen_parent_nr++;\n+\t\tif (!first_parent)\n+\t\t\tfirst_parent = list->item;\n+\t}\n+\n+\tif (!parent_nr)\n+\t\treturn;\n+\n+\tif (parent_nr == 1 && open_parent_nr == 1) {\n+\t\tfirst_parent->indegree = this_path;\n+\t\treturn;\n+\t}\n+\n+\t/* bail out on obscene parent/child #s */\n+\tif (parent_nr > 0x7f)\n+\t\tdie(\"%s: too many parents in merge!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t/* make merge list */\n+\tobject->merge_nr = parent_nr;\n+\tpaths[this_path] = parent_nr;\n+\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tstruct commit *p = list->item;\n+\t\tuint16_t write_path;\n+\n+\t\tif (p->object.flags & UNINTERESTING)\n+\t\t\tcontinue;\n+\n+\t\t/* unfortunately due to boundary tracking we can't re-use merge paths\n+\t\t * (unable to guarantee last parent path = this -> last won't always be able to\n+\t\t * set this as a boundary object */\n+\t\tif (!p->indegree)\n+\t\t\tp->indegree = get_new_path();\n+\n+\t\twrite_path = htons((uint16_t)p->indegree);\n+\t\tstrbuf_add(merge_str, &write_path, sizeof(uint16_t));\n+\n+\t\t/* make sure path is properly ended */\n+\t\tadd_path_to_track(p, this_path);\n+\t}\n+\n+}\n+\n+\n+static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,\n+\tstruct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tstruct rc_object_entry object;\n+\n+\tif (!nothisone) {\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.sha1 = (unsigned char *)sha1;\n+\t\tobject.type = type;\n+\n+\t\tif (merge_str)\n+\t\t\tobject.merge_nr = merge_str->len / sizeof(uint16_t);\n+\t\tif (split_str)\n+\t\t\tobject.split_nr = split_str->len / sizeof(uint16_t);\n+\n+\t\tnothisone = &object;\n+\t}\n+\n+\tstrbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));\n+\n+\tif (merge_str && merge_str->len)\n+\t\tstrbuf_add(acc_buffer, merge_str->buf, merge_str->len);\n+\tif (split_str && split_str->len)\n+\t\tstrbuf_add(acc_buffer, split_str->buf, split_str->len);\n+\n+}\n+\n+static void init_revcache_directory(void)\n+{\n+\tstruct stat fi;\n+\n+\tif (stat(git_path(\"rev-cache\"), &fi) || !S_ISDIR(fi.st_mode))\n+\t\tif (mkdir(git_path(\"rev-cache\"), 0777))\n+\t\t\tdie(\"can't make rev-cache directory\");\n+\n+}\n+\n+void init_rev_cache_info(struct rev_cache_info *rci)\n+{\n+\trci->objects = 1;\n+\trci->legs = 0;\n+\trci->make_index = 1;\n+\n+\trci->add_to_pending = 1;\n+\n+\trci->ignore_size = 0;\n+}\n+\n+void maybe_fill_with_defaults(struct rev_cache_info *rci)\n+{\n+\tstatic struct rev_cache_info def_rci;\n+\n+\tif (rci)\n+\t\treturn;\n+\n+\tinit_rev_cache_info(&def_rci);\n+\trci = &def_rci;\n+}\n+\n+int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1)\n+{\n+\tstruct rev_info therevs;\n+\tstruct strbuf buffer, startlist, endlist;\n+\tstruct rc_slice_header head;\n+\tstruct commit *commit;\n+\tunsigned char sha1[20];\n+\tstruct strbuf merge_paths, split_paths;\n+\tint object_nr, total_sz, fd;\n+\tchar file[PATH_MAX], *newfile;\n+\tstruct rev_cache_info *trci;\n+\tgit_SHA_CTX ctx;\n+\n+\tmaybe_fill_with_defaults(rci);\n+\n+\tinit_revcache_directory();\n+\tstrcpy(file, git_path(\"rev-cache/XXXXXX\"));\n+\tfd = xmkstemp(file);\n+\n+\tstrbuf_init(&buffer, 0);\n+\tstrbuf_init(&startlist, 0);\n+\tstrbuf_init(&endlist, 0);\n+\tstrbuf_init(&merge_paths, 0);\n+\tstrbuf_init(&split_paths, 0);\n+\tacc_buffer = &buffer;\n+\n+\tif (!revs) {\n+\t\trevs = &therevs;\n+\t\tinit_revisions(revs, 0);\n+\n+\t\t/* we're gonna assume no one else has already traversed this... */\n+\t\twhile ((commit = pop_commit(starts)))\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\n+\t\twhile ((commit = pop_commit(ends))) {\n+\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\t\t}\n+\t}\n+\n+\t/* write head placeholder */\n+\tmemset(&head, 0, sizeof(head));\n+\thead.ofs_objects = htonl(sizeof(head));\n+\txwrite(fd, &head, sizeof(head));\n+\n+\t/* init revisions! */\n+\trevs->tree_objects = 1;\n+\trevs->blob_objects = 1;\n+\trevs->topo_order = 1;\n+\trevs->lifo = 1;\n+\n+\t/* re-use info from other caches if possible */\n+\ttrci = &revs->rev_cache_info;\n+\tinit_rev_cache_info(trci);\n+\ttrci->add_to_pending = 0;\n+\n+\tsetup_revisions(0, 0, revs, 0);\n+\tif (prepare_revision_walk(revs))\n+\t\tdie(\"died preparing revision walk\");\n+\n+\tobject_nr = total_sz = 0;\n+\twhile ((commit = get_revision(revs)) != 0) {\n+\t\tstruct rc_object_entry object;\n+\n+\t\tstrbuf_setlen(&merge_paths, 0);\n+\t\tstrbuf_setlen(&split_paths, 0);\n+\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.type = OBJ_COMMIT;\n+\t\tobject.date = commit->date;\n+\t\tobject.sha1 = commit->object.sha1;\n+\n+\t\thandle_paths(commit, &object, &merge_paths, &split_paths);\n+\n+\t\tif (object.is_end) {\n+\t\t\tstrbuf_add(&endlist, object.sha1, 20);\n+\t\t\tif (ends)\n+\t\t\t\tcommit_list_insert(commit, ends);\n+\t\t}\n+\t\t/* the two *aren't* mutually exclusive */\n+\t\tif (object.is_start) {\n+\t\t\tstrbuf_add(&startlist, object.sha1, 20);\n+\t\t\tif (starts)\n+\t\t\t\tcommit_list_insert(commit, starts);\n+\t\t}\n+\n+\t\tcommit->indegree = 0;\n+\n+\t\tadd_object_entry(0, 0, &object, &merge_paths, &split_paths);\n+\t\tobject_nr++;\n+\n+\t\t/* print every ~1MB or so */\n+\t\tif (buffer.len > 1000000) {\n+\t\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\t\ttotal_sz += buffer.len;\n+\n+\t\t\tstrbuf_setlen(&buffer, 0);\n+\t\t}\n+\t}\n+\n+\tif (buffer.len) {\n+\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\ttotal_sz += buffer.len;\n+\t}\n+\n+\t/* go ahead a free some stuff... */\n+\tstrbuf_release(&buffer);\n+\tstrbuf_release(&merge_paths);\n+\tstrbuf_release(&split_paths);\n+\tif (path_sz)\n+\t\tfree(paths);\n+\twhile (path_track_alloc)\n+\t\tremove_path_track(&path_track_alloc, 1);\n+\n+\t/* the meaning of the hash name is more or less irrelevant, it's the uniqueness that matters */\n+\tstrbuf_add(&endlist, startlist.buf, startlist.len);\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, endlist.buf, endlist.len);\n+\tgit_SHA1_Final(sha1, &ctx);\n+\n+\t/* now actually initialize header */\n+\tstrcpy(head.signature, \"REVCACHE\");\n+\thead.version = SUPPORTED_REVCACHE_VERSION;\n+\n+\thead.object_nr = htonl(object_nr);\n+\thead.size = htonl(ntohl(head.ofs_objects) + total_sz);\n+\thead.path_nr = htons(path_nr);\n+\thashcpy(head.sha1, sha1);\n+\n+\t/* some info! */\n+\tfprintf(stderr, \"objects: %d\\n\", object_nr);\n+\tfprintf(stderr, \"paths: %d\\n\", path_nr);\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\txwrite(fd, &head, sizeof(head));\n+\n+\tif (rci->make_index && make_cache_index(rci, sha1, fd, ntohl(head.size)) < 0)\n+\t\tdie(\"can't update index\");\n+\n+\tclose(fd);\n+\n+\tnewfile = git_path(\"rev-cache/%s\", sha1_to_hex(sha1));\n+\tif (rename(file, newfile))\n+\t\tdie(\"can't move temp file\");\n+\n+\t/* let our caller know what we've just made */\n+\tif (cache_sha1)\n+\t\thashcpy(cache_sha1, sha1);\n+\n+\tstrbuf_release(&endlist);\n+\tstrbuf_release(&startlist);\n+\n+\treturn 0;\n+}\n+\n+\n+static int index_sort_hash(const void *a, const void *b)\n+{\n+\treturn hashcmp(((struct rc_index_entry_ondisk *)a)->sha1, ((struct rc_index_entry_ondisk *)b)->sha1);\n+}\n+\n+static int write_cache_index(struct strbuf *body)\n+{\n+\tstruct rc_index_header whead;\n+\tstruct lock_file *lk;\n+\tint fd, i;\n+\n+\t/* clear index map if loaded */\n+\tif (idx_map) {\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+\tlk = xcalloc(sizeof(struct lock_file), 1);\n+\tfd = hold_lock_file_for_update(lk, git_path(\"rev-cache/index\"), 0);\n+\tif (fd < 0) {\n+\t\tfree(lk);\n+\t\treturn -1;\n+\t}\n+\n+\t/* endianness yay! */\n+\tmemset(&whead, 0, sizeof(whead));\n+\tmemcpy(whead.signature, \"REVINDEX\", 8);\n+\twhead.version = idx_head.version;\n+\twhead.ofs_objects = htonl(idx_head.ofs_objects);\n+\twhead.object_nr = htonl(idx_head.object_nr);\n+\twhead.cache_nr = idx_head.cache_nr;\n+\twhead.max_date = htonl(idx_head.max_date);\n+\n+\twrite(fd, &whead, sizeof(struct rc_index_header));\n+\twrite_in_full(fd, idx_caches, idx_head.cache_nr * 20);\n+\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = htonl(fanout[i]);\n+\twrite_in_full(fd, fanout, 0x100 * sizeof(uint32_t));\n+\n+\twrite_in_full(fd, body->buf, body->len);\n+\n+\tif (commit_lock_file(lk) < 0)\n+\t\treturn -2;\n+\n+\t/* lk freed by lockfile.c */\n+\n+\treturn 0;\n+}\n+\n+int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size)\n+{\n+\tstruct strbuf buffer;\n+\tint i, cache_index, cur;\n+\tunsigned char *map;\n+\tunsigned long max_date;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\tmap = xmmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\treturn -1;\n+\n+\tstrbuf_init(&buffer, 0);\n+\tif (idx_map) {\n+\t\tstrbuf_add(&buffer, idx_map + fanout[0], fanout[0x100] - fanout[0]);\n+\t} else {\n+\t\t/* not an update */\n+\t\tmemset(&idx_head, 0, sizeof(struct rc_index_header));\n+\t\tidx_caches = 0;\n+\n+\t\tstrcpy(idx_head.signature, \"REVINDEX\");\n+\t\tidx_head.version = SUPPORTED_REVINDEX_VERSION;\n+\t\tidx_head.ofs_objects = sizeof(struct rc_index_header) + 0x100 * sizeof(uint32_t);\n+\t}\n+\n+\t/* are we remaking a slice? */\n+\tfor (i = 0; i < idx_head.cache_nr; i++)\n+\t\tif (!hashcmp(idx_caches + i * 20, cache_sha1))\n+\t\t\tbreak;\n+\n+\tif (i == idx_head.cache_nr) {\n+\t\tcache_index = idx_head.cache_nr++;\n+\t\tidx_head.ofs_objects += 20;\n+\n+\t\tidx_caches = xrealloc(idx_caches, idx_head.cache_nr * 20);\n+\t\thashcpy(idx_caches + cache_index * 20, cache_sha1);\n+\t} else\n+\t\tcache_index = i;\n+\n+\ti = sizeof(struct rc_slice_header); /* offset */\n+\tmax_date = idx_head.max_date;\n+\twhile (i < size) {\n+\t\tstruct rc_index_entry index_entry, *entry;\n+\t\tstruct rc_index_entry_ondisk *disked_entry;\n+\t\tstruct rc_object_entry *object_entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tunsigned long date;\n+\t\tint off, pos = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(object_entry);\n+\n+\t\tif (object_entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\n+\t\t/* don't include ends; otherwise we'll find ourselves in loops */\n+\t\tif (object_entry->is_end)\n+\t\t\tcontinue;\n+\n+\t\t/* handle index duplication\n+\t\t * -> keep old copy unless new one is a start -- based on expected usage, older ones will be more\n+\t\t * likely to lead to greater slice traversals than new ones */\n+\t\tdate = object_entry->date;\n+\t\tif (date > idx_head.max_date) {\n+\t\t\tdisked_entry = 0;\n+\t\t\tif (date > max_date)\n+\t\t\t\tmax_date = date;\n+\t\t} else\n+\t\t\tdisked_entry = search_index_1(object_entry->sha1);\n+\n+\t\tif (disked_entry && !object_entry->is_start)\n+\t\t\tcontinue;\n+\t\telse if (disked_entry) {\n+\t\t\t/* mmm, pointer arithmetic... tasty */  /* (entry - idx_map = offset, so cast is valid) */\n+\t\t\toff = (unsigned int)((unsigned char *)disked_entry - idx_map) - fanout[0];\n+\t\t\tdisked_entry = (struct rc_index_entry_ondisk *)(buffer.buf + off);\n+\t\t\tentry = from_disked_rc_index_entry(disked_entry, 0);\n+\t\t} else\n+\t\t\tentry = &index_entry;\n+\n+\t\tmemset(entry, 0, sizeof(index_entry));\n+\t\tentry->sha1 = object_entry->sha1;\n+\t\tentry->is_start = object_entry->is_start;\n+\t\tentry->cache_index = cache_index;\n+\t\tentry->pos = pos;\n+\n+\t\tif (entry == &index_entry) {\n+\t\t\tstrbuf_add(&buffer, to_disked_rc_index_entry(entry, 0), sizeof(struct rc_index_entry_ondisk));\n+\t\t\tidx_head.object_nr++;\n+\t\t} else\n+\t\t\tto_disked_rc_index_entry(entry, disked_entry);\n+\n+\t}\n+\n+\tidx_head.max_date = max_date;\n+\tqsort(buffer.buf, buffer.len / sizeof(struct rc_index_entry_ondisk), sizeof(struct rc_index_entry_ondisk), index_sort_hash);\n+\n+\t/* generate fanout */\n+\tcur = 0x00;\n+\tfor (i = 0; i < buffer.len; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry_ondisk *entry = (struct rc_index_entry_ondisk *)(buffer.buf + i);\n+\n+\t\twhile (cur <= entry->sha1[0])\n+\t\t\tfanout[cur++] = i + idx_head.ofs_objects;\n+\t}\n+\n+\twhile (cur <= 0xff)\n+\t\tfanout[cur++] = idx_head.ofs_objects + buffer.len;\n+\n+\t/* BOOM! */\n+\tif (write_cache_index(&buffer))\n+\t\treturn -1;\n+\n+\tmunmap(map, size);\n+\tstrbuf_release(&buffer);\n+\n+\t/* idx_map is unloaded without cleanup_cache_slices(), so regardless of previous index existence\n+\t * we can still free this up */\n+\tfree(idx_caches);\n+\n+\treturn 0;\n+}\n+\n+\n+/* add start-commits from each cache slice (uninterestingness will be propogated) */\n+void starts_from_slices(struct rev_info *revs, unsigned int flags)\n+{\n+\tstruct commit *commit;\n+\tint i;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn;\n+\n+\tfor (i = idx_head.ofs_objects; i < idx_size; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry *entry = RC_OBTAIN_INDEX_ENTRY(idx_map + i);\n+\n+\t\tif (!entry->is_start)\n+\t\t\tcontinue;\n+\n+\t\tcommit = lookup_commit(entry->sha1);\n+\t\tif (!commit)\n+\t\t\tcontinue;\n+\n+\t\tcommit->object.flags |= flags;\n+\t\tadd_pending_object(revs, &commit->object, 0);\n+\t}\n+\n+}\ndiff --git a/rev-cache.h b/rev-cache.h\nnew file mode 100644\nindex 0000000..a471fbf\n--- /dev/null\n+++ b/rev-cache.h\n@@ -0,0 +1,107 @@\n+#ifndef REV_CACHE_H\n+#define REV_CACHE_H\n+\n+#define SUPPORTED_REVCACHE_VERSION \t\t1\n+#define SUPPORTED_REVINDEX_VERSION\t\t1\n+\n+#define RC_PATH_SIZE(x)\t(sizeof(uint16_t) * (x))\n+\n+#define RC_OBTAIN_OBJECT_ENTRY(p)\t\t\tfrom_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)\n+#define RC_OBTAIN_INDEX_ENTRY(p)\t\t\tfrom_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)\n+\n+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e)\t\t(sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)\n+\n+/* single index maps objects to cache files */\n+struct rc_index_header {\n+\tchar signature[8]; /* REVINDEX */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tunsigned char cache_nr;\n+\n+\tuint32_t max_date;\n+};\n+\n+struct rc_index_entry_ondisk {\n+\tunsigned char sha1[20];\n+\tunsigned char flags;\n+\tuint32_t pos;\n+};\n+\n+struct rc_index_entry {\n+\tunsigned char *sha1;\n+\tunsigned is_start : 1;\n+\tunsigned cache_index : 7;\n+\tuint32_t pos;\n+};\n+\n+\n+/* structure for actual cache file */\n+struct rc_slice_header {\n+\tchar signature[8]; /* REVCACHE */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tuint16_t path_nr;\n+\tuint32_t size;\n+\n+\tunsigned char sha1[20];\n+};\n+\n+struct rc_object_entry_ondisk {\n+\tunsigned char flags;\n+\tunsigned char sha1[20];\n+\n+\tunsigned char merge_nr;\n+\tunsigned char split_nr;\n+\tunsigned char sizes;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+};\n+\n+struct rc_object_entry {\n+\tunsigned type : 3;\n+\tunsigned is_end : 1;\n+\tunsigned is_start : 1;\n+\tunsigned uninteresting : 1;\n+\tunsigned include : 1;\n+\tunsigned flag : 1; /* unused */\n+\tunsigned char *sha1; /* 20 byte */\n+\n+\tunsigned char merge_nr; /* : 7 */\n+\tunsigned char split_nr; /* : 7 */\n+\tunsigned size_size : 3;\n+\tunsigned padding : 5;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+\n+\t/* merge paths */\n+\t/* split paths */\n+\t/* size */\n+};\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst);\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst);\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);\n+\n+extern unsigned char *get_cache_slice(struct commit *commit);\n+extern int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work);\n+\n+extern void init_rev_cache_info(struct rev_cache_info *rci);\n+extern int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1);\n+extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size);\n+\n+extern void starts_from_slices(struct rev_info *revs, unsigned int flags);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 35eca4a..c7fd35f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -432,7 +432,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \tcommit->object.flags |= TREESAME;\n }\n \n-static void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+void insert_by_date_cached(struct commit *p, struct commit_list **head,\n \t\t    struct commit_list *cached_base, struct commit_list **cache)\n {\n \tstruct commit_list *new_entry;\ndiff --git a/revision.h b/revision.h\nindex 9d0dddb..7db4b9e 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -13,7 +13,8 @@\n #define CHILD_SHOWN\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define FACE_VALUE\t(1u<<9)\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n \n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n@@ -21,6 +22,19 @@\n struct rev_info;\n struct log_info;\n \n+struct rev_cache_info {\n+\t/* generation flags */\n+\tunsigned objects : 1,\n+\t\tlegs : 1,\n+\t\tmake_index : 1;\n+\n+\t/* traversal flags */\n+\tunsigned add_to_pending : 1;\n+\n+\t/* fuse options */\n+\tunsigned int ignore_size;\n+};\n+\n struct rev_info {\n \t/* Starting list */\n \tstruct commit_list *commits;\n@@ -76,6 +90,10 @@ struct rev_info {\n \t\t\tdense_combined_merges:1,\n \t\t\talways_show_header:1;\n \n+\t/* rev-cache flags */\n+\tunsigned int for_pack:1,\n+\t\tdont_cache_me:1;\n+\n \t/* Format info */\n \tunsigned int\tshown_one:1,\n \t\t\tshow_merge:1,\n@@ -119,6 +137,9 @@ struct rev_info {\n \tstruct reflog_walk_info *reflog_info;\n \tstruct decoration children;\n \tstruct decoration merge_simplification;\n+\n+\t/* caching info, used ONLY by traverse_cache_slice */\n+\tstruct rev_cache_info rev_cache_info;\n };\n \n #define REV_TREE_SAME\t\t0\n@@ -171,4 +192,7 @@ enum commit_action {\n extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);\n extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);\n \n+extern void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+\t\t    struct commit_list *cached_base, struct commit_list **cache);\n+\n #endif\ndiff --git a/t/t6017-rev-cache-list.sh b/t/t6017-rev-cache-list.sh\nnew file mode 100755\nindex 0000000..dc0fc07\n--- /dev/null\n+++ b/t/t6017-rev-cache-list.sh\n@@ -0,0 +1,112 @@\n+#!/bin/sh\n+\n+test_description='git rev-cache tests'\n+. ./test-lib.sh\n+\n+test_cmp_sorted() {\n+\tgrep -io \"[a-f0-9]*\" $1 | sort >.tmpfile1 &&\n+\tgrep -io \"[a-f0-9]*\" $2 | sort >.tmpfile2 &&\n+\ttest_cmp .tmpfile1 .tmpfile2\n+}\n+\n+# we want a totally wacked out branch structure...\n+# we need branching and merging of sizes up through 3, tree\n+# addition/deletion, and enough branching to exercise path\n+# reuse\n+test_expect_success 'init repo' '\n+\techo bla >file &&\n+\tgit add . &&\n+\tgit commit -m \"bla\" &&\n+\n+\tgit branch b1 &&\n+\tgit checkout b1 &&\n+\techo blu >file2 &&\n+\tmkdir d1 &&\n+\techo bang >d1/filed1 &&\n+\tgit add . &&\n+\tgit commit -m \"blu\" &&\n+\n+\tgit checkout master &&\n+\tgit branch b2 &&\n+\tgit checkout b2 &&\n+\techo kaplaa >>file &&\n+\tgit commit -a -m \"kaplaa\" &&\n+\n+\tgit checkout master &&\n+\tmkdir smoke &&\n+\techo omg >smoke/bong &&\n+\tgit add . &&\n+\tgit commit -m \"omg\" &&\n+\n+\tgit branch b4 &&\n+\tgit checkout b4 &&\n+\techo shazam >file8 &&\n+\tgit add . &&\n+\tgit commit -m \"shazam\" &&\n+\tgit merge -m \"merge b2\" b2 &&\n+\n+\techo bam >smoke/pipe &&\n+\tgit add .\n+\tgit commit -m \"bam\" &&\n+\n+\tgit checkout master &&\n+\techo pow >file7 &&\n+\tgit add . &&\n+\tgit commit -m \"pow\" &&\n+\tgit merge -m \"merge b4\" b4 &&\n+\n+\tgit checkout b1 &&\n+\techo stuff >d1/filed1 &&\n+\tgit commit -a -m \"stuff\" &&\n+\n+\tgit branch b11 &&\n+\tgit checkout b11 &&\n+\techo wazzup >file3 &&\n+\tgit add file3 &&\n+\tgit commit -m \"wazzup\" &&\n+\n+\tgit checkout b1 &&\n+\tmkdir d1/d2 &&\n+\techo lol >d1/d2/filed2 &&\n+\tgit add . &&\n+\tgit commit -m \"lol\" &&\n+\n+\tgit checkout master &&\n+\tgit merge -m \"triple merge\" b1 b11 &&\n+\tgit rm -r d1 &&\n+\tgit commit -a -m \"oh noes\"\n+'\n+\n+git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited\n+git-rev-list HEAD >proper_commit_list\n+git-rev-list HEAD --objects >proper_object_list\n+\n+test_expect_success 'make cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+test_expect_success 'remake cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+#check core mechanics and rev-list hook for commits\n+test_expect_success 'test rev-caches walker directly (limited)' '\n+\tgit-rev-cache walk HEAD --not HEAD~3 >list &&\n+\ttest_cmp_sorted list proper_commit_list_limited\n+'\n+\n+test_expect_success 'test rev-caches walker directly (unlimited)' '\n+\tgit-rev-cache walk HEAD >list &&\n+\ttest_cmp_sorted list proper_commit_list\n+'\n+\n+#do the same for objects\n+test_expect_success 'test rev-caches walker with objects' '\n+\tgit-rev-cache walk --objects HEAD >list &&\n+\ttest_cmp_sorted list proper_object_list\n+'\n+\n+test_done\n+\n-- \ntg: (6ea71fe..) t/revcache/basic (depends on: master)\n"},{"id":"124152","messageId":"op.u061bavktdk399@sirnot.ed.ac.uk","threadId":"20630","inReplyTo":"op.uyuwkovjtdk399@sirnot.private","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Nick Edelen","fromEmail":"sirnot@gmail.com","sentAt":"2009-10-02T22:12:36Z","receivedAt":"2009-10-02T22:12:36Z","isPatch":true,"sender":{"key":"sirnot@gmail.com","avatar":null},"body":"Second in the revision cache series, this particular patch provides:\n  - minimal API: caching only commit topo data\n  - minimal porcelain: add and walk cache slices\n  - appropriate tests\n\nSigned-off-by: Nick Edelen <sirnot@gmail.com>\n\n---\nremoved useless test.\n\n  Makefile                  |    2 +\n  builtin-rev-cache.c       |  207 ++++++++\n  builtin.h                 |    1 +\n  commit.c                  |    2 +\n  git.c                     |    1 +\n  rev-cache.c               | 1171 +++++++++++++++++++++++++++++++++++++++++++++\n  rev-cache.h               |  107 ++++\n  revision.c                |    2 +-\n  revision.h                |   26 +-\n  t/t6017-rev-cache-list.sh |  106 ++++\n  10 files changed, 1623 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 12defd4..3098cc7 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -534,6 +534,7 @@ LIB_OBJS += refs.o\n  LIB_OBJS += remote.o\n  LIB_OBJS += replace_object.o\n  LIB_OBJS += rerere.o\n+LIB_OBJS += rev-cache.o\n  LIB_OBJS += revision.o\n  LIB_OBJS += run-command.o\n  LIB_OBJS += server-info.o\n@@ -626,6 +627,7 @@ BUILTIN_OBJS += builtin-remote.o\n  BUILTIN_OBJS += builtin-replace.o\n  BUILTIN_OBJS += builtin-rerere.o\n  BUILTIN_OBJS += builtin-reset.o\n+BUILTIN_OBJS += builtin-rev-cache.o\n  BUILTIN_OBJS += builtin-rev-list.o\n  BUILTIN_OBJS += builtin-rev-parse.o\n  BUILTIN_OBJS += builtin-revert.o\ndiff --git a/builtin-rev-cache.c b/builtin-rev-cache.c\nnew file mode 100644\nindex 0000000..6eb7065\n--- /dev/null\n+++ b/builtin-rev-cache.c\n@@ -0,0 +1,207 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+\n+/* porcelain for rev-cache.c */\n+static int handle_add(int argc, const char *argv[]) /* args beyond this command */\n+{\n+\tstruct rev_info revs;\n+\tstruct rev_cache_info rci;\n+\tchar dostdin = 0;\n+\tunsigned int flags = 0;\n+\tint i, retval;\n+\tunsigned char cache_sha1[20];\n+\tstruct commit_list *starts = 0, *ends = 0;\n+\tstruct commit *commit;\n+\n+\tinit_revisions(&revs, 0);\n+\tinit_rev_cache_info(&rci);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--stdin\"))\n+\t\t\tdostdin = 1;\n+\t\telse if (!strcmp(argv[i], \"--fresh\") || !strcmp(argv[i], \"--incremental\"))\n+\t\t\tstarts_from_slices(&revs, UNINTERESTING);\n+\t\telse if (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--legs\"))\n+\t\t\trci.legs = 1;\n+\t\telse if (!strcmp(argv[i], \"--no-objects\"))\n+\t\t\trci.objects = 0;\n+\t\telse if (!strcmp(argv[i], \"--all\")) {\n+\t\t\tconst char *args[2];\n+\t\t\tint argn = 0;\n+\n+\t\t\targs[argn++] = \"rev-list\";\n+\t\t\targs[argn++] = \"--all\";\n+\t\t\tsetup_revisions(argn, args, &revs, 0);\n+\t\t} else\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\tif (dostdin) {\n+\t\tchar line[1000];\n+\n+\t\tflags = 0;\n+\t\twhile (fgets(line, sizeof(line), stdin)) {\n+\t\t\tint len = strlen(line);\n+\t\t\twhile (len && (line[len - 1] == '\\n' || line[len - 1] == '\\r'))\n+\t\t\t\tline[--len] = 0;\n+\n+\t\t\tif (!len)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (!strcmp(line, \"--not\"))\n+\t\t\t\tflags ^= UNINTERESTING;\n+\t\t\telse\n+\t\t\t\thandle_revision_arg(line, &revs, flags, 1);\n+\t\t}\n+\t}\n+\n+\tretval = make_cache_slice(&rci, &revs, &starts, &ends, cache_sha1);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tprintf(\"%s\\n\", sha1_to_hex(cache_sha1));\n+\n+\tfprintf(stderr, \"endpoints:\\n\");\n+\twhile ((commit = pop_commit(&starts)))\n+\t\tfprintf(stderr, \"S %s\\n\", sha1_to_hex(commit->object.sha1));\n+\twhile ((commit = pop_commit(&ends)))\n+\t\tfprintf(stderr, \"E %s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\treturn 0;\n+}\n+\n+static int handle_walk(int argc, const char *argv[])\n+{\n+\tstruct commit *commit;\n+\tstruct rev_info revs;\n+\tstruct commit_list *queue, *work, **qp;\n+\tunsigned char *sha1p, *sha1pt;\n+\tunsigned long date = 0;\n+\tunsigned int flags = 0;\n+\tint retval, slop = 5, i;\n+\n+\tinit_revisions(&revs, 0);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--objects\"))\n+\t\t\trevs.tree_objects = revs.blob_objects = 1;\n+\t\telse\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\twork = 0;\n+\tsha1p = 0;\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tcommit = lookup_commit(revs.pending.objects[i].item->sha1);\n+\n+\t\tsha1pt = get_cache_slice(commit);\n+\t\tif (!sha1pt)\n+\t\t\tdie(\"%s: not in a cache slice\", sha1_to_hex(commit->object.sha1));\n+\n+\t\tif (!i)\n+\t\t\tsha1p = sha1pt;\n+\t\telse if (sha1p != sha1pt)\n+\t\t\tdie(\"walking porcelain is /per/ cache slice; commits cannot be spread out amoung several\");\n+\n+\t\tinsert_by_date(commit, &work);\n+\t}\n+\n+\tif (!sha1p)\n+\t\tdie(\"nothing to traverse!\");\n+\n+\tqueue = 0;\n+\tqp = &queue;\n+\tcommit = pop_commit(&work);\n+\tretval = traverse_cache_slice(&revs, sha1p, commit, &date, &slop, &qp, &work);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tfprintf(stderr, \"queue:\\n\");\n+\twhile ((commit = pop_commit(&queue)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"work:\\n\");\n+\twhile ((commit = pop_commit(&work)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"pending:\\n\");\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tstruct object *obj = revs.pending.objects[i].item;\n+\n+\t\t/* unfortunately, despite our careful generation, object duplication *is* a possibility...\n+\t\t * (eg. same object introduced into two different branches) */\n+\t\tif (obj->flags & SEEN)\n+\t\t\tcontinue;\n+\n+\t\tprintf(\"%s\\n\", sha1_to_hex(revs.pending.objects[i].item->sha1));\n+\t\tobj->flags |= SEEN;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int handle_help(void)\n+{\n+\tchar *usage = \"\\\n+usage:\\n\\\n+git-rev-cache COMMAND [options] [<commit-id>...]\\n\\\n+commands:\\n\\\n+  add    - add revisions to the cache.  reads commit ids from stdin, \\n\\\n+           formatted as: START START ... --not END END ...\\n\\\n+           options:\\n\\\n+            --all                  use all branch heads as starts\\n\\\n+            --fresh/--incremental  exclude everything already in a cache slice\\n\\\n+            --stdin                also read commit ids from stdin (same form\\n\\\n+                                   as cmd)\\n\\\n+            --legs                 ensure branch is entirely self-contained\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+  walk   - walk a cache slice based on set of commits; formatted as add\\n\\\n+           options:\\n\\\n+           --objects               include non-commit objects in traversals\\n\\\n+  fuse   - coalesce cache slices into a single cache.\\n\\\n+           options:\\n\\\n+            --all                  include all objects in repository\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+            --ignore-size[=N]      ignore slices of size >= N; defaults to ~5MB\\n\\\n+  index  - regnerate the cache index.\";\n+\n+\tputs(usage);\n+\n+\treturn 0;\n+}\n+\n+int cmd_rev_cache(int argc, const char *argv[], const char *prefix)\n+{\n+\tconst char *arg;\n+\tint r;\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\tif (argc > 1)\n+\t\targ = argv[1];\n+\telse\n+\t\targ = \"\";\n+\n+\targc -= 2;\n+\targv += 2;\n+\tif (!strcmp(arg, \"add\"))\n+\t\tr = handle_add(argc, argv);\n+\telse if (!strcmp(arg, \"walk\"))\n+\t\tr = handle_walk(argc, argv);\n+\telse\n+\t\treturn handle_help();\n+\n+\tfprintf(stderr, \"final return value: %d\\n\", r);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex a2174dc..2f89feb 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -86,6 +86,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix);\n  extern int cmd_config(int argc, const char **argv, const char *prefix);\n  extern int cmd_rerere(int argc, const char **argv, const char *prefix);\n  extern int cmd_reset(int argc, const char **argv, const char *prefix);\n+extern int cmd_rev_cache(int argc, const char **argv, const char *prefix);\n  extern int cmd_rev_list(int argc, const char **argv, const char *prefix);\n  extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n  extern int cmd_revert(int argc, const char **argv, const char *prefix);\ndiff --git a/commit.c b/commit.c\nindex fedbd5e..61d83c6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -252,6 +252,8 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)\n  \titem->tree = lookup_tree(parent);\n  \tbufptr += 46; /* \"tree \" + \"hex sha1\" + \"\\n\" */\n  \tpptr = &item->parents;\n+\twhile (pop_commit(pptr))\n+\t\t; /* clear anything from cache */\n\n  \tgraft = lookup_commit_graft(item->object.sha1);\n  \twhile (bufptr + 48 < tail && !memcmp(bufptr, \"parent \", 7)) {\ndiff --git a/git.c b/git.c\nindex 9883009..dfaa07f 100644\n--- a/git.c\n+++ b/git.c\n@@ -343,6 +343,7 @@ static void handle_internal_command(int argc, const char **argv)\n  \t\t{ \"repo-config\", cmd_config },\n  \t\t{ \"rerere\", cmd_rerere, RUN_SETUP },\n  \t\t{ \"reset\", cmd_reset, RUN_SETUP },\n+\t\t{ \"rev-cache\", cmd_rev_cache, RUN_SETUP },\n  \t\t{ \"rev-list\", cmd_rev_list, RUN_SETUP },\n  \t\t{ \"rev-parse\", cmd_rev_parse },\n  \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/rev-cache.c b/rev-cache.c\nnew file mode 100644\nindex 0000000..8951cdf\n--- /dev/null\n+++ b/rev-cache.c\n@@ -0,0 +1,1171 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+#include \"run-command.h\"\n+\n+/* list resembles pack index format */\n+static uint32_t fanout[0xff + 2];\n+\n+static unsigned char *idx_map;\n+static int idx_size;\n+static struct rc_index_header idx_head;\n+static unsigned char *idx_caches;\n+static char no_idx;\n+\n+static struct strbuf *acc_buffer;\n+\n+#define SLOP\t\t\t5\n+\n+/* initialization */\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst)\n+{\n+\tstatic struct rc_index_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->is_start = !!(src->flags & 0x80);\n+\tdst->cache_index = src->flags & 0x7f;\n+\tdst->pos = ntohl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst)\n+{\n+\tstatic struct rc_index_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->flags = (unsigned char)src->is_start << 7 | (unsigned char)src->cache_index;\n+\tdst->pos = htonl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst)\n+{\n+\tstatic struct rc_object_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->type = src->flags >> 5;\n+\tdst->is_end = !!(src->flags & 0x10);\n+\tdst->is_start = !!(src->flags & 0x08);\n+\tdst->uninteresting = !!(src->flags & 0x04);\n+\tdst->include = !!(src->flags & 0x02);\n+\tdst->flag = !!(src->flags & 0x01);\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->size_size = src->sizes >> 5;\n+\tdst->padding = src->sizes & 0x1f;\n+\n+\tdst->date = ntohl(src->date);\n+\tdst->path = ntohs(src->path);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst)\n+{\n+\tstatic struct rc_object_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->flags  = (unsigned char)src->type << 5;\n+\tdst->flags |= (unsigned char)src->is_end << 4;\n+\tdst->flags |= (unsigned char)src->is_start << 3;\n+\tdst->flags |= (unsigned char)src->uninteresting << 2;\n+\tdst->flags |= (unsigned char)src->include << 1;\n+\tdst->flags |= (unsigned char)src->flag;\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->sizes  = (unsigned char)src->size_size << 5;\n+\tdst->sizes |= (unsigned char)src->padding;\n+\n+\tdst->date = htonl(src->date);\n+\tdst->path = htons(src->path);\n+\n+\treturn dst;\n+}\n+\n+static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)\n+{\n+\tstruct rc_index_header whead;\n+\tint i, index = sizeof(struct rc_index_header);\n+\n+\tmemcpy(&whead, map, sizeof(struct rc_index_header));\n+\tif (memcmp(whead.signature, \"REVINDEX\", 8) || whead.version != SUPPORTED_REVINDEX_VERSION)\n+\t\treturn -1;\n+\n+\tmemcpy(head->signature, \"REVINDEX\", 8);\n+\thead->version = whead.version;\n+\thead->ofs_objects = ntohl(whead.ofs_objects);\n+\thead->object_nr = ntohl(whead.object_nr);\n+\thead->cache_nr = whead.cache_nr;\n+\thead->max_date = ntohl(whead.max_date);\n+\n+\tif (len < index + head->cache_nr * 20 + 0x100 * sizeof(uint32_t))\n+\t\treturn -2;\n+\n+\t*caches = xmalloc(head->cache_nr * 20);\n+\tmemcpy(*caches, map + index, head->cache_nr * 20);\n+\tindex += head->cache_nr * 20;\n+\n+\tmemcpy(fanout, map + index, 0x100 * sizeof(uint32_t));\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = ntohl(fanout[i]);\n+\tfanout[0x100] = len;\n+\n+\treturn 0;\n+}\n+\n+/* added in init_index */\n+static void cleanup_cache_slices(void)\n+{\n+\tif (idx_map) {\n+\t\tfree(idx_caches);\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+}\n+\n+static int init_index(void)\n+{\n+\tint fd;\n+\tstruct stat fi;\n+\n+\tfd = open(git_path(\"rev-cache/index\"), O_RDONLY);\n+\tif (fd == -1 || fstat(fd, &fi))\n+\t\tgoto end;\n+\tif (fi.st_size < sizeof(struct rc_index_header))\n+\t\tgoto end;\n+\n+\tidx_size = fi.st_size;\n+\tidx_map = xmmap(0, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tif (idx_map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_index_head(idx_map, fi.st_size, &idx_head, fanout, &idx_caches))\n+\t\tgoto end;\n+\n+\tatexit(cleanup_cache_slices);\n+\n+\treturn 0;\n+\n+end:\n+\tidx_map = 0;\n+\tno_idx = 1;\n+\treturn -1;\n+}\n+\n+/* this assumes index is already loaded */\n+static struct rc_index_entry_ondisk *search_index_1(unsigned char *sha1)\n+{\n+\tint start, end, starti, endi, i, len, r;\n+\tstruct rc_index_entry_ondisk *ie;\n+\n+\tif (!idx_map)\n+\t\treturn 0;\n+\n+\t/* binary search */\n+\tstart = fanout[(int)sha1[0]];\n+\tend = fanout[(int)sha1[0] + 1];\n+\tlen = (end - start) / sizeof(struct rc_index_entry_ondisk);\n+\tif (!len || len * sizeof(struct rc_index_entry_ondisk) != end - start)\n+\t\treturn 0;\n+\n+\tstarti = 0;\n+\tendi = len - 1;\n+\tfor (;;) {\n+\t\ti = (endi + starti) / 2;\n+\t\tie = (struct rc_index_entry_ondisk *)(idx_map + start + i * sizeof(struct rc_index_entry_ondisk));\n+\t\tr = hashcmp(sha1, ie->sha1);\n+\n+\t\tif (r) {\n+\t\t\tif (starti + 1 == endi) {\n+\t\t\t\tstarti++;\n+\t\t\t\tcontinue;\n+\t\t\t} else if (starti == endi)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (r > 0)\n+\t\t\t\tstarti = i;\n+\t\t\telse /* r < 0 */\n+\t\t\t\tendi = i;\n+\t\t} else\n+\t\t\treturn ie;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static struct rc_index_entry *search_index(unsigned char *sha1)\n+{\n+\tstruct rc_index_entry_ondisk *ied = search_index_1(sha1);\n+\n+\tif (ied)\n+\t\treturn from_disked_rc_index_entry(ied, 0);\n+\n+\treturn 0;\n+}\n+\n+unsigned char *get_cache_slice(struct commit *commit)\n+{\n+\tstruct rc_index_entry *ie;\n+\n+\tif (!idx_map) {\n+\t\tif (no_idx)\n+\t\t\treturn 0;\n+\t\tinit_index();\n+\t}\n+\n+\tif (commit->date > idx_head.max_date)\n+\t\treturn 0;\n+\n+\tie = search_index(commit->object.sha1);\n+\tif (ie && ie->cache_index < idx_head.cache_nr)\n+\t\treturn idx_caches + ie->cache_index * 20;\n+\n+\treturn 0;\n+}\n+\n+\n+/* traversal */\n+\n+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)\n+{\n+\tstruct rc_index_entry *iep;\n+\tstruct rc_object_entry *oep;\n+\tstruct commit_list *prev, *wp, **wpp;\n+\tint retval;\n+\n+\tiep = search_index(commit->object.sha1), 0;\n+\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t/* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,\n+\t * but we might as well initialize it while we're at it */\n+\toep->include = 1;\n+\toep->uninteresting = !!(commit->object.flags & UNINTERESTING);\n+\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\tretval = iep->pos;\n+\n+\t/* include any others in the work array */\n+\tprev = 0;\n+\twpp = work;\n+\twp = *work;\n+\twhile (wp) {\n+\t\tstruct object *obj = &wp->item->object;\n+\t\tstruct commit *co;\n+\n+\t\t/* is this in our cache slice? */\n+\t\tiep = search_index(obj->sha1);\n+\t\tif (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {\n+\t\t\tprev = wp;\n+\t\t\twp = wp->next;\n+\t\t\twpp = &wp;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (iep->pos < retval)\n+\t\t\tretval = iep->pos;\n+\n+\t\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t\t/* mark this for later */\n+\t\toep->include = 1;\n+\t\toep->uninteresting = !!(obj->flags & UNINTERESTING);\n+\t\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\n+\t\t/* remove from work list */\n+\t\tco = pop_commit(wpp);\n+\t\twp = *wpp;\n+\t\tif (prev)\n+\t\t\tprev->next = wp;\n+\t}\n+\n+\treturn retval;\n+}\n+\n+#define IPATH\t\t\t\t0x40\n+#define UPATH\t\t\t\t0x80\n+\n+#define GET_COUNT(x)\t\t((x) & 0x3f)\n+#define SET_COUNT(x, s)\t\t((x) = ((x) & ~0x3f) | ((s) & 0x3f))\n+\n+static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *map,\n+\tstruct rev_info *revs, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tstruct commit_list *insert_cache = 0;\n+\tstruct commit **last_objects, *co;\n+\tint i, total_path_nr = head->path_nr, retval = -1;\n+\tchar consume_children = 0;\n+\tunsigned char *paths;\n+\n+\ti = setup_traversal(head, map, commit, work);\n+\tif (i < 0)\n+\t\treturn -1;\n+\n+\tpaths = xcalloc(total_path_nr, sizeof(uint16_t));\n+\tlast_objects = xcalloc(total_path_nr, sizeof(struct commit *));\n+\n+\t/* i already set */\n+\twhile (i < head->size) {\n+\t\tstruct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tint path = entry->path;\n+\t\tstruct object *obj;\n+\t\tint index = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);\n+\n+\t\t/* add extra objects if necessary */\n+\t\tif (entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tconsume_children = 0;\n+\n+\t\tif (path >= total_path_nr)\n+\t\t\tgoto end;\n+\n+\t\t/* in one of our branches?\n+\t\t * uninteresting trumps interesting */\n+\t\tif (entry->include)\n+\t\t\tpaths[path] |= entry->uninteresting ? UPATH : IPATH;\n+\t\telse if (!paths[path])\n+\t\t\tcontinue;\n+\n+\t\t/* date stuff */\n+\t\tif (revs->max_age != -1 && entry->date < revs->max_age)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\t/* lookup object */\n+\t\tco = lookup_commit(entry->sha1);\n+\t\tobj = &co->object;\n+\n+\t\tif (obj->flags & UNINTERESTING)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\tif ((paths[path] & IPATH) && (paths[path] & UPATH)) {\n+\t\t\tpaths[path] = UPATH;\n+\n+\t\t\t/* mark edge */\n+\t\t\tif (last_objects[path]) {\n+\t\t\t\tparse_commit(last_objects[path]);\n+\n+\t\t\t\tlast_objects[path]->object.flags &= ~FACE_VALUE;\n+\t\t\t\tlast_objects[path] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* now we gotta re-assess the whole interesting thing... */\n+\t\tentry->uninteresting = !!(paths[path] & UPATH);\n+\n+\t\t/* first close paths */\n+\t\tif (entry->split_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE(entry->merge_nr);\n+\n+\t\t\tfor (j = 0; j < entry->split_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\t/* boundary commit? */\n+\t\t\t\tif ((paths[p] & IPATH) && entry->uninteresting) {\n+\t\t\t\t\tif (last_objects[p]) {\n+\t\t\t\t\t\tparse_commit(last_objects[p]);\n+\n+\t\t\t\t\t\tlast_objects[p]->object.flags &= ~FACE_VALUE;\n+\t\t\t\t\t\tlast_objects[p] = 0;\n+\t\t\t\t\t}\n+\t\t\t\t} else if (last_objects[p] && !last_objects[p]->object.parsed)\n+\t\t\t\t\tcommit_list_insert(co, &last_objects[p]->parents);\n+\n+\t\t\t\t/* can't close a merge path until all are parents have been encountered */\n+\t\t\t\tif (GET_COUNT(paths[p])) {\n+\t\t\t\t\tSET_COUNT(paths[p], GET_COUNT(paths[p]) - 1);\n+\n+\t\t\t\t\tif (GET_COUNT(paths[p]))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tpaths[p] = 0;\n+\t\t\t\tlast_objects[p] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* make topo relations */\n+\t\tif (last_objects[path] && !last_objects[path]->object.parsed)\n+\t\t\tcommit_list_insert(co, &last_objects[path]->parents);\n+\n+\t\t/* initialize commit */\n+\t\tif (!entry->is_end) {\n+\t\t\tco->date = entry->date;\n+\t\t\tobj->flags |= ADDED | FACE_VALUE;\n+\t\t} else\n+\t\t\tparse_commit(co);\n+\n+\t\tobj->flags |= SEEN;\n+\n+\t\tif (entry->uninteresting)\n+\t\t\tobj->flags |= UNINTERESTING;\n+\n+\t\t/* we need to know what the edges are */\n+\t\tlast_objects[path] = co;\n+\n+\t\t/* add to list */\n+\t\tif (!(obj->flags & UNINTERESTING) || revs->show_all) {\n+\t\t\tif (entry->is_end)\n+\t\t\t\tinsert_by_date_cached(co, work, insert_cache, &insert_cache);\n+\t\t\telse\n+\t\t\t\t*queue = &commit_list_insert(co, *queue)->next;\n+\n+\t\t\t/* add children to list as well */\n+\t\t\tif (obj->flags & UNINTERESTING)\n+\t\t\t\tconsume_children = 0;\n+\t\t\telse\n+\t\t\t\tconsume_children = 1;\n+\t\t}\n+\n+\t\t/* open parents */\n+\t\tif (entry->merge_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk);\n+\t\t\tchar flag = entry->uninteresting ? UPATH : IPATH;\n+\n+\t\t\tfor (j = 0; j < entry->merge_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\tif (paths[p] & flag)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tpaths[p] |= flag;\n+\t\t\t}\n+\n+\t\t\t/* make sure we don't use this path before all our parents have had their say */\n+\t\t\tSET_COUNT(paths[path], entry->merge_nr);\n+\t\t}\n+\n+\t}\n+\n+\tretval = 0;\n+\n+end:\n+\tfree(paths);\n+\tfree(last_objects);\n+\n+\treturn retval;\n+}\n+\n+static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)\n+{\n+\tint t;\n+\n+\tmemcpy(head, map, sizeof(struct rc_slice_header));\n+\thead->ofs_objects = ntohl(head->ofs_objects);\n+\thead->object_nr = ntohl(head->object_nr);\n+\thead->size = ntohl(head->size);\n+\thead->path_nr = ntohs(head->path_nr);\n+\n+\tif (memcmp(head->signature, \"REVCACHE\", 8))\n+\t\treturn -1;\n+\tif (head->version != SUPPORTED_REVCACHE_VERSION)\n+\t\treturn -2;\n+\tif (hashcmp(head->sha1, cache_sha1))\n+\t\treturn -3;\n+\tt = sizeof(struct rc_slice_header);\n+\tif (t != head->ofs_objects || t >= len)\n+\t\treturn -4;\n+\n+\thead->size = len;\n+\n+\treturn 0;\n+}\n+\n+int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tint fd = -1, retval = -3;\n+\tstruct stat fi;\n+\tstruct rc_slice_header head;\n+\tstruct rev_cache_info *rci;\n+\tunsigned char *map = MAP_FAILED;\n+\n+\t/* the index should've been loaded already to find cache_sha1, but it's good\n+\t * to be absolutely sure... */\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn -1;\n+\n+\t/* load options */\n+\trci = &revs->rev_cache_info;\n+\n+\tmemset(&head, 0, sizeof(struct rc_slice_header));\n+\n+\tfd = open(git_path(\"rev-cache/%s\", sha1_to_hex(cache_sha1)), O_RDONLY);\n+\tif (fd == -1)\n+\t\tgoto end;\n+\tif (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))\n+\t\tgoto end;\n+\n+\tmap = xmmap(0, fi.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))\n+\t\tgoto end;\n+\n+\tretval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);\n+\n+end:\n+\tif (map != MAP_FAILED)\n+\t\tmunmap(map, fi.st_size);\n+\tif (fd != -1)\n+\t\tclose(fd);\n+\n+\treturn retval;\n+}\n+\n+\n+\n+/* generation */\n+\n+struct path_track {\n+\tstruct commit *commit;\n+\tint path; /* for keeping track of children */\n+\n+\tstruct path_track *next, *prev;\n+};\n+\n+static unsigned char *paths;\n+static int path_nr = 1, path_sz;\n+\n+static struct path_track *path_track;\n+static struct path_track *path_track_alloc;\n+\n+#define PATH_IN_USE\t\t\t0x80 /* biggest bit we can get as a char */\n+\n+static int get_new_path(void)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i < path_nr; i++)\n+\t\tif (!paths[i])\n+\t\t\tbreak;\n+\n+\tif (i == path_nr) {\n+\t\tif (path_nr >= path_sz) {\n+\t\t\tpath_sz += 50;\n+\t\t\tpaths = xrealloc(paths, path_sz);\n+\t\t\tmemset(paths + path_sz - 50, 0, 50);\n+\t\t}\n+\t\tpath_nr++;\n+\t}\n+\n+\tpaths[i] = PATH_IN_USE;\n+\treturn i;\n+}\n+\n+static void remove_path_track(struct path_track **ppt, char total_free)\n+{\n+\tstruct path_track *t = *ppt;\n+\n+\tif (t->next)\n+\t\tt->next->prev = t->prev;\n+\tif (t->prev)\n+\t\tt->prev->next = t->next;\n+\n+\tt = t->next;\n+\n+\tif (total_free)\n+\t\tfree(*ppt);\n+\telse {\n+\t\t(*ppt)->next = path_track_alloc;\n+\t\tpath_track_alloc = *ppt;\n+\t}\n+\n+\t*ppt = t;\n+}\n+\n+static struct path_track *make_path_track(struct path_track **head, struct commit *commit)\n+{\n+\tstruct path_track *pt;\n+\n+\tif (path_track_alloc) {\n+\t\tpt = path_track_alloc;\n+\t\tpath_track_alloc = pt->next;\n+\t} else\n+\t\tpt = xmalloc(sizeof(struct path_track));\n+\n+\tmemset(pt, 0, sizeof(struct path_track));\n+\tpt->commit = commit;\n+\n+\tpt->next = *head;\n+\tif (*head)\n+\t\t(*head)->prev = pt;\n+\t*head = pt;\n+\n+\treturn pt;\n+}\n+\n+static void add_path_to_track(struct commit *commit, int path)\n+{\n+\tmake_path_track(&path_track, commit);\n+\tpath_track->path = path;\n+}\n+\n+static void handle_paths(struct commit *commit, struct rc_object_entry *object, struct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tint child_nr, parent_nr, open_parent_nr, this_path;\n+\tstruct commit_list *list;\n+\tstruct commit *first_parent;\n+\tstruct path_track **ppt, *pt;\n+\n+\t/* we can only re-use a closed path once all it's children have been encountered,\n+\t * as we need to keep track of commit boundaries */\n+\tppt = &path_track;\n+\tpt = *ppt;\n+\tchild_nr = 0;\n+\twhile (pt) {\n+\t\tif (pt->commit == commit) {\n+\t\t\tuint16_t write_path;\n+\n+\t\t\tif (paths[pt->path] != PATH_IN_USE)\n+\t\t\t\tpaths[pt->path]--;\n+\n+\t\t\t/* make sure we can handle this */\n+\t\t\tchild_nr++;\n+\t\t\tif (child_nr > 0x7f)\n+\t\t\t\tdie(\"%s: too many branches!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t\t\t/* add to split list */\n+\t\t\tobject->split_nr++;\n+\t\t\twrite_path = htons((uint16_t)pt->path);\n+\t\t\tstrbuf_add(split_str, &write_path, sizeof(uint16_t));\n+\n+\t\t\tremove_path_track(ppt, 0);\n+\t\t\tpt = *ppt;\n+\t\t} else {\n+\t\t\tpt = pt->next;\n+\t\t\tppt = &pt;\n+\t\t}\n+\t}\n+\n+\t/* initialize our self! */\n+\tif (!commit->indegree) {\n+\t\tcommit->indegree = get_new_path();\n+\t\tobject->is_start = 1;\n+\t}\n+\n+\tthis_path = commit->indegree;\n+\tpaths[this_path] = PATH_IN_USE;\n+\tobject->path = this_path;\n+\n+\t/* count interesting parents */\n+\tparent_nr = open_parent_nr = 0;\n+\tfirst_parent = 0;\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tif (list->item->object.flags & UNINTERESTING) {\n+\t\t\tobject->is_end = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tparent_nr++;\n+\t\tif (!list->item->indegree)\n+\t\t\topen_parent_nr++;\n+\t\tif (!first_parent)\n+\t\t\tfirst_parent = list->item;\n+\t}\n+\n+\tif (!parent_nr)\n+\t\treturn;\n+\n+\tif (parent_nr == 1 && open_parent_nr == 1) {\n+\t\tfirst_parent->indegree = this_path;\n+\t\treturn;\n+\t}\n+\n+\t/* bail out on obscene parent/child #s */\n+\tif (parent_nr > 0x7f)\n+\t\tdie(\"%s: too many parents in merge!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t/* make merge list */\n+\tobject->merge_nr = parent_nr;\n+\tpaths[this_path] = parent_nr;\n+\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tstruct commit *p = list->item;\n+\t\tuint16_t write_path;\n+\n+\t\tif (p->object.flags & UNINTERESTING)\n+\t\t\tcontinue;\n+\n+\t\t/* unfortunately due to boundary tracking we can't re-use merge paths\n+\t\t * (unable to guarantee last parent path = this -> last won't always be able to\n+\t\t * set this as a boundary object */\n+\t\tif (!p->indegree)\n+\t\t\tp->indegree = get_new_path();\n+\n+\t\twrite_path = htons((uint16_t)p->indegree);\n+\t\tstrbuf_add(merge_str, &write_path, sizeof(uint16_t));\n+\n+\t\t/* make sure path is properly ended */\n+\t\tadd_path_to_track(p, this_path);\n+\t}\n+\n+}\n+\n+\n+static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,\n+\tstruct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tstruct rc_object_entry object;\n+\n+\tif (!nothisone) {\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.sha1 = (unsigned char *)sha1;\n+\t\tobject.type = type;\n+\n+\t\tif (merge_str)\n+\t\t\tobject.merge_nr = merge_str->len / sizeof(uint16_t);\n+\t\tif (split_str)\n+\t\t\tobject.split_nr = split_str->len / sizeof(uint16_t);\n+\n+\t\tnothisone = &object;\n+\t}\n+\n+\tstrbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));\n+\n+\tif (merge_str && merge_str->len)\n+\t\tstrbuf_add(acc_buffer, merge_str->buf, merge_str->len);\n+\tif (split_str && split_str->len)\n+\t\tstrbuf_add(acc_buffer, split_str->buf, split_str->len);\n+\n+}\n+\n+static void init_revcache_directory(void)\n+{\n+\tstruct stat fi;\n+\n+\tif (stat(git_path(\"rev-cache\"), &fi) || !S_ISDIR(fi.st_mode))\n+\t\tif (mkdir(git_path(\"rev-cache\"), 0777))\n+\t\t\tdie(\"can't make rev-cache directory\");\n+\n+}\n+\n+void init_rev_cache_info(struct rev_cache_info *rci)\n+{\n+\trci->objects = 1;\n+\trci->legs = 0;\n+\trci->make_index = 1;\n+\n+\trci->add_to_pending = 1;\n+\n+\trci->ignore_size = 0;\n+}\n+\n+void maybe_fill_with_defaults(struct rev_cache_info *rci)\n+{\n+\tstatic struct rev_cache_info def_rci;\n+\n+\tif (rci)\n+\t\treturn;\n+\n+\tinit_rev_cache_info(&def_rci);\n+\trci = &def_rci;\n+}\n+\n+int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1)\n+{\n+\tstruct rev_info therevs;\n+\tstruct strbuf buffer, startlist, endlist;\n+\tstruct rc_slice_header head;\n+\tstruct commit *commit;\n+\tunsigned char sha1[20];\n+\tstruct strbuf merge_paths, split_paths;\n+\tint object_nr, total_sz, fd;\n+\tchar file[PATH_MAX], *newfile;\n+\tstruct rev_cache_info *trci;\n+\tgit_SHA_CTX ctx;\n+\n+\tmaybe_fill_with_defaults(rci);\n+\n+\tinit_revcache_directory();\n+\tstrcpy(file, git_path(\"rev-cache/XXXXXX\"));\n+\tfd = xmkstemp(file);\n+\n+\tstrbuf_init(&buffer, 0);\n+\tstrbuf_init(&startlist, 0);\n+\tstrbuf_init(&endlist, 0);\n+\tstrbuf_init(&merge_paths, 0);\n+\tstrbuf_init(&split_paths, 0);\n+\tacc_buffer = &buffer;\n+\n+\tif (!revs) {\n+\t\trevs = &therevs;\n+\t\tinit_revisions(revs, 0);\n+\n+\t\t/* we're gonna assume no one else has already traversed this... */\n+\t\twhile ((commit = pop_commit(starts)))\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\n+\t\twhile ((commit = pop_commit(ends))) {\n+\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\t\t}\n+\t}\n+\n+\t/* write head placeholder */\n+\tmemset(&head, 0, sizeof(head));\n+\thead.ofs_objects = htonl(sizeof(head));\n+\txwrite(fd, &head, sizeof(head));\n+\n+\t/* init revisions! */\n+\trevs->tree_objects = 1;\n+\trevs->blob_objects = 1;\n+\trevs->topo_order = 1;\n+\trevs->lifo = 1;\n+\n+\t/* re-use info from other caches if possible */\n+\ttrci = &revs->rev_cache_info;\n+\tinit_rev_cache_info(trci);\n+\ttrci->add_to_pending = 0;\n+\n+\tsetup_revisions(0, 0, revs, 0);\n+\tif (prepare_revision_walk(revs))\n+\t\tdie(\"died preparing revision walk\");\n+\n+\tobject_nr = total_sz = 0;\n+\twhile ((commit = get_revision(revs)) != 0) {\n+\t\tstruct rc_object_entry object;\n+\n+\t\tstrbuf_setlen(&merge_paths, 0);\n+\t\tstrbuf_setlen(&split_paths, 0);\n+\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.type = OBJ_COMMIT;\n+\t\tobject.date = commit->date;\n+\t\tobject.sha1 = commit->object.sha1;\n+\n+\t\thandle_paths(commit, &object, &merge_paths, &split_paths);\n+\n+\t\tif (object.is_end) {\n+\t\t\tstrbuf_add(&endlist, object.sha1, 20);\n+\t\t\tif (ends)\n+\t\t\t\tcommit_list_insert(commit, ends);\n+\t\t}\n+\t\t/* the two *aren't* mutually exclusive */\n+\t\tif (object.is_start) {\n+\t\t\tstrbuf_add(&startlist, object.sha1, 20);\n+\t\t\tif (starts)\n+\t\t\t\tcommit_list_insert(commit, starts);\n+\t\t}\n+\n+\t\tcommit->indegree = 0;\n+\n+\t\tadd_object_entry(0, 0, &object, &merge_paths, &split_paths);\n+\t\tobject_nr++;\n+\n+\t\t/* print every ~1MB or so */\n+\t\tif (buffer.len > 1000000) {\n+\t\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\t\ttotal_sz += buffer.len;\n+\n+\t\t\tstrbuf_setlen(&buffer, 0);\n+\t\t}\n+\t}\n+\n+\tif (buffer.len) {\n+\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\ttotal_sz += buffer.len;\n+\t}\n+\n+\t/* go ahead a free some stuff... */\n+\tstrbuf_release(&buffer);\n+\tstrbuf_release(&merge_paths);\n+\tstrbuf_release(&split_paths);\n+\tif (path_sz)\n+\t\tfree(paths);\n+\twhile (path_track_alloc)\n+\t\tremove_path_track(&path_track_alloc, 1);\n+\n+\t/* the meaning of the hash name is more or less irrelevant, it's the uniqueness that matters */\n+\tstrbuf_add(&endlist, startlist.buf, startlist.len);\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, endlist.buf, endlist.len);\n+\tgit_SHA1_Final(sha1, &ctx);\n+\n+\t/* now actually initialize header */\n+\tstrcpy(head.signature, \"REVCACHE\");\n+\thead.version = SUPPORTED_REVCACHE_VERSION;\n+\n+\thead.object_nr = htonl(object_nr);\n+\thead.size = htonl(ntohl(head.ofs_objects) + total_sz);\n+\thead.path_nr = htons(path_nr);\n+\thashcpy(head.sha1, sha1);\n+\n+\t/* some info! */\n+\tfprintf(stderr, \"objects: %d\\n\", object_nr);\n+\tfprintf(stderr, \"paths: %d\\n\", path_nr);\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\txwrite(fd, &head, sizeof(head));\n+\n+\tif (rci->make_index && make_cache_index(rci, sha1, fd, ntohl(head.size)) < 0)\n+\t\tdie(\"can't update index\");\n+\n+\tclose(fd);\n+\n+\tnewfile = git_path(\"rev-cache/%s\", sha1_to_hex(sha1));\n+\tif (rename(file, newfile))\n+\t\tdie(\"can't move temp file\");\n+\n+\t/* let our caller know what we've just made */\n+\tif (cache_sha1)\n+\t\thashcpy(cache_sha1, sha1);\n+\n+\tstrbuf_release(&endlist);\n+\tstrbuf_release(&startlist);\n+\n+\treturn 0;\n+}\n+\n+\n+static int index_sort_hash(const void *a, const void *b)\n+{\n+\treturn hashcmp(((struct rc_index_entry_ondisk *)a)->sha1, ((struct rc_index_entry_ondisk *)b)->sha1);\n+}\n+\n+static int write_cache_index(struct strbuf *body)\n+{\n+\tstruct rc_index_header whead;\n+\tstruct lock_file *lk;\n+\tint fd, i;\n+\n+\t/* clear index map if loaded */\n+\tif (idx_map) {\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+\tlk = xcalloc(sizeof(struct lock_file), 1);\n+\tfd = hold_lock_file_for_update(lk, git_path(\"rev-cache/index\"), 0);\n+\tif (fd < 0) {\n+\t\tfree(lk);\n+\t\treturn -1;\n+\t}\n+\n+\t/* endianness yay! */\n+\tmemset(&whead, 0, sizeof(whead));\n+\tmemcpy(whead.signature, \"REVINDEX\", 8);\n+\twhead.version = idx_head.version;\n+\twhead.ofs_objects = htonl(idx_head.ofs_objects);\n+\twhead.object_nr = htonl(idx_head.object_nr);\n+\twhead.cache_nr = idx_head.cache_nr;\n+\twhead.max_date = htonl(idx_head.max_date);\n+\n+\twrite(fd, &whead, sizeof(struct rc_index_header));\n+\twrite_in_full(fd, idx_caches, idx_head.cache_nr * 20);\n+\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = htonl(fanout[i]);\n+\twrite_in_full(fd, fanout, 0x100 * sizeof(uint32_t));\n+\n+\twrite_in_full(fd, body->buf, body->len);\n+\n+\tif (commit_lock_file(lk) < 0)\n+\t\treturn -2;\n+\n+\t/* lk freed by lockfile.c */\n+\n+\treturn 0;\n+}\n+\n+int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size)\n+{\n+\tstruct strbuf buffer;\n+\tint i, cache_index, cur;\n+\tunsigned char *map;\n+\tunsigned long max_date;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\tmap = xmmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\treturn -1;\n+\n+\tstrbuf_init(&buffer, 0);\n+\tif (idx_map) {\n+\t\tstrbuf_add(&buffer, idx_map + fanout[0], fanout[0x100] - fanout[0]);\n+\t} else {\n+\t\t/* not an update */\n+\t\tmemset(&idx_head, 0, sizeof(struct rc_index_header));\n+\t\tidx_caches = 0;\n+\n+\t\tstrcpy(idx_head.signature, \"REVINDEX\");\n+\t\tidx_head.version = SUPPORTED_REVINDEX_VERSION;\n+\t\tidx_head.ofs_objects = sizeof(struct rc_index_header) + 0x100 * sizeof(uint32_t);\n+\t}\n+\n+\t/* are we remaking a slice? */\n+\tfor (i = 0; i < idx_head.cache_nr; i++)\n+\t\tif (!hashcmp(idx_caches + i * 20, cache_sha1))\n+\t\t\tbreak;\n+\n+\tif (i == idx_head.cache_nr) {\n+\t\tcache_index = idx_head.cache_nr++;\n+\t\tidx_head.ofs_objects += 20;\n+\n+\t\tidx_caches = xrealloc(idx_caches, idx_head.cache_nr * 20);\n+\t\thashcpy(idx_caches + cache_index * 20, cache_sha1);\n+\t} else\n+\t\tcache_index = i;\n+\n+\ti = sizeof(struct rc_slice_header); /* offset */\n+\tmax_date = idx_head.max_date;\n+\twhile (i < size) {\n+\t\tstruct rc_index_entry index_entry, *entry;\n+\t\tstruct rc_index_entry_ondisk *disked_entry;\n+\t\tstruct rc_object_entry *object_entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tunsigned long date;\n+\t\tint off, pos = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(object_entry);\n+\n+\t\tif (object_entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\n+\t\t/* don't include ends; otherwise we'll find ourselves in loops */\n+\t\tif (object_entry->is_end)\n+\t\t\tcontinue;\n+\n+\t\t/* handle index duplication\n+\t\t * -> keep old copy unless new one is a start -- based on expected usage, older ones will be more\n+\t\t * likely to lead to greater slice traversals than new ones */\n+\t\tdate = object_entry->date;\n+\t\tif (date > idx_head.max_date) {\n+\t\t\tdisked_entry = 0;\n+\t\t\tif (date > max_date)\n+\t\t\t\tmax_date = date;\n+\t\t} else\n+\t\t\tdisked_entry = search_index_1(object_entry->sha1);\n+\n+\t\tif (disked_entry && !object_entry->is_start)\n+\t\t\tcontinue;\n+\t\telse if (disked_entry) {\n+\t\t\t/* mmm, pointer arithmetic... tasty */  /* (entry - idx_map = offset, so cast is valid) */\n+\t\t\toff = (unsigned int)((unsigned char *)disked_entry - idx_map) - fanout[0];\n+\t\t\tdisked_entry = (struct rc_index_entry_ondisk *)(buffer.buf + off);\n+\t\t\tentry = from_disked_rc_index_entry(disked_entry, 0);\n+\t\t} else\n+\t\t\tentry = &index_entry;\n+\n+\t\tmemset(entry, 0, sizeof(index_entry));\n+\t\tentry->sha1 = object_entry->sha1;\n+\t\tentry->is_start = object_entry->is_start;\n+\t\tentry->cache_index = cache_index;\n+\t\tentry->pos = pos;\n+\n+\t\tif (entry == &index_entry) {\n+\t\t\tstrbuf_add(&buffer, to_disked_rc_index_entry(entry, 0), sizeof(struct rc_index_entry_ondisk));\n+\t\t\tidx_head.object_nr++;\n+\t\t} else\n+\t\t\tto_disked_rc_index_entry(entry, disked_entry);\n+\n+\t}\n+\n+\tidx_head.max_date = max_date;\n+\tqsort(buffer.buf, buffer.len / sizeof(struct rc_index_entry_ondisk), sizeof(struct rc_index_entry_ondisk), index_sort_hash);\n+\n+\t/* generate fanout */\n+\tcur = 0x00;\n+\tfor (i = 0; i < buffer.len; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry_ondisk *entry = (struct rc_index_entry_ondisk *)(buffer.buf + i);\n+\n+\t\twhile (cur <= entry->sha1[0])\n+\t\t\tfanout[cur++] = i + idx_head.ofs_objects;\n+\t}\n+\n+\twhile (cur <= 0xff)\n+\t\tfanout[cur++] = idx_head.ofs_objects + buffer.len;\n+\n+\t/* BOOM! */\n+\tif (write_cache_index(&buffer))\n+\t\treturn -1;\n+\n+\tmunmap(map, size);\n+\tstrbuf_release(&buffer);\n+\n+\t/* idx_map is unloaded without cleanup_cache_slices(), so regardless of previous index existence\n+\t * we can still free this up */\n+\tfree(idx_caches);\n+\n+\treturn 0;\n+}\n+\n+\n+/* add start-commits from each cache slice (uninterestingness will be propogated) */\n+void starts_from_slices(struct rev_info *revs, unsigned int flags)\n+{\n+\tstruct commit *commit;\n+\tint i;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn;\n+\n+\tfor (i = idx_head.ofs_objects; i < idx_size; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry *entry = RC_OBTAIN_INDEX_ENTRY(idx_map + i);\n+\n+\t\tif (!entry->is_start)\n+\t\t\tcontinue;\n+\n+\t\tcommit = lookup_commit(entry->sha1);\n+\t\tif (!commit)\n+\t\t\tcontinue;\n+\n+\t\tcommit->object.flags |= flags;\n+\t\tadd_pending_object(revs, &commit->object, 0);\n+\t}\n+\n+}\ndiff --git a/rev-cache.h b/rev-cache.h\nnew file mode 100644\nindex 0000000..a76dc53\n--- /dev/null\n+++ b/rev-cache.h\n@@ -0,0 +1,107 @@\n+#ifndef REV_CACHE_H\n+#define REV_CACHE_H\n+\n+#define SUPPORTED_REVCACHE_VERSION \t\t1\n+#define SUPPORTED_REVINDEX_VERSION\t\t1\n+\n+#define RC_PATH_SIZE(x)\t(sizeof(uint16_t) * (x))\n+\n+#define RC_OBTAIN_OBJECT_ENTRY(p)\t\t\tfrom_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)\n+#define RC_OBTAIN_INDEX_ENTRY(p)\t\t\tfrom_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)\n+\n+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e)\t\t(sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)\n+\n+/* single index maps objects to cache files */\n+struct rc_index_header {\n+\tchar signature[8]; /* REVINDEX */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tunsigned char cache_nr;\n+\n+\tuint32_t max_date;\n+};\n+\n+struct rc_index_entry_ondisk {\n+\tunsigned char sha1[20];\n+\tunsigned char flags;\n+\tuint32_t pos;\n+};\n+\n+struct rc_index_entry {\n+\tunsigned char *sha1;\n+\tunsigned is_start:1;\n+\tunsigned cache_index:7;\n+\tuint32_t pos;\n+};\n+\n+\n+/* structure for actual cache file */\n+struct rc_slice_header {\n+\tchar signature[8]; /* REVCACHE */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tuint16_t path_nr;\n+\tuint32_t size;\n+\n+\tunsigned char sha1[20];\n+};\n+\n+struct rc_object_entry_ondisk {\n+\tunsigned char flags;\n+\tunsigned char sha1[20];\n+\n+\tunsigned char merge_nr;\n+\tunsigned char split_nr;\n+\tunsigned char sizes;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+};\n+\n+struct rc_object_entry {\n+\tunsigned type:3;\n+\tunsigned is_end:1;\n+\tunsigned is_start:1;\n+\tunsigned uninteresting:1;\n+\tunsigned include:1;\n+\tunsigned flag:1; /* unused */\n+\tunsigned char *sha1; /* 20 byte */\n+\n+\tunsigned char merge_nr; /* : 7 */\n+\tunsigned char split_nr; /* : 7 */\n+\tunsigned size_size:3;\n+\tunsigned padding:5;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+\n+\t/* merge paths */\n+\t/* split paths */\n+\t/* size */\n+};\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst);\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst);\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);\n+\n+extern unsigned char *get_cache_slice(struct commit *commit);\n+extern int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work);\n+\n+extern void init_rev_cache_info(struct rev_cache_info *rci);\n+extern int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1);\n+extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size);\n+\n+extern void starts_from_slices(struct rev_info *revs, unsigned int flags);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 35eca4a..c7fd35f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -432,7 +432,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n  \tcommit->object.flags |= TREESAME;\n  }\n\n-static void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+void insert_by_date_cached(struct commit *p, struct commit_list **head,\n  \t\t    struct commit_list *cached_base, struct commit_list **cache)\n  {\n  \tstruct commit_list *new_entry;\ndiff --git a/revision.h b/revision.h\nindex 9d0dddb..7db4b9e 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -13,7 +13,8 @@\n  #define CHILD_SHOWN\t(1u<<6)\n  #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n  #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define FACE_VALUE\t(1u<<9)\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n\n  #define DECORATE_SHORT_REFS\t1\n  #define DECORATE_FULL_REFS\t2\n@@ -21,6 +22,19 @@\n  struct rev_info;\n  struct log_info;\n\n+struct rev_cache_info {\n+\t/* generation flags */\n+\tunsigned objects : 1,\n+\t\tlegs : 1,\n+\t\tmake_index : 1;\n+\n+\t/* traversal flags */\n+\tunsigned add_to_pending : 1;\n+\n+\t/* fuse options */\n+\tunsigned int ignore_size;\n+};\n+\n  struct rev_info {\n  \t/* Starting list */\n  \tstruct commit_list *commits;\n@@ -76,6 +90,10 @@ struct rev_info {\n  \t\t\tdense_combined_merges:1,\n  \t\t\talways_show_header:1;\n\n+\t/* rev-cache flags */\n+\tunsigned int for_pack:1,\n+\t\tdont_cache_me:1;\n+\n  \t/* Format info */\n  \tunsigned int\tshown_one:1,\n  \t\t\tshow_merge:1,\n@@ -119,6 +137,9 @@ struct rev_info {\n  \tstruct reflog_walk_info *reflog_info;\n  \tstruct decoration children;\n  \tstruct decoration merge_simplification;\n+\n+\t/* caching info, used ONLY by traverse_cache_slice */\n+\tstruct rev_cache_info rev_cache_info;\n  };\n\n  #define REV_TREE_SAME\t\t0\n@@ -171,4 +192,7 @@ enum commit_action {\n  extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);\n  extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);\n\n+extern void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+\t\t    struct commit_list *cached_base, struct commit_list **cache);\n+\n  #endif\ndiff --git a/t/t6017-rev-cache-list.sh b/t/t6017-rev-cache-list.sh\nnew file mode 100755\nindex 0000000..f59f568\n--- /dev/null\n+++ b/t/t6017-rev-cache-list.sh\n@@ -0,0 +1,106 @@\n+#!/bin/sh\n+\n+test_description='git rev-cache tests'\n+. ./test-lib.sh\n+\n+test_cmp_sorted() {\n+\tgrep -io \"[a-f0-9]*\" $1 | sort >.tmpfile1 &&\n+\tgrep -io \"[a-f0-9]*\" $2 | sort >.tmpfile2 &&\n+\ttest_cmp .tmpfile1 .tmpfile2\n+}\n+\n+# we want a totally wacked out branch structure...\n+# we need branching and merging of sizes up through 3, tree\n+# addition/deletion, and enough branching to exercise path\n+# reuse\n+test_expect_success 'init repo' '\n+\techo bla >file &&\n+\tgit add . &&\n+\tgit commit -m \"bla\" &&\n+\n+\tgit branch b1 &&\n+\tgit checkout b1 &&\n+\techo blu >file2 &&\n+\tmkdir d1 &&\n+\techo bang >d1/filed1 &&\n+\tgit add . &&\n+\tgit commit -m \"blu\" &&\n+\n+\tgit checkout master &&\n+\tgit branch b2 &&\n+\tgit checkout b2 &&\n+\techo kaplaa >>file &&\n+\tgit commit -a -m \"kaplaa\" &&\n+\n+\tgit checkout master &&\n+\tmkdir smoke &&\n+\techo omg >smoke/bong &&\n+\tgit add . &&\n+\tgit commit -m \"omg\" &&\n+\n+\tgit branch b4 &&\n+\tgit checkout b4 &&\n+\techo shazam >file8 &&\n+\tgit add . &&\n+\tgit commit -m \"shazam\" &&\n+\tgit merge -m \"merge b2\" b2 &&\n+\n+\techo bam >smoke/pipe &&\n+\tgit add .\n+\tgit commit -m \"bam\" &&\n+\n+\tgit checkout master &&\n+\techo pow >file7 &&\n+\tgit add . &&\n+\tgit commit -m \"pow\" &&\n+\tgit merge -m \"merge b4\" b4 &&\n+\n+\tgit checkout b1 &&\n+\techo stuff >d1/filed1 &&\n+\tgit commit -a -m \"stuff\" &&\n+\n+\tgit branch b11 &&\n+\tgit checkout b11 &&\n+\techo wazzup >file3 &&\n+\tgit add file3 &&\n+\tgit commit -m \"wazzup\" &&\n+\n+\tgit checkout b1 &&\n+\tmkdir d1/d2 &&\n+\techo lol >d1/d2/filed2 &&\n+\tgit add . &&\n+\tgit commit -m \"lol\" &&\n+\n+\tgit checkout master &&\n+\tgit merge -m \"triple merge\" b1 b11 &&\n+\tgit rm -r d1 &&\n+\tgit commit -a -m \"oh noes\"\n+'\n+\n+git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited\n+git-rev-list HEAD >proper_commit_list\n+git-rev-list HEAD --objects >proper_object_list\n+\n+test_expect_success 'make cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+test_expect_success 'remake cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+#check core mechanics and rev-list hook for commits\n+test_expect_success 'test rev-caches walker directly (limited)' '\n+\tgit-rev-cache walk HEAD --not HEAD~3 >list &&\n+\ttest_cmp_sorted list proper_commit_list_limited\n+'\n+\n+test_expect_success 'test rev-caches walker directly (unlimited)' '\n+\tgit-rev-cache walk HEAD >list &&\n+\ttest_cmp_sorted list proper_commit_list\n+'\n+\n+test_done\n+\n-- \ntg: (a6dbf88..) t/revcache/basic (depends on: master)\n"},{"id":"124214","messageId":"7vbpkmk2mm.fsf@alter.siamese.dyndns.org","threadId":"20630","inReplyTo":"op.u061bavktdk399@sirnot.ed.ac.uk","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-10-05T02:15:13Z","receivedAt":"2009-10-05T02:15:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The entire series is unusable due to whitespace breakage.\n"},{"id":"124248","messageId":"c77435a80910050551g6eaee163r6c08e007b8fe71f2@mail.gmail.com","threadId":"20630","inReplyTo":"7vbpkmk2mm.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Nick Edelen","fromEmail":"sirnot@gmail.com","sentAt":"2009-10-05T12:51:50Z","receivedAt":"2009-10-05T12:51:50Z","isPatch":true,"sender":{"key":"sirnot@gmail.com","avatar":null},"body":"> The entire series is unusable due to whitespace breakage.\n\nShit sorry about that.  I think my mail client reset some settings\nwhen I upgraded.  Will resend.\n\nI feel like such a klutz...\n"},{"id":"124249","messageId":"fabb9a1e0910050556y5ed23960n6e0bfd4777bc06f9@mail.gmail.com","threadId":"20630","inReplyTo":"op.uys3qlsjtdk399@sirnot.private","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2009-10-05T12:56:56Z","receivedAt":"2009-10-05T12:56:56Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Mon, Aug 17, 2009 at 14:31, Nick Edelen <sirnot@gmail.com> wrote:\n>  1618 insertions(+)\n\nReally?\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"125417","messageId":"4ADCCBEE.1070603@gmail.com","threadId":"20630","inReplyTo":"op.uys3qlsjtdk399@sirnot.private","subject":"Re: [PATCH 2/6 (v4)] basic revision cache system, no integration or features","fromName":"Nick Edelen","fromEmail":"sirnot@gmail.com","sentAt":"2009-10-19T20:28:30Z","receivedAt":"2009-10-19T20:28:30Z","isPatch":true,"sender":{"key":"sirnot@gmail.com","avatar":null},"body":"Second in the revision cache series, this particular patch provides:\n - minimal API: caching only commit topo data\n - minimal porcelain: add and walk cache slices\n - appropriate tests\n\nSigned-off-by: Nick Edelen <sirnot@gmail.com>\n\n---\nremoved useless test.  sorry about large size: this is less of a patch and more\nan addition of two files.\n\n Makefile                  |    2 +\n builtin-rev-cache.c       |  207 ++++++++\n builtin.h                 |    1 +\n commit.c                  |    2 +\n git.c                     |    1 +\n rev-cache.c               | 1171 +++++++++++++++++++++++++++++++++++++++++++++\n rev-cache.h               |  107 ++++\n revision.c                |    2 +-\n revision.h                |   26 +-\n t/t6017-rev-cache-list.sh |  106 ++++\n 10 files changed, 1623 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 42b7d60..93e20ab 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -538,6 +538,7 @@ LIB_OBJS += refs.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace_object.o\n LIB_OBJS += rerere.o\n+LIB_OBJS += rev-cache.o\n LIB_OBJS += revision.o\n LIB_OBJS += run-command.o\n LIB_OBJS += server-info.o\n@@ -630,6 +631,7 @@ BUILTIN_OBJS += builtin-remote.o\n BUILTIN_OBJS += builtin-replace.o\n BUILTIN_OBJS += builtin-rerere.o\n BUILTIN_OBJS += builtin-reset.o\n+BUILTIN_OBJS += builtin-rev-cache.o\n BUILTIN_OBJS += builtin-rev-list.o\n BUILTIN_OBJS += builtin-rev-parse.o\n BUILTIN_OBJS += builtin-revert.o\ndiff --git a/builtin-rev-cache.c b/builtin-rev-cache.c\nnew file mode 100644\nindex 0000000..6eb7065\n--- /dev/null\n+++ b/builtin-rev-cache.c\n@@ -0,0 +1,207 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+\n+/* porcelain for rev-cache.c */\n+static int handle_add(int argc, const char *argv[]) /* args beyond this command */\n+{\n+\tstruct rev_info revs;\n+\tstruct rev_cache_info rci;\n+\tchar dostdin = 0;\n+\tunsigned int flags = 0;\n+\tint i, retval;\n+\tunsigned char cache_sha1[20];\n+\tstruct commit_list *starts = 0, *ends = 0;\n+\tstruct commit *commit;\n+\n+\tinit_revisions(&revs, 0);\n+\tinit_rev_cache_info(&rci);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--stdin\"))\n+\t\t\tdostdin = 1;\n+\t\telse if (!strcmp(argv[i], \"--fresh\") || !strcmp(argv[i], \"--incremental\"))\n+\t\t\tstarts_from_slices(&revs, UNINTERESTING);\n+\t\telse if (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--legs\"))\n+\t\t\trci.legs = 1;\n+\t\telse if (!strcmp(argv[i], \"--no-objects\"))\n+\t\t\trci.objects = 0;\n+\t\telse if (!strcmp(argv[i], \"--all\")) {\n+\t\t\tconst char *args[2];\n+\t\t\tint argn = 0;\n+\n+\t\t\targs[argn++] = \"rev-list\";\n+\t\t\targs[argn++] = \"--all\";\n+\t\t\tsetup_revisions(argn, args, &revs, 0);\n+\t\t} else\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\tif (dostdin) {\n+\t\tchar line[1000];\n+\n+\t\tflags = 0;\n+\t\twhile (fgets(line, sizeof(line), stdin)) {\n+\t\t\tint len = strlen(line);\n+\t\t\twhile (len && (line[len - 1] == '\\n' || line[len - 1] == '\\r'))\n+\t\t\t\tline[--len] = 0;\n+\n+\t\t\tif (!len)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (!strcmp(line, \"--not\"))\n+\t\t\t\tflags ^= UNINTERESTING;\n+\t\t\telse\n+\t\t\t\thandle_revision_arg(line, &revs, flags, 1);\n+\t\t}\n+\t}\n+\n+\tretval = make_cache_slice(&rci, &revs, &starts, &ends, cache_sha1);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tprintf(\"%s\\n\", sha1_to_hex(cache_sha1));\n+\n+\tfprintf(stderr, \"endpoints:\\n\");\n+\twhile ((commit = pop_commit(&starts)))\n+\t\tfprintf(stderr, \"S %s\\n\", sha1_to_hex(commit->object.sha1));\n+\twhile ((commit = pop_commit(&ends)))\n+\t\tfprintf(stderr, \"E %s\\n\", sha1_to_hex(commit->object.sha1));\n+\n+\treturn 0;\n+}\n+\n+static int handle_walk(int argc, const char *argv[])\n+{\n+\tstruct commit *commit;\n+\tstruct rev_info revs;\n+\tstruct commit_list *queue, *work, **qp;\n+\tunsigned char *sha1p, *sha1pt;\n+\tunsigned long date = 0;\n+\tunsigned int flags = 0;\n+\tint retval, slop = 5, i;\n+\n+\tinit_revisions(&revs, 0);\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--not\"))\n+\t\t\tflags ^= UNINTERESTING;\n+\t\telse if (!strcmp(argv[i], \"--objects\"))\n+\t\t\trevs.tree_objects = revs.blob_objects = 1;\n+\t\telse\n+\t\t\thandle_revision_arg(argv[i], &revs, flags, 1);\n+\t}\n+\n+\twork = 0;\n+\tsha1p = 0;\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tcommit = lookup_commit(revs.pending.objects[i].item->sha1);\n+\n+\t\tsha1pt = get_cache_slice(commit);\n+\t\tif (!sha1pt)\n+\t\t\tdie(\"%s: not in a cache slice\", sha1_to_hex(commit->object.sha1));\n+\n+\t\tif (!i)\n+\t\t\tsha1p = sha1pt;\n+\t\telse if (sha1p != sha1pt)\n+\t\t\tdie(\"walking porcelain is /per/ cache slice; commits cannot be spread out amoung several\");\n+\n+\t\tinsert_by_date(commit, &work);\n+\t}\n+\n+\tif (!sha1p)\n+\t\tdie(\"nothing to traverse!\");\n+\n+\tqueue = 0;\n+\tqp = &queue;\n+\tcommit = pop_commit(&work);\n+\tretval = traverse_cache_slice(&revs, sha1p, commit, &date, &slop, &qp, &work);\n+\tif (retval < 0)\n+\t\treturn retval;\n+\n+\tfprintf(stderr, \"queue:\\n\");\n+\twhile ((commit = pop_commit(&queue)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"work:\\n\");\n+\twhile ((commit = pop_commit(&work)) != 0) {\n+\t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n+\t}\n+\n+\tfprintf(stderr, \"pending:\\n\");\n+\tfor (i = 0; i < revs.pending.nr; i++) {\n+\t\tstruct object *obj = revs.pending.objects[i].item;\n+\n+\t\t/* unfortunately, despite our careful generation, object duplication *is* a possibility...\n+\t\t * (eg. same object introduced into two different branches) */\n+\t\tif (obj->flags & SEEN)\n+\t\t\tcontinue;\n+\n+\t\tprintf(\"%s\\n\", sha1_to_hex(revs.pending.objects[i].item->sha1));\n+\t\tobj->flags |= SEEN;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int handle_help(void)\n+{\n+\tchar *usage = \"\\\n+usage:\\n\\\n+git-rev-cache COMMAND [options] [<commit-id>...]\\n\\\n+commands:\\n\\\n+  add    - add revisions to the cache.  reads commit ids from stdin, \\n\\\n+           formatted as: START START ... --not END END ...\\n\\\n+           options:\\n\\\n+            --all                  use all branch heads as starts\\n\\\n+            --fresh/--incremental  exclude everything already in a cache slice\\n\\\n+            --stdin                also read commit ids from stdin (same form\\n\\\n+                                   as cmd)\\n\\\n+            --legs                 ensure branch is entirely self-contained\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+  walk   - walk a cache slice based on set of commits; formatted as add\\n\\\n+           options:\\n\\\n+           --objects               include non-commit objects in traversals\\n\\\n+  fuse   - coalesce cache slices into a single cache.\\n\\\n+           options:\\n\\\n+            --all                  include all objects in repository\\n\\\n+            --no-objects           don't add non-commit objects to slice\\n\\\n+            --ignore-size[=N]      ignore slices of size >= N; defaults to ~5MB\\n\\\n+  index  - regnerate the cache index.\";\n+\n+\tputs(usage);\n+\n+\treturn 0;\n+}\n+\n+int cmd_rev_cache(int argc, const char *argv[], const char *prefix)\n+{\n+\tconst char *arg;\n+\tint r;\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\tif (argc > 1)\n+\t\targ = argv[1];\n+\telse\n+\t\targ = \"\";\n+\n+\targc -= 2;\n+\targv += 2;\n+\tif (!strcmp(arg, \"add\"))\n+\t\tr = handle_add(argc, argv);\n+\telse if (!strcmp(arg, \"walk\"))\n+\t\tr = handle_walk(argc, argv);\n+\telse\n+\t\treturn handle_help();\n+\n+\tfprintf(stderr, \"final return value: %d\\n\", r);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex a2174dc..2f89feb 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -86,6 +86,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\n extern int cmd_rerere(int argc, const char **argv, const char *prefix);\n extern int cmd_reset(int argc, const char **argv, const char *prefix);\n+extern int cmd_rev_cache(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_list(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n extern int cmd_revert(int argc, const char **argv, const char *prefix);\ndiff --git a/commit.c b/commit.c\nindex fedbd5e..61d83c6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -252,6 +252,8 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)\n \titem->tree = lookup_tree(parent);\n \tbufptr += 46; /* \"tree \" + \"hex sha1\" + \"\\n\" */\n \tpptr = &item->parents;\n+\twhile (pop_commit(pptr))\n+\t\t; /* clear anything from cache */\n \n \tgraft = lookup_commit_graft(item->object.sha1);\n \twhile (bufptr + 48 < tail && !memcmp(bufptr, \"parent \", 7)) {\ndiff --git a/git.c b/git.c\nindex bd2c5fe..c016f50 100644\n--- a/git.c\n+++ b/git.c\n@@ -345,6 +345,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"repo-config\", cmd_config },\n \t\t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t\t{ \"reset\", cmd_reset, RUN_SETUP },\n+\t\t{ \"rev-cache\", cmd_rev_cache, RUN_SETUP },\n \t\t{ \"rev-list\", cmd_rev_list, RUN_SETUP },\n \t\t{ \"rev-parse\", cmd_rev_parse },\n \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/rev-cache.c b/rev-cache.c\nnew file mode 100644\nindex 0000000..8951cdf\n--- /dev/null\n+++ b/rev-cache.c\n@@ -0,0 +1,1171 @@\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"rev-cache.h\"\n+#include \"run-command.h\"\n+\n+/* list resembles pack index format */\n+static uint32_t fanout[0xff + 2];\n+\n+static unsigned char *idx_map;\n+static int idx_size;\n+static struct rc_index_header idx_head;\n+static unsigned char *idx_caches;\n+static char no_idx;\n+\n+static struct strbuf *acc_buffer;\n+\n+#define SLOP\t\t\t5\n+\n+/* initialization */\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst)\n+{\n+\tstatic struct rc_index_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->is_start = !!(src->flags & 0x80);\n+\tdst->cache_index = src->flags & 0x7f;\n+\tdst->pos = ntohl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst)\n+{\n+\tstatic struct rc_index_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->flags = (unsigned char)src->is_start << 7 | (unsigned char)src->cache_index;\n+\tdst->pos = htonl(src->pos);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst)\n+{\n+\tstatic struct rc_object_entry entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->type = src->flags >> 5;\n+\tdst->is_end = !!(src->flags & 0x10);\n+\tdst->is_start = !!(src->flags & 0x08);\n+\tdst->uninteresting = !!(src->flags & 0x04);\n+\tdst->include = !!(src->flags & 0x02);\n+\tdst->flag = !!(src->flags & 0x01);\n+\n+\tdst->sha1 = src->sha1;\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->size_size = src->sizes >> 5;\n+\tdst->padding = src->sizes & 0x1f;\n+\n+\tdst->date = ntohl(src->date);\n+\tdst->path = ntohs(src->path);\n+\n+\treturn dst;\n+}\n+\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst)\n+{\n+\tstatic struct rc_object_entry_ondisk entry[4];\n+\tstatic int cur;\n+\n+\tif (!dst)\n+\t\tdst = &entry[cur++ & 0x3];\n+\n+\tdst->flags  = (unsigned char)src->type << 5;\n+\tdst->flags |= (unsigned char)src->is_end << 4;\n+\tdst->flags |= (unsigned char)src->is_start << 3;\n+\tdst->flags |= (unsigned char)src->uninteresting << 2;\n+\tdst->flags |= (unsigned char)src->include << 1;\n+\tdst->flags |= (unsigned char)src->flag;\n+\n+\tif (dst->sha1 != src->sha1)\n+\t\thashcpy(dst->sha1, src->sha1);\n+\tdst->merge_nr = src->merge_nr;\n+\tdst->split_nr = src->split_nr;\n+\n+\tdst->sizes  = (unsigned char)src->size_size << 5;\n+\tdst->sizes |= (unsigned char)src->padding;\n+\n+\tdst->date = htonl(src->date);\n+\tdst->path = htons(src->path);\n+\n+\treturn dst;\n+}\n+\n+static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)\n+{\n+\tstruct rc_index_header whead;\n+\tint i, index = sizeof(struct rc_index_header);\n+\n+\tmemcpy(&whead, map, sizeof(struct rc_index_header));\n+\tif (memcmp(whead.signature, \"REVINDEX\", 8) || whead.version != SUPPORTED_REVINDEX_VERSION)\n+\t\treturn -1;\n+\n+\tmemcpy(head->signature, \"REVINDEX\", 8);\n+\thead->version = whead.version;\n+\thead->ofs_objects = ntohl(whead.ofs_objects);\n+\thead->object_nr = ntohl(whead.object_nr);\n+\thead->cache_nr = whead.cache_nr;\n+\thead->max_date = ntohl(whead.max_date);\n+\n+\tif (len < index + head->cache_nr * 20 + 0x100 * sizeof(uint32_t))\n+\t\treturn -2;\n+\n+\t*caches = xmalloc(head->cache_nr * 20);\n+\tmemcpy(*caches, map + index, head->cache_nr * 20);\n+\tindex += head->cache_nr * 20;\n+\n+\tmemcpy(fanout, map + index, 0x100 * sizeof(uint32_t));\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = ntohl(fanout[i]);\n+\tfanout[0x100] = len;\n+\n+\treturn 0;\n+}\n+\n+/* added in init_index */\n+static void cleanup_cache_slices(void)\n+{\n+\tif (idx_map) {\n+\t\tfree(idx_caches);\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+}\n+\n+static int init_index(void)\n+{\n+\tint fd;\n+\tstruct stat fi;\n+\n+\tfd = open(git_path(\"rev-cache/index\"), O_RDONLY);\n+\tif (fd == -1 || fstat(fd, &fi))\n+\t\tgoto end;\n+\tif (fi.st_size < sizeof(struct rc_index_header))\n+\t\tgoto end;\n+\n+\tidx_size = fi.st_size;\n+\tidx_map = xmmap(0, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n+\tif (idx_map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_index_head(idx_map, fi.st_size, &idx_head, fanout, &idx_caches))\n+\t\tgoto end;\n+\n+\tatexit(cleanup_cache_slices);\n+\n+\treturn 0;\n+\n+end:\n+\tidx_map = 0;\n+\tno_idx = 1;\n+\treturn -1;\n+}\n+\n+/* this assumes index is already loaded */\n+static struct rc_index_entry_ondisk *search_index_1(unsigned char *sha1)\n+{\n+\tint start, end, starti, endi, i, len, r;\n+\tstruct rc_index_entry_ondisk *ie;\n+\n+\tif (!idx_map)\n+\t\treturn 0;\n+\n+\t/* binary search */\n+\tstart = fanout[(int)sha1[0]];\n+\tend = fanout[(int)sha1[0] + 1];\n+\tlen = (end - start) / sizeof(struct rc_index_entry_ondisk);\n+\tif (!len || len * sizeof(struct rc_index_entry_ondisk) != end - start)\n+\t\treturn 0;\n+\n+\tstarti = 0;\n+\tendi = len - 1;\n+\tfor (;;) {\n+\t\ti = (endi + starti) / 2;\n+\t\tie = (struct rc_index_entry_ondisk *)(idx_map + start + i * sizeof(struct rc_index_entry_ondisk));\n+\t\tr = hashcmp(sha1, ie->sha1);\n+\n+\t\tif (r) {\n+\t\t\tif (starti + 1 == endi) {\n+\t\t\t\tstarti++;\n+\t\t\t\tcontinue;\n+\t\t\t} else if (starti == endi)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (r > 0)\n+\t\t\t\tstarti = i;\n+\t\t\telse /* r < 0 */\n+\t\t\t\tendi = i;\n+\t\t} else\n+\t\t\treturn ie;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static struct rc_index_entry *search_index(unsigned char *sha1)\n+{\n+\tstruct rc_index_entry_ondisk *ied = search_index_1(sha1);\n+\n+\tif (ied)\n+\t\treturn from_disked_rc_index_entry(ied, 0);\n+\n+\treturn 0;\n+}\n+\n+unsigned char *get_cache_slice(struct commit *commit)\n+{\n+\tstruct rc_index_entry *ie;\n+\n+\tif (!idx_map) {\n+\t\tif (no_idx)\n+\t\t\treturn 0;\n+\t\tinit_index();\n+\t}\n+\n+\tif (commit->date > idx_head.max_date)\n+\t\treturn 0;\n+\n+\tie = search_index(commit->object.sha1);\n+\tif (ie && ie->cache_index < idx_head.cache_nr)\n+\t\treturn idx_caches + ie->cache_index * 20;\n+\n+\treturn 0;\n+}\n+\n+\n+/* traversal */\n+\n+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)\n+{\n+\tstruct rc_index_entry *iep;\n+\tstruct rc_object_entry *oep;\n+\tstruct commit_list *prev, *wp, **wpp;\n+\tint retval;\n+\n+\tiep = search_index(commit->object.sha1), 0;\n+\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t/* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,\n+\t * but we might as well initialize it while we're at it */\n+\toep->include = 1;\n+\toep->uninteresting = !!(commit->object.flags & UNINTERESTING);\n+\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\tretval = iep->pos;\n+\n+\t/* include any others in the work array */\n+\tprev = 0;\n+\twpp = work;\n+\twp = *work;\n+\twhile (wp) {\n+\t\tstruct object *obj = &wp->item->object;\n+\t\tstruct commit *co;\n+\n+\t\t/* is this in our cache slice? */\n+\t\tiep = search_index(obj->sha1);\n+\t\tif (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {\n+\t\t\tprev = wp;\n+\t\t\twp = wp->next;\n+\t\t\twpp = &wp;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (iep->pos < retval)\n+\t\t\tretval = iep->pos;\n+\n+\t\toep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);\n+\n+\t\t/* mark this for later */\n+\t\toep->include = 1;\n+\t\toep->uninteresting = !!(obj->flags & UNINTERESTING);\n+\t\tto_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));\n+\n+\t\t/* remove from work list */\n+\t\tco = pop_commit(wpp);\n+\t\twp = *wpp;\n+\t\tif (prev)\n+\t\t\tprev->next = wp;\n+\t}\n+\n+\treturn retval;\n+}\n+\n+#define IPATH\t\t\t\t0x40\n+#define UPATH\t\t\t\t0x80\n+\n+#define GET_COUNT(x)\t\t((x) & 0x3f)\n+#define SET_COUNT(x, s)\t\t((x) = ((x) & ~0x3f) | ((s) & 0x3f))\n+\n+static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *map,\n+\tstruct rev_info *revs, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tstruct commit_list *insert_cache = 0;\n+\tstruct commit **last_objects, *co;\n+\tint i, total_path_nr = head->path_nr, retval = -1;\n+\tchar consume_children = 0;\n+\tunsigned char *paths;\n+\n+\ti = setup_traversal(head, map, commit, work);\n+\tif (i < 0)\n+\t\treturn -1;\n+\n+\tpaths = xcalloc(total_path_nr, sizeof(uint16_t));\n+\tlast_objects = xcalloc(total_path_nr, sizeof(struct commit *));\n+\n+\t/* i already set */\n+\twhile (i < head->size) {\n+\t\tstruct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tint path = entry->path;\n+\t\tstruct object *obj;\n+\t\tint index = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);\n+\n+\t\t/* add extra objects if necessary */\n+\t\tif (entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\t\telse\n+\t\t\tconsume_children = 0;\n+\n+\t\tif (path >= total_path_nr)\n+\t\t\tgoto end;\n+\n+\t\t/* in one of our branches?\n+\t\t * uninteresting trumps interesting */\n+\t\tif (entry->include)\n+\t\t\tpaths[path] |= entry->uninteresting ? UPATH : IPATH;\n+\t\telse if (!paths[path])\n+\t\t\tcontinue;\n+\n+\t\t/* date stuff */\n+\t\tif (revs->max_age != -1 && entry->date < revs->max_age)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\t/* lookup object */\n+\t\tco = lookup_commit(entry->sha1);\n+\t\tobj = &co->object;\n+\n+\t\tif (obj->flags & UNINTERESTING)\n+\t\t\tpaths[path] |= UPATH;\n+\n+\t\tif ((paths[path] & IPATH) && (paths[path] & UPATH)) {\n+\t\t\tpaths[path] = UPATH;\n+\n+\t\t\t/* mark edge */\n+\t\t\tif (last_objects[path]) {\n+\t\t\t\tparse_commit(last_objects[path]);\n+\n+\t\t\t\tlast_objects[path]->object.flags &= ~FACE_VALUE;\n+\t\t\t\tlast_objects[path] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* now we gotta re-assess the whole interesting thing... */\n+\t\tentry->uninteresting = !!(paths[path] & UPATH);\n+\n+\t\t/* first close paths */\n+\t\tif (entry->split_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE(entry->merge_nr);\n+\n+\t\t\tfor (j = 0; j < entry->split_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\t/* boundary commit? */\n+\t\t\t\tif ((paths[p] & IPATH) && entry->uninteresting) {\n+\t\t\t\t\tif (last_objects[p]) {\n+\t\t\t\t\t\tparse_commit(last_objects[p]);\n+\n+\t\t\t\t\t\tlast_objects[p]->object.flags &= ~FACE_VALUE;\n+\t\t\t\t\t\tlast_objects[p] = 0;\n+\t\t\t\t\t}\n+\t\t\t\t} else if (last_objects[p] && !last_objects[p]->object.parsed)\n+\t\t\t\t\tcommit_list_insert(co, &last_objects[p]->parents);\n+\n+\t\t\t\t/* can't close a merge path until all are parents have been encountered */\n+\t\t\t\tif (GET_COUNT(paths[p])) {\n+\t\t\t\t\tSET_COUNT(paths[p], GET_COUNT(paths[p]) - 1);\n+\n+\t\t\t\t\tif (GET_COUNT(paths[p]))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tpaths[p] = 0;\n+\t\t\t\tlast_objects[p] = 0;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* make topo relations */\n+\t\tif (last_objects[path] && !last_objects[path]->object.parsed)\n+\t\t\tcommit_list_insert(co, &last_objects[path]->parents);\n+\n+\t\t/* initialize commit */\n+\t\tif (!entry->is_end) {\n+\t\t\tco->date = entry->date;\n+\t\t\tobj->flags |= ADDED | FACE_VALUE;\n+\t\t} else\n+\t\t\tparse_commit(co);\n+\n+\t\tobj->flags |= SEEN;\n+\n+\t\tif (entry->uninteresting)\n+\t\t\tobj->flags |= UNINTERESTING;\n+\n+\t\t/* we need to know what the edges are */\n+\t\tlast_objects[path] = co;\n+\n+\t\t/* add to list */\n+\t\tif (!(obj->flags & UNINTERESTING) || revs->show_all) {\n+\t\t\tif (entry->is_end)\n+\t\t\t\tinsert_by_date_cached(co, work, insert_cache, &insert_cache);\n+\t\t\telse\n+\t\t\t\t*queue = &commit_list_insert(co, *queue)->next;\n+\n+\t\t\t/* add children to list as well */\n+\t\t\tif (obj->flags & UNINTERESTING)\n+\t\t\t\tconsume_children = 0;\n+\t\t\telse\n+\t\t\t\tconsume_children = 1;\n+\t\t}\n+\n+\t\t/* open parents */\n+\t\tif (entry->merge_nr) {\n+\t\t\tint j, off = index + sizeof(struct rc_object_entry_ondisk);\n+\t\t\tchar flag = entry->uninteresting ? UPATH : IPATH;\n+\n+\t\t\tfor (j = 0; j < entry->merge_nr; j++) {\n+\t\t\t\tunsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));\n+\n+\t\t\t\tif (p >= total_path_nr)\n+\t\t\t\t\tgoto end;\n+\n+\t\t\t\tif (paths[p] & flag)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tpaths[p] |= flag;\n+\t\t\t}\n+\n+\t\t\t/* make sure we don't use this path before all our parents have had their say */\n+\t\t\tSET_COUNT(paths[path], entry->merge_nr);\n+\t\t}\n+\n+\t}\n+\n+\tretval = 0;\n+\n+end:\n+\tfree(paths);\n+\tfree(last_objects);\n+\n+\treturn retval;\n+}\n+\n+static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)\n+{\n+\tint t;\n+\n+\tmemcpy(head, map, sizeof(struct rc_slice_header));\n+\thead->ofs_objects = ntohl(head->ofs_objects);\n+\thead->object_nr = ntohl(head->object_nr);\n+\thead->size = ntohl(head->size);\n+\thead->path_nr = ntohs(head->path_nr);\n+\n+\tif (memcmp(head->signature, \"REVCACHE\", 8))\n+\t\treturn -1;\n+\tif (head->version != SUPPORTED_REVCACHE_VERSION)\n+\t\treturn -2;\n+\tif (hashcmp(head->sha1, cache_sha1))\n+\t\treturn -3;\n+\tt = sizeof(struct rc_slice_header);\n+\tif (t != head->ofs_objects || t >= len)\n+\t\treturn -4;\n+\n+\thead->size = len;\n+\n+\treturn 0;\n+}\n+\n+int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work)\n+{\n+\tint fd = -1, retval = -3;\n+\tstruct stat fi;\n+\tstruct rc_slice_header head;\n+\tstruct rev_cache_info *rci;\n+\tunsigned char *map = MAP_FAILED;\n+\n+\t/* the index should've been loaded already to find cache_sha1, but it's good\n+\t * to be absolutely sure... */\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn -1;\n+\n+\t/* load options */\n+\trci = &revs->rev_cache_info;\n+\n+\tmemset(&head, 0, sizeof(struct rc_slice_header));\n+\n+\tfd = open(git_path(\"rev-cache/%s\", sha1_to_hex(cache_sha1)), O_RDONLY);\n+\tif (fd == -1)\n+\t\tgoto end;\n+\tif (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))\n+\t\tgoto end;\n+\n+\tmap = xmmap(0, fi.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\tgoto end;\n+\tif (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))\n+\t\tgoto end;\n+\n+\tretval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);\n+\n+end:\n+\tif (map != MAP_FAILED)\n+\t\tmunmap(map, fi.st_size);\n+\tif (fd != -1)\n+\t\tclose(fd);\n+\n+\treturn retval;\n+}\n+\n+\n+\n+/* generation */\n+\n+struct path_track {\n+\tstruct commit *commit;\n+\tint path; /* for keeping track of children */\n+\n+\tstruct path_track *next, *prev;\n+};\n+\n+static unsigned char *paths;\n+static int path_nr = 1, path_sz;\n+\n+static struct path_track *path_track;\n+static struct path_track *path_track_alloc;\n+\n+#define PATH_IN_USE\t\t\t0x80 /* biggest bit we can get as a char */\n+\n+static int get_new_path(void)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i < path_nr; i++)\n+\t\tif (!paths[i])\n+\t\t\tbreak;\n+\n+\tif (i == path_nr) {\n+\t\tif (path_nr >= path_sz) {\n+\t\t\tpath_sz += 50;\n+\t\t\tpaths = xrealloc(paths, path_sz);\n+\t\t\tmemset(paths + path_sz - 50, 0, 50);\n+\t\t}\n+\t\tpath_nr++;\n+\t}\n+\n+\tpaths[i] = PATH_IN_USE;\n+\treturn i;\n+}\n+\n+static void remove_path_track(struct path_track **ppt, char total_free)\n+{\n+\tstruct path_track *t = *ppt;\n+\n+\tif (t->next)\n+\t\tt->next->prev = t->prev;\n+\tif (t->prev)\n+\t\tt->prev->next = t->next;\n+\n+\tt = t->next;\n+\n+\tif (total_free)\n+\t\tfree(*ppt);\n+\telse {\n+\t\t(*ppt)->next = path_track_alloc;\n+\t\tpath_track_alloc = *ppt;\n+\t}\n+\n+\t*ppt = t;\n+}\n+\n+static struct path_track *make_path_track(struct path_track **head, struct commit *commit)\n+{\n+\tstruct path_track *pt;\n+\n+\tif (path_track_alloc) {\n+\t\tpt = path_track_alloc;\n+\t\tpath_track_alloc = pt->next;\n+\t} else\n+\t\tpt = xmalloc(sizeof(struct path_track));\n+\n+\tmemset(pt, 0, sizeof(struct path_track));\n+\tpt->commit = commit;\n+\n+\tpt->next = *head;\n+\tif (*head)\n+\t\t(*head)->prev = pt;\n+\t*head = pt;\n+\n+\treturn pt;\n+}\n+\n+static void add_path_to_track(struct commit *commit, int path)\n+{\n+\tmake_path_track(&path_track, commit);\n+\tpath_track->path = path;\n+}\n+\n+static void handle_paths(struct commit *commit, struct rc_object_entry *object, struct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tint child_nr, parent_nr, open_parent_nr, this_path;\n+\tstruct commit_list *list;\n+\tstruct commit *first_parent;\n+\tstruct path_track **ppt, *pt;\n+\n+\t/* we can only re-use a closed path once all it's children have been encountered,\n+\t * as we need to keep track of commit boundaries */\n+\tppt = &path_track;\n+\tpt = *ppt;\n+\tchild_nr = 0;\n+\twhile (pt) {\n+\t\tif (pt->commit == commit) {\n+\t\t\tuint16_t write_path;\n+\n+\t\t\tif (paths[pt->path] != PATH_IN_USE)\n+\t\t\t\tpaths[pt->path]--;\n+\n+\t\t\t/* make sure we can handle this */\n+\t\t\tchild_nr++;\n+\t\t\tif (child_nr > 0x7f)\n+\t\t\t\tdie(\"%s: too many branches!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t\t\t/* add to split list */\n+\t\t\tobject->split_nr++;\n+\t\t\twrite_path = htons((uint16_t)pt->path);\n+\t\t\tstrbuf_add(split_str, &write_path, sizeof(uint16_t));\n+\n+\t\t\tremove_path_track(ppt, 0);\n+\t\t\tpt = *ppt;\n+\t\t} else {\n+\t\t\tpt = pt->next;\n+\t\t\tppt = &pt;\n+\t\t}\n+\t}\n+\n+\t/* initialize our self! */\n+\tif (!commit->indegree) {\n+\t\tcommit->indegree = get_new_path();\n+\t\tobject->is_start = 1;\n+\t}\n+\n+\tthis_path = commit->indegree;\n+\tpaths[this_path] = PATH_IN_USE;\n+\tobject->path = this_path;\n+\n+\t/* count interesting parents */\n+\tparent_nr = open_parent_nr = 0;\n+\tfirst_parent = 0;\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tif (list->item->object.flags & UNINTERESTING) {\n+\t\t\tobject->is_end = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tparent_nr++;\n+\t\tif (!list->item->indegree)\n+\t\t\topen_parent_nr++;\n+\t\tif (!first_parent)\n+\t\t\tfirst_parent = list->item;\n+\t}\n+\n+\tif (!parent_nr)\n+\t\treturn;\n+\n+\tif (parent_nr == 1 && open_parent_nr == 1) {\n+\t\tfirst_parent->indegree = this_path;\n+\t\treturn;\n+\t}\n+\n+\t/* bail out on obscene parent/child #s */\n+\tif (parent_nr > 0x7f)\n+\t\tdie(\"%s: too many parents in merge!  rev-cache can only handle %d parents/children per commit\",\n+\t\t\tsha1_to_hex(object->sha1), 0x7f);\n+\n+\t/* make merge list */\n+\tobject->merge_nr = parent_nr;\n+\tpaths[this_path] = parent_nr;\n+\n+\tfor (list = commit->parents; list; list = list->next) {\n+\t\tstruct commit *p = list->item;\n+\t\tuint16_t write_path;\n+\n+\t\tif (p->object.flags & UNINTERESTING)\n+\t\t\tcontinue;\n+\n+\t\t/* unfortunately due to boundary tracking we can't re-use merge paths\n+\t\t * (unable to guarantee last parent path = this -> last won't always be able to\n+\t\t * set this as a boundary object */\n+\t\tif (!p->indegree)\n+\t\t\tp->indegree = get_new_path();\n+\n+\t\twrite_path = htons((uint16_t)p->indegree);\n+\t\tstrbuf_add(merge_str, &write_path, sizeof(uint16_t));\n+\n+\t\t/* make sure path is properly ended */\n+\t\tadd_path_to_track(p, this_path);\n+\t}\n+\n+}\n+\n+\n+static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,\n+\tstruct strbuf *merge_str, struct strbuf *split_str)\n+{\n+\tstruct rc_object_entry object;\n+\n+\tif (!nothisone) {\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.sha1 = (unsigned char *)sha1;\n+\t\tobject.type = type;\n+\n+\t\tif (merge_str)\n+\t\t\tobject.merge_nr = merge_str->len / sizeof(uint16_t);\n+\t\tif (split_str)\n+\t\t\tobject.split_nr = split_str->len / sizeof(uint16_t);\n+\n+\t\tnothisone = &object;\n+\t}\n+\n+\tstrbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));\n+\n+\tif (merge_str && merge_str->len)\n+\t\tstrbuf_add(acc_buffer, merge_str->buf, merge_str->len);\n+\tif (split_str && split_str->len)\n+\t\tstrbuf_add(acc_buffer, split_str->buf, split_str->len);\n+\n+}\n+\n+static void init_revcache_directory(void)\n+{\n+\tstruct stat fi;\n+\n+\tif (stat(git_path(\"rev-cache\"), &fi) || !S_ISDIR(fi.st_mode))\n+\t\tif (mkdir(git_path(\"rev-cache\"), 0777))\n+\t\t\tdie(\"can't make rev-cache directory\");\n+\n+}\n+\n+void init_rev_cache_info(struct rev_cache_info *rci)\n+{\n+\trci->objects = 1;\n+\trci->legs = 0;\n+\trci->make_index = 1;\n+\n+\trci->add_to_pending = 1;\n+\n+\trci->ignore_size = 0;\n+}\n+\n+void maybe_fill_with_defaults(struct rev_cache_info *rci)\n+{\n+\tstatic struct rev_cache_info def_rci;\n+\n+\tif (rci)\n+\t\treturn;\n+\n+\tinit_rev_cache_info(&def_rci);\n+\trci = &def_rci;\n+}\n+\n+int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1)\n+{\n+\tstruct rev_info therevs;\n+\tstruct strbuf buffer, startlist, endlist;\n+\tstruct rc_slice_header head;\n+\tstruct commit *commit;\n+\tunsigned char sha1[20];\n+\tstruct strbuf merge_paths, split_paths;\n+\tint object_nr, total_sz, fd;\n+\tchar file[PATH_MAX], *newfile;\n+\tstruct rev_cache_info *trci;\n+\tgit_SHA_CTX ctx;\n+\n+\tmaybe_fill_with_defaults(rci);\n+\n+\tinit_revcache_directory();\n+\tstrcpy(file, git_path(\"rev-cache/XXXXXX\"));\n+\tfd = xmkstemp(file);\n+\n+\tstrbuf_init(&buffer, 0);\n+\tstrbuf_init(&startlist, 0);\n+\tstrbuf_init(&endlist, 0);\n+\tstrbuf_init(&merge_paths, 0);\n+\tstrbuf_init(&split_paths, 0);\n+\tacc_buffer = &buffer;\n+\n+\tif (!revs) {\n+\t\trevs = &therevs;\n+\t\tinit_revisions(revs, 0);\n+\n+\t\t/* we're gonna assume no one else has already traversed this... */\n+\t\twhile ((commit = pop_commit(starts)))\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\n+\t\twhile ((commit = pop_commit(ends))) {\n+\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tadd_pending_object(revs, &commit->object, 0);\n+\t\t}\n+\t}\n+\n+\t/* write head placeholder */\n+\tmemset(&head, 0, sizeof(head));\n+\thead.ofs_objects = htonl(sizeof(head));\n+\txwrite(fd, &head, sizeof(head));\n+\n+\t/* init revisions! */\n+\trevs->tree_objects = 1;\n+\trevs->blob_objects = 1;\n+\trevs->topo_order = 1;\n+\trevs->lifo = 1;\n+\n+\t/* re-use info from other caches if possible */\n+\ttrci = &revs->rev_cache_info;\n+\tinit_rev_cache_info(trci);\n+\ttrci->add_to_pending = 0;\n+\n+\tsetup_revisions(0, 0, revs, 0);\n+\tif (prepare_revision_walk(revs))\n+\t\tdie(\"died preparing revision walk\");\n+\n+\tobject_nr = total_sz = 0;\n+\twhile ((commit = get_revision(revs)) != 0) {\n+\t\tstruct rc_object_entry object;\n+\n+\t\tstrbuf_setlen(&merge_paths, 0);\n+\t\tstrbuf_setlen(&split_paths, 0);\n+\n+\t\tmemset(&object, 0, sizeof(object));\n+\t\tobject.type = OBJ_COMMIT;\n+\t\tobject.date = commit->date;\n+\t\tobject.sha1 = commit->object.sha1;\n+\n+\t\thandle_paths(commit, &object, &merge_paths, &split_paths);\n+\n+\t\tif (object.is_end) {\n+\t\t\tstrbuf_add(&endlist, object.sha1, 20);\n+\t\t\tif (ends)\n+\t\t\t\tcommit_list_insert(commit, ends);\n+\t\t}\n+\t\t/* the two *aren't* mutually exclusive */\n+\t\tif (object.is_start) {\n+\t\t\tstrbuf_add(&startlist, object.sha1, 20);\n+\t\t\tif (starts)\n+\t\t\t\tcommit_list_insert(commit, starts);\n+\t\t}\n+\n+\t\tcommit->indegree = 0;\n+\n+\t\tadd_object_entry(0, 0, &object, &merge_paths, &split_paths);\n+\t\tobject_nr++;\n+\n+\t\t/* print every ~1MB or so */\n+\t\tif (buffer.len > 1000000) {\n+\t\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\t\ttotal_sz += buffer.len;\n+\n+\t\t\tstrbuf_setlen(&buffer, 0);\n+\t\t}\n+\t}\n+\n+\tif (buffer.len) {\n+\t\twrite_in_full(fd, buffer.buf, buffer.len);\n+\t\ttotal_sz += buffer.len;\n+\t}\n+\n+\t/* go ahead a free some stuff... */\n+\tstrbuf_release(&buffer);\n+\tstrbuf_release(&merge_paths);\n+\tstrbuf_release(&split_paths);\n+\tif (path_sz)\n+\t\tfree(paths);\n+\twhile (path_track_alloc)\n+\t\tremove_path_track(&path_track_alloc, 1);\n+\n+\t/* the meaning of the hash name is more or less irrelevant, it's the uniqueness that matters */\n+\tstrbuf_add(&endlist, startlist.buf, startlist.len);\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, endlist.buf, endlist.len);\n+\tgit_SHA1_Final(sha1, &ctx);\n+\n+\t/* now actually initialize header */\n+\tstrcpy(head.signature, \"REVCACHE\");\n+\thead.version = SUPPORTED_REVCACHE_VERSION;\n+\n+\thead.object_nr = htonl(object_nr);\n+\thead.size = htonl(ntohl(head.ofs_objects) + total_sz);\n+\thead.path_nr = htons(path_nr);\n+\thashcpy(head.sha1, sha1);\n+\n+\t/* some info! */\n+\tfprintf(stderr, \"objects: %d\\n\", object_nr);\n+\tfprintf(stderr, \"paths: %d\\n\", path_nr);\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\txwrite(fd, &head, sizeof(head));\n+\n+\tif (rci->make_index && make_cache_index(rci, sha1, fd, ntohl(head.size)) < 0)\n+\t\tdie(\"can't update index\");\n+\n+\tclose(fd);\n+\n+\tnewfile = git_path(\"rev-cache/%s\", sha1_to_hex(sha1));\n+\tif (rename(file, newfile))\n+\t\tdie(\"can't move temp file\");\n+\n+\t/* let our caller know what we've just made */\n+\tif (cache_sha1)\n+\t\thashcpy(cache_sha1, sha1);\n+\n+\tstrbuf_release(&endlist);\n+\tstrbuf_release(&startlist);\n+\n+\treturn 0;\n+}\n+\n+\n+static int index_sort_hash(const void *a, const void *b)\n+{\n+\treturn hashcmp(((struct rc_index_entry_ondisk *)a)->sha1, ((struct rc_index_entry_ondisk *)b)->sha1);\n+}\n+\n+static int write_cache_index(struct strbuf *body)\n+{\n+\tstruct rc_index_header whead;\n+\tstruct lock_file *lk;\n+\tint fd, i;\n+\n+\t/* clear index map if loaded */\n+\tif (idx_map) {\n+\t\tmunmap(idx_map, idx_size);\n+\t\tidx_map = 0;\n+\t}\n+\n+\tlk = xcalloc(sizeof(struct lock_file), 1);\n+\tfd = hold_lock_file_for_update(lk, git_path(\"rev-cache/index\"), 0);\n+\tif (fd < 0) {\n+\t\tfree(lk);\n+\t\treturn -1;\n+\t}\n+\n+\t/* endianness yay! */\n+\tmemset(&whead, 0, sizeof(whead));\n+\tmemcpy(whead.signature, \"REVINDEX\", 8);\n+\twhead.version = idx_head.version;\n+\twhead.ofs_objects = htonl(idx_head.ofs_objects);\n+\twhead.object_nr = htonl(idx_head.object_nr);\n+\twhead.cache_nr = idx_head.cache_nr;\n+\twhead.max_date = htonl(idx_head.max_date);\n+\n+\twrite(fd, &whead, sizeof(struct rc_index_header));\n+\twrite_in_full(fd, idx_caches, idx_head.cache_nr * 20);\n+\n+\tfor (i = 0; i <= 0xff; i++)\n+\t\tfanout[i] = htonl(fanout[i]);\n+\twrite_in_full(fd, fanout, 0x100 * sizeof(uint32_t));\n+\n+\twrite_in_full(fd, body->buf, body->len);\n+\n+\tif (commit_lock_file(lk) < 0)\n+\t\treturn -2;\n+\n+\t/* lk freed by lockfile.c */\n+\n+\treturn 0;\n+}\n+\n+int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size)\n+{\n+\tstruct strbuf buffer;\n+\tint i, cache_index, cur;\n+\tunsigned char *map;\n+\tunsigned long max_date;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\tmap = xmmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED)\n+\t\treturn -1;\n+\n+\tstrbuf_init(&buffer, 0);\n+\tif (idx_map) {\n+\t\tstrbuf_add(&buffer, idx_map + fanout[0], fanout[0x100] - fanout[0]);\n+\t} else {\n+\t\t/* not an update */\n+\t\tmemset(&idx_head, 0, sizeof(struct rc_index_header));\n+\t\tidx_caches = 0;\n+\n+\t\tstrcpy(idx_head.signature, \"REVINDEX\");\n+\t\tidx_head.version = SUPPORTED_REVINDEX_VERSION;\n+\t\tidx_head.ofs_objects = sizeof(struct rc_index_header) + 0x100 * sizeof(uint32_t);\n+\t}\n+\n+\t/* are we remaking a slice? */\n+\tfor (i = 0; i < idx_head.cache_nr; i++)\n+\t\tif (!hashcmp(idx_caches + i * 20, cache_sha1))\n+\t\t\tbreak;\n+\n+\tif (i == idx_head.cache_nr) {\n+\t\tcache_index = idx_head.cache_nr++;\n+\t\tidx_head.ofs_objects += 20;\n+\n+\t\tidx_caches = xrealloc(idx_caches, idx_head.cache_nr * 20);\n+\t\thashcpy(idx_caches + cache_index * 20, cache_sha1);\n+\t} else\n+\t\tcache_index = i;\n+\n+\ti = sizeof(struct rc_slice_header); /* offset */\n+\tmax_date = idx_head.max_date;\n+\twhile (i < size) {\n+\t\tstruct rc_index_entry index_entry, *entry;\n+\t\tstruct rc_index_entry_ondisk *disked_entry;\n+\t\tstruct rc_object_entry *object_entry = RC_OBTAIN_OBJECT_ENTRY(map + i);\n+\t\tunsigned long date;\n+\t\tint off, pos = i;\n+\n+\t\ti += RC_ACTUAL_OBJECT_ENTRY_SIZE(object_entry);\n+\n+\t\tif (object_entry->type != OBJ_COMMIT)\n+\t\t\tcontinue;\n+\n+\t\t/* don't include ends; otherwise we'll find ourselves in loops */\n+\t\tif (object_entry->is_end)\n+\t\t\tcontinue;\n+\n+\t\t/* handle index duplication\n+\t\t * -> keep old copy unless new one is a start -- based on expected usage, older ones will be more\n+\t\t * likely to lead to greater slice traversals than new ones */\n+\t\tdate = object_entry->date;\n+\t\tif (date > idx_head.max_date) {\n+\t\t\tdisked_entry = 0;\n+\t\t\tif (date > max_date)\n+\t\t\t\tmax_date = date;\n+\t\t} else\n+\t\t\tdisked_entry = search_index_1(object_entry->sha1);\n+\n+\t\tif (disked_entry && !object_entry->is_start)\n+\t\t\tcontinue;\n+\t\telse if (disked_entry) {\n+\t\t\t/* mmm, pointer arithmetic... tasty */  /* (entry - idx_map = offset, so cast is valid) */\n+\t\t\toff = (unsigned int)((unsigned char *)disked_entry - idx_map) - fanout[0];\n+\t\t\tdisked_entry = (struct rc_index_entry_ondisk *)(buffer.buf + off);\n+\t\t\tentry = from_disked_rc_index_entry(disked_entry, 0);\n+\t\t} else\n+\t\t\tentry = &index_entry;\n+\n+\t\tmemset(entry, 0, sizeof(index_entry));\n+\t\tentry->sha1 = object_entry->sha1;\n+\t\tentry->is_start = object_entry->is_start;\n+\t\tentry->cache_index = cache_index;\n+\t\tentry->pos = pos;\n+\n+\t\tif (entry == &index_entry) {\n+\t\t\tstrbuf_add(&buffer, to_disked_rc_index_entry(entry, 0), sizeof(struct rc_index_entry_ondisk));\n+\t\t\tidx_head.object_nr++;\n+\t\t} else\n+\t\t\tto_disked_rc_index_entry(entry, disked_entry);\n+\n+\t}\n+\n+\tidx_head.max_date = max_date;\n+\tqsort(buffer.buf, buffer.len / sizeof(struct rc_index_entry_ondisk), sizeof(struct rc_index_entry_ondisk), index_sort_hash);\n+\n+\t/* generate fanout */\n+\tcur = 0x00;\n+\tfor (i = 0; i < buffer.len; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry_ondisk *entry = (struct rc_index_entry_ondisk *)(buffer.buf + i);\n+\n+\t\twhile (cur <= entry->sha1[0])\n+\t\t\tfanout[cur++] = i + idx_head.ofs_objects;\n+\t}\n+\n+\twhile (cur <= 0xff)\n+\t\tfanout[cur++] = idx_head.ofs_objects + buffer.len;\n+\n+\t/* BOOM! */\n+\tif (write_cache_index(&buffer))\n+\t\treturn -1;\n+\n+\tmunmap(map, size);\n+\tstrbuf_release(&buffer);\n+\n+\t/* idx_map is unloaded without cleanup_cache_slices(), so regardless of previous index existence\n+\t * we can still free this up */\n+\tfree(idx_caches);\n+\n+\treturn 0;\n+}\n+\n+\n+/* add start-commits from each cache slice (uninterestingness will be propogated) */\n+void starts_from_slices(struct rev_info *revs, unsigned int flags)\n+{\n+\tstruct commit *commit;\n+\tint i;\n+\n+\tif (!idx_map)\n+\t\tinit_index();\n+\tif (!idx_map)\n+\t\treturn;\n+\n+\tfor (i = idx_head.ofs_objects; i < idx_size; i += sizeof(struct rc_index_entry_ondisk)) {\n+\t\tstruct rc_index_entry *entry = RC_OBTAIN_INDEX_ENTRY(idx_map + i);\n+\n+\t\tif (!entry->is_start)\n+\t\t\tcontinue;\n+\n+\t\tcommit = lookup_commit(entry->sha1);\n+\t\tif (!commit)\n+\t\t\tcontinue;\n+\n+\t\tcommit->object.flags |= flags;\n+\t\tadd_pending_object(revs, &commit->object, 0);\n+\t}\n+\n+}\ndiff --git a/rev-cache.h b/rev-cache.h\nnew file mode 100644\nindex 0000000..a76dc53\n--- /dev/null\n+++ b/rev-cache.h\n@@ -0,0 +1,107 @@\n+#ifndef REV_CACHE_H\n+#define REV_CACHE_H\n+\n+#define SUPPORTED_REVCACHE_VERSION \t\t1\n+#define SUPPORTED_REVINDEX_VERSION\t\t1\n+\n+#define RC_PATH_SIZE(x)\t(sizeof(uint16_t) * (x))\n+\n+#define RC_OBTAIN_OBJECT_ENTRY(p)\t\t\tfrom_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)\n+#define RC_OBTAIN_INDEX_ENTRY(p)\t\t\tfrom_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)\n+\n+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e)\t\t(sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)\n+\n+/* single index maps objects to cache files */\n+struct rc_index_header {\n+\tchar signature[8]; /* REVINDEX */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tunsigned char cache_nr;\n+\n+\tuint32_t max_date;\n+};\n+\n+struct rc_index_entry_ondisk {\n+\tunsigned char sha1[20];\n+\tunsigned char flags;\n+\tuint32_t pos;\n+};\n+\n+struct rc_index_entry {\n+\tunsigned char *sha1;\n+\tunsigned is_start:1;\n+\tunsigned cache_index:7;\n+\tuint32_t pos;\n+};\n+\n+\n+/* structure for actual cache file */\n+struct rc_slice_header {\n+\tchar signature[8]; /* REVCACHE */\n+\tunsigned char version;\n+\tuint32_t ofs_objects;\n+\n+\tuint32_t object_nr;\n+\tuint16_t path_nr;\n+\tuint32_t size;\n+\n+\tunsigned char sha1[20];\n+};\n+\n+struct rc_object_entry_ondisk {\n+\tunsigned char flags;\n+\tunsigned char sha1[20];\n+\n+\tunsigned char merge_nr;\n+\tunsigned char split_nr;\n+\tunsigned char sizes;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+};\n+\n+struct rc_object_entry {\n+\tunsigned type:3;\n+\tunsigned is_end:1;\n+\tunsigned is_start:1;\n+\tunsigned uninteresting:1;\n+\tunsigned include:1;\n+\tunsigned flag:1; /* unused */\n+\tunsigned char *sha1; /* 20 byte */\n+\n+\tunsigned char merge_nr; /* : 7 */\n+\tunsigned char split_nr; /* : 7 */\n+\tunsigned size_size:3;\n+\tunsigned padding:5;\n+\n+\tuint32_t date;\n+\tuint16_t path;\n+\n+\t/* merge paths */\n+\t/* split paths */\n+\t/* size */\n+};\n+\n+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);\n+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst);\n+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst);\n+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);\n+\n+extern unsigned char *get_cache_slice(struct commit *commit);\n+extern int traverse_cache_slice(struct rev_info *revs,\n+\tunsigned char *cache_sha1, struct commit *commit,\n+\tunsigned long *date_so_far, int *slop_so_far,\n+\tstruct commit_list ***queue, struct commit_list **work);\n+\n+extern void init_rev_cache_info(struct rev_cache_info *rci);\n+extern int make_cache_slice(struct rev_cache_info *rci,\n+\tstruct rev_info *revs, struct commit_list **starts, struct commit_list **ends,\n+\tunsigned char *cache_sha1);\n+extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,\n+\tint fd, unsigned int size);\n+\n+extern void starts_from_slices(struct rev_info *revs, unsigned int flags);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 9fc4e8d..de9e2e3 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -432,7 +432,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \tcommit->object.flags |= TREESAME;\n }\n \n-static void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+void insert_by_date_cached(struct commit *p, struct commit_list **head,\n \t\t    struct commit_list *cached_base, struct commit_list **cache)\n {\n \tstruct commit_list *new_entry;\ndiff --git a/revision.h b/revision.h\nindex b6421a6..1b7b93f 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -13,7 +13,8 @@\n #define CHILD_SHOWN\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define FACE_VALUE\t(1u<<9)\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n \n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n@@ -21,6 +22,19 @@\n struct rev_info;\n struct log_info;\n \n+struct rev_cache_info {\n+\t/* generation flags */\n+\tunsigned objects : 1,\n+\t\tlegs : 1,\n+\t\tmake_index : 1;\n+\n+\t/* traversal flags */\n+\tunsigned add_to_pending : 1;\n+\n+\t/* fuse options */\n+\tunsigned int ignore_size;\n+};\n+\n struct rev_info {\n \t/* Starting list */\n \tstruct commit_list *commits;\n@@ -76,6 +90,10 @@ struct rev_info {\n \t\t\tdense_combined_merges:1,\n \t\t\talways_show_header:1;\n \n+\t/* rev-cache flags */\n+\tunsigned int for_pack:1,\n+\t\tdont_cache_me:1;\n+\n \t/* Format info */\n \tunsigned int\tshown_one:1,\n \t\t\tshow_merge:1,\n@@ -120,6 +138,9 @@ struct rev_info {\n \tstruct reflog_walk_info *reflog_info;\n \tstruct decoration children;\n \tstruct decoration merge_simplification;\n+\n+\t/* caching info, used ONLY by traverse_cache_slice */\n+\tstruct rev_cache_info rev_cache_info;\n };\n \n #define REV_TREE_SAME\t\t0\n@@ -172,4 +193,7 @@ enum commit_action {\n extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);\n extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);\n \n+extern void insert_by_date_cached(struct commit *p, struct commit_list **head,\n+\t\t    struct commit_list *cached_base, struct commit_list **cache);\n+\n #endif\ndiff --git a/t/t6017-rev-cache-list.sh b/t/t6017-rev-cache-list.sh\nnew file mode 100755\nindex 0000000..f59f568\n--- /dev/null\n+++ b/t/t6017-rev-cache-list.sh\n@@ -0,0 +1,106 @@\n+#!/bin/sh\n+\n+test_description='git rev-cache tests'\n+. ./test-lib.sh\n+\n+test_cmp_sorted() {\n+\tgrep -io \"[a-f0-9]*\" $1 | sort >.tmpfile1 &&\n+\tgrep -io \"[a-f0-9]*\" $2 | sort >.tmpfile2 &&\n+\ttest_cmp .tmpfile1 .tmpfile2\n+}\n+\n+# we want a totally wacked out branch structure...\n+# we need branching and merging of sizes up through 3, tree\n+# addition/deletion, and enough branching to exercise path\n+# reuse\n+test_expect_success 'init repo' '\n+\techo bla >file &&\n+\tgit add . &&\n+\tgit commit -m \"bla\" &&\n+\n+\tgit branch b1 &&\n+\tgit checkout b1 &&\n+\techo blu >file2 &&\n+\tmkdir d1 &&\n+\techo bang >d1/filed1 &&\n+\tgit add . &&\n+\tgit commit -m \"blu\" &&\n+\n+\tgit checkout master &&\n+\tgit branch b2 &&\n+\tgit checkout b2 &&\n+\techo kaplaa >>file &&\n+\tgit commit -a -m \"kaplaa\" &&\n+\n+\tgit checkout master &&\n+\tmkdir smoke &&\n+\techo omg >smoke/bong &&\n+\tgit add . &&\n+\tgit commit -m \"omg\" &&\n+\n+\tgit branch b4 &&\n+\tgit checkout b4 &&\n+\techo shazam >file8 &&\n+\tgit add . &&\n+\tgit commit -m \"shazam\" &&\n+\tgit merge -m \"merge b2\" b2 &&\n+\n+\techo bam >smoke/pipe &&\n+\tgit add .\n+\tgit commit -m \"bam\" &&\n+\n+\tgit checkout master &&\n+\techo pow >file7 &&\n+\tgit add . &&\n+\tgit commit -m \"pow\" &&\n+\tgit merge -m \"merge b4\" b4 &&\n+\n+\tgit checkout b1 &&\n+\techo stuff >d1/filed1 &&\n+\tgit commit -a -m \"stuff\" &&\n+\n+\tgit branch b11 &&\n+\tgit checkout b11 &&\n+\techo wazzup >file3 &&\n+\tgit add file3 &&\n+\tgit commit -m \"wazzup\" &&\n+\n+\tgit checkout b1 &&\n+\tmkdir d1/d2 &&\n+\techo lol >d1/d2/filed2 &&\n+\tgit add . &&\n+\tgit commit -m \"lol\" &&\n+\n+\tgit checkout master &&\n+\tgit merge -m \"triple merge\" b1 b11 &&\n+\tgit rm -r d1 &&\n+\tgit commit -a -m \"oh noes\"\n+'\n+\n+git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited\n+git-rev-list HEAD >proper_commit_list\n+git-rev-list HEAD --objects >proper_object_list\n+\n+test_expect_success 'make cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+test_expect_success 'remake cache slice' '\n+\tgit-rev-cache add HEAD 2>output.err &&\n+\tgrep \"final return value: 0\" output.err\n+'\n+\n+#check core mechanics and rev-list hook for commits\n+test_expect_success 'test rev-caches walker directly (limited)' '\n+\tgit-rev-cache walk HEAD --not HEAD~3 >list &&\n+\ttest_cmp_sorted list proper_commit_list_limited\n+'\n+\n+test_expect_success 'test rev-caches walker directly (unlimited)' '\n+\tgit-rev-cache walk HEAD >list &&\n+\ttest_cmp_sorted list proper_commit_list\n+'\n+\n+test_done\n+\n-- \ntg: (e79999b..) t/revcache/basic (depends on: master)\n"}]}