diff --git a/include/git2/commit.h b/include/git2/commit.h index 4372954d6..5490ff030 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -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); diff --git a/src/libgit2/commit.c b/src/libgit2/commit.c index 633279227..28a062215 100644 --- a/src/libgit2/commit.c +++ b/src/libgit2/commit.c @@ -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) { diff --git a/tests/libgit2/commit/amend.c b/tests/libgit2/commit/amend.c new file mode 100644 index 000000000..d75f8be62 --- /dev/null +++ b/tests/libgit2/commit/amend.c @@ -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); +} diff --git a/tests/libgit2/commit/create.c b/tests/libgit2/commit/create.c index 9f627dc87..7dadb6964 100644 --- a/tests/libgit2/commit/create.c +++ b/tests/libgit2/commit/create.c @@ -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); +} diff --git a/tests/libgit2/commit/write.c b/tests/libgit2/commit/write.c index 0fd228bc8..87e2d9987 100644 --- a/tests/libgit2/commit/write.c +++ b/tests/libgit2/commit/write.c @@ -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 987654321 +0130\n\ +committer Vicent Marti 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 1358451456 -0800\n\ +committer Ben Burkert 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 1358451456 -0800\n\ +committer Ben Burkert 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 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -\n\ -a simple commit which works\n"; - const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ parent 8496071c1b46c854b31185ea97743be6a8774479\n\ author Ben Burkert 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 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -\n\ -a simple commit which works\n"; - -const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ -parent 8496071c1b46c854b31185ea97743be6a8774479\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 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); +}