Merge pull request #7237 from libgit2/ethomson/commit

Introduce a new commit API
This commit is contained in:
Edward Thomson
2026-06-01 22:14:48 +01:00
committed by GitHub
5 changed files with 954 additions and 237 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
@@ -316,6 +317,182 @@ GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit,
*/
GIT_EXTERN(int) git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field);
/** The field name and value for a custom commit header entry. */
typedef struct {
const char *field;
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;
/**
* Flags for creating the commit.
*
* If `allow_empty_commit` is specified, a commit with no changes
* from the prior commit (an "empty" commit) is allowed. Otherwise,
* commit creation will be stopped.
*/
unsigned int allow_empty_commit : 1;
/** The commit author, or NULL for the default. */
const git_signature *author;
/** The committer, or NULL for the default. */
const git_signature *committer;
/** Encoding for the commit message; leave NULL for default. */
const char *message_encoding;
/**
* Extra headers can be specified as an array of field name and
* value pairs.
*/
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_options;
/** Current version for the `git_commit_create_options` structure */
#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1
/** Static constructor for `git_commit_create_options` */
#define GIT_COMMIT_CREATE_OPTIONS_INIT { GIT_COMMIT_CREATE_OPTIONS_VERSION }
/**
* Commits the staged changes in the repository; this is a near analog to
* `git commit -m message`.
*
* By default, empty commits are not allowed.
*
* @param id pointer to store the new commit's object id
* @param repo repository to commit changes in
* @param message the commit message
* @param opts options for creating the commit
* @return 0 on success, GIT_EUNCHANGED if there were no changes to commit, or an error code
*/
GIT_EXTERN(int) git_commit_create_from_stage(
git_oid *id,
git_repository *repo,
const char *message,
const git_commit_create_options *opts);
typedef struct {
unsigned int version;
/**
* If not NULL, the name of the reference that will be updated
* to point to this commit. If the reference is not direct, it
* will be resolved to a direct reference. Use `HEAD` to update
* the `HEAD` of the current branch and make it point to this
* commit. If the reference doesn't exist yet, it will be created.
* If it does exist, the first parent must be the tip of this
* branch.
*/
const char *update_ref;
/** Encoding for the commit message; leave NULL for default. */
const char *message_encoding;
/**
* Extra headers can be specified as an array of field name and
* value pairs.
*/
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 */
#define GIT_COMMIT_CREATE_EXT_OPTIONS_VERSION 1
/** Static constructor for `git_commit_create_ext_options` */
#define GIT_COMMIT_CREATE_EXT_OPTIONS_INIT { GIT_COMMIT_CREATE_EXT_OPTIONS_VERSION }
/**
* Create a new commit object and write it to the object database.
*
* Note that the message will _not_ be cleaned up automatically. You
* may wish to use the `git_message_prettify` API to clean it up.
*
* This API provides the most control for generating commit objects;
* this is useful if (for example) you have a tree object already.
* If you have staged changes, and just want to emulate `git commit`
* on the command line, then `git_commit_create_from_stage` may be a
* simpler option.
*
* @param id_out pointer to store the new commit's object id
* @param repo repository to store the commit in
* @param author The name of the author and time of authorship
* @param committer The name of the committer and time of commit
* @param message Full message for this commit
* @param tree The `git_tree` that will be used as the tree for this commit
* @param parent_count Number of parents for this commit
* @param parents Array pointers to `git_commit` objects that will be used
* as parents for this commit.
* @param opts Additional options for creating this commit,
* or `NULL` for defaults
* @return 0 on success or an error code
*/
GIT_EXTERN(int) git_commit_create_ext(
git_oid *id_out,
git_repository *repo,
const git_signature *author,
const git_signature *committer,
const char *message,
const git_tree *tree,
size_t parent_count,
git_commit * const parents[],
const git_commit_create_ext_options *opts);
/**
* Create new commit in the repository from a list of `git_object` pointers
*
@@ -429,49 +606,55 @@ GIT_EXTERN(int) git_commit_create_v(
size_t parent_count,
...);
typedef struct {
unsigned int version;
/**
* Flags for creating the commit.
*
* If `allow_empty_commit` is specified, a commit with no changes
* from the prior commit (and "empty" commit) is allowed. Otherwise,
* commit creation will be stopped.
*/
unsigned int allow_empty_commit : 1;
/** The commit author, or NULL for the default. */
const git_signature *author;
/** The committer, or NULL for the default. */
const git_signature *committer;
/** Encoding for the commit message; leave NULL for default. */
const char *message_encoding;
} git_commit_create_options;
/** Current version for the `git_commit_create_options` structure */
#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1
/** Static constructor for `git_commit_create_options` */
#define GIT_COMMIT_CREATE_OPTIONS_INIT { GIT_COMMIT_CREATE_OPTIONS_VERSION }
/**
* Commits the staged changes in the repository; this is a near analog to
* `git commit -m message`.
* A "batteries included" commit creation from a tree using defaults.
*
* By default, empty commits are not allowed.
*
* @param id pointer to store the new commit's object id
* @param repo repository to commit changes in
* @param tree tree to point the commit to
* @param message the commit message
* @param opts options for creating the commit
* @return 0 on success, GIT_EUNCHANGED if there were no changes to commit, or an error code
*/
GIT_EXTERN(int) git_commit_create_from_stage(
GIT_EXTERN(int) git_commit_create_from_tree(
git_oid *id,
git_repository *repo,
const git_tree *tree,
const char *message,
const git_commit_create_options *opts);
/**
* Amends the HEAD commit in the repository using the staged changes;
* this is a near analog to `git commit --amend -m message`.
*
* @param id pointer to store the new commit's object id
* @param repo repository to commit changes in
* @param message the commit message
* @param opts options for creating the commit
* @return 0 on success or an error code
*/
GIT_EXTERN(int) git_commit_amend_from_stage(
git_oid *id,
git_repository *repo,
const char *message,
const git_commit_create_options *opts);
/**
* Amends the HEAD commit in the repository using the given tree.
*
* @param id pointer to store the new commit's object id
* @param repo repository to commit changes in
* @param tree tree to point the commit to
* @param message the commit message
* @param opts options for creating the commit
* @return 0 on success or an error code
*/
GIT_EXTERN(int) git_commit_amend_from_tree(
git_oid *id,
git_repository *repo,
const git_tree *tree,
const char *message,
const git_commit_create_options *opts);

View File

@@ -42,14 +42,40 @@ void git_commit__free(void *_commit)
git__free(commit);
}
/**
* Append to 'out' properly marking continuations when there's a newline in 'content'
*/
static int format_header_field(git_str *out, const char *field, const char *content)
{
const char *lf;
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(field);
GIT_ASSERT_ARG(content);
git_str_puts(out, field);
git_str_putc(out, ' ');
while ((lf = strchr(content, '\n')) != NULL) {
git_str_put(out, content, lf - content);
git_str_puts(out, "\n ");
content = lf + 1;
}
git_str_puts(out, content);
git_str_putc(out, '\n');
return git_str_oom(out) ? -1 : 0;
}
static int git_commit__create_buffer_internal(
git_str *out,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
git_array_oid_t *parents)
git_array_oid_t *parents,
const git_commit_create_ext_options *opts)
{
size_t i = 0;
const git_oid *parent;
@@ -69,8 +95,20 @@ static int git_commit__create_buffer_internal(
git_signature__writebuf(out, "author ", author);
git_signature__writebuf(out, "committer ", committer);
if (message_encoding != NULL)
git_str_printf(out, "encoding %s\n", message_encoding);
if (opts && opts->message_encoding != NULL) {
if (format_header_field(out, "encoding",
opts->message_encoding) < 0)
goto on_error;
}
if (opts && opts->extra_headers_len) {
for (i = 0; i < opts->extra_headers_len; i++) {
if (format_header_field(out,
opts->extra_headers[i].field,
opts->extra_headers[i].value) < 0)
goto on_error;
}
}
git_str_putc(out, '\n');
@@ -123,59 +161,67 @@ on_error:
return error;
}
struct git_commitbuilder {
git_str *contents;
};
static int git_commit__create_internal(
git_oid *id,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
git_commit_parent_callback parent_cb,
void *parent_payload,
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,
repo, opts->update_ref, 10);
if (update_ref) {
error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
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_encoding, message, tree,
&parents);
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;
if (update_ref != NULL) {
if (opts && opts->update_ref != NULL) {
error = git_reference__update_for_commit(
repo, ref, update_ref, id, "commit");
repo, ref, opts->update_ref, id, "commit");
goto cleanup;
}
@@ -198,9 +244,14 @@ int git_commit_create_from_callback(
git_commit_parent_callback parent_cb,
void *parent_payload)
{
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
opts.update_ref = update_ref;
opts.message_encoding = message_encoding;
return git_commit__create_internal(
id, repo, update_ref, author, committer, message_encoding, message,
tree, parent_cb, parent_payload, true);
id, repo, author, committer, message,
tree, parent_cb, parent_payload, &opts, true);
}
typedef struct {
@@ -230,8 +281,9 @@ int git_commit_create_v(
size_t parent_count,
...)
{
int error = 0;
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
commit_parent_varargs data;
int error = 0;
GIT_ASSERT_ARG(tree);
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
@@ -239,10 +291,12 @@ int git_commit_create_v(
data.total = parent_count;
va_start(data.args, parent_count);
opts.update_ref = update_ref;
opts.message_encoding = message_encoding;
error = git_commit__create_internal(
id, repo, update_ref, author, committer,
message_encoding, message, git_tree_id(tree),
commit_parent_from_varargs, &data, false);
id, repo, author, committer, message, git_tree_id(tree),
commit_parent_from_varargs, &data, &opts, false);
va_end(data.args);
return error;
@@ -271,17 +325,20 @@ int git_commit_create_from_ids(
size_t parent_count,
const git_oid *parents[])
{
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
commit_parent_oids data = { parent_count, parents };
opts.update_ref = update_ref;
opts.message_encoding = message_encoding;
return git_commit__create_internal(
id, repo, update_ref, author, committer,
message_encoding, message, tree,
commit_parent_from_ids, &data, true);
id, repo, author, committer, message, tree,
commit_parent_from_ids, &data, &opts, true);
}
typedef struct {
size_t total;
const git_commit **parents;
git_commit * const *parents;
git_repository *repo;
} commit_parent_data;
@@ -297,6 +354,78 @@ static const git_oid *commit_parent_from_array(size_t curr, void *payload)
return git_commit_id(commit);
}
int git_commit_create_ext(
git_oid *id,
git_repository *repo,
const git_signature *author,
const git_signature *committer,
const char *message,
const git_tree *tree,
size_t parent_count,
git_commit * const parents[],
const git_commit_create_ext_options *opts)
{
commit_parent_data data = { parent_count, parents, repo };
GIT_ASSERT_ARG(tree);
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
return git_commit__create_internal(
id, repo, author, committer, message, git_tree_id(tree),
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,
@@ -309,15 +438,18 @@ int git_commit_create(
size_t parent_count,
const git_commit *parents[])
{
commit_parent_data data = { parent_count, parents, repo };
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
commit_parent_data data = { parent_count, (git_commit * const *)parents, repo };
GIT_ASSERT_ARG(tree);
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
opts.update_ref = update_ref;
opts.message_encoding = message_encoding;
return git_commit__create_internal(
id, repo, update_ref, author, committer,
message_encoding, message, git_tree_id(tree),
commit_parent_from_array, &data, false);
id, repo, author, committer, message, git_tree_id(tree),
commit_parent_from_array, &data, &opts, false);
}
static const git_oid *commit_parent_for_amend(size_t curr, void *payload)
@@ -341,6 +473,7 @@ int git_commit_amend(
git_repository *repo;
git_oid tree_id;
git_reference *ref = NULL;
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
int error;
GIT_ASSERT_ARG(id);
@@ -378,9 +511,12 @@ int git_commit_amend(
}
}
opts.message_encoding = message_encoding;
error = git_commit__create_internal(
id, repo, NULL, author, committer, message_encoding, message,
&tree_id, commit_parent_for_amend, (void *)commit_to_amend, false);
id, repo, author, committer, message, &tree_id,
commit_parent_for_amend, (void *)commit_to_amend,
&opts, false);
if (!error && update_ref) {
error = git_reference__update_for_commit(
@@ -964,9 +1100,10 @@ int git_commit__create_buffer(
const git_commit *parents[])
{
int error;
commit_parent_data data = { parent_count, parents, repo };
commit_parent_data data = { parent_count, (git_commit * const *)parents, repo };
git_array_oid_t parents_arr = GIT_ARRAY_INIT;
const git_oid *tree_id;
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
GIT_ASSERT_ARG(tree);
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
@@ -976,47 +1113,21 @@ int git_commit__create_buffer(
if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0)
return error;
opts.message_encoding = message_encoding;
error = git_commit__create_buffer_internal(
out, author, committer,
message_encoding, message, tree_id,
&parents_arr);
out, author, committer, message, tree_id,
&parents_arr, &opts);
git_array_clear(parents_arr);
return error;
}
/**
* Append to 'out' properly marking continuations when there's a newline in 'content'
*/
static int format_header_field(git_str *out, const char *field, const char *content)
{
const char *lf;
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(field);
GIT_ASSERT_ARG(content);
git_str_puts(out, field);
git_str_putc(out, ' ');
while ((lf = strchr(content, '\n')) != NULL) {
git_str_put(out, content, lf - content);
git_str_puts(out, "\n ");
content = lf + 1;
}
git_str_puts(out, content);
git_str_putc(out, '\n');
return git_str_oom(out) ? -1 : 0;
}
static const git_oid *commit_parent_from_commit(size_t n, void *payload)
{
const git_commit *commit = (const git_commit *) payload;
return git_array_get(commit->parent_ids, n);
}
int git_commit_create_with_signature(
@@ -1027,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;
}
@@ -1050,65 +1170,98 @@ 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;
}
int git_commit_create_from_stage(
git_oid *out,
git_repository *repo,
const char *message,
const git_commit_create_options *given_opts)
static int check_for_empty_commit(
git_repository *repo, const git_tree *tree, git_index *index)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_signature *default_signature = NULL;
const git_signature *author, *committer;
git_index *index = NULL;
git_diff *diff = NULL;
git_oid tree_id;
git_tree *head_tree = NULL, *tree = NULL;
git_commitarray parents = { 0 };
git_tree *head_tree = NULL;
int error = -1;
GIT_ASSERT_ARG(out && repo);
error = git_repository_head_tree(&head_tree, repo);
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
if (error && error != GIT_EUNBORNBRANCH)
goto done;
author = opts.author;
committer = opts.committer;
/*
* TODO: stop comparison on first delta
* TODO: stop casting away constness
*/
if (tree)
error = git_diff_tree_to_tree(&diff, repo, head_tree, (git_tree *)tree, NULL);
else
error = git_diff_tree_to_index(&diff, repo, head_tree, index, NULL);
if (error < 0)
goto done;
if (git_diff_num_deltas(diff) == 0) {
git_error_set(GIT_ERROR_REPOSITORY,
"no changes are staged for commit");
error = GIT_EUNCHANGED;
goto done;
}
done:
git_diff_free(diff);
git_tree_free(head_tree);
return error;
}
static void init_ext_options(
git_commit_create_ext_options *ext_opts,
const git_commit_create_options *create_opts)
{
if (!create_opts)
return;
ext_opts->message_encoding = create_opts->message_encoding;
ext_opts->extra_headers = create_opts->extra_headers;
ext_opts->extra_headers_len = create_opts->extra_headers_len;
ext_opts->sign = create_opts->sign;
ext_opts->payload = create_opts->payload;
}
static int create_from_tree(
git_oid *out,
git_repository *repo,
const git_tree *tree,
const char *message,
const git_commit_create_options *opts)
{
git_signature *default_signature = NULL;
const git_signature *author, *committer;
git_commitarray parents = { 0 };
git_commit_create_ext_options ext_opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
int error = -1;
init_ext_options(&ext_opts, opts);
ext_opts.update_ref = "HEAD";
author = opts ? opts->author : NULL;
committer = opts ? opts->committer : NULL;
if (!author || !committer) {
if (git_signature_default(&default_signature, repo) < 0)
@@ -1121,48 +1274,170 @@ int git_commit_create_from_stage(
committer = default_signature;
}
if (git_repository_index(&index, repo) < 0)
if (git_repository_commit_parents(&parents, repo) < 0)
goto done;
if (!opts.allow_empty_commit) {
error = git_repository_head_tree(&head_tree, repo);
if (error && error != GIT_EUNBORNBRANCH)
goto done;
error = -1;
if (git_diff_tree_to_index(&diff, repo, head_tree, index, NULL) < 0)
goto done;
if (git_diff_num_deltas(diff) == 0) {
git_error_set(GIT_ERROR_REPOSITORY,
"no changes are staged for commit");
error = GIT_EUNCHANGED;
goto done;
}
}
if (git_index_write_tree(&tree_id, index) < 0 ||
git_tree_lookup(&tree, repo, &tree_id) < 0 ||
git_repository_commit_parents(&parents, repo) < 0)
goto done;
error = git_commit_create(out, repo, "HEAD", author, committer,
opts.message_encoding, message,
tree, parents.count,
(const git_commit **)parents.commits);
error = git_commit_create_ext(out, repo, author, committer,
message, tree, parents.count,
parents.commits, &ext_opts);
done:
git_commitarray_dispose(&parents);
git_signature_free(default_signature);
return error;
}
int git_commit_create_from_stage(
git_oid *out,
git_repository *repo,
const char *message,
const git_commit_create_options *given_opts)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_index *index = NULL;
git_oid tree_id;
git_tree *tree = NULL;
int error = -1;
GIT_ASSERT_ARG(out && repo && message);
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
if (git_repository_index(&index, repo) < 0)
goto done;
if (!opts.allow_empty_commit &&
(error = check_for_empty_commit(repo, NULL, index)) < 0)
goto done;
if (git_index_write_tree(&tree_id, index) < 0 ||
git_tree_lookup(&tree, repo, &tree_id) < 0) {
error = -1;
goto done;
}
error = create_from_tree(out, repo, tree, message, &opts);
done:
git_tree_free(tree);
git_tree_free(head_tree);
git_diff_free(diff);
git_index_free(index);
return error;
}
int git_commit_create_from_tree(
git_oid *out,
git_repository *repo,
const git_tree *tree,
const char *message,
const git_commit_create_options *given_opts)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
GIT_ASSERT_ARG(out && repo && tree && message);
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
if (!opts.allow_empty_commit &&
check_for_empty_commit(repo, tree, NULL) < 0)
return -1;
return create_from_tree(out, repo, tree, message, &opts);
}
int git_commit_amend_from_stage(
git_oid *out,
git_repository *repo,
const char *message,
const git_commit_create_options *opts)
{
git_index *index = NULL;
git_oid tree_id;
git_tree *tree = NULL;
int error = 0;
GIT_ASSERT_ARG(out && repo);
if (git_repository_index(&index, repo) < 0 ||
git_index_write_tree(&tree_id, index) < 0 ||
git_tree_lookup(&tree, repo, &tree_id) < 0) {
error = -1;
goto done;
}
error = git_commit_amend_from_tree(out, repo, tree, message, opts);
done:
git_tree_free(tree);
git_index_free(index);
return error;
}
int git_commit_amend_from_tree(
git_oid *out,
git_repository *repo,
const git_tree *tree,
const char *given_message,
const git_commit_create_options *opts)
{
git_commit_create_ext_options ext_opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
git_reference *head_ref = NULL;
git_commit *head_commit = NULL;
git_signature *new_committer = NULL;
const git_signature *author, *committer;
const char *message;
int error;
GIT_ASSERT_ARG(out && repo && tree);
init_ext_options(&ext_opts, opts);
if ((error = git_repository_head(&head_ref, repo)) < 0 ||
(error = git_reference_peel((git_object **)&head_commit, head_ref, GIT_OBJECT_COMMIT)) < 0)
goto done;
if (opts && opts->author) {
author = opts->author;
} else {
author = git_commit_author(head_commit);
}
if (opts && opts->committer) {
committer = opts->committer;
} else {
if ((error = git_signature_default(&new_committer, repo)) < 0)
goto done;
committer = new_committer;
}
if (given_message) {
message = given_message;
} else {
message = git_commit_message(head_commit);
ext_opts.message_encoding = git_commit_message_encoding(head_commit);
}
error = git_commit__create_internal(
out, repo, author, committer, message, git_tree_id(tree),
commit_parent_for_amend, (void *)head_commit, &ext_opts,
false);
if (!error)
error = git_reference__update_for_commit(
repo, head_ref, NULL, out, "commit");
done:
git_signature_free(new_committer);
git_commit_free(head_commit);
git_reference_free(head_ref);
return error;
}
int git_commit_committer_with_mailmap(
git_signature **out, const git_commit *commit, const git_mailmap *mailmap)
{

View File

@@ -0,0 +1,122 @@
#include "clar_libgit2.h"
#include "repository.h"
/* Fixture setup */
static git_repository *g_repo;
static git_signature *g_committer;
void test_commit_amend__initialize(void)
{
g_repo = cl_git_sandbox_init("testrepo2");
cl_git_pass(git_signature_new(&g_committer, "libgit2 user", "nobody@noreply.libgit2.org", 987654321, 90));
}
void test_commit_amend__cleanup(void)
{
git_signature_free(g_committer);
cl_git_sandbox_cleanup();
}
void test_commit_amend__from_stage_simple(void)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_index *index;
git_oid commit_id;
git_tree *tree;
opts.committer = g_committer;
cl_git_rewritefile("testrepo2/newfile.txt", "This is a new file.\n");
cl_git_rewritefile("testrepo2/newfile2.txt", "This is a new file.\n");
cl_git_rewritefile("testrepo2/README", "hello, world.\n");
cl_git_rewritefile("testrepo2/new.txt", "hi there.\n");
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_index_add_bypath(index, "newfile2.txt"));
cl_git_pass(git_index_add_bypath(index, "README"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_commit_amend_from_stage(&commit_id, g_repo, NULL, &opts));
cl_git_pass(git_repository_head_tree(&tree, g_repo));
cl_assert_equal_oidstr("63ec0b083fd14c22a68fd2b1794f26e4b396b6b3", &commit_id);
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(tree));
git_index_free(index);
git_tree_free(tree);
}
void test_commit_amend__from_stage_newmessage(void)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_oid commit_id;
git_tree *tree;
opts.committer = g_committer;
cl_git_pass(git_commit_amend_from_stage(&commit_id, g_repo, "New message goes here.", &opts));
cl_git_pass(git_repository_head_tree(&tree, g_repo));
cl_assert_equal_oidstr("8b0e1cacc8380023705192466aaef8a15ddae7b3", &commit_id);
cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree));
git_tree_free(tree);
}
void test_commit_amend__from_stage_nochanges(void)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_oid commit_id;
git_tree *tree;
opts.committer = g_committer;
cl_git_pass(git_commit_amend_from_stage(&commit_id, g_repo, NULL, &opts));
cl_git_pass(git_repository_head_tree(&tree, g_repo));
cl_assert_equal_oidstr("da86907c6d505a92c5683bece08f23d68ac785bd", &commit_id);
cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree));
git_tree_free(tree);
}
void test_commit_amend__from_tree(void)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_index *index;
git_oid commit_id;
git_oid tree_id;
git_tree *tree, *lookedup;
opts.committer = g_committer;
cl_git_rewritefile("testrepo2/newfile.txt", "This is a new file.\n");
cl_git_rewritefile("testrepo2/newfile2.txt", "This is a new file.\n");
cl_git_rewritefile("testrepo2/README", "hello, world.\n");
cl_git_rewritefile("testrepo2/new.txt", "hi there.\n");
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_index_add_bypath(index, "newfile2.txt"));
cl_git_pass(git_index_add_bypath(index, "README"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_index_write_tree(&tree_id, index));
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", &tree_id);
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(git_commit_amend_from_tree(&commit_id, g_repo, tree, NULL, &opts));
cl_git_pass(git_repository_head_tree(&lookedup, g_repo));
cl_assert_equal_oidstr("63ec0b083fd14c22a68fd2b1794f26e4b396b6b3", &commit_id);
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(lookedup));
git_index_free(index);
git_tree_free(tree);
git_tree_free(lookedup);
}

