Skip to content

Commit b0692d6

Browse files
authored
Merge pull request libgit2#4913 from implausible/feature/signing-rebase-commits
Add sign capability to git_rebase_commit
2 parents f627ba6 + 998f9c1 commit b0692d6

File tree

5 files changed

+335
-14
lines changed

5 files changed

+335
-14
lines changed

include/git2/commit.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,8 @@ GIT_EXTERN(int) git_commit_create_buffer(
480480
*
481481
* @param out the resulting commit id
482482
* @param commit_content the content of the unsigned commit object
483-
* @param signature the signature to add to the commit
483+
* @param signature the signature to add to the commit. Leave `NULL`
484+
* to create a commit without adding a signature field.
484485
* @param signature_field which header field should contain this
485486
* signature. Leave `NULL` for the default of "gpgsig"
486487
* @return 0 or an error code
@@ -501,6 +502,27 @@ GIT_EXTERN(int) git_commit_create_with_signature(
501502
*/
502503
GIT_EXTERN(int) git_commit_dup(git_commit **out, git_commit *source);
503504

505+
/**
506+
* Commit signing callback.
507+
*
508+
* The callback will be called with the commit content, giving a user an
509+
* opportunity to sign the commit content. The signature_field
510+
* buf may be left empty to specify the default field "gpgsig".
511+
*
512+
* Signatures can take the form of any string, and can be created on an arbitrary
513+
* header field. Signatures are most commonly used for verifying authorship of a
514+
* commit using GPG or a similar cryptographically secure signing algorithm.
515+
* See https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work for more
516+
* details.
517+
*
518+
* When the callback:
519+
* - returns GIT_PASSTHROUGH, no signature will be added to the commit.
520+
* - returns < 0, commit creation will be aborted.
521+
* - returns GIT_OK, the signature parameter is expected to be filled.
522+
*/
523+
typedef int (*git_commit_signing_cb)(
524+
git_buf *signature, git_buf *signature_field, const char *commit_content, void *payload);
525+
504526
/** @} */
505527
GIT_END_DECL
506528
#endif

include/git2/rebase.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "annotated_commit.h"
1414
#include "merge.h"
1515
#include "checkout.h"
16+
#include "commit.h"
1617

1718
/**
1819
* @file git2/rebase.h
@@ -72,6 +73,21 @@ typedef struct {
7273
* `abort` to match git semantics.
7374
*/
7475
git_checkout_options checkout_options;
76+
77+
/**
78+
* If provided, this will be called with the commit content, allowing
79+
* a signature to be added to the rebase commit. Can be skipped with
80+
* GIT_PASSTHROUGH. If GIT_PASSTHROUGH is returned, a commit will be made
81+
* without a signature.
82+
* This field is only used when performing git_rebase_commit.
83+
*/
84+
git_commit_signing_cb signing_cb;
85+
86+
/**
87+
* This will be passed to each of the callbacks in this struct
88+
* as the last parameter.
89+
*/
90+
void *payload;
7591
} git_rebase_options;
7692

7793
/**
@@ -118,7 +134,7 @@ typedef enum {
118134
#define GIT_REBASE_OPTIONS_VERSION 1
119135
#define GIT_REBASE_OPTIONS_INIT \
120136
{ GIT_REBASE_OPTIONS_VERSION, 0, 0, NULL, GIT_MERGE_OPTIONS_INIT, \
121-
GIT_CHECKOUT_OPTIONS_INIT}
137+
GIT_CHECKOUT_OPTIONS_INIT, NULL, NULL }
122138

123139
/** Indicates that a rebase operation is not (yet) in progress. */
124140
#define GIT_REBASE_NO_OPERATION SIZE_MAX

src/commit.c

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ static int git_commit__create_buffer_internal(
8080
}
8181

8282
static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree,
83-
git_commit_parent_callback parent_cb, void *parent_payload,
84-
const git_oid *current_id, bool validate)
83+
git_commit_parent_callback parent_cb, void *parent_payload,
84+
const git_oid *current_id, bool validate)
8585
{
8686
size_t i;
8787
int error;
@@ -152,8 +152,8 @@ static int git_commit__create_internal(
152152
goto cleanup;
153153

154154
error = git_commit__create_buffer_internal(&buf, author, committer,
155-
message_encoding, message, tree,
156-
&parents);
155+
message_encoding, message, tree,
156+
&parents);
157157

158158
if (error < 0)
159159
goto cleanup;
@@ -582,7 +582,7 @@ const char *git_commit_body(git_commit *commit)
582582
break;
583583

584584
if (*msg)
585-
commit->body = git__strndup(msg, end - msg + 1);
585+
commit->body = git__strndup(msg, end - msg + 1);
586586
}
587587

