mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-10 05:34:34 +00:00

When parsing a commit, we will treat all bytes left after parsing the headers as the commit message. When no bytes are left, we leave the commit's message uninitialized. While uncommon to have a commit without message, this is the right behavior as Git unfortunately allows for empty commit messages. Given that this scenario is so uncommon, most programs acting on the commit message will never check if the message is actually set, which may lead to errors. To work around the error and not lay the burden of checking for empty commit messages to the developer, initialize the commit message with an empty string when no commit message is given.
887 lines
21 KiB
C
887 lines
21 KiB
C
/*
|
|
* Copyright (C) the libgit2 contributors. All rights reserved.
|
|
*
|
|
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
|
* a Linking Exception. For full terms see the included COPYING file.
|
|
*/
|
|
|
|
#include "git2/common.h"
|
|
#include "git2/object.h"
|
|
#include "git2/repository.h"
|
|
#include "git2/signature.h"
|
|
#include "git2/sys/commit.h"
|
|
|
|
#include "common.h"
|
|
#include "odb.h"
|
|
#include "commit.h"
|
|
#include "signature.h"
|
|
#include "message.h"
|
|
#include "refs.h"
|
|
#include "object.h"
|
|
#include "oidarray.h"
|
|
|
|
void git_commit__free(void *_commit)
|
|
{
|
|
git_commit *commit = _commit;
|
|
|
|
git_array_clear(commit->parent_ids);
|
|
|
|
git_signature_free(commit->author);
|
|
git_signature_free(commit->committer);
|
|
|
|
git__free(commit->raw_header);
|
|
git__free(commit->raw_message);
|
|
git__free(commit->message_encoding);
|
|
git__free(commit->summary);
|
|
git__free(commit->body);
|
|
|
|
git__free(commit);
|
|
}
|
|
|
|
static int git_commit__create_buffer_internal(
|
|
git_buf *out,
|
|
const git_signature *author,
|
|
const git_signature *committer,
|
|
const char *message_encoding,
|
|
const char *message,
|
|
const git_oid *tree,
|
|
git_array_oid_t *parents)
|
|
{
|
|
size_t i = 0;
|
|
const git_oid *parent;
|
|
|
|
assert(out && tree);
|
|
|
|
git_oid__writebuf(out, "tree ", tree);
|
|
|
|
for (i = 0; i < git_array_size(*parents); i++) {
|
|
parent = git_array_get(*parents, i);
|
|
git_oid__writebuf(out, "parent ", parent);
|
|
}
|
|
|
|
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');
|
|
|
|
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;
|
|
}
|
|
|
|
parent_cpy = git_array_alloc(*parents);
|
|
GITERR_CHECK_ALLOC(parent_cpy);
|
|
|
|
git_oid_cpy(parent_cpy, parent);
|
|
i++;
|
|
}
|
|
|
|
if (current_id && (parents->size == 0 || 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");
|
|
error = GIT_EMODIFIED;
|
|
goto on_error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
on_error:
|
|
git_array_clear(*parents);
|
|
return error;
|
|
}
|
|
|
|
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 (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, author, committer,
|
|
message_encoding, message, tree,
|
|
&parents);
|
|
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
if (git_repository_odb__weakptr(&odb, repo) < 0)
|
|
goto cleanup;
|
|
|
|
if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJ_COMMIT) < 0)
|
|
goto cleanup;
|
|
|
|
|
|
if (update_ref != NULL) {
|
|
error = git_reference__update_for_commit(
|
|
repo, ref, update_ref, id, "commit");
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
git_array_clear(parents);
|
|
git_reference_free(ref);
|
|
git_buf_free(&buf);
|
|
return error;
|
|
}
|
|
|
|
int git_commit_create_from_callback(
|
|
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)
|
|
{
|
|
return git_commit__create_internal(
|
|
id, repo, update_ref, author, committer, message_encoding, message,
|
|
tree, parent_cb, parent_payload, true);
|
|
}
|
|
|
|
typedef struct {
|
|
size_t total;
|
|
va_list args;
|
|
} commit_parent_varargs;
|
|
|
|
static const git_oid *commit_parent_from_varargs(size_t curr, void *payload)
|
|
{
|
|
commit_parent_varargs *data = payload;
|
|
const git_commit *commit;
|
|
if (curr >= data->total)
|
|
return NULL;
|
|
commit = va_arg(data->args, const git_commit *);
|
|
return commit ? git_commit_id(commit) : NULL;
|
|
}
|
|
|
|
int git_commit_create_v(
|
|
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_tree *tree,
|
|
size_t parent_count,
|
|
...)
|
|
{
|
|
int error = 0;
|
|
commit_parent_varargs data;
|
|
|
|
assert(tree && git_tree_owner(tree) == repo);
|
|
|
|
data.total = parent_count;
|
|
va_start(data.args, parent_count);
|
|
|
|
error = git_commit__create_internal(
|
|
id, repo, update_ref, author, committer,
|
|
message_encoding, message, git_tree_id(tree),
|
|
commit_parent_from_varargs, &data, false);
|
|
|
|
va_end(data.args);
|
|
return error;
|
|
}
|
|
|
|
typedef struct {
|
|
size_t total;
|
|
const git_oid **parents;
|
|
} commit_parent_oids;
|
|
|
|
static const git_oid *commit_parent_from_ids(size_t curr, void *payload)
|
|
{
|
|
commit_parent_oids *data = payload;
|
|
return (curr < data->total) ? data->parents[curr] : NULL;
|
|
}
|
|
|
|
int git_commit_create_from_ids(
|
|
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,
|
|
size_t parent_count,
|
|
const git_oid *parents[])
|
|
{
|
|
commit_parent_oids data = { parent_count, parents };
|
|
|
|
return git_commit__create_internal(
|
|
id, repo, update_ref, author, committer,
|
|
message_encoding, message, tree,
|
|
commit_parent_from_ids, &data, true);
|
|
}
|
|
|
|
typedef struct {
|
|
size_t total;
|
|
const git_commit **parents;
|
|
git_repository *repo;
|
|
} commit_parent_data;
|
|
|
|
static const git_oid *commit_parent_from_array(size_t curr, void *payload)
|
|
{
|
|
commit_parent_data *data = payload;
|
|
const git_commit *commit;
|
|
if (curr >= data->total)
|
|
return NULL;
|
|
commit = data->parents[curr];
|
|
if (git_commit_owner(commit) != data->repo)
|
|
return NULL;
|
|
return git_commit_id(commit);
|
|
}
|
|
|
|
int git_commit_create(
|
|
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_tree *tree,
|
|
size_t parent_count,
|
|
const git_commit *parents[])
|
|
{
|
|
commit_parent_data data = { parent_count, parents, repo };
|
|
|
|
assert(tree && git_tree_owner(tree) == repo);
|
|
|
|
return git_commit__create_internal(
|
|
id, repo, update_ref, author, committer,
|
|
message_encoding, message, git_tree_id(tree),
|
|
commit_parent_from_array, &data, false);
|
|
}
|
|
|
|
static const git_oid *commit_parent_for_amend(size_t curr, void *payload)
|
|
{
|
|
const git_commit *commit_to_amend = payload;
|
|
if (curr >= git_array_size(commit_to_amend->parent_ids))
|
|
return NULL;
|
|
return git_array_get(commit_to_amend->parent_ids, curr);
|
|
}
|
|
|
|
int git_commit_amend(
|
|
git_oid *id,
|
|
const git_commit *commit_to_amend,
|
|
const char *update_ref,
|
|
const git_signature *author,
|
|
const git_signature *committer,
|
|
const char *message_encoding,
|
|
const char *message,
|
|
const git_tree *tree)
|
|
{
|
|
git_repository *repo;
|
|
git_oid tree_id;
|
|
git_reference *ref;
|
|
int error;
|
|
|
|
assert(id && commit_to_amend);
|
|
|
|
repo = git_commit_owner(commit_to_amend);
|
|
|
|
if (!author)
|
|
author = git_commit_author(commit_to_amend);
|
|
if (!committer)
|
|
committer = git_commit_committer(commit_to_amend);
|
|
if (!message_encoding)
|
|
message_encoding = git_commit_message_encoding(commit_to_amend);
|
|
if (!message)
|
|
message = git_commit_message(commit_to_amend);
|
|
|
|
if (!tree) {
|
|
git_tree *old_tree;
|
|
GITERR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) );
|
|
git_oid_cpy(&tree_id, git_tree_id(old_tree));
|
|
git_tree_free(old_tree);
|
|
} else {
|
|
assert(git_tree_owner(tree) == repo);
|
|
git_oid_cpy(&tree_id, git_tree_id(tree));
|
|
}
|
|
|
|
if (update_ref) {
|
|
if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0)
|
|
return error;
|
|
|
|
if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) {
|
|
git_reference_free(ref);
|
|
giterr_set(GITERR_REFERENCE, "commit to amend is not the tip of the given branch");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
error = git_commit__create_internal(
|
|
id, repo, NULL, author, committer, message_encoding, message,
|
|
&tree_id, commit_parent_for_amend, (void *)commit_to_amend, false);
|
|
|
|
if (!error && update_ref) {
|
|
error = git_reference__update_for_commit(
|
|
repo, ref, NULL, id, "commit");
|
|
git_reference_free(ref);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_commit__parse(void *_commit, git_odb_object *odb_obj)
|
|
{
|
|
git_commit *commit = _commit;
|
|
const char *buffer_start = git_odb_object_data(odb_obj), *buffer;
|
|
const char *buffer_end = buffer_start + git_odb_object_size(odb_obj);
|
|
git_oid parent_id;
|
|
size_t header_len;
|
|
git_signature dummy_sig;
|
|
|
|
buffer = buffer_start;
|
|
|
|
/* Allocate for one, which will allow not to realloc 90% of the time */
|
|
git_array_init_to_size(commit->parent_ids, 1);
|
|
GITERR_CHECK_ARRAY(commit->parent_ids);
|
|
|
|
/* The tree is always the first field */
|
|
if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
|
|
goto bad_buffer;
|
|
|
|
/*
|
|
* TODO: commit grafts!
|
|
*/
|
|
|
|
while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
|
|
git_oid *new_id = git_array_alloc(commit->parent_ids);
|
|
GITERR_CHECK_ALLOC(new_id);
|
|
|
|
git_oid_cpy(new_id, &parent_id);
|
|
}
|
|
|
|
commit->author = git__malloc(sizeof(git_signature));
|
|
GITERR_CHECK_ALLOC(commit->author);
|
|
|
|
if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0)
|
|
return -1;
|
|
|
|
/* Some tools create multiple author fields, ignore the extra ones */
|
|
while ((size_t)(buffer_end - buffer) >= strlen("author ") && !git__prefixcmp(buffer, "author ")) {
|
|
if (git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n') < 0)
|
|
return -1;
|
|
|
|
git__free(dummy_sig.name);
|
|
git__free(dummy_sig.email);
|
|
}
|
|
|
|
/* Always parse the committer; we need the commit time */
|
|
commit->committer = git__malloc(sizeof(git_signature));
|
|
GITERR_CHECK_ALLOC(commit->committer);
|
|
|
|
if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
|
|
return -1;
|
|
|
|
/* Parse add'l header entries */
|
|
while (buffer < buffer_end) {
|
|
const char *eoln = buffer;
|
|
if (buffer[-1] == '\n' && buffer[0] == '\n')
|
|
break;
|
|
|
|
while (eoln < buffer_end && *eoln != '\n')
|
|
++eoln;
|
|
|
|
if (git__prefixcmp(buffer, "encoding ") == 0) {
|
|
buffer += strlen("encoding ");
|
|
|
|
commit->message_encoding = git__strndup(buffer, eoln - buffer);
|
|
GITERR_CHECK_ALLOC(commit->message_encoding);
|
|
}
|
|
|
|
if (eoln < buffer_end && *eoln == '\n')
|
|
++eoln;
|
|
buffer = eoln;
|
|
}
|
|
|
|
header_len = buffer - buffer_start;
|
|
commit->raw_header = git__strndup(buffer_start, header_len);
|
|
GITERR_CHECK_ALLOC(commit->raw_header);
|
|
|
|
/* point "buffer" to data after header, +1 for the final LF */
|
|
buffer = buffer_start + header_len + 1;
|
|
|
|
/* extract commit message */
|
|
if (buffer <= buffer_end)
|
|
commit->raw_message = git__strndup(buffer, buffer_end - buffer);
|
|
else
|
|
commit->raw_message = git__strdup("");
|
|
GITERR_CHECK_ALLOC(commit->raw_message);
|
|
|
|
return 0;
|
|
|
|
bad_buffer:
|
|
giterr_set(GITERR_OBJECT, "Failed to parse bad commit object");
|
|
return -1;
|
|
}
|
|
|
|
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
|
|
_rvalue git_commit_##_name(const git_commit *commit) \
|
|
{\
|
|
assert(commit); \
|
|
return _return; \
|
|
}
|
|
|
|
GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
|
|
GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
|
|
GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message)
|
|
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
|
|
GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
|
|
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
|
|
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
|
|
GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
|
|
GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id)
|
|
|
|
const char *git_commit_message(const git_commit *commit)
|
|
{
|
|
const char *message;
|
|
|
|
assert(commit);
|
|
|
|
message = commit->raw_message;
|
|
|
|
/* trim leading newlines from raw message */
|
|
while (*message && *message == '\n')
|
|
++message;
|
|
|
|
return message;
|
|
}
|
|
|
|
const char *git_commit_summary(git_commit *commit)
|
|
{
|
|
git_buf summary = GIT_BUF_INIT;
|
|
const char *msg, *space;
|
|
bool space_contains_newline = false;
|
|
|
|
assert(commit);
|
|
|
|
if (!commit->summary) {
|
|
for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) {
|
|
char next_character = msg[0];
|
|
/* stop processing at the end of the first paragraph */
|
|
if (next_character == '\n' && (!msg[1] || msg[1] == '\n'))
|
|
break;
|
|
/* record the beginning of contiguous whitespace runs */
|
|
else if (git__isspace(next_character)) {
|
|
if(space == NULL) {
|
|
space = msg;
|
|
space_contains_newline = false;
|
|
}
|
|
space_contains_newline |= next_character == '\n';
|
|
}
|
|
/* the next character is non-space */
|
|
else {
|
|
/* process any recorded whitespace */
|
|
if (space) {
|
|
if(space_contains_newline)
|
|
git_buf_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */
|
|
else
|
|
git_buf_put(&summary, space, (msg - space)); /* otherwise copy it */
|
|
space = NULL;
|
|
}
|
|
/* copy the next character */
|
|
git_buf_putc(&summary, next_character);
|
|
}
|
|
}
|
|
|
|
commit->summary = git_buf_detach(&summary);
|
|
if (!commit->summary)
|
|
commit->summary = git__strdup("");
|
|
}
|
|
|
|
return commit->summary;
|
|
}
|
|
|
|
const char *git_commit_body(git_commit *commit)
|
|
{
|
|
const char *msg, *end;
|
|
|
|
assert(commit);
|
|
|
|
if (!commit->body) {
|
|
/* search for end of summary */
|
|
for (msg = git_commit_message(commit); *msg; ++msg)
|
|
if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n'))
|
|
break;
|
|
|
|
/* trim leading and trailing whitespace */
|
|
for (; *msg; ++msg)
|
|
if (!git__isspace(*msg))
|
|
break;
|
|
for (end = msg + strlen(msg) - 1; msg <= end; --end)
|
|
if (!git__isspace(*end))
|
|
break;
|
|
|
|
if (*msg)
|
|
commit->body = git__strndup(msg, end - msg + 1);
|
|
}
|
|
|
|
return commit->body;
|
|
}
|
|
|
|
int git_commit_tree(git_tree **tree_out, const git_commit *commit)
|
|
{
|
|
assert(commit);
|
|
return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
|
|
}
|
|
|
|
const git_oid *git_commit_parent_id(
|
|
const git_commit *commit, unsigned int n)
|
|
{
|
|
assert(commit);
|
|
|
|
return git_array_get(commit->parent_ids, n);
|
|
}
|
|
|
|
int git_commit_parent(
|
|
git_commit **parent, const git_commit *commit, unsigned int n)
|
|
{
|
|
const git_oid *parent_id;
|
|
assert(commit);
|
|
|
|
parent_id = git_commit_parent_id(commit, n);
|
|
if (parent_id == NULL) {
|
|
giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
|
|
return GIT_ENOTFOUND;
|
|
}
|
|
|
|
return git_commit_lookup(parent, commit->object.repo, parent_id);
|
|
}
|
|
|
|
int git_commit_nth_gen_ancestor(
|
|
git_commit **ancestor,
|
|
const git_commit *commit,
|
|
unsigned int n)
|
|
{
|
|
git_commit *current, *parent = NULL;
|
|
int error;
|
|
|
|
assert(ancestor && commit);
|
|
|
|
if (git_commit_dup(¤t, (git_commit *)commit) < 0)
|
|
return -1;
|
|
|
|
if (n == 0) {
|
|
*ancestor = current;
|
|
return 0;
|
|
}
|
|
|
|
while (n--) {
|
|
error = git_commit_parent(&parent, current, 0);
|
|
|
|
git_commit_free(current);
|
|
|
|
if (error < 0)
|
|
return error;
|
|
|
|
current = parent;
|
|
}
|
|
|
|
*ancestor = parent;
|
|
return 0;
|
|
}
|
|
|
|
int git_commit_header_field(git_buf *out, const git_commit *commit, const char *field)
|
|
{
|
|
const char *eol, *buf = commit->raw_header;
|
|
|
|
git_buf_sanitize(out);
|
|
|
|
while ((eol = strchr(buf, '\n'))) {
|
|
/* We can skip continuations here */
|
|
if (buf[0] == ' ') {
|
|
buf = eol + 1;
|
|
continue;
|
|
}
|
|
|
|
/* Skip until we find the field we're after */
|
|
if (git__prefixcmp(buf, field)) {
|
|
buf = eol + 1;
|
|
continue;
|
|
}
|
|
|
|
buf += strlen(field);
|
|
/* Check that we're not matching a prefix but the field itself */
|
|
if (buf[0] != ' ') {
|
|
buf = eol + 1;
|
|
continue;
|
|
}
|
|
|
|
buf++; /* skip the SP */
|
|
|
|
git_buf_put(out, buf, eol - buf);
|
|
if (git_buf_oom(out))
|
|
goto oom;
|
|
|
|
/* If the next line starts with SP, it's multi-line, we must continue */
|
|
while (eol[1] == ' ') {
|
|
git_buf_putc(out, '\n');
|
|
buf = eol + 2;
|
|
eol = strchr(buf, '\n');
|
|
if (!eol)
|
|
goto malformed;
|
|
|
|
git_buf_put(out, buf, eol - buf);
|
|
}
|
|
|
|
if (git_buf_oom(out))
|
|
goto oom;
|
|
|
|
return 0;
|
|
}
|
|
|
|
giterr_set(GITERR_OBJECT, "no such field '%s'", field);
|
|
return GIT_ENOTFOUND;
|
|
|
|
malformed:
|
|
giterr_set(GITERR_OBJECT, "malformed header");
|
|
return -1;
|
|
oom:
|
|
giterr_set_oom();
|
|
return -1;
|
|
}
|
|
|
|
int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field)
|
|
{
|
|
git_odb_object *obj;
|
|
git_odb *odb;
|
|
const char *buf;
|
|
const char *h, *eol;
|
|
int error;
|
|
|
|
git_buf_sanitize(signature);
|
|
git_buf_sanitize(signed_data);
|
|
|
|
if (!field)
|
|
field = "gpgsig";
|
|
|
|
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
|
|
return error;
|
|
|
|
if ((error = git_odb_read(&obj, odb, commit_id)) < 0)
|
|
return error;
|
|
|
|
if (obj->cached.type != GIT_OBJ_COMMIT) {
|
|
giterr_set(GITERR_INVALID, "the requested type does not match the type in ODB");
|
|
error = GIT_ENOTFOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
buf = git_odb_object_data(obj);
|
|
|
|
while ((h = strchr(buf, '\n')) && h[1] != '\0') {
|
|
h++;
|
|
if (git__prefixcmp(buf, field)) {
|
|
if (git_buf_put(signed_data, buf, h - buf) < 0)
|
|
return -1;
|
|
|
|
buf = h;
|
|
continue;
|
|
}
|
|
|
|
h = buf;
|
|
h += strlen(field);
|
|
eol = strchr(h, '\n');
|
|
if (h[0] != ' ') {
|
|
buf = h;
|
|
continue;
|
|
}
|
|
if (!eol)
|
|
goto malformed;
|
|
|
|
h++; /* skip the SP */
|
|
|
|
git_buf_put(signature, h, eol - h);
|
|
if (git_buf_oom(signature))
|
|
goto oom;
|
|
|
|
/* If the next line starts with SP, it's multi-line, we must continue */
|
|
while (eol[1] == ' ') {
|
|
git_buf_putc(signature, '\n');
|
|
h = eol + 2;
|
|
eol = strchr(h, '\n');
|
|
if (!eol)
|
|
goto malformed;
|
|
|
|
git_buf_put(signature, h, eol - h);
|
|
}
|
|
|
|
if (git_buf_oom(signature))
|
|
goto oom;
|
|
|
|
git_odb_object_free(obj);
|
|
return git_buf_puts(signed_data, eol+1);
|
|
}
|
|
|
|
giterr_set(GITERR_OBJECT, "this commit is not signed");
|
|
error = GIT_ENOTFOUND;
|
|
goto cleanup;
|
|
|
|
malformed:
|
|
giterr_set(GITERR_OBJECT, "malformed header");
|
|
error = -1;
|
|
goto cleanup;
|
|
oom:
|
|
giterr_set_oom();
|
|
error = -1;
|
|
goto cleanup;
|
|
|
|
cleanup:
|
|
git_odb_object_free(obj);
|
|
git_buf_clear(signature);
|
|
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, author, committer,
|
|
message_encoding, message, tree_id,
|
|
&parents_arr);
|
|
|
|
git_array_clear(parents_arr);
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* Append to 'out' properly marking continuations when there's a newline in 'content'
|
|
*/
|
|
static void format_header_field(git_buf *out, const char *field, const char *content)
|
|
{
|
|
const char *lf;
|
|
|
|
assert(out && field && content);
|
|
|
|
git_buf_puts(out, field);
|
|
git_buf_putc(out, ' ');
|
|
|
|
while ((lf = strchr(content, '\n')) != NULL) {
|
|
git_buf_put(out, content, lf - content);
|
|
git_buf_puts(out, "\n ");
|
|
content = lf + 1;
|
|
}
|
|
|
|
git_buf_puts(out, content);
|
|
git_buf_putc(out, '\n');
|
|
}
|
|
|
|
int git_commit_create_with_signature(
|
|
git_oid *out,
|
|
git_repository *repo,
|
|
const char *commit_content,
|
|
const char *signature,
|
|
const char *signature_field)
|
|
{
|
|
git_odb *odb;
|
|
int error = 0;
|
|
const char *field;
|
|
const char *header_end;
|
|
git_buf commit = GIT_BUF_INIT;
|
|
|
|
/* We start by identifying the end of the commit header */
|
|
header_end = strstr(commit_content, "\n\n");
|
|
if (!header_end) {
|
|
giterr_set(GITERR_INVALID, "malformed commit contents");
|
|
return -1;
|
|
}
|
|
|
|
field = signature_field ? signature_field : "gpgsig";
|
|
|
|
/* The header ends after the first LF */
|
|
header_end++;
|
|
git_buf_put(&commit, commit_content, header_end - commit_content);
|
|
format_header_field(&commit, field, signature);
|
|
git_buf_puts(&commit, header_end);
|
|
|
|
if (git_buf_oom(&commit))
|
|
return -1;
|
|
|
|
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
|
|
goto cleanup;
|
|
|
|
if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT)) < 0)
|
|
goto cleanup;
|
|
|
|
cleanup:
|
|
git_buf_free(&commit);
|
|
return error;
|
|
}
|