Thanks to visit codestin.com
Credit goes to github.com

Skip to content

commit: split creating the commit and writing it out #3652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ v0.24 + 1

### API additions

* `git_commit_create_buffer()` creates a commit and writes it into a
user-provided buffer instead of writing it into the object db.

### API removals

### Breaking API changes
Expand Down
46 changes: 46 additions & 0 deletions include/git2/commit.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,52 @@ GIT_EXTERN(int) git_commit_amend(
const char *message,
const git_tree *tree);

/**
* Create a commit and write it into a buffer
*
* Create a commit as with `git_commit_create()` but instead of
* writing it to the objectdb, write the contents of the object into a
* buffer.
*
* @param out the buffer into which to write the commit object content
*
* @param repo Repository where the referenced tree and parents live
*
* @param author Signature with author and author time of commit
*
* @param committer Signature with committer and * commit time of commit
*
* @param message_encoding The encoding for the message in the
* commit, represented with a standard encoding name.
* E.g. "UTF-8". If NULL, no encoding header is written and
* UTF-8 is assumed.
*
* @param message Full message for this commit
*
* @param tree An instance of a `git_tree` object that will
* be used as the tree for the commit. This tree object must
* also be owned by the given `repo`.
*
* @param parent_count Number of parents for this commit
*
* @param parents Array of `parent_count` pointers to `git_commit`
* objects that will be used as the parents for this commit. This
* array may be NULL if `parent_count` is 0 (root commit). All the
* given commits must be owned by the `repo`.
*
* @return 0 or an error code
*/
GIT_EXTERN(int) git_commit_create_buffer(
git_buf *out,
git_repository *repo,
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[]);

/** @} */
GIT_END_DECL
#endif
175 changes: 128 additions & 47 deletions src/commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "message.h"
#include "refs.h"
#include "object.h"
#include "oidarray.h"

void git_commit__free(void *_commit)
{
Expand All @@ -37,94 +38,143 @@ void git_commit__free(void *_commit)
git__free(commit);
}

static int git_commit__create_internal(
git_oid *id,
static int git_commit__create_buffer_internal(
git_buf *out,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
git_commit_parent_callback parent_cb,
void *parent_payload,
bool validate)
git_array_oid_t *parents)
{
git_reference *ref = NULL;
int error = 0, matched_parent = 0;
const git_oid *current_id = NULL;
git_buf commit = GIT_BUF_INIT;
size_t i = 0;
git_odb *odb;
const git_oid *parent;

assert(id && repo && tree && parent_cb);
assert(out && repo && tree);

if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE))
return -1;
git_oid__writebuf(out, "tree ", tree);

if (update_ref) {
error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
if (error < 0 && error != GIT_ENOTFOUND)
return error;
for (i = 0; i < git_array_size(*parents); i++) {
parent = git_array_get(*parents, i);
git_oid__writebuf(out, "parent ", parent);
}
giterr_clear();

if (ref)
current_id = git_reference_target(ref);
git_signature__writebuf(out, "author ", author);
git_signature__writebuf(out, "committer ", committer);

if (message_encoding != NULL)
git_buf_printf(out, "encoding %s\n", message_encoding);

git_buf_putc(out, '\n');

git_oid__writebuf(&commit, "tree ", tree);
if (git_buf_puts(out, message) < 0)
goto on_error;

return 0;

on_error:
git_buf_free(out);
return -1;
}

static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree,
git_commit_parent_callback parent_cb, void *parent_payload,
const git_oid *current_id, bool validate)
{
size_t i;
int error;
git_oid *parent_cpy;
const git_oid *parent;

if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE))
return -1;

i = 0;
while ((parent = parent_cb(i, parent_payload)) != NULL) {
if (validate && !git_object__is_valid(repo, parent, GIT_OBJ_COMMIT)) {
error = -1;
goto on_error;
}

git_oid__writebuf(&commit, "parent ", parent);
if (i == 0 && current_id && git_oid_equal(current_id, parent))
matched_parent = 1;
parent_cpy = git_array_alloc(*parents);
GITERR_CHECK_ALLOC(parent_cpy);

git_oid_cpy(parent_cpy, parent);
i++;
}

