mirror of
https://github.com/libgit2/libgit2.git
synced 2026-06-22 06:26:26 +00:00
rebase: introduce git_commit_create_cb
Introduce a new mechanism for `git_rebase_commit` for callers to customize the experience. Instead of assuming that we produce the commit for them, provide a commit creation callback that allows callers to produce the commit themselves and return the resulting commit id.
This commit is contained in:
@@ -502,6 +502,43 @@ GIT_EXTERN(int) git_commit_create_with_signature(
|
||||
*/
|
||||
GIT_EXTERN(int) git_commit_dup(git_commit **out, git_commit *source);
|
||||
|
||||
/**
|
||||
* Commit creation callback: used when a function is going to create
|
||||
* commits (for example, in `git_rebase_commit`) to allow callers to
|
||||
* override the commit creation behavior. For example, users may
|
||||
* wish to sign commits by providing this information to
|
||||
* `git_commit_create_buffer`, signing that buffer, then calling
|
||||
* `git_commit_create_with_signature`. The resultant commit id
|
||||
* should be set in the `out` object id parameter.
|
||||
*
|
||||
* @param out pointer that this callback will populate with the object
|
||||
* id of the commit that is created
|
||||
* @param author the author name and time of the commit
|
||||
* @param committer the committer name and time of the commit
|
||||
* @param message_encoding the encoding of the given message, or NULL
|
||||
* to assume UTF8
|
||||
* @param message the commit message
|
||||
* @param tree the tree to be committed
|
||||
* @param parent_count the number of parents for this commit
|
||||
* @param parents the commit parents
|
||||
* @param payload the payload pointer in the rebase options
|
||||
* @return 0 if this callback has created the commit and populated the out
|
||||
* parameter, GIT_PASSTHROUGH if the callback has not created a
|
||||
* commit and wants the calling function to create the commit as
|
||||
* if no callback had been specified, any other value to stop
|
||||
* and return a failure
|
||||
*/
|
||||
typedef int (*git_commit_create_cb)(
|
||||
git_oid *out,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message_encoding,
|
||||
const char *message,
|
||||
const git_tree *tree,
|
||||
size_t parent_count,
|
||||
const git_commit *parents[],
|
||||
void *payload);
|
||||
|
||||
/**
|
||||
* Commit signing callback.
|
||||
*
|
||||
|
||||
@@ -74,12 +74,27 @@ typedef struct {
|
||||
*/
|
||||
git_checkout_options checkout_options;
|
||||
|
||||
/**
|
||||
* Optional callback that allows users to override commit
|
||||
* creation in `git_rebase_commit`. If specified, users can
|
||||
* create their own commit and provide the commit ID, which
|
||||
* may be useful for signing commits or otherwise customizing
|
||||
* the commit creation.
|
||||
*
|
||||
* If this callback returns `GIT_PASSTHROUGH`, then
|
||||
* `git_rebase_commit` will continue to create the commit.
|
||||
*/
|
||||
git_commit_create_cb commit_create_cb;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This callback is not invoked if a `git_commit_create_cb` is
|
||||
* specified.
|
||||
*/
|
||||
git_commit_signing_cb signing_cb;
|
||||
|
||||
|
||||
99
src/rebase.c
99
src/rebase.c
@@ -943,6 +943,52 @@ int git_rebase_inmemory_index(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int create_signed(
|
||||
git_oid *out,
|
||||
git_rebase *rebase,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message_encoding,
|
||||
const char *message,
|
||||
git_tree *tree,
|
||||
size_t parent_count,
|
||||
const git_commit **parents)
|
||||
{
|
||||
git_buf commit_content = GIT_BUF_INIT,
|
||||
commit_signature = GIT_BUF_INIT,
|
||||
signature_field = GIT_BUF_INIT;
|
||||
int error;
|
||||
|
||||
git_error_clear();
|
||||
|
||||
if ((error = git_commit_create_buffer(&commit_content,
|
||||
rebase->repo, author, committer, message_encoding,
|
||||
message, tree, parent_count, parents)) < 0)
|
||||
goto done;
|
||||
|
||||
error = rebase->options.signing_cb(&commit_signature,
|
||||
&signature_field, commit_content.ptr,
|
||||
rebase->options.payload);
|
||||
|
||||
if (error) {
|
||||
if (error != GIT_PASSTHROUGH)
|
||||
git_error_set_after_callback_function(error, "signing_cb");
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
error = git_commit_create_with_signature(out, rebase->repo,
|
||||
commit_content.ptr,
|
||||
commit_signature.size > 0 ? commit_signature.ptr : NULL,
|
||||
signature_field.size > 0 ? signature_field.ptr : NULL);
|
||||
|
||||
done:
|
||||
git_buf_dispose(&commit_signature);
|
||||
git_buf_dispose(&signature_field);
|
||||
git_buf_dispose(&commit_content);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int rebase_commit__create(
|
||||
git_commit **out,
|
||||
git_rebase *rebase,
|
||||
@@ -957,10 +1003,6 @@ 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, commit_signature = GIT_BUF_INIT,
|
||||
signature_field = GIT_BUF_INIT;
|
||||
const char *signature_field_string = NULL,
|
||||
*commit_signature_string = NULL;
|
||||
int error;
|
||||
|
||||
operation = git_array_get(rebase->operations, rebase->current);
|
||||
@@ -991,37 +1033,29 @@ static int rebase_commit__create(
|
||||
message = git_commit_message(current_commit);
|
||||
}
|
||||
|
||||
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;
|
||||
git_error_clear();
|
||||
error = GIT_PASSTHROUGH;
|
||||
|
||||
if (rebase->options.signing_cb) {
|
||||
git_error_clear();
|
||||
error = git_error_set_after_callback_function(rebase->options.signing_cb(
|
||||
&commit_signature, &signature_field, git_buf_cstr(&commit_content),
|
||||
rebase->options.payload), "commit signing_cb failed");
|
||||
if (error == GIT_PASSTHROUGH) {
|
||||
git_buf_dispose(&commit_signature);
|
||||
git_buf_dispose(&signature_field);
|
||||
git_error_clear();
|
||||
error = GIT_OK;
|
||||
} else if (error < 0)
|
||||
goto done;
|
||||
if (rebase->options.commit_create_cb) {
|
||||
error = rebase->options.commit_create_cb(&commit_id,
|
||||
author, committer, message_encoding, message,
|
||||
tree, 1, (const git_commit **)&parent_commit,
|
||||
rebase->options.payload);
|
||||
|
||||
git_error_set_after_callback_function(error,
|
||||
"commit_create_cb");
|
||||
} else if (rebase->options.signing_cb) {
|
||||
error = create_signed(&commit_id, rebase, author,
|
||||
committer, message_encoding, message, tree,
|
||||
1, (const git_commit **)&parent_commit);
|
||||
}
|
||||
|
||||
if (git_buf_is_allocated(&commit_signature)) {
|
||||
GIT_ASSERT(git_buf_contains_nul(&commit_signature));
|
||||
commit_signature_string = git_buf_cstr(&commit_signature);
|
||||
}
|
||||
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);
|
||||
|
||||
if (git_buf_is_allocated(&signature_field)) {
|
||||
GIT_ASSERT(git_buf_contains_nul(&signature_field));
|
||||
signature_field_string = git_buf_cstr(&signature_field);
|
||||
}
|
||||
|
||||
if ((error = git_commit_create_with_signature(&commit_id, rebase->repo,
|
||||
git_buf_cstr(&commit_content), commit_signature_string,
|
||||
signature_field_string)))
|
||||
if (error)
|
||||
goto done;
|
||||
|
||||
if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0)
|
||||
@@ -1033,9 +1067,6 @@ done:
|
||||
if (error < 0)
|
||||
git_commit_free(commit);
|
||||
|
||||
git_buf_dispose(&commit_signature);
|
||||
git_buf_dispose(&signature_field);
|
||||
git_buf_dispose(&commit_content);
|
||||
git_commit_free(current_commit);
|
||||
git_tree_free(parent_tree);
|
||||
git_tree_free(tree);
|
||||
|
||||
@@ -18,6 +18,236 @@ void test_rebase_sign__cleanup(void)
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
static int create_cb_passthrough(
|
||||
git_oid *out,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message_encoding,
|
||||
const char *message,
|
||||
const git_tree *tree,
|
||||
size_t parent_count,
|
||||
const git_commit *parents[],
|
||||
void *payload)
|
||||
{
|
||||
GIT_UNUSED(out);
|
||||
GIT_UNUSED(author);
|
||||
GIT_UNUSED(committer);
|
||||
GIT_UNUSED(message_encoding);
|
||||
GIT_UNUSED(message);
|
||||
GIT_UNUSED(tree);
|
||||
GIT_UNUSED(parent_count);
|
||||
GIT_UNUSED(parents);
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
return GIT_PASSTHROUGH;
|
||||
}
|
||||
|
||||
/* git checkout gravy ; git rebase --merge veal */
|
||||
void test_rebase_sign__passthrough_create_cb(void)
|
||||
{
|
||||
git_rebase *rebase;
|
||||
git_reference *branch_ref, *upstream_ref;
|
||||
git_annotated_commit *branch_head, *upstream_head;
|
||||
git_rebase_operation *rebase_operation;
|
||||
git_oid commit_id, expected_id;
|
||||
git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT;
|
||||
git_commit *commit;
|
||||
const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\
|
||||
parent f87d14a4a236582a0278a916340a793714256864\n\
|
||||
author Edward Thomson <ethomson@edwardthomson.com> 1405625055 -0400\n\
|
||||
committer Rebaser <rebaser@rebaser.rb> 1405694510 +0000\n";
|
||||
|
||||
rebase_opts.commit_create_cb = create_cb_passthrough;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy"));
|
||||
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal"));
|
||||
|
||||
cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
|
||||
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts));
|
||||
|
||||
cl_git_pass(git_rebase_next(&rebase_operation, rebase));
|
||||
cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL));
|
||||
|
||||
git_oid_fromstr(&expected_id, "129183968a65abd6c52da35bff43325001bfc630");
|
||||
cl_assert_equal_oid(&expected_id, &commit_id);
|
||||
|
||||
cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
|
||||
cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit));
|
||||
|
||||
cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase));
|
||||
|
||||
git_reference_free(branch_ref);
|
||||
git_reference_free(upstream_ref);
|
||||
git_annotated_commit_free(branch_head);
|
||||
git_annotated_commit_free(upstream_head);
|
||||
git_commit_free(commit);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
int create_cb_signed_gpg(
|
||||
git_oid *out,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message_encoding,
|
||||
const char *message,
|
||||
const git_tree *tree,
|
||||
size_t parent_count,
|
||||
const git_commit *parents[],
|
||||
void *payload)
|
||||
{
|
||||
git_buf commit_content = GIT_BUF_INIT;
|
||||
const char *gpg_signature = "-----BEGIN PGP SIGNATURE-----\n\
|
||||
\n\
|
||||
iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\
|
||||
ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\
|
||||
ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\
|
||||
7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\
|
||||
km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\
|
||||
nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\
|
||||
jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\
|
||||
dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\
|
||||
fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\
|
||||
cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\
|
||||
xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\
|
||||
cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\
|
||||
=KbsY\n\
|
||||
-----END PGP SIGNATURE-----";
|
||||
|
||||
git_repository *repo = (git_repository *)payload;
|
||||
int error;
|
||||
|
||||
if ((error = git_commit_create_buffer(&commit_content,
|
||||
repo, author, committer, message_encoding, message,
|
||||
tree, parent_count, parents)) < 0)
|
||||
goto done;
|
||||
|
||||
error = git_commit_create_with_signature(out, repo,
|
||||
commit_content.ptr,
|
||||
gpg_signature,
|
||||
NULL);
|
||||
|
||||
done:
|
||||
git_buf_dispose(&commit_content);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* git checkout gravy ; git rebase --merge veal */
|
||||
void test_rebase_sign__create_gpg_signed(void)
|
||||
{
|
||||
git_rebase *rebase;
|
||||
git_reference *branch_ref, *upstream_ref;
|
||||
git_annotated_commit *branch_head, *upstream_head;
|
||||
git_rebase_operation *rebase_operation;
|
||||
git_oid commit_id, expected_id;
|
||||
git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT;
|
||||
git_commit *commit;
|
||||
const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\
|
||||
parent f87d14a4a236582a0278a916340a793714256864\n\
|
||||
author Edward Thomson <ethomson@edwardthomson.com> 1405625055 -0400\n\
|
||||
committer Rebaser <rebaser@rebaser.rb> 1405694510 +0000\n\
|
||||
gpgsig -----BEGIN PGP SIGNATURE-----\n\
|
||||
\n\
|
||||
iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\
|
||||
ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\
|
||||
ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\
|
||||
7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\
|
||||
km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\
|
||||
nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\
|
||||
jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\
|
||||
dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\
|
||||
fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\
|
||||
cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\
|
||||
xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\
|
||||
cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\
|
||||
=KbsY\n\
|
||||
-----END PGP SIGNATURE-----\n";
|
||||
|
||||
rebase_opts.commit_create_cb = create_cb_signed_gpg;
|
||||
rebase_opts.payload = repo;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy"));
|
||||
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal"));
|
||||
|
||||
cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
|
||||
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts));
|
||||
|
||||
cl_git_pass(git_rebase_next(&rebase_operation, rebase));
|
||||
cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL));
|
||||
|
||||
git_oid_fromstr(&expected_id, "bf78348e45c8286f52b760f1db15cb6da030f2ef");
|
||||
cl_assert_equal_oid(&expected_id, &commit_id);
|
||||
|
||||
cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
|
||||
cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit));
|
||||
|
||||
cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase));
|
||||
|
||||
git_reference_free(branch_ref);
|
||||
git_reference_free(upstream_ref);
|
||||
git_annotated_commit_free(branch_head);
|
||||
git_annotated_commit_free(upstream_head);
|
||||
git_commit_free(commit);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
static int create_cb_error(
|
||||
git_oid *out,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message_encoding,
|
||||
const char *message,
|
||||
const git_tree *tree,
|
||||
size_t parent_count,
|
||||
const git_commit *parents[],
|
||||
void *payload)
|
||||
{
|
||||
GIT_UNUSED(out);
|
||||
GIT_UNUSED(author);
|
||||
GIT_UNUSED(committer);
|
||||
GIT_UNUSED(message_encoding);
|
||||
GIT_UNUSED(message);
|
||||
GIT_UNUSED(tree);
|
||||
GIT_UNUSED(parent_count);
|
||||
GIT_UNUSED(parents);
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
return GIT_EUSER;
|
||||
}
|
||||
|
||||
/* git checkout gravy ; git rebase --merge veal */
|
||||
void test_rebase_sign__create_propagates_error(void)
|
||||
{
|
||||
git_rebase *rebase;
|
||||
git_reference *branch_ref, *upstream_ref;
|
||||
git_annotated_commit *branch_head, *upstream_head;
|
||||
git_oid commit_id;
|
||||
git_rebase_operation *rebase_operation;
|
||||
git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT;
|
||||
|
||||
rebase_opts.commit_create_cb = create_cb_error;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy"));
|
||||
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal"));
|
||||
|
||||
cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
|
||||
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts));
|
||||
|
||||
cl_git_pass(git_rebase_next(&rebase_operation, rebase));
|
||||
cl_git_fail_with(GIT_EUSER, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL));
|
||||
|
||||
git_reference_free(branch_ref);
|
||||
git_reference_free(upstream_ref);
|
||||
git_annotated_commit_free(branch_head);
|
||||
git_annotated_commit_free(upstream_head);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
static const char *expected_commit_content = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\
|
||||
parent f87d14a4a236582a0278a916340a793714256864\n\
|
||||
author Edward Thomson <ethomson@edwardthomson.com> 1405625055 -0400\n\
|
||||
@@ -241,3 +471,4 @@ magicsig magic word: pretty please\n";
|
||||
git_commit_free(commit);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user