8000 commit: introduce `git_commit_create_from_stage` · russell/libgit2@67a4d04 · GitHub
[go: up one dir, main page]

Skip to content

Commit 67a4d04

Browse files
committed
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.
1 parent fddfca3 commit 67a4d04

File tree

4 files changed

+217
-1
lines changed

4 files changed

+217
-1
lines changed

include/git2/commit.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,36 @@ GIT_EXTERN(int) git_commit_create_v(
394394
size_t parent_count,
395395
...);
396396

397+
typedef struct {
398+
unsigned int version;
399+
400+
unsigned int allow_empty_commit : 1;
401+
402+
const git_signature *author;
403+
const git_signature *committer;
404+
} git_commit_create_options;
405+
406+
#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1
407+
#define GIT_COMMIT_CREATE_OPTIONS_INIT { GIT_COMMIT_CREATE_OPTIONS_VERSION }
408+
409+
/**
410+
* Commits the staged changes in the repository; this is a near analog to
411+
* `git commit -m message`.
412+
*
413+
* By default, empty commits are not allowed.
414+
*
415+
* @param id pointer to store the new commit's object id
416+
* @param repo repository to commit changes in
417+
* @param message the commit message
418+
* @param opts options for creating the commit
419+
* @return 0 on success, GIT_EUNCHANGED if there were no changes to commit, or an error code
420+
*/
421+
GIT_EXTERN(int) git_commit_create_from_stage(
422+
git_oid *id,
423+
git_repository *repo,
424+
const char *message,
425+
const git_commit_create_options *opts);
426+
397427
/**
398428
* Amend an existing commit by replacing only non-NULL values.
399429
*

include/git2/errors.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ typedef enum {
5959
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
6060
GIT_EAPPLYFAIL = -35, /**< Patch application failed */
6161
GIT_EOWNER = -36, /**< The object is not owned by the current user */
62-
GIT_TIMEOUT = -37 /**< The operation timed out */
62+
GIT_TIMEOUT = -37, /**< The operation timed out */
63+
GIT_EUNCHANGED = -38 /**< There were no changes */
6364
} git_error_code;
6465

