commit: introduce git_commit_create_from_stage

Provide a simple helper method that allows users to create a commit from
the current index with minimal information.
This commit is contained in:
Edward Thomson
2024-01-15 00:08:16 +00:00
parent fddfca3526
commit 67a4d04b59
4 changed files with 217 additions and 1 deletions

View File

@@ -394,6 +394,36 @@ GIT_EXTERN(int) git_commit_create_v(
size_t parent_count,
...);
typedef struct {
unsigned int version;
unsigned int allow_empty_commit : 1;
const git_signature *author;
const git_signature *committer;
} git_commit_create_options;
#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1
#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);
/**
* Amend an existing commit by replacing only non-NULL values.
*

View File

@@ -59,7 +59,8 @@ typedef enum {
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
GIT_EAPPLYFAIL = -35, /**< Patch application failed */
GIT_EOWNER = -36, /**< The object is not owned by the current user */
GIT_TIMEOUT = -37 /**< The operation timed out */
GIT_TIMEOUT = -37, /**< The operation timed out */
GIT_EUNCHANGED = -38 /**< There were no changes */
} git_error_code;
/**

View File

@@ -1086,6 +1086,79 @@ cleanup:
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_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 };
int error = -1;
GIT_ASSERT_ARG(out && repo);
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
if ((author = opts.author) == NULL ||
(committer = opts.committer) == NULL) {
if (git_signature_default(&default_signature, repo) < 0)
goto done;
if (!author)
author = default_signature;
if (!committer)
committer = default_signature;
}
if (git_repository_index(&index, 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,
NULL, message, tree, parents.count, parents.commits);
done:
git_commitarray_dispose(&parents);
git_signature_free(default_signature);
git_tree_free(tree);
git_tree_free(head_tree);
git_diff_free(diff);
git_index_free(index);
return error;
}
int git_commit_committer_with_mailmap(
git_signature **out, const git_commit *commit, const git_mailmap *mailmap)
{

View File

@@ -0,0 +1,112 @@
#include "clar_libgit2.h"
#include "repository.h"
/* Fixture setup */
static git_repository *g_repo;
static git_signature *g_author, *g_committer;
void test_commit_create__initialize(void)
{
g_repo = cl_git_sandbox_init("testrepo2");
cl_git_pass(git_signature_new(&g_author, "Edward Thomson", "ethomson@edwardthomson.com", 123456789, 60));
cl_git_pass(git_signature_new(&g_committer, "libgit2 user", "nobody@noreply.libgit2.org", 987654321, 90));
}
void test_commit_create__cleanup(void)
{
git_signature_free(g_committer);
git_signature_free(g_author);
cl_git_sandbox_cleanup();
}
void test_commit_create__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.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_commit_create_from_stage(&commit_id, g_repo, "This is the message.", &opts));
cl_git_pass(git_repository_head_tree(&tree, g_repo));
cl_assert_equal_oidstr("241b5b04e847bc38dd7b4b9f49f21e55da40f3a6", &commit_id);
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(tree));
git_index_free(index);
git_tree_free(tree);
}
void test_commit_create__from_stage_nochanges(void)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_oid commit_id;
git_tree *tree;
opts.author = g_author;
opts.committer = g_committer;
cl_git_fail_with(GIT_EUNCHANGED, git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts));
opts.allow_empty_commit = 1;
cl_git_pass(git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts));
cl_git_pass(git_repository_head_tree(&tree, g_repo));
cl_assert_equal_oidstr("f776dc4c7fd8164b7127dc8e4f9b44421cb01b56", &commit_id);
cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree));
git_tree_free(tree);
}
void test_commit_create__from_stage_newrepo(void)
{
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
git_repository *newrepo;
git_index *index;
git_commit *commit;
git_tree *tree;
git_oid commit_id;
opts.author = g_author;
opts.committer = g_committer;
git_repository_init(&newrepo, "newrepo", false);
cl_git_pass(git_repository_index(&index, newrepo));
cl_git_rewritefile("newrepo/hello.txt", "hello, world.\n");
cl_git_rewritefile("newrepo/hi.txt", "hi there.\n");
cl_git_rewritefile("newrepo/foo.txt", "bar.\n");
cl_git_pass(git_index_add_bypath(index, "hello.txt"));
cl_git_pass(git_index_add_bypath(index, "foo.txt"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_commit_create_from_stage(&commit_id, newrepo, "Initial commit.", &opts));
cl_git_pass(git_repository_head_commit(&commit, newrepo));
cl_git_pass(git_repository_head_tree(&tree, newrepo));
cl_assert_equal_oid(&commit_id, git_commit_id(commit));
cl_assert_equal_oidstr("b2fa96a4f191c76eb172437281c66aa29609dcaa", git_commit_tree_id(commit));
git_tree_free(tree);
git_commit_free(commit);
git_index_free(index);
git_repository_free(newrepo);
cl_fixture_cleanup("newrepo");
}