mirror of
https://github.com/libgit2/libgit2.git
synced 2026-06-22 06:26:26 +00:00
Merge pull request #7237 from libgit2/ethomson/commit
Introduce a new commit API
This commit is contained in:
@@ -294,7 +294,8 @@ GIT_EXTERN(int) git_commit_nth_gen_ancestor(
|
||||
* @return 0 on succeess, GIT_ENOTFOUND if the field does not exist,
|
||||
* or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit, const char *field);
|
||||
GIT_EXTERN(int) git_commit_header_field(
|
||||
git_buf *out, const git_commit *commit, const char *field);
|
||||
|
||||
/**
|
||||
* Extract the signature from a commit
|
||||
@@ -316,6 +317,182 @@ GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit,
|
||||
*/
|
||||
GIT_EXTERN(int) git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field);
|
||||
|
||||
|
||||
/** The field name and value for a custom commit header entry. */
|
||||
typedef struct {
|
||||
const char *field;
|
||||
const char *value;
|
||||
} git_commit_header;
|
||||
|
||||
/** An accessor object for setting commit signature data. */
|
||||
typedef struct git_commitbuilder git_commitbuilder;
|
||||
|
||||
/**
|
||||
* Add a header on a commit being created.
|
||||
*
|
||||
* @param builder in-progress commitbuilder object
|
||||
* @param field the header field to add
|
||||
* @param value the header value to add
|
||||
* @return 0 on success or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_commitbuilder_add_header(
|
||||
git_commitbuilder *builder,
|
||||
const char *field,
|
||||
const char *value);
|
||||
|
||||
/**
|
||||
* Commit signing callback: used when a function is going to write
|
||||
* a commit (for example, in `git_commit_create_ext`) to allow callers
|
||||
* to sign the commit.
|
||||
*
|
||||
* @param builder commit builder object to populate with the signature
|
||||
* @param repo the repository being committed in
|
||||
* @param commit_content the raw contents of the commit (to be signed)
|
||||
* @param payload the caller-specified callback payload
|
||||
* @return 0 if this callback has created a signature and populated the
|
||||
* field and signature buffers, `GIT_PASSTHROUGH` if the callback
|
||||
* does not want to sign the commit, any other value to stop and
|
||||
* return a failure
|
||||
*/
|
||||
typedef int GIT_CALLBACK(git_commit_signature_cb)(
|
||||
git_commitbuilder *builder,
|
||||
git_repository *repo,
|
||||
const char *commit_content,
|
||||
void *payload);
|
||||
|
||||
typedef struct {
|
||||
unsigned int version;
|
||||
|
||||
/**
|
||||
* Flags for creating the commit.
|
||||
*
|
||||
* If `allow_empty_commit` is specified, a commit with no changes
|
||||
* from the prior commit (an "empty" commit) is allowed. Otherwise,
|
||||
* commit creation will be stopped.
|
||||
*/
|
||||
unsigned int allow_empty_commit : 1;
|
||||
|
||||
/** The commit author, or NULL for the default. */
|
||||
const git_signature *author;
|
||||
|
||||
/** The committer, or NULL for the default. */
|
||||
const git_signature *committer;
|
||||
|
||||
/** Encoding for the commit message; leave NULL for default. */
|
||||
const char *message_encoding;
|
||||
|
||||
/**
|
||||
* Extra headers can be specified as an array of field name and
|
||||
* value pairs.
|
||||
*/
|
||||
const git_commit_header *extra_headers;
|
||||
size_t extra_headers_len; /**< Number of extra headers */
|
||||
|
||||
/** Signing callback; leave NULL for no commit signing. */
|
||||
git_commit_signature_cb sign;
|
||||
|
||||
/** Callback payload (optional) */
|
||||
void *payload;
|
||||
} git_commit_create_options;
|
||||
|
||||
/** Current version for the `git_commit_create_options` structure */
|
||||
#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1
|
||||
|
||||
/** Static constructor for `git_commit_create_options` */
|
||||
#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);
|
||||
|
||||
typedef struct {
|
||||
unsigned int version;
|
||||
|
||||
/**
|
||||
* If not NULL, the name of the reference that will be updated
|
||||
* to point to this commit. If the reference is not direct, it
|
||||
* will be resolved to a direct reference. Use `HEAD` to update
|
||||
* the `HEAD` of the current branch and make it point to this
|
||||
* commit. If the reference doesn't exist yet, it will be created.
|
||||
* If it does exist, the first parent must be the tip of this
|
||||
* branch.
|
||||
*/
|
||||
const char *update_ref;
|
||||
|
||||
/** Encoding for the commit message; leave NULL for default. */
|
||||
const char *message_encoding;
|
||||
|
||||
/**
|
||||
* Extra headers can be specified as an array of field name and
|
||||
* value pairs.
|
||||
*/
|
||||
const git_commit_header *extra_headers;
|
||||
size_t extra_headers_len; /**< Number of extra headers */
|
||||
|
||||
/** Signing callback; leave NULL for no commit signing. */
|
||||
git_commit_signature_cb sign;
|
||||
|
||||
/** Callback payload (optional) */
|
||||
void *payload;
|
||||
} git_commit_create_ext_options;
|
||||
|
||||
/** Current version for the `git_commit_create_ext_options` structure */
|
||||
#define GIT_COMMIT_CREATE_EXT_OPTIONS_VERSION 1
|
||||
|
||||
/** Static constructor for `git_commit_create_ext_options` */
|
||||
#define GIT_COMMIT_CREATE_EXT_OPTIONS_INIT { GIT_COMMIT_CREATE_EXT_OPTIONS_VERSION }
|
||||
|
||||
/**
|
||||
* Create a new commit object and write it to the object database.
|
||||
*
|
||||
* Note that the message will _not_ be cleaned up automatically. You
|
||||
* may wish to use the `git_message_prettify` API to clean it up.
|
||||
*
|
||||
* This API provides the most control for generating commit objects;
|
||||
* this is useful if (for example) you have a tree object already.
|
||||
* If you have staged changes, and just want to emulate `git commit`
|
||||
* on the command line, then `git_commit_create_from_stage` may be a
|
||||
* simpler option.
|
||||
*
|
||||
* @param id_out pointer to store the new commit's object id
|
||||
* @param repo repository to store the commit in
|
||||
* @param author The name of the author and time of authorship
|
||||
* @param committer The name of the committer and time of commit
|
||||
* @param message Full message for this commit
|
||||
* @param tree The `git_tree` that will be used as the tree for this commit
|
||||
* @param parent_count Number of parents for this commit
|
||||
* @param parents Array pointers to `git_commit` objects that will be used
|
||||
* as parents for this commit.
|
||||
* @param opts Additional options for creating this commit,
|
||||
* or `NULL` for defaults
|
||||
* @return 0 on success or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_commit_create_ext(
|
||||
git_oid *id_out,
|
||||
git_repository *repo,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message,
|
||||
const git_tree *tree,
|
||||
size_t parent_count,
|
||||
git_commit * const parents[],
|
||||
const git_commit_create_ext_options *opts);
|
||||
|
||||
|
||||
/**
|
||||
* Create new commit in the repository from a list of `git_object` pointers
|
||||
*
|
||||
@@ -429,49 +606,55 @@ GIT_EXTERN(int) git_commit_create_v(
|
||||
size_t parent_count,
|
||||
...);
|
||||
|
||||
typedef struct {
|
||||
unsigned int version;
|
||||
|
||||
/**
|
||||
* Flags for creating the commit.
|
||||
*
|
||||
* If `allow_empty_commit` is specified, a commit with no changes
|
||||
* from the prior commit (and "empty" commit) is allowed. Otherwise,
|
||||
* commit creation will be stopped.
|
||||
*/
|
||||
unsigned int allow_empty_commit : 1;
|
||||
|
||||
/** The commit author, or NULL for the default. */
|
||||
const git_signature *author;
|
||||
|
||||
/** The committer, or NULL for the default. */
|
||||
const git_signature *committer;
|
||||
|
||||
/** Encoding for the commit message; leave NULL for default. */
|
||||
const char *message_encoding;
|
||||
} git_commit_create_options;
|
||||
|
||||
/** Current version for the `git_commit_create_options` structure */
|
||||
#define GIT_COMMIT_CREATE_OPTIONS_VERSION 1
|
||||
|
||||
/** Static constructor for `git_commit_create_options` */
|
||||
#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`.
|
||||
* A "batteries included" commit creation from a tree using defaults.
|
||||
*
|
||||
* 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 tree tree to point the commit to
|
||||
* @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_EXTERN(int) git_commit_create_from_tree(
|
||||
git_oid *id,
|
||||
git_repository *repo,
|
||||
const git_tree *tree,
|
||||
const char *message,
|
||||
const git_commit_create_options *opts);
|
||||
|
||||
/**
|
||||
* Amends the HEAD commit in the repository using the staged changes;
|
||||
* this is a near analog to `git commit --amend -m message`.
|
||||
*
|
||||
* @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 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_commit_amend_from_stage(
|
||||
git_oid *id,
|
||||
git_repository *repo,
|
||||
const char *message,
|
||||
const git_commit_create_options *opts);
|
||||
|
||||
/**
|
||||
* Amends the HEAD commit in the repository using the given tree.
|
||||
*
|
||||
* @param id pointer to store the new commit's object id
|
||||
* @param repo repository to commit changes in
|
||||
* @param tree tree to point the commit to
|
||||
* @param message the commit message
|
||||
* @param opts options for creating the commit
|
||||
* @return 0 on success or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_commit_amend_from_tree(
|
||||
git_oid *id,
|
||||
git_repository *repo,
|
||||
const git_tree *tree,
|
||||
const char *message,
|
||||
const git_commit_create_options *opts);
|
||||
|
||||
|
||||
@@ -42,14 +42,40 @@ void git_commit__free(void *_commit)
|
||||
git__free(commit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to 'out' properly marking continuations when there's a newline in 'content'
|
||||
*/
|
||||
static int format_header_field(git_str *out, const char *field, const char *content)
|
||||
{
|
||||
const char *lf;
|
||||
|
||||
GIT_ASSERT_ARG(out);
|
||||
GIT_ASSERT_ARG(field);
|
||||
GIT_ASSERT_ARG(content);
|
||||
|
||||
git_str_puts(out, field);
|
||||
git_str_putc(out, ' ');
|
||||
|
||||
while ((lf = strchr(content, '\n')) != NULL) {
|
||||
git_str_put(out, content, lf - content);
|
||||
git_str_puts(out, "\n ");
|
||||
content = lf + 1;
|
||||
}
|
||||
|
||||
git_str_puts(out, content);
|
||||
git_str_putc(out, '\n');
|
||||
|
||||
return git_str_oom(out) ? -1 : 0;
|
||||
}
|
||||
|
||||
static int git_commit__create_buffer_internal(
|
||||
git_str *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)
|
||||
git_array_oid_t *parents,
|
||||
const git_commit_create_ext_options *opts)
|
||||
{
|
||||
size_t i = 0;
|
||||
const git_oid *parent;
|
||||
@@ -69,8 +95,20 @@ static int git_commit__create_buffer_internal(
|
||||
git_signature__writebuf(out, "author ", author);
|
||||
git_signature__writebuf(out, "committer ", committer);
|
||||
|
||||
if (message_encoding != NULL)
|
||||
git_str_printf(out, "encoding %s\n", message_encoding);
|
||||
if (opts && opts->message_encoding != NULL) {
|
||||
if (format_header_field(out, "encoding",
|
||||
opts->message_encoding) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if (opts && opts->extra_headers_len) {
|
||||
for (i = 0; i < opts->extra_headers_len; i++) {
|
||||
if (format_header_field(out,
|
||||
opts->extra_headers[i].field,
|
||||
opts->extra_headers[i].value) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
git_str_putc(out, '\n');
|
||||
|
||||
@@ -123,59 +161,67 @@ on_error:
|
||||
return error;
|
||||
}
|
||||
|
||||
struct git_commitbuilder {
|
||||
git_str *contents;
|
||||
};
|
||||
|
||||
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,
|
||||
const git_commit_create_ext_options *opts,
|
||||
bool validate)
|
||||
{
|
||||
int error;
|
||||
git_odb *odb;
|
||||
git_reference *ref = NULL;
|
||||
git_str buf = GIT_STR_INIT;
|
||||
const git_oid *current_id = NULL;
|
||||
git_array_oid_t parents = GIT_ARRAY_INIT;
|
||||
int error = 0;
|
||||
|
||||
if (opts && opts->update_ref) {
|
||||
error = git_reference_lookup_resolved(&ref,
|
||||
repo, opts->update_ref, 10);
|
||||
|
||||
if (update_ref) {
|
||||
error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
|
||||
if (error < 0 && error != GIT_ENOTFOUND)
|
||||
return error;
|
||||
}
|
||||
|
||||
git_error_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)
|
||||
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)
|
||||
if ((error = git_commit__create_buffer_internal(&buf,
|
||||
author, committer, message, tree, &parents, opts)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_repository_odb__weakptr(&odb, repo) < 0)
|
||||
goto cleanup;
|
||||
if (opts && opts->sign) {
|
||||
git_commitbuilder builder = { &buf };
|
||||
|
||||
if (git_odb__freshen(odb, tree) < 0)
|
||||
if ((error = opts->sign(&builder, repo, buf.ptr,
|
||||
opts->payload)) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT) < 0)
|
||||
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
|
||||
(error = git_odb__freshen(odb, tree)) < 0 ||
|
||||
(error = git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
|
||||
if (update_ref != NULL) {
|
||||
if (opts && opts->update_ref != NULL) {
|
||||
error = git_reference__update_for_commit(
|
||||
repo, ref, update_ref, id, "commit");
|
||||
repo, ref, opts->update_ref, id, "commit");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -198,9 +244,14 @@ int git_commit_create_from_callback(
|
||||
git_commit_parent_callback parent_cb,
|
||||
void *parent_payload)
|
||||
{
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
|
||||
opts.update_ref = update_ref;
|
||||
opts.message_encoding = message_encoding;
|
||||
|
||||
return git_commit__create_internal(
|
||||
id, repo, update_ref, author, committer, message_encoding, message,
|
||||
tree, parent_cb, parent_payload, true);
|
||||
id, repo, author, committer, message,
|
||||
tree, parent_cb, parent_payload, &opts, true);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@@ -230,8 +281,9 @@ int git_commit_create_v(
|
||||
size_t parent_count,
|
||||
...)
|
||||
{
|
||||
int error = 0;
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
commit_parent_varargs data;
|
||||
int error = 0;
|
||||
|
||||
GIT_ASSERT_ARG(tree);
|
||||
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
|
||||
@@ -239,10 +291,12 @@ int git_commit_create_v(
|
||||
data.total = parent_count;
|
||||
va_start(data.args, parent_count);
|
||||
|
||||
opts.update_ref = update_ref;
|
||||
opts.message_encoding = message_encoding;
|
||||
|
||||
error = git_commit__create_internal(
|
||||
id, repo, update_ref, author, committer,
|
||||
message_encoding, message, git_tree_id(tree),
|
||||
commit_parent_from_varargs, &data, false);
|
||||
id, repo, author, committer, message, git_tree_id(tree),
|
||||
commit_parent_from_varargs, &data, &opts, false);
|
||||
|
||||
va_end(data.args);
|
||||
return error;
|
||||
@@ -271,17 +325,20 @@ int git_commit_create_from_ids(
|
||||
size_t parent_count,
|
||||
const git_oid *parents[])
|
||||
{
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
commit_parent_oids data = { parent_count, parents };
|
||||
|
||||
opts.update_ref = update_ref;
|
||||
opts.message_encoding = message_encoding;
|
||||
|
||||
return git_commit__create_internal(
|
||||
id, repo, update_ref, author, committer,
|
||||
message_encoding, message, tree,
|
||||
commit_parent_from_ids, &data, true);
|
||||
id, repo, author, committer, message, tree,
|
||||
commit_parent_from_ids, &data, &opts, true);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t total;
|
||||
const git_commit **parents;
|
||||
git_commit * const *parents;
|
||||
git_repository *repo;
|
||||
} commit_parent_data;
|
||||
|
||||
@@ -297,6 +354,78 @@ static const git_oid *commit_parent_from_array(size_t curr, void *payload)
|
||||
return git_commit_id(commit);
|
||||
}
|
||||
|
||||
int git_commit_create_ext(
|
||||
git_oid *id,
|
||||
git_repository *repo,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message,
|
||||
const git_tree *tree,
|
||||
size_t parent_count,
|
||||
git_commit * const parents[],
|
||||
const git_commit_create_ext_options *opts)
|
||||
{
|
||||
commit_parent_data data = { parent_count, parents, repo };
|
||||
|
||||
GIT_ASSERT_ARG(tree);
|
||||
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
|
||||
|
||||
return git_commit__create_internal(
|
||||
id, repo, author, committer, message, git_tree_id(tree),
|
||||
commit_parent_from_array, &data, opts, false);
|
||||
}
|
||||
|
||||
static int append_header(
|
||||
git_str *out,
|
||||
const char *raw_content,
|
||||
const char *name,
|
||||
const char *value)
|
||||
{
|
||||
const char *header_end;
|
||||
|
||||
/* Identifying the end of the commit header area */
|
||||
header_end = strstr(raw_content, "\n\n");
|
||||
|
||||
if (!header_end) {
|
||||
git_error_set(GIT_ERROR_INVALID, "malformed commit contents");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* The header ends after the first LF */
|
||||
header_end++;
|
||||
|
||||
git_str_put(out, raw_content, header_end - raw_content);
|
||||
|
||||
if (format_header_field(out, name, value) < 0)
|
||||
return -1;
|
||||
|
||||
git_str_puts(out, header_end);
|
||||
|
||||
if (git_str_oom(out))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_commitbuilder_add_header(
|
||||
git_commitbuilder *builder,
|
||||
const char *field,
|
||||
const char *value)
|
||||
{
|
||||
git_str signed_data = GIT_BUF_INIT;
|
||||
int error;
|
||||
|
||||
if ((error = append_header(&signed_data, builder->contents->ptr,
|
||||
field, value)) < 0)
|
||||
goto done;
|
||||
|
||||
git_str_swap(builder->contents, &signed_data);
|
||||
|
||||
done:
|
||||
git_str_dispose(&signed_data);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_commit_create(
|
||||
git_oid *id,
|
||||
git_repository *repo,
|
||||
@@ -309,15 +438,18 @@ int git_commit_create(
|
||||
size_t parent_count,
|
||||
const git_commit *parents[])
|
||||
{
|
||||
commit_parent_data data = { parent_count, parents, repo };
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
commit_parent_data data = { parent_count, (git_commit * const *)parents, repo };
|
||||
|
||||
GIT_ASSERT_ARG(tree);
|
||||
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
|
||||
|
||||
opts.update_ref = update_ref;
|
||||
opts.message_encoding = message_encoding;
|
||||
|
||||
return git_commit__create_internal(
|
||||
id, repo, update_ref, author, committer,
|
||||
message_encoding, message, git_tree_id(tree),
|
||||
commit_parent_from_array, &data, false);
|
||||
id, repo, author, committer, message, git_tree_id(tree),
|
||||
commit_parent_from_array, &data, &opts, false);
|
||||
}
|
||||
|
||||
static const git_oid *commit_parent_for_amend(size_t curr, void *payload)
|
||||
@@ -341,6 +473,7 @@ int git_commit_amend(
|
||||
git_repository *repo;
|
||||
git_oid tree_id;
|
||||
git_reference *ref = NULL;
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
int error;
|
||||
|
||||
GIT_ASSERT_ARG(id);
|
||||
@@ -378,9 +511,12 @@ int git_commit_amend(
|
||||
}
|
||||
}
|
||||
|
||||
opts.message_encoding = message_encoding;
|
||||
|
||||
error = git_commit__create_internal(
|
||||
id, repo, NULL, author, committer, message_encoding, message,
|
||||
&tree_id, commit_parent_for_amend, (void *)commit_to_amend, false);
|
||||
id, repo, author, committer, message, &tree_id,
|
||||
commit_parent_for_amend, (void *)commit_to_amend,
|
||||
&opts, false);
|
||||
|
||||
if (!error && update_ref) {
|
||||
error = git_reference__update_for_commit(
|
||||
@@ -964,9 +1100,10 @@ int git_commit__create_buffer(
|
||||
const git_commit *parents[])
|
||||
{
|
||||
int error;
|
||||
commit_parent_data data = { parent_count, parents, repo };
|
||||
commit_parent_data data = { parent_count, (git_commit * const *)parents, repo };
|
||||
git_array_oid_t parents_arr = GIT_ARRAY_INIT;
|
||||
const git_oid *tree_id;
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
|
||||
GIT_ASSERT_ARG(tree);
|
||||
GIT_ASSERT_ARG(git_tree_owner(tree) == repo);
|
||||
@@ -976,47 +1113,21 @@ int git_commit__create_buffer(
|
||||
if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0)
|
||||
return error;
|
||||
|
||||
opts.message_encoding = message_encoding;
|
||||
|
||||
error = git_commit__create_buffer_internal(
|
||||
out, author, committer,
|
||||
message_encoding, message, tree_id,
|
||||
&parents_arr);
|
||||
out, author, committer, message, tree_id,
|
||||
&parents_arr, &opts);
|
||||
|
||||
git_array_clear(parents_arr);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to 'out' properly marking continuations when there's a newline in 'content'
|
||||
*/
|
||||
static int format_header_field(git_str *out, const char *field, const char *content)
|
||||
{
|
||||
const char *lf;
|
||||
|
||||
GIT_ASSERT_ARG(out);
|
||||
GIT_ASSERT_ARG(field);
|
||||
GIT_ASSERT_ARG(content);
|
||||
|
||||
git_str_puts(out, field);
|
||||
git_str_putc(out, ' ');
|
||||
|
||||
while ((lf = strchr(content, '\n')) != NULL) {
|
||||
git_str_put(out, content, lf - content);
|
||||
git_str_puts(out, "\n ");
|
||||
content = lf + 1;
|
||||
}
|
||||
|
||||
git_str_puts(out, content);
|
||||
git_str_putc(out, '\n');
|
||||
|
||||
return git_str_oom(out) ? -1 : 0;
|
||||
}
|
||||
|
||||
static const git_oid *commit_parent_from_commit(size_t n, void *payload)
|
||||
{
|
||||
const git_commit *commit = (const git_commit *) payload;
|
||||
|
||||
return git_array_get(commit->parent_ids, n);
|
||||
|
||||
}
|
||||
|
||||
int git_commit_create_with_signature(
|
||||
@@ -1027,20 +1138,29 @@ int git_commit_create_with_signature(
|
||||
const char *signature_field)
|
||||
{
|
||||
git_odb *odb;
|
||||
int error = 0;
|
||||
const char *field;
|
||||
const char *header_end;
|
||||
git_str commit = GIT_STR_INIT;
|
||||
git_str signed_content = GIT_STR_INIT;
|
||||
git_commit *parsed;
|
||||
git_array_oid_t parents = GIT_ARRAY_INIT;
|
||||
git_commit__parse_options parse_opts = {0};
|
||||
size_t commit_content_len;
|
||||
int error = 0;
|
||||
|
||||
GIT_ASSERT_ARG(out);
|
||||
GIT_ASSERT_ARG(repo);
|
||||
GIT_ASSERT_ARG(commit_content);
|
||||
|
||||
if (!signature_field)
|
||||
signature_field = "gpgsig";
|
||||
|
||||
commit_content_len = strlen(commit_content);;
|
||||
|
||||
parse_opts.oid_type = repo->oid_type;
|
||||
|
||||
/* The first step is to verify that all the tree and parents exist */
|
||||
parsed = git__calloc(1, sizeof(git_commit));
|
||||
GIT_ERROR_CHECK_ALLOC(parsed);
|
||||
if (commit_parse(parsed, commit_content, strlen(commit_content), &parse_opts) < 0) {
|
||||
|
||||
if (commit_parse(parsed, commit_content, commit_content_len, &parse_opts) < 0) {
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -1050,65 +1170,98 @@ int git_commit_create_with_signature(
|
||||
|
||||
git_array_clear(parents);
|
||||
|
||||
/* Then we start appending by identifying the end of the commit header */
|
||||
header_end = strstr(commit_content, "\n\n");
|
||||
if (!header_end) {
|
||||
git_error_set(GIT_ERROR_INVALID, "malformed commit contents");
|
||||
error = -1;
|
||||
if (signature) {
|
||||
if ((error = append_header(&signed_content,
|
||||
commit_content, signature_field, signature)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
commit_content = signed_content.ptr;
|
||||
commit_content_len = signed_content.size;
|
||||
}
|
||||
|
||||
/* The header ends after the first LF */
|
||||
header_end++;
|
||||
git_str_put(&commit, commit_content, header_end - commit_content);
|
||||
|
||||
if (signature != NULL) {
|
||||
field = signature_field ? signature_field : "gpgsig";
|
||||
|
||||
if ((error = format_header_field(&commit, field, signature)) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
git_str_puts(&commit, header_end);
|
||||
|
||||
if (git_str_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_OBJECT_COMMIT)) < 0)
|
||||
if ((error = git_odb_write(out, odb, commit_content,
|
||||
commit_content_len, GIT_OBJECT_COMMIT)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
git_commit__free(parsed);
|
||||
git_str_dispose(&commit);
|
||||
git_str_dispose(&signed_content);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_commit_create_from_stage(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
const char *message,
|
||||
const git_commit_create_options *given_opts)
|
||||
static int check_for_empty_commit(
|
||||
git_repository *repo, const git_tree *tree, git_index *index)
|
||||
{
|
||||
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 };
|
||||
git_tree *head_tree = NULL;
|
||||
int error = -1;
|
||||
|
||||
GIT_ASSERT_ARG(out && repo);
|
||||
error = git_repository_head_tree(&head_tree, repo);
|
||||
|
||||
if (given_opts)
|
||||
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
|
||||
if (error && error != GIT_EUNBORNBRANCH)
|
||||
goto done;
|
||||
|
||||
author = opts.author;
|
||||
committer = opts.committer;
|
||||
/*
|
||||
* TODO: stop comparison on first delta
|
||||
* TODO: stop casting away constness
|
||||
*/
|
||||
if (tree)
|
||||
error = git_diff_tree_to_tree(&diff, repo, head_tree, (git_tree *)tree, NULL);
|
||||
else
|
||||
error = git_diff_tree_to_index(&diff, repo, head_tree, index, NULL);
|
||||
|
||||
if (error < 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;
|
||||
}
|
||||
|
||||
done:
|
||||
git_diff_free(diff);
|
||||
git_tree_free(head_tree);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void init_ext_options(
|
||||
git_commit_create_ext_options *ext_opts,
|
||||
const git_commit_create_options *create_opts)
|
||||
{
|
||||
if (!create_opts)
|
||||
return;
|
||||
|
||||
ext_opts->message_encoding = create_opts->message_encoding;
|
||||
ext_opts->extra_headers = create_opts->extra_headers;
|
||||
ext_opts->extra_headers_len = create_opts->extra_headers_len;
|
||||
ext_opts->sign = create_opts->sign;
|
||||
ext_opts->payload = create_opts->payload;
|
||||
}
|
||||
|
||||
static int create_from_tree(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
const git_tree *tree,
|
||||
const char *message,
|
||||
const git_commit_create_options *opts)
|
||||
{
|
||||
git_signature *default_signature = NULL;
|
||||
const git_signature *author, *committer;
|
||||
git_commitarray parents = { 0 };
|
||||
git_commit_create_ext_options ext_opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
int error = -1;
|
||||
|
||||
init_ext_options(&ext_opts, opts);
|
||||
ext_opts.update_ref = "HEAD";
|
||||
|
||||
author = opts ? opts->author : NULL;
|
||||
committer = opts ? opts->committer : NULL;
|
||||
|
||||
if (!author || !committer) {
|
||||
if (git_signature_default(&default_signature, repo) < 0)
|
||||
@@ -1121,48 +1274,170 @@ int git_commit_create_from_stage(
|
||||
committer = default_signature;
|
||||
}
|
||||
|
||||
if (git_repository_index(&index, repo) < 0)
|
||||
if (git_repository_commit_parents(&parents, 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,
|
||||
opts.message_encoding, message,
|
||||
tree, parents.count,
|
||||
(const git_commit **)parents.commits);
|
||||
error = git_commit_create_ext(out, repo, author, committer,
|
||||
message, tree, parents.count,
|
||||
parents.commits, &ext_opts);
|
||||
|
||||
done:
|
||||
git_commitarray_dispose(&parents);
|
||||
git_signature_free(default_signature);
|
||||
|
||||
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_index *index = NULL;
|
||||
git_oid tree_id;
|
||||
git_tree *tree = NULL;
|
||||
int error = -1;
|
||||
|
||||
GIT_ASSERT_ARG(out && repo && message);
|
||||
|
||||
if (given_opts)
|
||||
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
|
||||
|
||||
if (git_repository_index(&index, repo) < 0)
|
||||
goto done;
|
||||
|
||||
if (!opts.allow_empty_commit &&
|
||||
(error = check_for_empty_commit(repo, NULL, index)) < 0)
|
||||
goto done;
|
||||
|
||||
if (git_index_write_tree(&tree_id, index) < 0 ||
|
||||
git_tree_lookup(&tree, repo, &tree_id) < 0) {
|
||||
error = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
error = create_from_tree(out, repo, tree, message, &opts);
|
||||
|
||||
done:
|
||||
git_tree_free(tree);
|
||||
git_tree_free(head_tree);
|
||||
git_diff_free(diff);
|
||||
git_index_free(index);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_commit_create_from_tree(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
const git_tree *tree,
|
||||
const char *message,
|
||||
const git_commit_create_options *given_opts)
|
||||
{
|
||||
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
|
||||
|
||||
GIT_ASSERT_ARG(out && repo && tree && message);
|
||||
|
||||
if (given_opts)
|
||||
memcpy(&opts, given_opts, sizeof(git_commit_create_options));
|
||||
|
||||
if (!opts.allow_empty_commit &&
|
||||
check_for_empty_commit(repo, tree, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
return create_from_tree(out, repo, tree, message, &opts);
|
||||
}
|
||||
|
||||
int git_commit_amend_from_stage(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
const char *message,
|
||||
const git_commit_create_options *opts)
|
||||
{
|
||||
git_index *index = NULL;
|
||||
git_oid tree_id;
|
||||
git_tree *tree = NULL;
|
||||
int error = 0;
|
||||
|
||||
GIT_ASSERT_ARG(out && repo);
|
||||
|
||||
if (git_repository_index(&index, repo) < 0 ||
|
||||
git_index_write_tree(&tree_id, index) < 0 ||
|
||||
git_tree_lookup(&tree, repo, &tree_id) < 0) {
|
||||
error = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
error = git_commit_amend_from_tree(out, repo, tree, message, opts);
|
||||
|
||||
done:
|
||||
git_tree_free(tree);
|
||||
git_index_free(index);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_commit_amend_from_tree(
|
||||
git_oid *out,
|
||||
git_repository *repo,
|
||||
const git_tree *tree,
|
||||
const char *given_message,
|
||||
const git_commit_create_options *opts)
|
||||
{
|
||||
git_commit_create_ext_options ext_opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
git_reference *head_ref = NULL;
|
||||
git_commit *head_commit = NULL;
|
||||
git_signature *new_committer = NULL;
|
||||
const git_signature *author, *committer;
|
||||
const char *message;
|
||||
int error;
|
||||
|
||||
GIT_ASSERT_ARG(out && repo && tree);
|
||||
|
||||
init_ext_options(&ext_opts, opts);
|
||||
|
||||
if ((error = git_repository_head(&head_ref, repo)) < 0 ||
|
||||
(error = git_reference_peel((git_object **)&head_commit, head_ref, GIT_OBJECT_COMMIT)) < 0)
|
||||
goto done;
|
||||
|
||||
if (opts && opts->author) {
|
||||
author = opts->author;
|
||||
} else {
|
||||
author = git_commit_author(head_commit);
|
||||
}
|
||||
|
||||
if (opts && opts->committer) {
|
||||
committer = opts->committer;
|
||||
} else {
|
||||
if ((error = git_signature_default(&new_committer, repo)) < 0)
|
||||
goto done;
|
||||
|
||||
committer = new_committer;
|
||||
}
|
||||
|
||||
if (given_message) {
|
||||
message = given_message;
|
||||
} else {
|
||||
message = git_commit_message(head_commit);
|
||||
ext_opts.message_encoding = git_commit_message_encoding(head_commit);
|
||||
}
|
||||
|
||||
error = git_commit__create_internal(
|
||||
out, repo, author, committer, message, git_tree_id(tree),
|
||||
commit_parent_for_amend, (void *)head_commit, &ext_opts,
|
||||
false);
|
||||
|
||||
if (!error)
|
||||
error = git_reference__update_for_commit(
|
||||
repo, head_ref, NULL, out, "commit");
|
||||
|
||||
done:
|
||||
git_signature_free(new_committer);
|
||||
git_commit_free(head_commit);
|
||||
git_reference_free(head_ref);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
int git_commit_committer_with_mailmap(
|
||||
git_signature **out, const git_commit *commit, const git_mailmap *mailmap)
|
||||
{
|
||||
|
||||
122
tests/libgit2/commit/amend.c
Normal file
122
tests/libgit2/commit/amend.c
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "repository.h"
|
||||
|
||||
/* Fixture setup */
|
||||
static git_repository *g_repo;
|
||||
static git_signature *g_committer;
|
||||
|
||||
void test_commit_amend__initialize(void)
|
||||
{
|
||||
g_repo = cl_git_sandbox_init("testrepo2");
|
||||
cl_git_pass(git_signature_new(&g_committer, "libgit2 user", "nobody@noreply.libgit2.org", 987654321, 90));
|
||||
}
|
||||
|
||||
void test_commit_amend__cleanup(void)
|
||||
{
|
||||
git_signature_free(g_committer);
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
|
||||
void test_commit_amend__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.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_amend_from_stage(&commit_id, g_repo, NULL, &opts));
|
||||
|
||||
cl_git_pass(git_repository_head_tree(&tree, g_repo));
|
||||
|
||||
cl_assert_equal_oidstr("63ec0b083fd14c22a68fd2b1794f26e4b396b6b3", &commit_id);
|
||||
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(tree));
|
||||
|
||||
git_index_free(index);
|
||||
git_tree_free(tree);
|
||||
}
|
||||
|
||||
void test_commit_amend__from_stage_newmessage(void)
|
||||
{
|
||||
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
|
||||
git_oid commit_id;
|
||||
git_tree *tree;
|
||||
|
||||
opts.committer = g_committer;
|
||||
|
||||
cl_git_pass(git_commit_amend_from_stage(&commit_id, g_repo, "New message goes here.", &opts));
|
||||
|
||||
cl_git_pass(git_repository_head_tree(&tree, g_repo));
|
||||
|
||||
cl_assert_equal_oidstr("8b0e1cacc8380023705192466aaef8a15ddae7b3", &commit_id);
|
||||
cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree));
|
||||
|
||||
git_tree_free(tree);
|
||||
}
|
||||
|
||||
void test_commit_amend__from_stage_nochanges(void)
|
||||
{
|
||||
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
|
||||
git_oid commit_id;
|
||||
git_tree *tree;
|
||||
|
||||
opts.committer = g_committer;
|
||||
|
||||
cl_git_pass(git_commit_amend_from_stage(&commit_id, g_repo, NULL, &opts));
|
||||
|
||||
cl_git_pass(git_repository_head_tree(&tree, g_repo));
|
||||
|
||||
cl_assert_equal_oidstr("da86907c6d505a92c5683bece08f23d68ac785bd", &commit_id);
|
||||
cl_assert_equal_oidstr("c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b", git_tree_id(tree));
|
||||
|
||||
git_tree_free(tree);
|
||||
}
|
||||
|
||||
void test_commit_amend__from_tree(void)
|
||||
{
|
||||
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
|
||||
git_index *index;
|
||||
git_oid commit_id;
|
||||
git_oid tree_id;
|
||||
git_tree *tree, *lookedup;
|
||||
|
||||
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_index_write_tree(&tree_id, index));
|
||||
|
||||
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", &tree_id);
|
||||
|
||||
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
|
||||
|
||||
cl_git_pass(git_commit_amend_from_tree(&commit_id, g_repo, tree, NULL, &opts));
|
||||
|
||||
cl_git_pass(git_repository_head_tree(&lookedup, g_repo));
|
||||
|
||||
cl_assert_equal_oidstr("63ec0b083fd14c22a68fd2b1794f26e4b396b6b3", &commit_id);
|
||||
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(lookedup));
|
||||
|
||||
git_index_free(index);
|
||||
git_tree_free(tree);
|
||||
git_tree_free(lookedup);
|
||||
}
|
||||
@@ -110,3 +110,41 @@ void test_commit_create__from_stage_newrepo(void)
|
||||
git_repository_free(newrepo);
|
||||
cl_fixture_cleanup("newrepo");
|
||||
}
|
||||
|
||||
void test_commit_create__from_tree(void)
|
||||
{
|
||||
git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
|
||||
git_index *index;
|
||||
git_oid commit_id;
|
||||
git_oid tree_id;
|
||||
git_tree *tree, *lookedup;
|
||||
|
||||
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_index_write_tree(&tree_id, index));
|
||||
|
||||
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", &tree_id);
|
||||
|
||||
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
|
||||
|
||||
cl_git_pass(git_commit_create_from_tree(&commit_id, g_repo, tree, "This is the message.", &opts));
|
||||
|
||||
cl_git_pass(git_repository_head_tree(&lookedup, g_repo));
|
||||
|
||||
cl_assert_equal_oidstr("241b5b04e847bc38dd7b4b9f49f21e55da40f3a6", &commit_id);
|
||||
cl_assert_equal_oidstr("b27210772d0633870b4f486d04ed3eb5ebbef5e7", git_tree_id(lookedup));
|
||||
|
||||
git_index_free(index);
|
||||
git_tree_free(tree);
|
||||
git_tree_free(lookedup);
|
||||
}
|
||||
|
||||
@@ -299,6 +299,115 @@ void test_commit_write__can_validate_objects(void)
|
||||
cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id));
|
||||
}
|
||||
|
||||
void test_commit_write__can_add_extra_headers(void)
|
||||
{
|
||||
git_odb *odb;
|
||||
git_signature *author, *committer;
|
||||
git_oid tree_id, parent_id, commit_id;
|
||||
git_commit *parent;
|
||||
git_tree *tree;
|
||||
git_odb_object *obj;
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
const git_commit_header extra_headers[] = {
|
||||
{ "line_one", "First extra header" },
|
||||
{ "line_two", "Second extra header" }
|
||||
};
|
||||
|
||||
const char *expected = "tree 1810dff58d8a660512d4832e740f692884338ccd\n\
|
||||
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
|
||||
author Vicent Marti <vicent@github.com> 987654321 +0130\n\
|
||||
committer Vicent Marti <vicent@github.com> 123456789 +0100\n\
|
||||
line_one First extra header\n\
|
||||
line_two Second extra header\n\
|
||||
\n\
|
||||
This is a fun new commit.";
|
||||
|
||||
cl_git_pass(git_signature_new(
|
||||
&author, committer_name, committer_email, 987654321, 90));
|
||||
cl_git_pass(git_signature_new(
|
||||
&committer, committer_name, committer_email, 123456789, 60));
|
||||
|
||||
/* this is a valid tree and parent */
|
||||
git_oid_from_string(&tree_id, tree_id_str, GIT_OID_SHA1);
|
||||
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
|
||||
|
||||
git_oid_from_string(&parent_id, parent_id_str, GIT_OID_SHA1);
|
||||
cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id));
|
||||
|
||||
opts.extra_headers = extra_headers;
|
||||
opts.extra_headers_len = 2;
|
||||
|
||||
cl_git_pass(git_commit_create_ext(
|
||||
&commit_id, g_repo, author, committer,
|
||||
"This is a fun new commit.",
|
||||
tree, 1, &parent, &opts));
|
||||
|
||||
cl_git_pass(git_repository_odb(&odb, g_repo));
|
||||
cl_git_pass(git_odb_read(&obj, odb, &commit_id));
|
||||
|
||||
cl_assert_equal_s(expected, git_odb_object_data(obj));
|
||||
|
||||
git_odb_object_free(obj);
|
||||
git_tree_free(tree);
|
||||
git_commit_free(parent);
|
||||
git_commit_free(commit);
|
||||
git_signature_free(committer);
|
||||
git_signature_free(author);
|
||||
git_odb_free(odb);
|
||||
}
|
||||
|
||||
static const char *unsigned_data =
|
||||
"tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
|
||||
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
|
||||
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
\n\
|
||||
a simple commit which works\n";
|
||||
|
||||
static const char *gpgsig =
|
||||
"-----BEGIN PGP SIGNATURE-----\n\
|
||||
Version: GnuPG v1.4.12 (Darwin)\n\
|
||||
\n\
|
||||
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
|
||||
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
|
||||
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
|
||||
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
|
||||
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
|
||||
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
|
||||
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
|
||||
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
|
||||
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
|
||||
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
|
||||
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
|
||||
cpxtDQQMGYFpXK/71stq\n\
|
||||
=ozeK\n\
|
||||
-----END PGP SIGNATURE-----";
|
||||
|
||||
static const char *signed_data =
|
||||
"tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
|
||||
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
|
||||
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
gpgsig -----BEGIN PGP SIGNATURE-----\n\
|
||||
Version: GnuPG v1.4.12 (Darwin)\n\
|
||||
\n\
|
||||
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
|
||||
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
|
||||
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
|
||||
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
|
||||
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
|
||||
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
|
||||
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
|
||||
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
|
||||
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
|
||||
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
|
||||
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
|
||||
cpxtDQQMGYFpXK/71stq\n\
|
||||
=ozeK\n\
|
||||
-----END PGP SIGNATURE-----\n\
|
||||
\n\
|
||||
a simple commit which works\n";
|
||||
|
||||
void test_commit_write__attach_signature_checks_objects(void)
|
||||
{
|
||||
const char *sig = "magic word: pretty please";
|
||||
@@ -327,13 +436,6 @@ void test_commit_write__attach_singleline_signature(void)
|
||||
{
|
||||
const char *sig = "magic word: pretty please";
|
||||
|
||||
const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
|
||||
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
|
||||
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
\n\
|
||||
a simple commit which works\n";
|
||||
|
||||
const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
|
||||
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
|
||||
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
@@ -346,7 +448,7 @@ a simple commit which works\n";
|
||||
git_odb *odb;
|
||||
git_odb_object *obj;
|
||||
|
||||
cl_git_pass(git_commit_create_with_signature(&id, g_repo, data, sig, "magicsig"));
|
||||
cl_git_pass(git_commit_create_with_signature(&id, g_repo, unsigned_data, sig, "magicsig"));
|
||||
|
||||
cl_git_pass(git_repository_odb(&odb, g_repo));
|
||||
cl_git_pass(git_odb_read(&obj, odb, &id));
|
||||
@@ -358,67 +460,64 @@ a simple commit which works\n";
|
||||
|
||||
void test_commit_write__attach_multiline_signature(void)
|
||||
{
|
||||
const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\
|
||||
Version: GnuPG v1.4.12 (Darwin)\n\
|
||||
\n\
|
||||
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
|
||||
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
|
||||
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
|
||||
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
|
||||
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
|
||||
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
|
||||
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
|
||||
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
|
||||
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
|
||||
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
|
||||
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
|
||||
cpxtDQQMGYFpXK/71stq\n\
|
||||
=ozeK\n\
|
||||
-----END PGP SIGNATURE-----";
|
||||
|
||||
const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
|
||||
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
|
||||
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
\n\
|
||||
a simple commit which works\n";
|
||||
|
||||
const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\
|
||||
parent 8496071c1b46c854b31185ea97743be6a8774479\n\
|
||||
author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
|
||||
gpgsig -----BEGIN PGP SIGNATURE-----\n\
|
||||
Version: GnuPG v1.4.12 (Darwin)\n\
|
||||
\n\
|
||||
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
|
||||
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
|
||||
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
|
||||
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
|
||||
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
|
||||
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
|
||||
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
|
||||
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
|
||||
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
|
||||
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
|
||||
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
|
||||
cpxtDQQMGYFpXK/71stq\n\
|
||||
=ozeK\n\
|
||||
-----END PGP SIGNATURE-----\n\
|
||||
\n\
|
||||
a simple commit which works\n";
|
||||
|
||||
git_oid one, two;
|
||||
git_odb *odb;
|
||||
git_odb_object *obj;
|
||||
|
||||
cl_git_pass(git_commit_create_with_signature(&one, g_repo, data, gpgsig, "gpgsig"));
|
||||
cl_git_pass(git_commit_create_with_signature(&two, g_repo, data, gpgsig, NULL));
|
||||
cl_git_pass(git_commit_create_with_signature(&one, g_repo, unsigned_data, gpgsig, "gpgsig"));
|
||||
cl_git_pass(git_commit_create_with_signature(&two, g_repo, unsigned_data, gpgsig, NULL));
|
||||
|
||||
cl_assert(!git_oid_cmp(&one, &two));
|
||||
cl_git_pass(git_repository_odb(&odb, g_repo));
|
||||
cl_git_pass(git_odb_read(&obj, odb, &one));
|
||||
cl_assert_equal_s(complete, git_odb_object_data(obj));
|
||||
cl_assert_equal_s(signed_data, git_odb_object_data(obj));
|
||||
|
||||
git_odb_object_free(obj);
|
||||
git_odb_free(odb);
|
||||
}
|
||||
|
||||
static int sign_cb(
|
||||
git_commitbuilder *builder, git_repository *repo,
|
||||
const char *commit_buffer, void *payload)
|
||||
{
|
||||
GIT_UNUSED(repo);
|
||||
GIT_UNUSED(commit_buffer);
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
return git_commitbuilder_add_header(builder,
|
||||
"gpgsig", gpgsig);
|
||||
}
|
||||
|
||||
void test_commit_write__signature_callback(void)
|
||||
{
|
||||
git_oid result_id, tree_id, parent_id;
|
||||
git_odb *odb;
|
||||
git_odb_object *obj;
|
||||
git_signature *committer;
|
||||
git_tree *tree;
|
||||
git_commit *parent;
|
||||
git_commit_create_ext_options opts = GIT_COMMIT_CREATE_EXT_OPTIONS_INIT;
|
||||
|
||||
cl_git_pass(git_oid_from_string(&tree_id, "4b825dc642cb6eb9a060e54bf8d69288fbee4904", GIT_OID_SHA1));
|
||||
cl_git_pass(git_oid_from_string(&parent_id, "8496071c1b46c854b31185ea97743be6a8774479", GIT_OID_SHA1));
|
||||
|
||||
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
|
||||
cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id));
|
||||
cl_git_pass(git_signature_new(&committer, "Ben Burkert", "ben@benburkert.com", 1358451456, -480));
|
||||
|
||||
opts.sign = sign_cb;
|
||||
|
||||
cl_git_pass(git_commit_create_ext(&result_id, g_repo, committer,
|
||||
committer, "a simple commit which works\n", tree,
|
||||
1, &parent, &opts));
|
||||
|
||||
cl_git_pass(git_repository_odb(&odb, g_repo));
|
||||
cl_git_pass(git_odb_read(&obj, odb, &result_id));
|
||||
cl_assert_equal_s(signed_data, git_odb_object_data(obj));
|
||||
|
||||
git_tree_free(tree);
|
||||
git_commit_free(parent);
|
||||
git_signature_free(committer);
|
||||
git_odb_object_free(obj);
|
||||
git_odb_free(odb);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user