{"thread":{"id":"1326","subject":"[PATCH 4/6] Add update-server-info.","startedAt":"2005-07-24T00:54:41Z","lastAt":"2005-07-24T00:54:41Z","messageCount":1,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"6400","messageId":"7vzmsdja5q.fsf@assigned-by-dhcp.cox.net","threadId":"1326","inReplyTo":null,"subject":"[PATCH 4/6] Add update-server-info.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-07-24T00:54:41Z","receivedAt":"2005-07-24T00:54:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The git-update-server-info command prepares informational files\nto help clients discover the contents of a repository, and pull\nfrom it via a dumb transport protocols.  Currently, the\nfollowing files are produced.\n\n - The $repo/info/refs file lists the name of heads and tags\n   available in the $repo/refs/ directory, along with their\n   SHA1.  This can be used by git-ls-remote command running on\n   the client side.\n\n - The $repo/info/rev-cache file describes the commit ancestry\n   reachable from references in the $repo/refs/ directory.  This\n   file is in an append-only binary format to make the server\n   side friendly to rsync mirroring scheme, and can be read by\n   git-show-rev-cache command.\n\n - The $repo/objects/info/pack file lists the name of the packs\n   available, the interdependencies among them, and the head\n   commits and tags contained in them.  Along with the other two\n   files, this is designed to help clients to make smart pull\n   decisions.\n\nThe git-receive-pack command is changed to invoke it at the end,\nso just after a push to a public repository finishes via \"git\npush\", the server info is automatically updated.\n\nIn addition, building of the rev-cache file can be done by a\nstandalone git-build-rev-cache command separately.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\n Makefile             |   11 +\n build-rev-cache.c    |   56 +++++\n cache.h              |    3 \n receive-pack.c       |    1 \n rev-cache.c          |  320 ++++++++++++++++++++++++++++\n rev-cache.h          |   29 +++\n server-info.c        |  565 ++++++++++++++++++++++++++++++++++++++++++++++++++\n show-rev-cache.c     |   18 ++\n update-server-info.c |   23 ++\n 9 files changed, 1025 insertions(+), 1 deletions(-)\n create mode 100644 build-rev-cache.c\n create mode 100644 rev-cache.c\n create mode 100644 rev-cache.h\n create mode 100644 server-info.c\n create mode 100644 show-rev-cache.c\n create mode 100644 update-server-info.c\n\n895f412b3df0a9a4884db6231bb590fd1e388db7\ndiff --git a/Makefile b/Makefile\n--- a/Makefile\n+++ b/Makefile\n@@ -50,7 +50,8 @@ PROG=   git-update-cache git-diff-files \n \tgit-diff-stages git-rev-parse git-patch-id git-pack-objects \\\n \tgit-unpack-objects git-verify-pack git-receive-pack git-send-pack \\\n \tgit-prune-packed git-fetch-pack git-upload-pack git-clone-pack \\\n-\tgit-show-index git-daemon git-var git-peek-remote\n+\tgit-show-index git-daemon git-var git-peek-remote \\\n+\tgit-update-server-info git-show-rev-cache git-build-rev-cache\n \n all: $(PROG)\n \n@@ -65,6 +66,9 @@ LIB_FILE=libgit.a\n LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h csum-file.h \\\n \tpack.h pkt-line.h refs.h\n \n+LIB_H += rev-cache.h\n+LIB_OBJS += rev-cache.o\n+\n LIB_H += strbuf.h\n LIB_OBJS += strbuf.o\n \n@@ -76,6 +80,7 @@ LIB_OBJS += diff.o diffcore-rename.o dif\n \tcount-delta.o diffcore-break.o diffcore-order.o\n \n LIB_OBJS += gitenv.o\n+LIB_OBJS += server-info.o\n \n LIBS = $(LIB_FILE)\n LIBS += -lz\n@@ -152,6 +157,9 @@ git-prune-packed: prune-packed.c\n git-fetch-pack: fetch-pack.c\n git-var: var.c\n git-peek-remote: peek-remote.c\n+git-update-server-info: update-server-info.c\n+git-build-rev-cache: build-rev-cache.c\n+git-show-rev-cache: show-rev-cache.c\n \n git-http-pull: LIBS += -lcurl\n git-rev-list: LIBS += -lssl\n@@ -165,6 +173,7 @@ object.o: $(LIB_H)\n read-cache.o: $(LIB_H)\n sha1_file.o: $(LIB_H)\n usage.o: $(LIB_H)\n+rev-cache.o: $(LIB_H)\n strbuf.o: $(LIB_H)\n gitenv.o: $(LIB_H)\n entry.o: $(LIB_H)\ndiff --git a/build-rev-cache.c b/build-rev-cache.c\nnew file mode 100644\n--- /dev/null\n+++ b/build-rev-cache.c\n@@ -0,0 +1,56 @@\n+#include \"refs.h\"\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"rev-cache.h\"\n+\n+static void process_head_list(int verbose)\n+{\n+\tchar buf[512];\n+\n+\twhile (fgets(buf, sizeof(buf), stdin)) {\n+\t\tunsigned char sha1[20];\n+\t\tstruct commit *commit;\n+\n+\t\tif (get_sha1_hex(buf, sha1)) {\n+\t\t\terror(\"ignoring: %s\", buf);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!(commit = lookup_commit_reference(sha1))) {\n+\t\t\terror(\"not a commit: %s\", sha1_to_hex(sha1));\n+\t\t\tcontinue;\n+\t\t}\n+\t\trecord_rev_cache(commit->object.sha1, verbose ? stderr : NULL);\n+\t}\n+}\n+\n+\n+static const char *build_rev_cache_usage =\n+\"git-build-rev-cache <rev-cache-file> < list-of-heads\";\n+\n+int main(int ac, char **av)\n+{\n+\tint verbose = 0;\n+\tconst char *path;\n+\n+\twhile (1 < ac && av[1][0] == '-') {\n+\t\tif (!strcmp(av[1], \"-v\"))\n+\t\t\tverbose = 1;\n+\t\telse\n+\t\t\tusage(build_rev_cache_usage);\n+\t\tac--; av++;\n+\t}\n+\n+\tif (ac != 2)\n+\t\tusage(build_rev_cache_usage);\n+\n+\tpath = av[1];\n+\n+\t/* read existing rev-cache */\n+\tread_rev_cache(path, NULL, 0);\n+\n+\tprocess_head_list(verbose);\n+\n+\t/* update the rev-cache database by appending newly found one to it */\n+\twrite_rev_cache(path, path);\n+\treturn 0;\n+}\ndiff --git a/cache.h b/cache.h\n--- a/cache.h\n+++ b/cache.h\n@@ -308,4 +308,7 @@ extern int find_pack_entry_one(const uns\n extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);\n extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, int *, unsigned char *);\n \n+/* Dumb servers support */\n+extern int update_server_info(int);\n+\n #endif /* CACHE_H */\ndiff --git a/receive-pack.c b/receive-pack.c\n--- a/receive-pack.c\n+++ b/receive-pack.c\n@@ -110,6 +110,7 @@ static void execute_commands(void)\n \t\tupdate(cmd->ref_name, cmd->old_sha1, cmd->new_sha1);\n \t\tcmd = cmd->next;\n \t}\n+\tupdate_server_info(0);\n }\n \n static void read_head_info(void)\ndiff --git a/rev-cache.c b/rev-cache.c\nnew file mode 100644\n--- /dev/null\n+++ b/rev-cache.c\n@@ -0,0 +1,320 @@\n+#include \"refs.h\"\n+#include \"cache.h\"\n+#include \"rev-cache.h\"\n+\n+struct rev_cache **rev_cache;\n+int nr_revs, alloc_revs;\n+\n+struct rev_list_elem *rle_free;\n+\n+#define BATCH_SIZE 512\n+\n+int find_rev_cache(const unsigned char *sha1)\n+{\n+\tint lo = 0, hi = nr_revs;\n+\twhile (lo < hi) {\n+\t\tint mi = (lo + hi) / 2;\n+\t\tstruct rev_cache *ri = rev_cache[mi];\n+\t\tint cmp = memcmp(sha1, ri->sha1, 20);\n+\t\tif (!cmp)\n+\t\t\treturn mi;\n+\t\tif (cmp < 0)\n+\t\t\thi = mi;\n+\t\telse\n+\t\t\tlo = mi + 1;\n+\t}\n+\treturn -lo - 1;\n+}\n+\n+static struct rev_list_elem *alloc_list_elem(void)\n+{\n+\tstruct rev_list_elem *rle;\n+\tif (!rle_free) {\n+\t\tint i;\n+\n+\t\trle = xmalloc(sizeof(*rle) * BATCH_SIZE);\n+\t\tfor (i = 0; i < BATCH_SIZE - 1; i++) {\n+\t\t\trle[i].ri = NULL;\n+\t\t\trle[i].next = &rle[i + 1];\n+\t\t}\n+\t\trle[BATCH_SIZE - 1].ri = NULL;\n+\t\trle[BATCH_SIZE - 1].next = NULL;\n+\t\trle_free = rle;\n+\t}\n+\trle = rle_free;\n+\trle_free = rle->next;\n+\treturn rle;\n+}\n+\n+static struct rev_cache *create_rev_cache(const unsigned char *sha1)\n+{\n+\tstruct rev_cache *ri;\n+\tint pos = find_rev_cache(sha1);\n+\n+\tif (0 <= pos)\n+\t\treturn rev_cache[pos];\n+\tpos = -pos - 1;\n+\tif (alloc_revs <= ++nr_revs) {\n+\t\talloc_revs = alloc_nr(alloc_revs);\n+\t\trev_cache = xrealloc(rev_cache, sizeof(ri) * alloc_revs);\n+\t}\n+\tif (pos < nr_revs)\n+\t\tmemmove(rev_cache + pos + 1, rev_cache + pos,\n+\t\t\t(nr_revs - pos - 1) * sizeof(ri));\n+\tri = xcalloc(1, sizeof(*ri));\n+\tmemcpy(ri->sha1, sha1, 20);\n+\trev_cache[pos] = ri;\n+\treturn ri;\n+}\n+\n+static unsigned char last_sha1[20];\n+\n+static void write_one_rev_cache(FILE *rev_cache_file, struct rev_cache *ri)\n+{\n+\tunsigned char flag;\n+\tstruct rev_list_elem *rle;\n+\n+\tif (ri->written)\n+\t\treturn;\n+\n+\tif (ri->parsed) {\n+\t\t/* We use last_sha1 compression only for the first parent;\n+\t\t * otherwise the resulting rev-cache would lose the parent\n+\t\t * order information.\n+\t\t */\n+\t\tif (ri->parents &&\n+\t\t    !memcmp(ri->parents->ri->sha1, last_sha1, 20))\n+\t\t\tflag = (ri->num_parents - 1) | 0x80;\n+\t\telse\n+\t\t\tflag = ri->num_parents;\n+\n+\t\tfwrite(ri->sha1, 20, 1, rev_cache_file);\n+\t\tfwrite(&flag, 1, 1, rev_cache_file);\n+\t\tfor (rle = ri->parents; rle; rle = rle->next) {\n+\t\t\tif (flag & 0x80 && rle == ri->parents)\n+\t\t\t\tcontinue;\n+\t\t\tfwrite(rle->ri->sha1, 20, 1, rev_cache_file);\n+\t\t}\n+\t\tmemcpy(last_sha1, ri->sha1, 20);\n+\t\tri->written = 1;\n+\t}\n+\t/* recursively write children depth first */\n+\tfor (rle = ri->children; rle; rle = rle->next)\n+\t\twrite_one_rev_cache(rev_cache_file, rle->ri);\n+}\n+\n+void write_rev_cache(const char *newpath, const char *oldpath)\n+{\n+\t/* write the following commit ancestry information in\n+\t * $GIT_DIR/info/rev-cache.\n+\t *\n+\t * The format is:\n+\t * 20-byte SHA1 (commit ID)\n+\t * 1-byte flag:\n+\t * - bit 0-6 records \"number of parent commit SHA1s to\n+\t *   follow\" (i.e. up to 127 children can be listed).\n+\t * - when the bit 7 is on, then \"the entry immediately\n+\t *   before this entry is one of the parents of this\n+         *   commit\".\n+\t * N x 20-byte SHA1 (parent commit IDs)\n+\t */\n+\tFILE *rev_cache_file;\n+\tint i;\n+\tstruct rev_cache *ri;\n+\n+\tif (!strcmp(newpath, oldpath)) {\n+\t\t/* If we are doing it in place */\n+\t\trev_cache_file = fopen(newpath, \"a\");\n+\t}\n+\telse {\n+\t\tchar buf[8096];\n+\t\tsize_t sz;\n+\t\tFILE *oldfp = fopen(oldpath, \"r\");\n+\t\trev_cache_file = fopen(newpath, \"w\");\n+\t\tif (oldfp) {\n+\t\t\twhile (1) {\n+\t\t\t\tsz = fread(buf, 1, sizeof(buf), oldfp);\n+\t\t\t\tif (sz == 0)\n+\t\t\t\t\tbreak;\n+\t\t\t\tfwrite(buf, 1, sz, rev_cache_file);\n+\t\t\t}\n+\t\t\tfclose(oldfp);\n+\t\t}\n+\t}\n+\n+\tmemset(last_sha1, 0, 20);\n+\n+\t/* Go through available rev_cache structures, starting from\n+\t * parentless ones first, so that we would get most out of\n+\t * last_sha1 optimization by the depth first behaviour of\n+\t * write_one_rev_cache().\n+\t */\n+\tfor (i = 0; i < nr_revs; i++) {\n+\t\tri = rev_cache[i];\n+\t\tif (ri->num_parents)\n+\t\t\tcontinue;\n+\t\twrite_one_rev_cache(rev_cache_file, ri);\n+\t}\n+\t/* Then the rest */\n+\tfor (i = 0; i < nr_revs; i++) {\n+\t\tri = rev_cache[i];\n+\t\twrite_one_rev_cache(rev_cache_file, ri);\n+\t}\n+\tfclose(rev_cache_file);\n+}\n+\n+static void add_parent(struct rev_cache *child,\n+\t\t       const unsigned char *parent_sha1)\n+{\n+\tstruct rev_cache *parent = create_rev_cache(parent_sha1);\n+\tstruct rev_list_elem *e = alloc_list_elem();\n+\n+\t/* Keep the parent list ordered in the same way the commit\n+\t * object records them.\n+\t */\n+\te->ri = parent;\n+\te->next = NULL;\n+\tif (!child->parents_tail)\n+\t\tchild->parents = e;\n+\telse\n+\t\tchild->parents_tail->next = e;\n+\tchild->parents_tail = e;\n+\tchild->num_parents++;\n+\n+\t/* There is no inherent order of the children so we just\n+\t * LIFO them together.\n+\t */\n+\te = alloc_list_elem();\n+\te->next = parent->children;\n+\tparent->children = e;\n+\te->ri = child;\n+\tparent->num_children++;\n+}\n+\n+int read_rev_cache(const char *path, FILE *dumpfile, int dry_run)\n+{\n+\tunsigned char *map;\n+\tint fd;\n+\tstruct stat st;\n+\tunsigned long ofs, len;\n+\tstruct rev_cache *ri = NULL;\n+\n+\tfd = open(path, O_RDONLY);\n+\tif (fd < 0) {\n+\t\tif (dry_run)\n+\t\t\treturn error(\"cannot open %s\", path);\n+\t\tif (errno == ENOENT)\n+\t\t\treturn 0;\n+\t\treturn -1;\n+\t}\n+\tif (fstat(fd, &st)) {\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\tmap = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tif (map == MAP_FAILED) {\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\tclose(fd);\n+\n+\tmemset(last_sha1, 0, 20);\n+\tofs = 0;\n+\tlen = st.st_size;\n+\twhile (ofs < len) {\n+\t\tunsigned char sha1[20];\n+\t\tint flag, cnt, i;\n+\t\tif (len < ofs + 21)\n+\t\t\tdie(\"rev-cache too short\");\n+\t\tmemcpy(sha1, map + ofs, 20);\n+\t\tflag = map[ofs + 20];\n+\t\tofs += 21;\n+\t\tcnt = (flag & 0x7f) + ((flag & 0x80) != 0);\n+\t\tif (len < ofs + (flag & 0x7f) * 20)\n+\t\t\tdie(\"rev-cache too short to have %d more parents\",\n+\t\t\t    (flag & 0x7f));\n+\t\tif (dumpfile)\n+\t\t\tfprintf(dumpfile, \"%s\", sha1_to_hex(sha1));\n+\t\tif (!dry_run) {\n+\t\t\tri = create_rev_cache(sha1);\n+\t\t\tif (!ri)\n+\t\t\t\tdie(\"cannot create rev-cache for %s\",\n+\t\t\t\t    sha1_to_hex(sha1));\n+\t\t\tri->written = ri->parsed = 1;\n+\t\t}\n+\t\ti = 0;\n+\t\tif (flag & 0x80) {\n+\t\t\tif (!dry_run)\n+\t\t\t\tadd_parent(ri, last_sha1);\n+\t\t\tif (dumpfile)\n+\t\t\t\tfprintf(dumpfile, \" %s\",\n+\t\t\t\t\tsha1_to_hex(last_sha1));\n+\t\t\ti++;\n+\t\t}\n+\t\twhile (i++ < cnt) {\n+\t\t\tif (!dry_run)\n+\t\t\t\tadd_parent(ri, map + ofs);\n+\t\t\tif (dumpfile)\n+\t\t\t\tfprintf(dumpfile, \" %s\",\n+\t\t\t\t\tsha1_to_hex(last_sha1));\n+\t\t\tofs += 20;\n+\t\t}\n+\t\tif (dumpfile)\n+\t\t\tfprintf(dumpfile, \"\\n\");\n+\t\tmemcpy(last_sha1, sha1, 20);\n+\t}\n+\tif (ofs != len)\n+\t\tdie(\"rev-cache truncated?\");\n+\tmunmap(map, len);\n+\treturn 0;\n+}\n+\n+int record_rev_cache(const unsigned char *sha1, FILE *dumpfile)\n+{\n+\tunsigned char parent[20];\n+\tchar type[20];\n+\tunsigned long size, ofs;\n+\tunsigned int cnt, i;\n+\tvoid *buf;\n+\tstruct rev_cache *ri;\n+\n+\tbuf = read_sha1_file(sha1, type, &size);\n+\tif (!buf)\n+\t\treturn error(\"%s: not found\", sha1_to_hex(sha1));\n+\tif (strcmp(type, \"commit\")) {\n+\t\tfree(buf);\n+\t\treturn error(\"%s: not a commit but a %s\",\n+\t\t\t     sha1_to_hex(sha1), type);\n+\t}\n+\tri = create_rev_cache(sha1);\n+\tif (ri->parsed)\n+\t\treturn 0;\n+\tif (dumpfile)\n+\t\tfprintf(dumpfile, \"commit %s\\n\", sha1_to_hex(sha1));\n+\n+\tcnt = 0;\n+\tofs = 46; /* \"tree \" + hex-sha1 + \"\\n\" */\n+\twhile (!memcmp(buf + ofs, \"parent \", 7) &&\n+\t       !get_sha1_hex(buf + ofs + 7, parent)) {\n+\t\tofs += 48;\n+\t\tcnt++;\n+\t}\n+\tif (cnt * 48 + 46 != ofs) {\n+\t\tfree(buf);\n+\t\tdie(\"internal error in record_rev_cache\");\n+\t}\n+\n+\tri = create_rev_cache(sha1);\n+\tri->parsed = 1;\n+\n+\tfor (i = 0; i < cnt; i++) {\n+\t\tunsigned char parent_sha1[20];\n+\n+\t\tofs = 46 + i * 48 + 7;\n+\t\tget_sha1_hex(buf + ofs, parent_sha1);\n+\t\tadd_parent(ri, parent_sha1);\n+\t\trecord_rev_cache(parent_sha1, dumpfile);\n+\t}\n+\tfree(buf);\n+\treturn 0;\n+}\ndiff --git a/rev-cache.h b/rev-cache.h\nnew file mode 100644\n--- /dev/null\n+++ b/rev-cache.h\n@@ -0,0 +1,29 @@\n+#ifndef REV_CACHE_H\n+#define REV_CACHE_H\n+\n+extern struct rev_cache {\n+\tstruct rev_cache *head_list;\n+\tstruct rev_list_elem *children;\n+\tstruct rev_list_elem *parents;\n+\tstruct rev_list_elem *parents_tail;\n+\tunsigned short num_parents;\n+\tunsigned short num_children;\n+\tunsigned int written : 1;\n+\tunsigned int parsed : 1;\n+\tunsigned int work : 30;\n+\tvoid *work_ptr;\n+\tunsigned char sha1[20];\n+} **rev_cache;\n+extern int nr_revs, alloc_revs;\n+\n+struct rev_list_elem {\n+\tstruct rev_list_elem *next;\n+\tstruct rev_cache *ri;\n+};\n+\n+extern int find_rev_cache(const unsigned char *);\n+extern int read_rev_cache(const char *, FILE *, int);\n+extern int record_rev_cache(const unsigned char *, FILE *);\n+extern void write_rev_cache(const char *new, const char *old);\n+\n+#endif\ndiff --git a/server-info.c b/server-info.c\nnew file mode 100644\n--- /dev/null\n+++ b/server-info.c\n@@ -0,0 +1,565 @@\n+#include \"cache.h\"\n+#include \"refs.h\"\n+#include \"object.h\"\n+#include \"commit.h\"\n+#include \"rev-cache.h\"\n+\n+/* refs */\n+static FILE *info_ref_fp;\n+static unsigned long info_ref_time;\n+static int info_ref_is_stale = 0;\n+\n+static int stat_ref(const char *path, const unsigned char *sha1)\n+{\n+\tstruct stat st;\n+\tif (!stat(path, &st) && info_ref_time < st.st_mtime)\n+\t\tinfo_ref_is_stale = 1;\n+\treturn 0;\n+}\n+\n+static int add_info_ref(const char *path, const unsigned char *sha1)\n+{\n+\tfprintf(info_ref_fp, \"%s\t%s\\n\", sha1_to_hex(sha1), path);\n+\treturn 0;\n+}\n+\n+static int update_info_refs(int force)\n+{\n+\tstruct stat st;\n+\tchar *path0 = strdup(git_path(\"info/refs\"));\n+\tint len = strlen(path0);\n+\tchar *path1 = xmalloc(len + 2);\n+\n+\tstrcpy(path1, path0);\n+\tstrcpy(path1 + len, \"+\");\n+\n+\tif (!force) {\n+\t\tif (stat(path0, &st)) {\n+\t\t\tif (errno == ENOENT)\n+\t\t\t\tinfo_ref_is_stale = 1;\n+\t\t\telse\n+\t\t\t\treturn error(\"cannot stat %s\", path0);\n+\t\t}\n+\t\telse {\n+\t\t\tinfo_ref_time = st.st_mtime;\n+\t\t\tfor_each_ref(stat_ref);\n+\t\t}\n+\t\tif (!info_ref_is_stale)\n+\t\t\treturn 0;\n+\t}\n+\n+\tsafe_create_leading_directories(path0);\n+\tinfo_ref_fp = fopen(path1, \"w\");\n+\tif (!info_ref_fp)\n+\t\treturn error(\"unable to update %s\", path0);\n+\tfor_each_ref(add_info_ref);\n+\tfclose(info_ref_fp);\n+\trename(path1, path0);\n+\tfree(path0);\n+\tfree(path1);\n+\treturn 0;\n+}\n+\n+/* packs */\n+struct pack_info {\n+\tunsigned long latest;\n+\tstruct packed_git *p;\n+\tint old_num;\n+\tint new_num;\n+\tint nr_alloc;\n+\tint nr_heads;\n+\tunsigned char (*head)[20];\n+\tchar dep[0]; /* more */\n+} **info;\n+static int num_pack;\n+static const char *objdir;\n+static int objdirlen;\n+\n+static struct object *parse_object_cheap(const unsigned char *sha1)\n+{\n+\tstruct object *o;\n+\n+\tif ((o = parse_object(sha1)) == NULL)\n+\t\treturn NULL;\n+\tif (o->type == commit_type) {\n+\t\tstruct commit *commit = (struct commit *)o;\n+\t\tfree(commit->buffer);\n+\t\tcommit->buffer = NULL;\n+\t}\n+\treturn o;\n+}\n+\n+static struct pack_info *find_pack_by_name(const char *name)\n+{\n+\tint i;\n+\tfor (i = 0; i < num_pack; i++) {\n+\t\tstruct packed_git *p = info[i]->p;\n+\t\t/* skip \"/pack/\" after \".git/objects\" */\n+\t\tif (!strcmp(p->pack_name + objdirlen + 6, name))\n+\t\t\treturn info[i];\n+\t}\n+\treturn NULL;\n+}\n+\n+static struct pack_info *find_pack_by_old_num(int old_num)\n+{\n+\tint i;\n+\tfor (i = 0; i < num_pack; i++)\n+\t\tif (info[i]->old_num == old_num)\n+\t\t\treturn info[i];\n+\treturn NULL;\n+}\n+\n+static int add_head_def(struct pack_info *this, unsigned char *sha1)\n+{\n+\tif (this->nr_alloc <= this->nr_heads) {\n+\t\tthis->nr_alloc = alloc_nr(this->nr_alloc);\n+\t\tthis->head = xrealloc(this->head, this->nr_alloc * 20);\n+\t}\n+\tmemcpy(this->head[this->nr_heads++], sha1, 20);\n+\treturn 0;\n+}\n+\n+/* Returns non-zero when we detect that the info in the\n+ * old file is useless.\n+ */\n+static int parse_pack_def(const char *line, int old_cnt)\n+{\n+\tstruct pack_info *i = find_pack_by_name(line + 2);\n+\tif (i) {\n+\t\ti->old_num = old_cnt;\n+\t\treturn 0;\n+\t}\n+\telse {\n+\t\t/* The file describes a pack that is no longer here;\n+\t\t * dependencies between packs needs to be recalculated.\n+\t\t */\n+\t\treturn 1;\n+\t}\n+}\n+\n+/* Returns non-zero when we detect that the info in the\n+ * old file is useless.\n+ */\n+static int parse_depend_def(char *line)\n+{\n+\tunsigned long num;\n+\tchar *cp, *ep;\n+\tstruct pack_info *this, *that;\n+\n+\tcp = line + 2;\n+\tnum = strtoul(cp, &ep, 10);\n+\tif (ep == cp)\n+\t\treturn error(\"invalid input %s\", line);\n+\tthis = find_pack_by_old_num(num);\n+\tif (!this)\n+\t\treturn 0;\n+\twhile (ep && *(cp = ep)) {\n+\t\tnum = strtoul(cp, &ep, 10);\n+\t\tif (ep == cp)\n+\t\t\tbreak;\n+\t\tthat = find_pack_by_old_num(num);\n+\t\tif (!that)\n+\t\t\t/* The pack this one depends on does not\n+\t\t\t * exist; this should not happen because\n+\t\t\t * we write out the list of packs first and\n+\t\t\t * then dependency information, but it means\n+\t\t\t * the file is useless anyway.\n+\t\t\t */\n+\t\t\treturn 1;\n+\t\tthis->dep[that->new_num] = 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/* Returns non-zero when we detect that the info in the\n+ * old file is useless.\n+ */\n+static int parse_head_def(char *line)\n+{\n+\tunsigned char sha1[20];\n+\tunsigned long num;\n+\tchar *cp, *ep;\n+\tstruct pack_info *this;\n+\tstruct object *o;\n+\n+\tcp = line + 2;\n+\tnum = strtoul(cp, &ep, 10);\n+\tif (ep == cp || *ep++ != ' ')\n+\t\treturn error(\"invalid input ix %s\", line);\n+\tthis = find_pack_by_old_num(num);\n+\tif (!this)\n+\t\treturn 1; /* You know the drill. */\n+\tif (get_sha1_hex(ep, sha1) || ep[40] != ' ')\n+\t\treturn error(\"invalid input sha1 %s (%s)\", line, ep);\n+\tif ((o = parse_object_cheap(sha1)) == NULL)\n+\t\treturn error(\"no such object: %s\", line);\n+\treturn add_head_def(this, sha1);\n+}\n+\n+/* Returns non-zero when we detect that the info in the\n+ * old file is useless.\n+ */\n+static int read_pack_info_file(const char *infofile)\n+{\n+\tFILE *fp;\n+\tchar line[1000];\n+\tint old_cnt = 0;\n+\n+\tfp = fopen(infofile, \"r\");\n+\tif (!fp)\n+\t\treturn 1; /* nonexisting is not an error. */\n+\n+\twhile (fgets(line, sizeof(line), fp)) {\n+\t\tint len = strlen(line);\n+\t\tif (line[len-1] == '\\n')\n+\t\t\tline[len-1] = 0;\n+\n+\t\tswitch (line[0]) {\n+\t\tcase 'P': /* P name */\n+\t\t\tif (parse_pack_def(line, old_cnt++))\n+\t\t\t\tgoto out_stale;\n+\t\t\tbreak;\n+\t\tcase 'D': /* D ix dep-ix1 dep-ix2... */\n+\t\t\tif (parse_depend_def(line))\n+\t\t\t\tgoto out_stale;\n+\t\t\tbreak;\n+\t\tcase 'T': /* T ix sha1 type */\n+\t\t\tif (parse_head_def(line))\n+\t\t\t\tgoto out_stale;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\terror(\"unrecognized: %s\", line);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tfclose(fp);\n+\treturn 0;\n+ out_stale:\n+\tfclose(fp);\n+\treturn 1;\n+}\n+\n+/* We sort the packs according to the date of the latest commit.  That\n+ * in turn indicates how young the pack is, and in general we would\n+ * want to depend on younger packs.\n+ */\n+static unsigned long get_latest_commit_date(struct packed_git *p)\n+{\n+\tunsigned char sha1[20];\n+\tstruct object *o;\n+\tint num = num_packed_objects(p);\n+\tint i;\n+\tunsigned long latest = 0;\n+\n+\tfor (i = 0; i < num; i++) {\n+\t\tif (nth_packed_object_sha1(p, i, sha1))\n+\t\t\tdie(\"corrupt pack file %s?\", p->pack_name);\n+\t\tif ((o = parse_object_cheap(sha1)) == NULL)\n+\t\t\tdie(\"cannot parse %s\", sha1_to_hex(sha1));\n+\t\tif (o->type == commit_type) {\n+\t\t\tstruct commit *commit = (struct commit *)o;\n+\t\t\tif (latest < commit->date)\n+\t\t\t\tlatest = commit->date;\n+\t\t}\n+\t}\n+\treturn latest;\n+}\n+\n+static int compare_info(const void *a_, const void *b_)\n+{\n+\tstruct pack_info * const* a = a_;\n+\tstruct pack_info * const* b = b_;\n+\n+\tif (0 <= (*a)->old_num && 0 <= (*b)->old_num)\n+\t\t/* Keep the order in the original */\n+\t\treturn (*a)->old_num - (*b)->old_num;\n+\telse if (0 <= (*a)->old_num)\n+\t\t/* Only A existed in the original so B is obviously newer */\n+\t\treturn -1;\n+\telse if (0 <= (*b)->old_num)\n+\t\t/* The other way around. */\n+\t\treturn 1;\n+\n+\tif ((*a)->latest < (*b)->latest)\n+\t\treturn -1;\n+\telse if ((*a)->latest == (*b)->latest)\n+\t\treturn 0;\n+\telse\n+\t\treturn 1;\n+}\n+\n+static void init_pack_info(const char *infofile, int force)\n+{\n+\tstruct packed_git *p;\n+\tint stale;\n+\tint i = 0;\n+\tchar *dep_temp;\n+\n+\tobjdir = get_object_directory();\n+\tobjdirlen = strlen(objdir);\n+\n+\tprepare_packed_git();\n+\tfor (p = packed_git; p; p = p->next) {\n+\t\t/* we ignore things on alternate path since they are\n+\t\t * not available to the pullers in general.\n+\t\t */\n+\t\tif (strncmp(p->pack_name, objdir, objdirlen) ||\n+\t\t    strncmp(p->pack_name + objdirlen, \"/pack/\", 6))\n+\t\t\tcontinue;\n+\t\ti++;\n+\t}\n+\tnum_pack = i;\n+\tinfo = xcalloc(num_pack, sizeof(struct pack_info *));\n+\tfor (i = 0, p = packed_git; p; p = p->next) {\n+\t\tif (strncmp(p->pack_name, objdir, objdirlen) ||\n+\t\t    p->pack_name[objdirlen] != '/')\n+\t\t\tcontinue;\n+\t\tinfo[i] = xcalloc(1, sizeof(struct pack_info) + num_pack);\n+\t\tinfo[i]->p = p;\n+\t\tinfo[i]->old_num = -1;\n+\t\ti++;\n+\t}\n+\n+\tif (infofile && !force)\n+\t\tstale = read_pack_info_file(infofile);\n+\telse\n+\t\tstale = 1;\n+\n+\tfor (i = 0; i < num_pack; i++) {\n+\t\tif (stale) {\n+\t\t\tinfo[i]->old_num = -1;\n+\t\t\tmemset(info[i]->dep, 0, num_pack);\n+\t\t\tinfo[i]->nr_heads = 0;\n+\t\t}\n+\t\tif (info[i]->old_num < 0)\n+\t\t\tinfo[i]->latest = get_latest_commit_date(info[i]->p);\n+\t}\n+\n+\tqsort(info, num_pack, sizeof(info[0]), compare_info);\n+\tfor (i = 0; i < num_pack; i++)\n+\t\tinfo[i]->new_num = i;\n+\n+\t/* we need to fix up the dependency information\n+\t * for the old ones.\n+\t */\n+\tdep_temp = NULL;\n+\tfor (i = 0; i < num_pack; i++) {\n+\t\tint old;\n+\n+\t\tif (info[i]->old_num < 0)\n+\t\t\tcontinue;\n+\t\tif (! dep_temp)\n+\t\t\tdep_temp = xmalloc(num_pack);\n+\t\tmemset(dep_temp, 0, num_pack);\n+\t\tfor (old = 0; old < num_pack; old++) {\n+\t\t\tstruct pack_info *base;\n+\t\t\tif (!info[i]->dep[old])\n+\t\t\t\tcontinue;\n+\t\t\tbase = find_pack_by_old_num(old);\n+\t\t\tif (!base)\n+\t\t\t\tdie(\"internal error renumbering\");\n+\t\t\tdep_temp[base->new_num] = 1;\n+\t\t}\n+\t\tmemcpy(info[i]->dep, dep_temp, num_pack);\n+\t}\n+\tfree(dep_temp);\n+}\n+\n+static void write_pack_info_file(FILE *fp)\n+{\n+\tint i, j;\n+\tfor (i = 0; i < num_pack; i++)\n+\t\tfprintf(fp, \"P %s\\n\", info[i]->p->pack_name + objdirlen + 6);\n+\n+\tfor (i = 0; i < num_pack; i++) {\n+\t\tfprintf(fp, \"D %1d\", i);\n+\t\tfor (j = 0; j < num_pack; j++) {\n+\t\t\tif ((i == j) || !(info[i]->dep[j]))\n+\t\t\t\tcontinue;\n+\t\t\tfprintf(fp, \" %1d\", j);\n+\t\t}\n+\t\tfputc('\\n', fp);\n+\t}\n+\n+\tfor (i = 0; i < num_pack; i++) {\n+\t\tstruct pack_info *this = info[i];\n+\t\tfor (j = 0; j < this->nr_heads; j++) {\n+\t\t\tstruct object *o = lookup_object(this->head[j]);\n+\t\t\tfprintf(fp, \"T %1d %s %s\\n\",\n+\t\t\t\ti, sha1_to_hex(this->head[j]), o->type);\n+\t\t}\n+\t}\n+\n+}\n+\n+#define REFERENCED 01\n+#define INTERNAL  02\n+#define EMITTED   04\n+\n+static void show(struct object *o, int pack_ix)\n+{\n+\t/*\n+\t * We are interested in objects that are not referenced,\n+\t * and objects that are referenced but not internal.\n+\t */\n+\tif (o->flags & EMITTED)\n+\t\treturn;\n+\n+\tif (!(o->flags & REFERENCED))\n+\t\tadd_head_def(info[pack_ix], o->sha1);\n+\telse if ((o->flags & REFERENCED) && !(o->flags & INTERNAL)) {\n+\t\tint i;\n+\n+\t\t/* Which pack contains this object?  That is what\n+\t\t * pack_ix can depend on.  We earlier sorted info\n+\t\t * array from youngest to oldest, so try newer packs\n+\t\t * first to favor them here.\n+\t\t */\n+\t\tfor (i = num_pack - 1; 0 <= i; i--) {\n+\t\t\tstruct packed_git *p = info[i]->p;\n+\t\t\tstruct pack_entry ent;\n+\t\t\tif (find_pack_entry_one(o->sha1, &ent, p)) {\n+\t\t\t\tinfo[pack_ix]->dep[i] = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\to->flags |= EMITTED;\n+}\n+\n+static void find_pack_info_one(int pack_ix)\n+{\n+\tunsigned char sha1[20];\n+\tstruct object *o;\n+\tstruct object_list *ref;\n+\tint i;\n+\tstruct packed_git *p = info[pack_ix]->p;\n+\tint num = num_packed_objects(p);\n+\n+\t/* Scan objects, clear flags from all the edge ones and\n+\t * internal ones, possibly marked in the previous round.\n+\t */\n+\tfor (i = 0; i < num; i++) {\n+\t\tif (nth_packed_object_sha1(p, i, sha1))\n+\t\t\tdie(\"corrupt pack file %s?\", p->pack_name);\n+\t\tif ((o = lookup_object(sha1)) == NULL)\n+\t\t\tdie(\"cannot parse %s\", sha1_to_hex(sha1));\n+\t\tfor (ref = o->refs; ref; ref = ref->next)\n+\t\t\tref->item->flags = 0;\n+\t\to->flags = 0;\n+\t}\n+\n+\t/* Mark all the internal ones */\n+\tfor (i = 0; i < num; i++) {\n+\t\tif (nth_packed_object_sha1(p, i, sha1))\n+\t\t\tdie(\"corrupt pack file %s?\", p->pack_name);\n+\t\tif ((o = lookup_object(sha1)) == NULL)\n+\t\t\tdie(\"cannot find %s\", sha1_to_hex(sha1));\n+\t\tfor (ref = o->refs; ref; ref = ref->next)\n+\t\t\tref->item->flags |= REFERENCED;\n+\t\to->flags |= INTERNAL;\n+\t}\n+\n+\tfor (i = 0; i < num; i++) {\n+\t\tif (nth_packed_object_sha1(p, i, sha1))\n+\t\t\tdie(\"corrupt pack file %s?\", p->pack_name);\n+\t\tif ((o = lookup_object(sha1)) == NULL)\n+\t\t\tdie(\"cannot find %s\", sha1_to_hex(sha1));\n+\n+\t\tshow(o, pack_ix);\n+\t\tfor (ref = o->refs; ref; ref = ref->next)\n+\t\t\tshow(ref->item, pack_ix);\n+\t}\n+\n+}\n+\n+static void find_pack_info(void)\n+{\n+\tint i;\n+\tfor (i = 0; i < num_pack; i++) {\n+\t\t/* The packed objects are cast in stone, and a head\n+\t\t * in a pack will stay as head, so is the set of missing\n+\t\t * objects.  If the repo has been reorganized and we\n+\t\t * are missing some packs available back then, we have\n+\t\t * already discarded the info read from the file, so\n+\t\t * we will find (old_num < 0) in that case.\n+\t\t */\n+\t\tif (0 <= info[i]->old_num)\n+\t\t\tcontinue;\n+\t\tfind_pack_info_one(i);\n+\t}\n+}\n+\n+static int update_info_packs(int force)\n+{\n+\tchar infofile[PATH_MAX];\n+\tchar name[PATH_MAX];\n+\tint namelen;\n+\tFILE *fp;\n+\n+\tnamelen = sprintf(infofile, \"%s/info/packs\", get_object_directory());\n+\tstrcpy(name, infofile);\n+\tstrcpy(name + namelen, \"+\");\n+\n+\tinit_pack_info(infofile, force);\n+\tfind_pack_info();\n+\n+\tsafe_create_leading_directories(name);\n+\tfp = fopen(name, \"w\");\n+\tif (!fp)\n+\t\treturn error(\"cannot open %s\", name);\n+\twrite_pack_info_file(fp);\n+\tfclose(fp);\n+\trename(name, infofile);\n+\treturn 0;\n+}\n+\n+/* rev-cache */\n+static int record_rev_cache_ref(const char *path, const unsigned char *sha1)\n+{\n+\tstruct commit *commit;\n+\tif (!(commit = lookup_commit_reference(sha1)))\n+\t\treturn error(\"not a commit: %s\", sha1_to_hex(sha1));\n+\treturn record_rev_cache(commit->object.sha1, NULL);\n+}\n+\n+static int update_info_revs(int force)\n+{\n+\tchar *path0 = strdup(git_path(\"info/rev-cache\"));\n+\tint len = strlen(path0);\n+\tchar *path1 = xmalloc(len + 2);\n+\n+\tstrcpy(path1, path0);\n+\tstrcpy(path1 + len, \"+\");\n+\n+\t/* read existing rev-cache */\n+\tif (!force)\n+\t\tread_rev_cache(path0, NULL, 0);\n+\tsafe_create_leading_directories(path0);\n+\n+\tfor_each_ref(record_rev_cache_ref);\n+\n+\t/* update the rev-cache database */\n+\twrite_rev_cache(path1, force ? \"/dev/null\" : path0);\n+\trename(path1, path0);\n+\tfree(path1);\n+\tfree(path0);\n+\treturn 0;\n+}\n+\n+/* public */\n+int update_server_info(int force)\n+{\n+\t/* We would add more dumb-server support files later,\n+\t * including index of available pack files and their\n+\t * intended audiences.\n+\t */\n+\tint errs = 0;\n+\n+\terrs = errs | update_info_refs(force);\n+\terrs = errs | update_info_packs(force);\n+\terrs = errs | update_info_revs(force);\n+\n+\treturn errs;\n+}\ndiff --git a/show-rev-cache.c b/show-rev-cache.c\nnew file mode 100644\n--- /dev/null\n+++ b/show-rev-cache.c\n@@ -0,0 +1,18 @@\n+#include \"cache.h\"\n+#include \"rev-cache.h\"\n+\n+static char *show_rev_cache_usage =\n+\"git-show-rev-cache <rev-cache-file>\";\n+\n+int main(int ac, char **av)\n+{\n+\twhile (1 < ac && av[0][1] == '-') {\n+\t\t/* do flags here */\n+\t\tbreak;\n+\t\tac--; av++;\n+\t}\n+\tif (ac != 2)\n+\t\tusage(show_rev_cache_usage);\n+\n+\treturn read_rev_cache(av[1], stdout, 1);\n+}\ndiff --git a/update-server-info.c b/update-server-info.c\nnew file mode 100644\n--- /dev/null\n+++ b/update-server-info.c\n@@ -0,0 +1,23 @@\n+#include \"cache.h\"\n+\n+static const char update_server_info_usage[] =\n+\"git-update-server-info [--force]\";\n+\n+int main(int ac, char **av)\n+{\n+\tint i;\n+\tint force = 0;\n+\tfor (i = 1; i < ac; i++) {\n+\t\tif (av[i][0] == '-') {\n+\t\t\tif (!strcmp(\"--force\", av[i]) ||\n+\t\t\t    !strcmp(\"-f\", av[i]))\n+\t\t\t\tforce = 1;\n+\t\t\telse\n+\t\t\t\tusage(update_server_info_usage);\n+\t\t}\n+\t}\n+\tif (i != ac)\n+\t\tusage(update_server_info_usage);\n+\n+\treturn !!update_server_info(force);\n+}\n"}]}