commit: introduce a signing callback

Callers can now introduce a commit signing callback instead of needing
to call a specialized commit buffer creation function.
This commit is contained in:
Edward Thomson
2026-04-28 09:29:56 +01:00
parent a7786fcd7e
commit 096cc6f76d
3 changed files with 303 additions and 164 deletions

View File

@@ -294,7 +294,8 @@ GIT_EXTERN(int) git_commit_nth_gen_ancestor(
* @return 0 on succeess, GIT_ENOTFOUND if the field does not exist,
* or an error code
*/
GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit, const char *field);
GIT_EXTERN(int) git_commit_header_field(
git_buf *out, const git_commit *commit, const char *field);
/**
* Extract the signature from a commit
@@ -369,6 +370,42 @@ typedef struct {
const char *value;
} git_commit_header;
/** An accessor object for setting commit signature data. */
typedef struct git_commitbuilder git_commitbuilder;
/**
* Add a header on a commit being created.
*
* @param builder in-progress commitbuilder object
* @param field the header field to add
* @param value the header value to add
* @return 0 on success or an error code
*/
GIT_EXTERN(int) git_commitbuilder_add_header(
git_commitbuilder *builder,
const char *field,
const char *value);
/**
* Commit signing callback: used when a function is going to write
* a commit (for example, in `git_commit_create_ext`) to allow callers
* to sign the commit.
*
* @param builder commit builder object to populate with the signature
* @param repo the repository being committed in
* @param commit_content the raw contents of the commit (to be signed)
* @param payload the caller-specified callback payload
* @return 0 if this callback has created a signature and populated the
* field and signature buffers, `GIT_PASSTHROUGH` if the callback
* does not want to sign the commit, any other value to stop and
* return a failure
*/
typedef int GIT_CALLBACK(git_commit_signature_cb)(
git_commitbuilder *builder,
git_repository *repo,
const char *commit_content,
void *payload);
typedef struct {
unsigned int version;
@@ -392,6 +429,12 @@ typedef struct {
*/
const git_commit_header *extra_headers;
size_t extra_headers_len; /**< Number of extra headers */
/** Signing callback; leave NULL for no commit signing. */
git_commit_signature_cb sign;
/** Callback payload (optional) */
void *payload;
} git_commit_create_ext_options;
/** Current version for the `git_commit_create_ext_options` structure */

View File

@@ -161,6 +161,10 @@ on_error:
return error;
}
struct git_commitbuilder {
git_str *contents;
};
static int git_commit__create_internal(
git_oid *id,
git_repository *repo,
@@ -173,12 +177,12 @@ static int git_commit__create_internal(
const git_commit_create_ext_options *opts,
bool validate)
{
int error;
git_odb *odb;
git_reference *ref = NULL;
git_str buf = GIT_STR_INIT;
const git_oid *current_id = NULL;
git_array_oid_t parents = GIT_ARRAY_INIT;
int error = 0;
if (opts && opts->update_ref) {
error = git_reference_lookup_resolved(&ref,
@@ -187,27 +191,31 @@ static int git_commit__create_internal(
if (error < 0 && error != GIT_ENOTFOUND)
return error;
}
git_error_clear();
if (ref)
current_id = git_reference_target(ref);
if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0)
if ((error = validate_tree_and_parents(&parents, repo, tree,
parent_cb, parent_payload, current_id, validate)) < 0)
goto cleanup;
error = git_commit__create_buffer_internal(&buf,
author, committer, message, tree, &parents, opts);
if (error < 0)
if ((error = git_commit__create_buffer_internal(&buf,
author, committer, message, tree, &parents, opts)) < 0)
goto cleanup;
if (git_repository_odb__weakptr(&odb, repo) < 0)
goto cleanup;
if (opts && opts->sign) {
git_commitbuilder builder = { &buf };
if (git_odb__freshen(odb, tree) < 0)
goto cleanup;
if ((error = opts->sign(&builder, repo, buf.ptr,
opts->payload)) < 0)
goto cleanup;
}
if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT) < 0)
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
(error = git_odb__freshen(odb, tree)) < 0 ||
(error = git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT)) < 0)
goto cleanup;
@@ -367,6 +375,57 @@ int git_commit_create_ext(
commit_parent_from_array, &data, opts, false);
}
static int append_header(
git_str *out,
const char *raw_content,
const char *name,
const char *value)
{
const char *header_end;
/* Identifying the end of the commit header area */
header_end = strstr(raw_content, "\n\n");
if (!header_end) {
git_error_set(GIT_ERROR_INVALID, "malformed commit contents");
return -1;
}
/* The header ends after the first LF */
header_end++;
git_str_put(out, raw_content, header_end - raw_content);
if (format_header_field(out, name, value) < 0)
return -1;
git_str_puts(out, header_end);
if (git_str_oom(out))
return -1;
return 0;
}
int git_commitbuilder_add_header(
git_commitbuilder *builder,
const char *field,
const char *value)
{
git_str signed_data = GIT_BUF_INIT;
int error;
if ((error = append_header(&signed_data, builder->contents->ptr,
field, value)) < 0)
goto done;
git_str_swap(builder->contents, &signed_data);
done:
git_str_dispose(&signed_data);
return error;
}
int git_commit_create(
git_oid *id,
git_repository *repo,
@@ -1079,20 +1138,29 @@ int git_commit_create_with_signature(
const char *signature_field)
{
git_odb *odb;
int error = 0;
const char *field;
const char *header_end;
git_str commit = GIT_STR_INIT;
git_str signed_content = GIT_STR_INIT;
git_commit *parsed;
git_array_oid_t parents = GIT_ARRAY_INIT;
git_commit__parse_options parse_opts = {0};
size_t commit_content_len;
int error = 0;
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(repo);
GIT_ASSERT_ARG(commit_content);
if (!signature_field)
signature_field = "gpgsig";
commit_content_len = strlen(commit_content);;
parse_opts.oid_type = repo->oid_type;
/* The first step is to verify that all the tree and parents exist */
parsed = git__calloc(1, sizeof(git_commit));
GIT_ERROR_CHECK_ALLOC(parsed);
if (commit_parse(parsed, commit_content, strlen(commit_content), &parse_opts) < 0) {
if (commit_parse(parsed, commit_content, commit_content_len, &parse_opts) < 0) {
error = -1;
goto cleanup;
}
@@ -1102,39 +1170,25 @@ int git_commit_create_with_signature(
git_array_clear(parents);
/* Then we start appending by identifying the end of the commit header */
header_end = strstr(commit_content, "\n\n");
if (!header_end) {
git_error_set(GIT_ERROR_INVALID, "malformed commit contents");
error = -1;
goto cleanup;
}
/* The header ends after the first LF */
header_end++;
git_str_put(&commit, commit_content, header_end - commit_content);
if (signature != NULL) {
field = signature_field ? signature_field : "gpgsig";
if ((error = format_header_field(&commit, field, signature)) < 0)
if (signature) {
if ((error = append_header(&signed_content,
commit_content, signature_field, signature)) < 0)
goto cleanup;
commit_content = signed_content.ptr;
commit_content_len = signed_content.size;
}
git_str_puts(&commit, header_end);
if (git_str_oom(&commit))
return -1;
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
goto cleanup;
if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0)
if ((error = git_odb_write(out, odb, commit_content,
commit_content_len, GIT_OBJECT_COMMIT)) < 0)
goto cleanup;
cleanup:
git_commit__free(parsed);
git_str_dispose(&commit);
git_str_dispose(&signed_content);
return error;
}

View File

@@ -299,130 +299,6 @@ void test_commit_write__can_validate_objects(void)
cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id));
}
void test_commit_write__attach_signature_checks_objects(void)
{
const char *sig = "magic word: pretty please";
const char *badtree = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\
parent 34734e478d6cf50c27c9d69026d93974d052c454\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
\n\
a simple commit which does not work\n";
const char *badparent = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 34734e478d6cf50c27c9d69026d93974d052c454\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
\n\
a simple commit which does not work\n";
git_oid id;
cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badtree, sig, "magicsig"));
cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badparent, sig, "magicsig"));
}
void test_commit_write__attach_singleline_signature(void)
{
const char *sig = "magic word: pretty please";
const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
\n\
a simple commit which works\n";
const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
magicsig magic word: pretty please\n\
\n\
a simple commit which works\n";
git_oid id;
git_odb *odb;
git_odb_object *obj;
cl_git_pass(git_commit_create_with_signature(&id, g_repo, data, sig, "magicsig"));
cl_git_pass(git_repository_odb(&odb, g_repo));
cl_git_pass(git_odb_read(&obj, odb, &id));
cl_assert_equal_s(complete, git_odb_object_data(obj));
git_odb_object_free(obj);
git_odb_free(odb);
}
void test_commit_write__attach_multiline_signature(void)
{
const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\
Version: GnuPG v1.4.12 (Darwin)\n\
\n\
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
cpxtDQQMGYFpXK/71stq\n\
=ozeK\n\
-----END PGP SIGNATURE-----";
const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
\n\
a simple commit which works\n";
const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
gpgsig -----BEGIN PGP SIGNATURE-----\n\
Version: GnuPG v1.4.12 (Darwin)\n\
\n\
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
cpxtDQQMGYFpXK/71stq\n\
=ozeK\n\
-----END PGP SIGNATURE-----\n\
\n\
a simple commit which works\n";
git_oid one, two;
git_odb *odb;
git_odb_object *obj;
cl_git_pass(git_commit_create_with_signature(&one, g_repo, data, gpgsig, "gpgsig"));
cl_git_pass(git_commit_create_with_signature(&two, g_repo, data, gpgsig, NULL));
cl_assert(!git_oid_cmp(&one, &two));
cl_git_pass(git_repository_odb(&odb, g_repo));
cl_git_pass(git_odb_read(&obj, odb, &one));
cl_assert_equal_s(complete, git_odb_object_data(obj));
git_odb_object_free(obj);
git_odb_free(odb);
}
void test_commit_write__can_add_extra_headers(void)
{
git_odb *odb;
@@ -479,3 +355,169 @@ This is a fun new commit.";
git_signature_free(author);
git_odb_free(odb);
}
static const char *unsigned_data =
"tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
\n\
a simple commit which works\n";
static const char *gpgsig =
"-----BEGIN PGP SIGNATURE-----\n\
Version: GnuPG v1.4.12 (Darwin)\n\
\n\
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
cpxtDQQMGYFpXK/71stq\n\
=ozeK\n\
-----END PGP SIGNATURE-----";
static const char *signed_data =
"tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
gpgsig -----BEGIN PGP SIGNATURE-----\n\
Version: GnuPG v1.4.12 (Darwin)\n\
\n\
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
cpxtDQQMGYFpXK/71stq\n\
=ozeK\n\
-----END PGP SIGNATURE-----\n\
\n\
a simple commit which works\n";
void test_commit_write__attach_signature_checks_objects(void)
{
const char *sig = "magic word: pretty please";
const char *badtree = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\
parent 34734e478d6cf50c27c9d69026d93974d052c454\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
\n\
a simple commit which does not work\n";
const char *badparent = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 34734e478d6cf50c27c9d69026d93974d052c454\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
\n\
a simple commit which does not work\n";
git_oid id;
cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badtree, sig, "magicsig"));
cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badparent, sig, "magicsig"));
}
void test_commit_write__attach_singleline_signature(void)
{
const char *sig = "magic word: pretty please";
const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
magicsig magic word: pretty please\n\
\n\
a simple commit which works\n";
git_oid id;
git_odb *odb;
git_odb_object *obj;
cl_git_pass(git_commit_create_with_signature(&id, g_repo, unsigned_data, sig, "magicsig"));
cl_git_pass(git_repository_odb(&odb, g_repo));
cl_git_pass(git_odb_read(&obj, odb, &id));
cl_assert_equal_s(complete, git_odb_object_data(obj));
git_odb_object_free(obj);
git_odb_free(odb);
}
void test_commit_write__attach_multiline_signature(void)
{
git_oid one, two;
git_odb *odb;
git_odb_object *obj;
cl_git_pass(git_commit_create_with_signature(&one, g_repo, unsigned_data, gpgsig, "gpgsig"));
cl_git_pass(git_commit_create_with_signature(&two, g_repo, unsigned_data, gpgsig, NULL));
cl_assert(!git_oid_cmp(&one, &two));
cl_git_pass(git_repository_odb(&odb, g_repo));
cl_git_pass(git_odb_read(&obj, odb, &one));
cl_assert_equal_s(signed_data, git_odb_object_data(obj));
git_odb_object_free(obj);
git_odb_free(odb);
}
static int sign_cb(
git_commitbuilder *builder, git_repository *repo,
const char *commit_buffer, void *payload)
{
GIT_UNUSED(repo);
GIT_UNUSED(commit_buffer);
GIT_UNUSED(payload);
return git_commitbuilder_add_header(builder,
"gpgsig", gpgsig);
}
void test_commit_write__signature_callback(void)
{
git_oid result_id, tree_id, parent_id;
git_odb *odb;
git_odb_object *obj;
git_signature *committer;
git_tree *tree;
git_commit *parent;
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
cl_git_pass(git_oid_from_string(&tree_id, "4b825dc642cb6eb9a060e54bf8d69288fbee4904", GIT_OID_SHA1));
cl_git_pass(git_oid_from_string(&parent_id, "8496071c1b46c854b31185ea97743be6a8774479", GIT_OID_SHA1));
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id));
cl_git_pass(git_signature_new(&committer, "Ben Burkert", "ben@benburkert.com", 1358451456, -480));
opts.sign = sign_cb;
cl_git_pass(git_commit_create_ext(&result_id, g_repo, committer,
committer, "a simple commit which works\n", tree,
1, &parent, &opts));
cl_git_pass(git_repository_odb(&odb, g_repo));
cl_git_pass(git_odb_read(&obj, odb, &result_id));
cl_assert_equal_s(signed_data, git_odb_object_data(obj));
git_tree_free(tree);
git_commit_free(parent);
git_signature_free(committer);
git_odb_object_free(obj);
git_odb_free(odb);
}