6566
/**

src/libgit2/commit.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,79 @@ int git_commit_create_with_signature(
10861086
return error;
10871087
}
10881088

1089+
int git_commit_create_from_stage(
1090+
git_oid *out,
1091+
git_repository *repo,
1092+
const char *message,
1093+
const git_commit_create_options *given_opts)
1094+
{
1095+
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
1096+
git_signature *default_signature = NULL;
1097+
const git_signature *author, *committer;
1098+
git_index *index = NULL;
1099+
git_diff *diff = NULL;
1100+
git_oid tree_id;
1101+
git_tree *head_tree = NULL, *tree = NULL;
1102+
git_commitarray parents = { 0 };
1103+
int error = -1;
1104+
1105+
GIT_ASSERT_ARG(out && repo);
1106+
1107+
if (given_opts)
1108+
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
1109+
1110+
if ((author = opts.author) == NULL ||
1111+
(committer = opts.committer) == NULL) {
1112+
if (git_signature_default(&default_signature, repo) < 0)
1113+
goto done;
1114+
1115+
if (!author)
1116+
author = default_signature;
1117+
1118+
if (!committer)
1119+
committer = default_signature;
1120+
}
1121+
1122+
if (git_repository_index(&index, repo) < 0)
1123+
goto done;
1124+
1125+
if (!opts.allow_empty_commit) {
1126+
error = git_repository_head_tree(&head_tree, repo);
1127+
1128+
if (error && error != GIT_EUNBORNBRANCH)
1129 9E81 +
goto done;
1130+
1131+
error = -1;
1132+
1133+
if (git_diff_tree_to_index(&diff, repo, head_tree, index, NULL) < 0)
1134+
goto done;
1135+
1136+
if (git_diff_num_deltas(diff) == 0) {
1137+
git_error_set(GIT_ERROR_REPOSITORY,
1138+
"no changes are staged for commit");
1139+
error = GIT_EUNCHANGED;
1140+
goto done;
1141+
}
1142+
}
1143+
1144+
if (git_index_write_tree(&tree_id, index) < 0 ||
1145+
git_tree_lookup(&tree, repo, &tree_id) < 0 ||
1146+
git_repository_commit_parents(&parents, repo) < 0)
1147+
goto done;
1148+
1149+
error = git_commit_create(out, repo, "HEAD", author, committer,
1150+
NULL, message, tree, parents.count, parents.commits);
1151+
1152+
done:
1153+
git_commitarray_dispose(&parents);
1154+
git_signature_free(default_signature);
1155+
git_tree_free(tree);
1156+
git_tree_free(head_tree);
1157+
git_diff_free(diff);
1158+
git_index_free(index);
1159+
return error;
1160+
}
1161+
10891162
int git_commit_committer_with_mailmap(
10901163
git_signature **out, const git_commit *commit, const git_mailmap *mailmap)
10911164
{

tests/libgit2/commit/create.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#include "clar_libgit2.h"
2+
#include "repository.h"
3+
4+
/* Fixture setup */
5+
static git_repository *g_repo;
6+
static git_signature *g_author, *g_committer;
7+
8+
void test_commit_create__initialize(void)
9+
{
10+
g_repo = cl_git_sandbox_init("testrepo2");
11+
cl_git_pass(git_signature_new(&g_author, "Edward Thomson", "ethomson@edwardthomson.com", 123456789, 60));
12+
cl_git_pass(git_signature_new(&g_committer, "libgit2 user", "nobody@noreply.libgit2.org", 987654321, 90));
13+
}
14+
15+
void test_commit_create__cleanup(void)
16+
{
17+
git_signature_free(g_committer);
18+
git_signature_free(g_author);
19+
cl_git_sandbox_cleanup();
20+
}
21+
22+
23+
void test_commit_create__from_stage_simple(void)
24+
{
25+
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
26+
git_index *index;
27+
git_oid commit_id;
28+
git_tree *tree;
29+
30+
opts.author = g_author;
31+
opts.committer = g_committer;
32+
33+
cl_git_rewritefile("testrepo2/newfile.txt", "This is a new file.\n");
34+
cl_git_rewritefile("testrepo2/newfile2.txt", "This is a new file.\n");
35+
cl_git_rewritefile("testrepo2/README", "hello, world.\n");
36+
cl_git_rewritefile("testrepo2/new.txt", "hi there.\n");
37+
38+
cl_git_pass(git_repository_index(&index, g_repo));
39+
cl_git_pass(git_index_add_bypath(index, "newfile2.txt"));
40+
cl_git_pass(git_index_add_bypath(index, "README"));
41+
cl_git_pass(git_index_write(index));
42+
43+
cl_git_pass(git_commit_create_from_stage(&commit_id, g_repo, "This is the message.", &opts));
44+
45+
cl_git_pass(git_repository_head_tree(&tree, g_repo));
46+
47+
cl_assert_equal_oidstr("241b5b04e847bc38dd7b4b9f49f21e55da40f3a6", &commit_id);
48+
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(tree));
49+
50+
git_index_free(index);
51+
git_tree_free(tree);
52+
}
53+
54+
void test_commit_create__from_stage_nochanges(void)
55+
{
56+
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
57+
git_oid commit_id;
58+
git_tree *tree;
59+
60+
opts.author = g_author;
61+
opts.committer = g_committer;
62+
63+
cl_git_fail_with(GIT_EUNCHANGED, git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts));
64+
65+
opts.allow_empty_commit = 1;
66+
67+
cl_git_pass(git_commit_create_from_stage(&commit_id, g_repo, "Message goes here.", &opts));
68+
69+
cl_git_pass(git_repository_head_tree(&tree, g_repo));
70+
71+
cl_assert_equal_oidstr("f776dc4c7fd8164b7127dc8e4f9b44421cb01b56", &commit_id);
72+
cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree));
73+
74+
git_tree_free(tree);
75+
}
76+
77+
void test_commit_create__from_stage_newrepo(void)
78+
{
79+
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
80+
git_repository *newrepo;
81+
git_index *index;
82+
git_commit *commit;
83+
git_tree *tree;
84+
git_oid commit_id;
85+
86+
opts.author = g_author;
87+
opts.committer = g_committer;
88+
89+
git_repository_init(&newrepo, "newrepo", false);
90+
cl_git_pass(git_repository_index(&index, newrepo));
91+
92+
cl_git_rewritefile("newrepo/hello.txt", "hello, world.\n");
93+
cl_git_rewritefile("newrepo/hi.txt", "hi there.\n");
94+
cl_git_rewritefile("newrepo/foo.txt", "bar.\n");
95+
96+
cl_git_pass(git_index_add_bypath(index, "hello.txt"));
97+
cl_git_pass(git_index_add_bypath(index, "foo.txt"));
98+
cl_git_pass(git_index_write(index));
99+
100+
cl_git_pass(git_commit_create_from_stage(&commit_id, newrepo, "Initial commit.", &opts));
101+
cl_git_pass(git_repository_head_commit(&commit, newrepo));
102+
cl_git_pass(git_repository_head_tree(&tree, newrepo));
103+
104+
cl_assert_equal_oid(&commit_id, git_commit_id(commit));
105+
cl_assert_equal_oidstr("b2fa96a4f191c76eb172437281c66aa29609dcaa", git_commit_tree_id(commit));
106+
107+
git_tree_free(tree);
108+
git_commit_free(commit);
109+
git_index_free(index);
110+
git_repository_free(newrepo);
111+
cl_fixture_cleanup("newrepo");
112+
}

0 commit comments

Comments
 (0)
0