View File

@@ -110,3 +110,41 @@ void test_commit_create__from_stage_newrepo(void)
git_repository_free(newrepo);
cl_fixture_cleanup("newrepo");
}
void test_commit_create__from_tree(void)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_index *index;
git_oid commit_id;
git_oid tree_id;
git_tree *tree, *lookedup;
opts.author = g_author;
opts.committer = g_committer;
cl_git_rewritefile("testrepo2/newfile.txt", "This is a new file.\n");
cl_git_rewritefile("testrepo2/newfile2.txt", "This is a new file.\n");
cl_git_rewritefile("testrepo2/README", "hello, world.\n");
cl_git_rewritefile("testrepo2/new.txt", "hi there.\n");
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_index_add_bypath(index, "newfile2.txt"));
cl_git_pass(git_index_add_bypath(index, "README"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_index_write_tree(&tree_id, index));
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", &tree_id);
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(git_commit_create_from_tree(&commit_id, g_repo, tree, "This is the message.", &opts));
cl_git_pass(git_repository_head_tree(&lookedup, g_repo));
cl_assert_equal_oidstr("241b5b04e847bc38dd7b4b9f49f21e55da40f3a6", &commit_id);
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(lookedup));
git_index_free(index);
git_tree_free(tree);
git_tree_free(lookedup);
}

