{"thread":{"id":"29223","subject":"[PATCH 4/6] Add digests to commit objects.","startedAt":"2011-12-21T07:12:10Z","lastAt":"2011-12-21T07:12:10Z","messageCount":1,"participants":["Bill Zaumen"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"181565","messageId":"1324451530.1684.22.camel@yos","threadId":"29223","inReplyTo":null,"subject":"[PATCH 4/6] Add digests to commit objects.","fromName":"Bill Zaumen","fromEmail":"bill.zaumen+git@gmail.com","sentAt":"2011-12-21T07:12:10Z","receivedAt":"2011-12-21T07:12:10Z","isPatch":true,"sender":{"key":"bill.zaumen+git@gmail.com","avatar":null},"body":"When COMMIT_DIGEST is defined in the Makefile, a new\nheader is added to commits. The header is named 'digest'\nand is a digest of the digests associated with the\ncommit's tree (computed recursively) and parents, and\nof the other fields. This digest is included in the\nSHA-1 hash computation and the commit's digest.\n\nA function named verify_commit allows the digest to\nbe recomputed and checked.\n\nSigned-off-by: Bill Zaumen <bill.zaumen+git@gmail.com>\n---\n commit.c |  436 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n commit.h |   11 ++\n 2 files changed, 443 insertions(+), 4 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex b781274..ac0d492 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -6,11 +6,395 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"notes.h\"\n+#ifdef PACKDB\n+#include \"packdb.h\"\n+#endif\n \n int save_commit_buffer = 1;\n \n const char *commit_type = \"commit\";\n \n+struct commit_mds_context {\n+\tunsigned long missing;\n+\tmdigest_context_t context;\n+#ifdef PACKDB\n+\tint packdb_opened;\n+#endif /* PACKDB */\n+};\n+\n+#if defined(COMMIT_DIGEST)||defined(PACKDB_TEST)||defined(COMMIT_DIGEST_TEST)\n+static int get_objects_mds_f(const unsigned char *sha1,\n+\t\t\t  const char *basebuf, int baselen,\n+\t\t\t  const char *path, unsigned int mode, int stage,\n+\t\t\t  void *context)\n+{\n+\tstruct commit_mds_context *c = (struct commit_mds_context *)context;\n+\tmdigest_t digest;\n+\tunsigned long size;\n+\tint type;\n+\n+\tif (S_ISGITLINK(mode)) {\n+\t\t/*\n+\t\t * Submodule entry - SHA-1 of a commit in the submodule\n+\t\t * with no entry in our repository.\n+\t\t */\n+\t\tmdigest_Update(&c->context, sha1, 20);\n+\t\treturn -1;\n+\t}\n+\tif (!has_sha1_file(sha1)) {\n+\t\tc->missing++;\n+\t\treturn -1;\n+\t}\n+\tif (has_sha1_file_digest(sha1, &digest)) {\n+\t\tint wcode = get_mdigest_wcode(&digest);\n+\t\tunsigned char ucwcode = (unsigned char) (wcode & 0xff);\n+\t\tmdigest_Update(&c->context, &ucwcode, 1);\n+\t\tmdigest_Update(&c->context, get_mdigest_buffer(&digest),\n+\t\t\t       get_mdigest_len(&digest));\n+#if defined(PACKDB) && defined (PACKDB_TEST)\n+\t\t{\n+\t\t  static int firsttime = 1;\n+\t\t  mdigest_t xdigest;\n+\t\t  if (!c->packdb_opened) {\n+\t\t    packdb_open();\n+\t\t    c->packdb_opened = 1;\n+\t\t    firsttime = 0;\n+\t\t  }\n+\t\t  if (firsttime) {\n+\t\t    if (!packdb_lookup(sha1, &xdigest)) {\n+\t\t      packdb_process(sha1, &digest);\n+\t\t      packdb_lookup(sha1, &xdigest);\n+\t\t      if (mdigest_tst(&digest, &xdigest)) {\n+\t\t\tdie(\"digest for %s failed with packdb\\n\",\n+\t\t\t    sha1_to_hex(sha1));\n+\t\t      }\n+\t\t    }\n+\t\t  }\n+\t\t}\n+#endif\n+\t} else {\n+\t\tenum object_type xtype;\n+\t\tunsigned long xsize;\n+\t\tmdigest_t xdigest;\n+\t\tunsigned char xsha1[20];\n+\t\tint wcode;\n+\t\tunsigned char ucwcode;\n+#ifdef PACKDB\n+\t\tif (!c->packdb_opened) {\n+\t\t\tpackdb_open();\n+\t\t\tc->packdb_opened = 1;\n+\t\t}\n+\t\tif (!packdb_lookup(sha1, &xdigest)) {\n+#endif /* PACKDB */\n+\t\t\tvoid *data = read_sha1_file(sha1, &xtype, &xsize);\n+\t\t\thash_sha1_file_extended(data, xsize,\n+\t\t\t\t\t\ttypename(xtype),\n+\t\t\t\t\t\txsha1,\n+\t\t\t\t\t\t&xdigest);\n+\t\t\tfree(data);\n+#ifdef PACKDB\n+\t\t\tpackdb_process(sha1, &xdigest);\n+\t\t  }\n+#endif /* PACKDB */\n+\t\twcode = get_mdigest_wcode(&xdigest);\n+\t\tucwcode = (unsigned char) (wcode & 0xff);\n+\t\tmdigest_Update(&c->context, &ucwcode, 1);\n+\t\tmdigest_Update(&c->context, get_mdigest_buffer(&xdigest),\n+\t\t\t       get_mdigest_len(&xdigest));\n+\t}\n+\ttype = sha1_object_info(sha1, &size);\n+\tswitch(type) {\n+\tcase OBJ_TREE:\n+\t  return (S_ISDIR(mode))? READ_TREE_RECURSIVE: 0;\n+\tcase OBJ_BLOB:\n+\t\treturn 0;\n+\tdefault:\n+\t\tif (type <= OBJ_NONE) {\n+\t\t  c->missing++;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+}\n+\n+/*\n+ * Works with a tree or a commit sha1 - recursively traverses the trees\n+ * and computes the CRC of each blob's CRC. With a commit sha1, the\n+ * caller must provide the 'parents' list - we sometimes call this\n+ * function with the commit-object's tree before the sha1 value is\n+ * computed.\n+ */\n+static int get_objects_mds(const unsigned char *sha1,\n+\t\t\t   struct commit_list *parents,\n+\t\t\t   const char *author, size_t author_len,\n+\t\t\t   const char *committer, size_t committer_len,\n+\t\t\t   const char *encoding, size_t encoding_len,\n+\t\t\t   struct commit_extra_header *extra,\n+\t\t\t   const char *msg, size_t msg_len,\n+\t\t\t   mdigest_t *digestp)\n+{\n+\tstruct commit_mds_context context;\n+\tstruct tree *tree = parse_tree_indirect(sha1);\n+\tstruct pathspec ps;\n+\tmdigest_t xdigest;\n+\tstruct commit_extra_header *extra_head = extra;\n+\tmdigest_Init(&context.context, MDIGEST_DEFAULT);\n+\tcontext.missing = 0;\n+#ifdef PACKDB\n+\tcontext.packdb_opened = 0;\n+#endif /* PACKDB */\n+\n+\tif (tree == NULL) {\n+\t\treturn -1;\n+\t} else {\n+\t\tinit_pathspec(&ps, NULL);\n+\t\tparse_tree(tree);\n+\t\tif (has_sha1_file_digest(tree->object.sha1, &xdigest)) {\n+\t\t\tint wcode = get_mdigest_wcode(&xdigest);\n+\t\t\tunsigned char ucwcode =\n+\t\t\t\t(unsigned char) (wcode & 0xff);\n+\t\t\tmdigest_Update(&context.context, &ucwcode, 1);\n+\t\t\tmdigest_Update(&context.context,\n+\t\t\t\t       get_mdigest_buffer(&xdigest),\n+\t\t\t\t       get_mdigest_len(&xdigest));\n+\t\t} else {\n+\t\t\tenum object_type xtype;\n+\t\t\tunsigned long xsize;\n+\t\t\tmdigest_t xdigest;\n+\t\t\tunsigned char xsha1[20];\n+\t\t\tint wcode;\n+\t\t\tunsigned char ucwcode;\n+#ifdef PACKDB\n+\t\t\tif (!context.packdb_opened) {\n+\t\t\t\tpackdb_open();\n+\t\t\t\tcontext.packdb_opened = 1;\n+\t\t\t}\n+\t\t\tif (!packdb_lookup(sha1, &xdigest)) {\n+#endif /* PACKDB */\n+\n+\t\t\t\tvoid *data = read_sha1_file(sha1, &xtype,\n+\t\t\t\t\t\t\t    &xsize);\n+\t\t\t\thash_sha1_file_extended(data, xsize,\n+\t\t\t\t\t\t\ttypename(xtype),\n+\t\t\t\t\t\t\txsha1,\n+\t\t\t\t\t\t\t&xdigest);\n+\t\t\t\tfree(data);\n+#ifdef PACKDB\n+\t\t\t\tpackdb_process(sha1, &xdigest);\n+\t\t\t}\n+#endif /* PACKDB */\n+\t\t\twcode = get_mdigest_wcode(&xdigest);\n+\t\t\tucwcode = (unsigned char)(wcode & 0xff);\n+\t\t\tmdigest_Update(&context.context, &ucwcode, 1);\n+\t\t\tmdigest_Update(&context.context,\n+\t\t\t\t       get_mdigest_buffer(&xdigest),\n+\t\t\t\t       get_mdigest_len(&xdigest));\n+\t\t}\n+\t\tread_tree_recursive(tree, \"\", 0, 0, &ps, get_objects_mds_f,\n+\t\t\t\t    &context);\n+\t\twhile (parents) {\n+\t\t\t/*\n+\t\t\t * Include the message digests of the parent commits.\n+\t\t\t */\n+\t\t\tstruct commit_list *next = parents->next;\n+\t\t\tif (!has_sha1_file(parents->item->object.sha1)) {\n+\t\t\t\treturn -1;\n+\t\t\t} else if (has_sha1_file_digest\n+\t\t\t\t   (parents->item->object.sha1, &xdigest)) {\n+\t\t\t\tint wcode = get_mdigest_wcode(&xdigest);\n+\t\t\t\tunsigned char ucwcode =\n+\t\t\t\t\t(unsigned char) (wcode & 0xff);\n+\t\t\t\tmdigest_Update(&context.context, &ucwcode, 1);\n+\t\t\t\tmdigest_Update(&context.context,\n+\t\t\t\t\t       get_mdigest_buffer(&xdigest),\n+\t\t\t\t\t       get_mdigest_len(&xdigest));\n+\t\t\t} else {\n+\t\t\t\tenum object_type xtype;\n+\t\t\t\tunsigned long xsize;\n+\t\t\t\tmdigest_t xdigest;\n+\t\t\t\tunsigned char xsha1[20];\n+\t\t\t\tint wcode;\n+\t\t\t\tunsigned char ucwcode;\n+#ifdef PACKDB\n+\t\t\t\tif (!context.packdb_opened) {\n+\t\t\t\t\tpackdb_open();\n+\t\t\t\t\tcontext.packdb_opened = 1;\n+\t\t\t\t}\n+\t\t\t\tif (!packdb_lookup(sha1, &xdigest)) {\n+#endif /* PACKDB */\n+\n+\t\t\t\t\tvoid *data = read_sha1_file(sha1,\n+\t\t\t\t\t\t\t\t    &xtype,\n+\t\t\t\t\t\t\t\t    &xsize);\n+\t\t\t\t\thash_sha1_file_extended(data, xsize,\n+\t\t\t\t\t\t\t\ttypename(xtype),\n+\t\t\t\t\t\t\t\txsha1,\n+\t\t\t\t\t\t\t\t&xdigest);\n+\t\t\t\t\tfree(data);\n+#ifdef PACKDB\n+\t\t\t\t\tpackdb_process(sha1, &xdigest);\n+\t\t\t\t}\n+#endif /* PACKDB */\n+\t\t\t\twcode = get_mdigest_wcode(&xdigest);\n+\t\t\t\tucwcode = (unsigned char)(wcode & 0xff);\n+\t\t\t\tmdigest_Update(&context.context, &ucwcode, 1);\n+\t\t\t\tmdigest_Update(&context.context,\n+\t\t\t\t\t       get_mdigest_buffer(&xdigest),\n+\t\t\t\t\t       get_mdigest_len(&xdigest));\n+\t\t\t}\n+\t\t\tparents = next;\n+\t\t}\n+#ifdef PACKDB\n+\t\tif (context.packdb_opened) packdb_close();\n+#endif /* PACKDB */\n+\t\tif (msg && author && committer && digestp) {\n+\t\t\tmdigest_Update(&context.context, author, author_len);\n+\t\t\tmdigest_Update(&context.context, committer,\n+\t\t\t\t       committer_len);\n+\t\t\tif (encoding) mdigest_Update(&context.context, encoding,\n+\t\t\t\t\t\t     encoding_len);\n+\t\t\twhile (extra) {\n+\t\t\t  mdigest_Update(&context.context, extra->key,\n+\t\t\t\t\t strlen(extra->key));\n+\t\t\t  mdigest_Update(&context.context, \" \", 1);\n+\t\t\t  mdigest_Update(&context.context, extra->value,\n+\t\t\t\t\t extra->len);\n+\t\t\t  mdigest_Update(&context.context, \"\\n\", 1);\n+\t\t\t  extra = extra->next;\n+\t\t\t}\n+\t\t\tfree_commit_extra_headers(extra_head);\n+\t\t\tmdigest_Update(&context.context, msg, msg_len);\n+\t\t}\n+\t\tif (digestp) mdigest_Final(digestp, &context.context);\n+\t\treturn ((context.missing == 0)? 0: -1);\n+\t}\n+}\n+#endif /* defined(COMMIT_DIGEST)||defined(PACKDB_TEST)||defined(COMMIT_DIGEST_TEST) */\n+\n+int verify_commit(struct commit *commit) {\n+#ifdef COMMIT_DIGEST\n+\tif (save_commit_buffer) {\n+\t\tmdigest_t edigest;\n+\t\tmdigest_t digest;\n+\t\tconst char *author = NULL;\n+\t\tsize_t author_len = 0;\n+\t\tconst char *committer = NULL;\n+\t\tsize_t committer_len = 0;\n+\t\tconst char *encoding = NULL;\n+\t\tsize_t encoding_len = 0;\n+\t\tconst char *msg = NULL;\n+\t\tsize_t msg_len = 0;\n+\t\tconst char *mdstring = NULL;\n+\t\tsize_t mdstring_len = 0;\n+\t\tconst char *bufptr;\n+\t\tconst char *tail;\n+\t\tstruct commit_extra_header *extra = NULL;\n+\n+\t\tparse_commit(commit);\n+\t\textra = read_commit_extra_header_lines(commit->buffer,\n+\t\t\t\t\t\t       commit->buffer_len);\n+\t\tbufptr = commit->buffer;\n+\t\ttail = bufptr + commit->buffer_len;\n+\t\twhile (*bufptr != '\\n' && bufptr < tail) {\n+\t\t\tif (*bufptr == 'a') {\n+\t\t\t\tif ((bufptr + 7) < tail) {\n+\t\t\t\t\tif (memcmp(bufptr, \"author \", 7) == 0) {\n+\t\t\t\t\t\tbufptr += 7;\n+\t\t\t\t\t\tauthor = bufptr;\n+\t\t\t\t\t\twhile (*bufptr != '\\n' &&\n+\t\t\t\t\t\t       bufptr < tail) {\n+\t\t\t\t\t\t\tbufptr++;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\tauthor_len = bufptr - author;\n+\t\t\t\t\t} else while (*bufptr != '\\n' &&\n+\t\t\t\t\t\t      bufptr < tail) bufptr++;\n+\t\t\t\t\tif (bufptr < tail) bufptr++;\n+\t\t\t\t}\n+\t\t\t} else if (*bufptr == 'c') {\n+\t\t\t\tif ((bufptr + 10) < tail) {\n+\t\t\t\t\tif (memcmp(bufptr,\n+\t\t\t\t\t\t   \"committer \", 10) == 0) {\n+\t\t\t\t\t\tbufptr += 10;\n+\t\t\t\t\t\tcommitter = bufptr;\n+\t\t\t\t\t\twhile (*bufptr != '\\n' &&\n+\t\t\t\t\t\t       bufptr < tail) {\n+\t\t\t\t\t\t\tbufptr++;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\tcommitter_len =\n+\t\t\t\t\t\t\tbufptr - committer;\n+\t\t\t\t\t} else  while (*bufptr != '\\n' &&\n+\t\t\t\t\t\t      bufptr < tail) bufptr++;\n+\t\t\t\t\tif (bufptr < tail) bufptr++;\n+\t\t\t\t}\n+\t\t\t} else if(*bufptr == 'e') {\n+\t\t\t\tif ((bufptr + 9) < tail) {\n+\t\t\t\t\tif (memcmp(bufptr,\n+\t\t\t\t\t\t   \"encoding \", 9) == 0) {\n+\t\t\t\t\t\tbufptr += 9;\n+\t\t\t\t\t\tencoding = bufptr;\n+\t\t\t\t\t\twhile (*bufptr != '\\n' &&\n+\t\t\t\t\t\t       bufptr < tail) {\n+\t\t\t\t\t\t\tbufptr++;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\tencoding_len =\n+\t\t\t\t\t\t\tbufptr - encoding;\n+\t\t\t\t\t} else  while (*bufptr != '\\n' &&\n+\t\t\t\t\t\t      bufptr < tail) bufptr++;\n+\t\t\t\t\tif (bufptr < tail) bufptr++;\n+\t\t\t\t}\n+\t\t\t} else if (*bufptr == 'd') {\n+\t\t\t\tif ((bufptr + 7) < tail) {\n+\t\t\t\t\tif (memcmp(bufptr, \"digest \", 7) == 0) {\n+\t\t\t\t\t\tbufptr += 7;\n+\t\t\t\t\t\tmdstring = bufptr;\n+\t\t\t\t\t\twhile (*bufptr != '\\n' &&\n+\t\t\t\t\t\t       bufptr < tail) {\n+\t\t\t\t\t\t\tbufptr++;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\tmdstring_len =\n+\t\t\t\t\t\t\tbufptr - mdstring;\n+\t\t\t\t\t} else  while (*bufptr != '\\n' &&\n+\t\t\t\t\t\t      bufptr < tail) bufptr++;\n+\t\t\t\t\tif (bufptr < tail) bufptr++;\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\twhile (*bufptr != '\\n' && bufptr < tail)\n+\t\t\t\t\tbufptr++;\n+\t\t\t\tif (bufptr < tail) bufptr++;\n+\t\t\t}\n+\t\t}\n+\t\tif (*bufptr == '\\n' && bufptr < tail) {\n+\t\t\tbufptr++;\n+\t\t\tmsg = bufptr;\n+\t\t\tmsg_len = tail - bufptr;\n+\t\t}\n+\t\tif (mdstring &&\n+\t\t    get_mdigest_from_external_hex(&edigest, mdstring) < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (author && committer && msg) {\n+\t\t\tif (mdstring == NULL) return 0;\n+\t\t\tif (get_objects_mds(commit->object.sha1,\n+\t\t\t\t\t    commit->parents,\n+\t\t\t\t\t    author, author_len,\n+\t\t\t\t\t    committer, committer_len,\n+\t\t\t\t\t    encoding, encoding_len,\n+\t\t\t\t\t    extra,\n+\t\t\t\t\t    msg, msg_len,\n+\t\t\t\t\t    &digest)) {\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\treturn mdigest_tst(&edigest, &digest);\n+\t\t} else {\n+\t\t\treturn -1;\n+\t\t}\n+\t} else {\n+\t  return 0;\n+\t}\n+#else /* COMMIT_DIGEST */\n+\treturn 0;\n+#endif /* COMMIT_DIGEST */\n+}\n+\n static struct commit *check_commit(struct object *obj,\n \t\t\t\t   const unsigned char *sha1,\n \t\t\t\t   int quiet)\n@@ -325,6 +709,9 @@ int parse_commit(struct commit *item)\n \tret = parse_commit_buffer(item, buffer, size);\n \tif (save_commit_buffer && !ret) {\n \t\titem->buffer = buffer;\n+#ifdef COMMIT_DIGEST\n+\t\titem->buffer_len = (size_t) size;\n+#endif\n \t\treturn 0;\n \t}\n \tfree(buffer);\n@@ -916,6 +1303,9 @@ static inline int standard_header_field(const char *field, size_t len)\n {\n \treturn ((len == 4 && !memcmp(field, \"tree \", 5)) ||\n \t\t(len == 6 && !memcmp(field, \"parent \", 7)) ||\n+#ifdef COMMIT_DIGEST\n+\t\t(len == 6 && !memcmp(field, \"digest \", 7)) ||\n+#endif\n \t\t(len == 6 && !memcmp(field, \"author \", 7)) ||\n \t\t(len == 9 && !memcmp(field, \"committer \", 10)) ||\n \t\t(len == 8 && !memcmp(field, \"encoding \", 9)));\n@@ -998,12 +1388,43 @@ int commit_tree_extended(const char *msg, unsigned char *tree,\n \tint result;\n \tint encoding_is_utf8;\n \tstruct strbuf buffer;\n+\tstatic char committer[1000];\n+\tconst char *encoding = NULL;\n+#if defined(COMMIT_DIGEST) || defined (COMMIT_DIGEST_TEST)\n+\tmdigest_t digest;\n+#endif /* defined(COMMIT_DIGEST) || defined(PACKDB_TEST) */\n+\t/*\n+\t * git_committer_info returns a static buffer of size 1000, so\n+\t * we have to copy it - assume git_committer_info does necessary\n+\t * buffer-overflow tests.\n+\t */\n+\tstrcpy (committer, git_committer_info(IDENT_ERROR_ON_NO_NAME));\n \n \tassert_sha1_type(tree, OBJ_TREE);\n \n \t/* Not having i18n.commitencoding is the same as having utf-8 */\n \tencoding_is_utf8 = is_encoding_utf8(git_commit_encoding);\n+\tif (!encoding_is_utf8)\n+\t\tencoding = git_commit_encoding;\n \n+\t/* Person/date information setup*/\n+\tif (!author)\n+\t\tauthor = git_author_info(IDENT_ERROR_ON_NO_NAME);\n+#if defined(COMMIT_DIGEST) || defined(PACKDB_TEST) || defined(COMMIT_DIGEST_TEST)\n+\t/*\n+\t * Have all the pieces so compute the message digest. We do it here\n+\t * because the list 'parents' will be destroyed by the following loop.\n+\t */\n+\tif (get_objects_mds(tree, parents,\n+\t\t\t    author, (author? strlen(author): 0),\n+\t\t\t    committer, strlen(committer),\n+\t\t\t    encoding, (encoding? strlen(encoding): 0),\n+\t\t\t    extra,\n+\t\t\t    msg, (msg? strlen(msg): 0),\n+\t\t\t    &digest)) {\n+\t\tdie(\"could not compute message digest for commit\");\n+\t}\n+#endif /* defined(COMMIT_DIGEST) || defined(PACKDB_TEST)  || defined(COMMIT_DIGEST_TEST)*/\n \tstrbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */\n \tstrbuf_addf(&buffer, \"tree %s\\n\", sha1_to_hex(tree));\n \n@@ -1023,17 +1444,19 @@ int commit_tree_extended(const char *msg, unsigned char *tree,\n \t}\n \n \t/* Person/date information */\n-\tif (!author)\n-\t\tauthor = git_author_info(IDENT_ERROR_ON_NO_NAME);\n \tstrbuf_addf(&buffer, \"author %s\\n\", author);\n-\tstrbuf_addf(&buffer, \"committer %s\\n\", git_committer_info(IDENT_ERROR_ON_NO_NAME));\n+\tstrbuf_addf(&buffer, \"committer %s\\n\", committer);\n+\n \tif (!encoding_is_utf8)\n \t\tstrbuf_addf(&buffer, \"encoding %s\\n\", git_commit_encoding);\n-\n \twhile (extra) {\n \t\tadd_extra_header(&buffer, extra);\n \t\textra = extra->next;\n \t}\n+#ifdef COMMIT_DIGEST\n+\tstrbuf_addf(&buffer, \"digest %s\\n\", mdigest_to_external_hex(&digest));\n+#endif /* COMMIT_DIGEST */\n+\n \tstrbuf_addch(&buffer, '\\n');\n \n \t/* And add the comment */\n@@ -1045,6 +1468,11 @@ int commit_tree_extended(const char *msg, unsigned char *tree,\n \n \tresult = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);\n \tstrbuf_release(&buffer);\n+#if defined(COMMIT_DIGEST) && defined (COMMIT_DIGEST_TEST)\n+\tif (verify_commit(lookup_commit(ret))) {\n+\t    die(\"commit verification failed for %s\\n\", sha1_to_hex(ret));\n+\t  }\n+#endif\n \treturn result;\n }\n \ndiff --git a/commit.h b/commit.h\nindex 3745f12..7a91519 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -5,6 +5,7 @@\n #include \"tree.h\"\n #include \"strbuf.h\"\n #include \"decorate.h\"\n+#include \"mdigest.h\"\n \n struct commit_list {\n \tstruct commit *item;\n@@ -19,6 +20,9 @@ struct commit {\n \tstruct commit_list *parents;\n \tstruct tree *tree;\n \tchar *buffer;\n+#ifdef COMMIT_DIGEST\n+\tsize_t buffer_len;\n+#endif\n };\n \n extern int save_commit_buffer;\n@@ -218,4 +222,11 @@ struct merge_remote_desc {\n  */\n struct commit *get_merge_parent(const char *name);\n \n+/*\n+ * Returns 0 if OK or if save_commit_buffer == 0 or if COMMIT_DIGEST was\n+ * not defined during compilation; non-zero otherwise.  If a commit does\n+ * not have a digest field, 0 is returned.\n+ */\n+extern int verify_commit(struct commit *commit);\n+\n #endif /* COMMIT_H */\n-- \n1.7.1\n"}]}