From f8823e4e11e8afcc53ef59972f854ff88057b11b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 25 May 2026 16:29:09 +0100 Subject: [PATCH] commit: add headers and signature to `create_from` The `create_from` and `amend_from` APIs use a slightly different options structure. They should both allow extra headers and signature callbacks. --- include/git2/commit.h | 105 ++++++++++++++++++++++++------------------ src/libgit2/commit.c | 44 ++++++++++++------ 2 files changed, 88 insertions(+), 61 deletions(-) diff --git a/include/git2/commit.h b/include/git2/commit.h index c5815118a..5490ff030 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -318,52 +318,6 @@ GIT_EXTERN(int) git_commit_header_field( GIT_EXTERN(int) git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field); -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; -} 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); - /** The field name and value for a custom commit header entry. */ typedef struct { const char *field; @@ -406,6 +360,65 @@ typedef int GIT_CALLBACK(git_commit_signature_cb)( 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; diff --git a/src/libgit2/commit.c b/src/libgit2/commit.c index 75786f0ea..28a062215 100644 --- a/src/libgit2/commit.c +++ b/src/libgit2/commit.c @@ -1230,6 +1230,20 @@ done: 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, @@ -1240,10 +1254,14 @@ static int create_from_tree( 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; - author = opts->author; - committer = opts->committer; + 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) @@ -1259,10 +1277,9 @@ static int create_from_tree( if (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); @@ -1363,9 +1380,8 @@ int git_commit_amend_from_tree( git_repository *repo, const git_tree *tree, const char *given_message, - const git_commit_create_options *given_opts) + const git_commit_create_options *opts) { - git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT; git_commit_create_ext_options ext_opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT; git_reference *head_ref = NULL; git_commit *head_commit = NULL; @@ -1376,21 +1392,20 @@ int git_commit_amend_from_tree( GIT_ASSERT_ARG(out && repo && tree); - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_commit_create_options)); + 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.author) { - author = opts.author; + if (opts && opts->author) { + author = opts->author; } else { author = git_commit_author(head_commit); } - if (opts.committer) { - committer = opts.committer; + if (opts && opts->committer) { + committer = opts->committer; } else { if ((error = git_signature_default(&new_committer, repo)) < 0) goto done; @@ -1400,7 +1415,6 @@ int git_commit_amend_from_tree( if (given_message) { message = given_message; - ext_opts.message_encoding = opts.message_encoding; } else { message = git_commit_message(head_commit); ext_opts.message_encoding = git_commit_message_encoding(head_commit);