if (ref && !matched_parent) {
git_reference_free(ref);
git_buf_free(&commit);
if (current_id && git_oid_cmp(current_id, git_array_get(*parents, 0))) {
giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent");
return GIT_EMODIFIED;
error = GIT_EMODIFIED;
goto on_error;
}

git_signature__writebuf(&commit, "author ", author);
git_signature__writebuf(&commit, "committer ", committer);
return 0;

if (message_encoding != NULL)
git_buf_printf(&commit, "encoding %s\n", message_encoding);
on_error:
git_array_clear(*parents);
return error;
}

git_buf_putc(&commit, '\n');
static int git_commit__create_internal(
git_oid *id,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
git_commit_parent_callback parent_cb,
void *parent_payload,
bool validate)
{
int error;
git_odb *odb;
git_reference *ref = NULL;
git_buf buf = GIT_BUF_INIT;
const git_oid *current_id = NULL;
git_array_oid_t parents = GIT_ARRAY_INIT;

if (git_buf_puts(&commit, message) < 0)
goto on_error;
if (update_ref) {
error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
if (error < 0 && error != GIT_ENOTFOUND)
return error;
}
giterr_clear();

if (ref)
current_id = git_reference_target(ref);

if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0)
goto cleanup;

error = git_commit__create_buffer_internal(&buf, repo, author, committer,
message_encoding, message, tree,
&parents);

if (error < 0)
goto cleanup;

if (git_repository_odb__weakptr(&odb, repo) < 0)
goto on_error;
goto cleanup;

if (git_odb_write(id, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0)
goto on_error;
if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJ_COMMIT) < 0)
goto cleanup;

git_buf_free(&commit);

if (update_ref != NULL) {
error = git_reference__update_for_commit(
repo, ref, update_ref, id, "commit");
git_reference_free(ref);
return error;
goto cleanup;
}

return 0;

on_error:
git_buf_free(&commit);
return -1;
cleanup:
git_array_clear(parents);
git_reference_free(ref);
git_buf_free(&buf);
return error;
}

int git_commit_create_from_callback(
Expand Down Expand Up @@ -739,3 +789,34 @@ int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_r
git_buf_clear(signed_data);
return error;
}

int git_commit_create_buffer(git_buf *out,
git_repository *repo,
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[])
{
int error;
commit_parent_data data = { parent_count, parents, repo };
git_array_oid_t parents_arr = GIT_ARRAY_INIT;
const git_oid *tree_id;

assert(tree && git_tree_owner(tree) == repo);

tree_id = git_tree_id(tree);

if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0)
return error;

error = git_commit__create_buffer_internal(
out, repo, author, committer,
message_encoding, message, tree_id,
&parents_arr);

git_array_clear(parents_arr);
return error;
}
39 changes: 39 additions & 0 deletions tests/commit/write.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,45 @@ void test_commit_write__from_memory(void)
cl_assert_equal_s(commit_message, git_commit_message(commit));
}

void test_commit_write__into_buf(void)
{
git_oid tree_id;
git_signature *author, *committer;
git_tree *tree;
git_commit *parent;
git_oid parent_id;
git_buf commit = GIT_BUF_INIT;

git_oid_fromstr(&tree_id, tree_id_str);
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));

/* create signatures */
cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60));
cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90));

git_oid_fromstr(&parent_id, parent_id_str);
cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id));

cl_git_pass(git_commit_create_buffer(&commit, g_repo, author, committer,
NULL, root_commit_message, tree, 1, (const git_commit **) &parent));

cl_assert_equal_s(commit.ptr,
"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
author Vicent Marti <[email protected]> 987654321 +0130\n\
committer Vicent Marti <[email protected]> 123456789 +0100\n\
\n\
This is a root commit\n\
This is a root commit and should be the only one in this branch\n\
");

git_buf_free(&commit);
git_tree_free(tree);
git_commit_free(parent);
git_signature_free(author);
git_signature_free(committer);
}

// create a root commit
void test_commit_write__root(void)
{
Expand Down