View File

@@ -299,6 +299,115 @@ 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__can_add_extra_headers(void)
{
git_odb *odb;
git_signature *author, *committer;
git_oid tree_id, parent_id, commit_id;
git_commit *parent;
git_tree *tree;
git_odb_object *obj;
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
const git_commit_header extra_headers[] = {
{ "line_one", "First extra header" },
{ "line_two", "Second extra header" }
};
const char *expected = "tree 1810dff58d8a660512d4832e740f692884338ccd\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Vicent Marti <vicent@github.com> 987654321 +0130\n\
committer Vicent Marti <vicent@github.com> 123456789 +0100\n\
line_one First extra header\n\
line_two Second extra header\n\
\n\
This is a fun new commit.";
cl_git_pass(git_signature_new(
&author, committer_name, committer_email, 987654321, 90));
cl_git_pass(git_signature_new(
&committer, committer_name, committer_email, 123456789, 60));
/* this is a valid tree and parent */
git_oid_from_string(&tree_id, tree_id_str, GIT_OID_SHA1);
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
git_oid_from_string(&parent_id, parent_id_str, GIT_OID_SHA1);
cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id));
opts.extra_headers = extra_headers;
opts.extra_headers_len = 2;
cl_git_pass(git_commit_create_ext(
&commit_id, g_repo, author, committer,
"This is a fun new commit.",
tree, 1, &parent, &opts));
cl_git_pass(git_repository_odb(&odb, g_repo));
cl_git_pass(git_odb_read(&obj, odb, &commit_id));
cl_assert_equal_s(expected, git_odb_object_data(obj));
git_odb_object_free(obj);
git_tree_free(tree);
git_commit_free(parent);
git_commit_free(commit);
git_signature_free(committer);
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";
@@ -327,13 +436,6 @@ 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\
@@ -346,7 +448,7 @@ a simple commit which works\n";
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_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));
@@ -358,67 +460,64 @@ a simple commit which works\n";
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_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(complete, git_odb_object_data(obj));
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);
}