588588
return commit->body;
@@ -876,12 +876,15 @@ int git_commit_create_with_signature(
876876
return -1;
877877
}
878878

879-
field = signature_field ? signature_field : "gpgsig";
880-
881879
/* The header ends after the first LF */
882880
header_end++;
883881
git_buf_put(&commit, commit_content, header_end - commit_content);
884-
format_header_field(&commit, field, signature);
882+
883+
if (signature != NULL) {
884+
field = signature_field ? signature_field : "gpgsig";
885+
format_header_field(&commit, field, signature);
886+
}
887+
885888
git_buf_puts(&commit, header_end);
886889

887890
if (git_buf_oom(&commit))

src/rebase.c

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,10 @@ static int rebase_commit__create(
951951
git_commit *current_commit = NULL, *commit = NULL;
952952
git_tree *parent_tree = NULL, *tree = NULL;
953953
git_oid tree_id, commit_id;
954+
git_buf commit_content = GIT_BUF_INIT, commit_signature = GIT_BUF_INIT,
955+
signature_field = GIT_BUF_INIT;
956+
const char *signature_field_string = NULL,
957+
*commit_signature_string = NULL;
954958
int error;
955959

956960
operation = git_array_get(rebase->operations, rebase->current);
@@ -981,10 +985,40 @@ static int rebase_commit__create(
981985
message = git_commit_message(current_commit);
982986
}
983987

984-
if ((error = git_commit_create(&commit_id, rebase->repo, NULL, author,
985-
committer, message_encoding, message, tree, 1,
986-
(const git_commit **)&parent_commit)) < 0 ||
987-
(error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0)
988+
if ((error = git_commit_create_buffer(&commit_content, rebase->repo, author, committer,
989+
message_encoding, message, tree, 1, (const git_commit **)&parent_commit)) < 0)
990+
goto done;
991+
992+
if (rebase->options.signing_cb) {
993+
git_error_clear();
994+
error = git_error_set_after_callback_function(rebase->options.signing_cb(
995+
&commit_signature, &signature_field, git_buf_cstr(&commit_content),
996+
rebase->options.payload), "commit signing_cb failed");
997+
if (error == GIT_PASSTHROUGH) {
998+
git_buf_dispose(&commit_signature);
999+
git_buf_dispose(&signature_field);
1000+
git_error_clear();
1001+
error = GIT_OK;
1002+
} else if (error < 0)
1003+
goto done;
1004+
}
1005+
1006+
if (git_buf_is_allocated(&commit_signature)) {
1007+
assert(git_buf_contains_nul(&commit_signature));
1008+
commit_signature_string = git_buf_cstr(&commit_signature);
1009+
}
1010+
1011+
if (git_buf_is_allocated(&signature_field)) {
1012+
assert(git_buf_contains_nul(&signature_field));
1013+
signature_field_string = git_buf_cstr(&signature_field);
1014+
}
1015+
1016+
if ((error = git_commit_create_with_signature(&commit_id, rebase->repo,
1017+
git_buf_cstr(&commit_content), commit_signature_string,
1018+
signature_field_string)))
1019+
goto done;
1020+
1021+
if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0)
9881022
goto done;
9891023

9901024
*out = commit;
@@ -993,6 +1027,9 @@ static int rebase_commit__create(
9931027
if (error < 0)
9941028
git_commit_free(commit);
9951029

1030+
git_buf_dispose(&commit_signature);
1031+
git_buf_dispose(&signature_field);
1032+
git_buf_dispose(&commit_content);
9961033
git_commit_free(current_commit);
9971034
git_tree_free(parent_tree);
9981035
git_tree_free(tree);

0 commit comments

Comments
 (0)