Commit 546e40c9b9da8cc7f7c992829ae927d07132236a

Tyler Wanek 2019-01-11T09:16:58

Add signing callbacks for git_rebase_commit in git_rebase_options 2 callbacks have been added to git_rebase_options, git_rebase_commit_signature_cb and git_rebase_commit_signature_field_cb. When git_rebase_commit_signature_cb is present in git_rebase_options, it will be called whenever git_rebase_commit is performed, giving an opportunity to sign the commit. The signing procedure can be skipped if the callback specifies passthrough as the error. The git_rebase_commit_signature_field_cb will only be called if the other callback is present or did not passthrough, and it provides means to specify which field a signature is for. Git_rebase_options was chosen as the home for these callbacks as it keeps backwards compatibility with the current rebase api.

diff --git a/include/git2/rebase.h b/include/git2/rebase.h
index f6b2e20..81d194e 100644
--- a/include/git2/rebase.h
+++ b/include/git2/rebase.h
@@ -24,6 +24,36 @@
 GIT_BEGIN_DECL
 
 /**
+ * Rebase commit signature callback.
+ *
+ * The callback will be called with the commit content, giving a user an
+ * opportunity to sign the commit content in a rebase.
+ * The signature parameter will be owned by LibGit2 after this callback returns.
+ *
+ * When the callback:
+ * - returns GIT_PASSTHROUGH, no signature will be added to the commit.
+ * - returns < 0, git_rebase_commit will be aborted.
+ * - returns GIT_OK, the signature parameter is expected to be filled.
+ */
+typedef int (*git_rebase_commit_signature_cb)(
+	char **signature, const char *commit_content, void *payload);
+
+/**
+ * Rebase commit signature field callback.
+ *
+ * The callback will be called if a signature_cb was called and successful.
+ * This callback will provide the field that a user is signing in a git_rebase_commit.
+ * The signature_field parameter will be owned by LibGit2 after this callback returns.
+ *
+ * When the callback:
+ * - returns GIT_PASSTHROUGH, signature_field is expected to remain null.
+ * - returns < 0, git_rebase_commit will be aborted.
+ * - returns GIT_OK, the signature_field parameter is expected to be filled.
+ */
+typedef int (*git_rebase_commit_signature_field_cb)(
+	char **signature_field, void *payload);
+
+/**
  * Rebase options
  *
  * Use to tell the rebase machinery how to operate.
@@ -72,6 +102,28 @@ typedef struct {
 	 * `abort` to match git semantics.
 	 */
 	git_checkout_options checkout_options;
+
+	/**
+	 * If provided, this will be called with the commit content, allowing
+	 * a signature to be added to the rebase commit. Can be skipped with
+	 * GIT_PASSTHROUGH. If GIT_PASSTHROUGH is returned, a commit will be made
+	 * without a signature.
+	 * This field is only used when performing git_rebase_commit.
+	 */
+	git_rebase_commit_signature_cb signature_cb;
+
+	/**
+	 * If provided and the signature_cb is provided, this will be called asking
+	 * for the field to write the signature to. Can be skipped with GIT_PASSTHROUGH.
+	 * This field is only used when performing git_rebase_commit.
+	 */
+	git_rebase_commit_signature_field_cb signature_field_cb;
+
+	/**
+	 * This will be passed to each of the callbacks in this struct
+	 * as the last parameter.
+	 */
+	void *payload;
 } git_rebase_options;
 
 /**
@@ -118,7 +170,7 @@ typedef enum {
 #define GIT_REBASE_OPTIONS_VERSION 1
 #define GIT_REBASE_OPTIONS_INIT \
 	{ GIT_REBASE_OPTIONS_VERSION, 0, 0, NULL, GIT_MERGE_OPTIONS_INIT, \
-	  GIT_CHECKOUT_OPTIONS_INIT}
+	  GIT_CHECKOUT_OPTIONS_INIT, 0, 0, NULL }
 
 /** Indicates that a rebase operation is not (yet) in progress. */
 #define GIT_REBASE_NO_OPERATION SIZE_MAX
diff --git a/src/rebase.c b/src/rebase.c
index 4546036..8ead1cf 100644
--- a/src/rebase.c
+++ b/src/rebase.c
@@ -945,6 +945,8 @@ static int rebase_commit__create(
 	git_commit *current_commit = NULL, *commit = NULL;
 	git_tree *parent_tree = NULL, *tree = NULL;
 	git_oid tree_id, commit_id;
+	git_buf commit_content = GIT_BUF_INIT;
+	char *signature = NULL, *signature_field = NULL;
 	int error;
 
 	operation = git_array_get(rebase->operations, rebase->current);
@@ -975,10 +977,40 @@ static int rebase_commit__create(
 		message = git_commit_message(current_commit);
 	}
 
-	if ((error = git_commit_create(&commit_id, rebase->repo, NULL, author,
-		committer, message_encoding, message, tree, 1,
-		(const git_commit **)&parent_commit)) < 0 ||
-		(error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0)
+	/* this error will be cleared by the signing process, but should be set
+	 * to signal the unsigned commit create process if we are not going to sign */
+	error = GIT_PASSTHROUGH;
+	if (rebase->options.signature_cb) {
+		if ((error = git_commit_create_buffer(&commit_content, rebase->repo, author, committer,
+				message_encoding, message, tree, 1, (const git_commit **)&parent_commit)) < 0)
+			goto done;
+
+		if ((error = rebase->options.signature_cb(&signature, git_buf_cstr(&commit_content),
+				rebase->options.payload)) < 0 && error != GIT_PASSTHROUGH)
+			goto done;
+
+		if (error != GIT_PASSTHROUGH) {
+			if (rebase->options.signature_field_cb &&
+				(error = rebase->options.signature_field_cb(&signature_field, rebase->options.payload)) < 0) {
+				if (error == GIT_PASSTHROUGH)
+					assert(signature_field == NULL);
+				else
+					goto done;
+			}
+
+			if ((error = git_commit_create_with_signature(&commit_id, rebase->repo,
+					git_buf_cstr(&commit_content), signature, signature_field)) < 0)
+				goto done;
+		}
+	}
+
+	/* if we skipped signing, create the commit normally */
+	if (error == GIT_PASSTHROUGH &&
+		(error = git_commit_create(&commit_id, rebase->repo, NULL, author, committer,
+			message_encoding, message, tree, 1, (const git_commit **)&parent_commit)) < 0)
+		goto done;
+
+	if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0)
 		goto done;
 
 	*out = commit;
@@ -987,6 +1019,11 @@ done:
 	if (error < 0)
 		git_commit_free(commit);
 
+	if (signature)
+		free(signature);
+	if (signature_field)
+		free(signature_field);
+	git_buf_dispose(&commit_content);
 	git_commit_free(current_commit);
 	git_tree_free(parent_tree);
 	git_tree_free(tree);