mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 19:37:29 +00:00
Merge pull request #3610 from ethomson/rebase_bare
rebase: introduce bare rebasing
This commit is contained in:
commit
5a296ad07e
@ -38,6 +38,15 @@ typedef struct {
|
||||
*/
|
||||
int quiet;
|
||||
|
||||
/**
|
||||
* Used by `git_rebase_init`, this will begin an in-memory rebase,
|
||||
* which will allow callers to step through the rebase operations and
|
||||
* commit the rebased changes, but will not rewind HEAD or update the
|
||||
* repository to be in a rebasing state. This will not interfere with
|
||||
* the working directory (if there is one).
|
||||
*/
|
||||
int inmemory;
|
||||
|
||||
/**
|
||||
* Used by `git_rebase_finish`, this is the name of the notes reference
|
||||
* used to rewrite notes for rebased commits when finishing the rebase;
|
||||
@ -48,6 +57,11 @@ typedef struct {
|
||||
*/
|
||||
const char *rewrite_notes_ref;
|
||||
|
||||
/**
|
||||
* Options to control how trees are merged during `git_rebase_next`.
|
||||
*/
|
||||
git_merge_options merge_options;
|
||||
|
||||
/**
|
||||
* Options to control how files are written during `git_rebase_init`,
|
||||
* `git_checkout_next` and `git_checkout_abort`. Note that a minimum
|
||||
@ -101,7 +115,8 @@ typedef enum {
|
||||
|
||||
#define GIT_REBASE_OPTIONS_VERSION 1
|
||||
#define GIT_REBASE_OPTIONS_INIT \
|
||||
{GIT_REBASE_OPTIONS_VERSION, 0, NULL, GIT_CHECKOUT_OPTIONS_INIT}
|
||||
{ GIT_REBASE_OPTIONS_VERSION, 0, 0, NULL, GIT_MERGE_OPTIONS_INIT, \
|
||||
GIT_CHECKOUT_OPTIONS_INIT}
|
||||
|
||||
/** Indicates that a rebase operation is not (yet) in progress. */
|
||||
#define GIT_REBASE_NO_OPERATION SIZE_MAX
|
||||
@ -127,6 +142,12 @@ typedef struct {
|
||||
* be populated for operations of type `GIT_REBASE_OPERATION_EXEC`.
|
||||
*/
|
||||
const char *exec;
|
||||
|
||||
/**
|
||||
* The index that is the result of an operation.
|
||||
* This is set only for in-memory rebases.
|
||||
*/
|
||||
git_index *index;
|
||||
} git_rebase_operation;
|
||||
|
||||
/**
|
||||
|
409
src/rebase.c
409
src/rebase.c
@ -63,17 +63,23 @@ struct git_rebase {
|
||||
char *state_path;
|
||||
|
||||
int head_detached : 1,
|
||||
inmemory : 1,
|
||||
quiet : 1,
|
||||
started : 1;
|
||||
|
||||
char *orig_head_name;
|
||||
git_array_t(git_rebase_operation) operations;
|
||||
size_t current;
|
||||
|
||||
/* Used by in-memory rebase */
|
||||
git_commit *last_commit;
|
||||
git_index *last_index;
|
||||
|
||||
/* Used by regular (not in-memory) merge-style rebase */
|
||||
git_oid orig_head_id;
|
||||
char *orig_head_name;
|
||||
|
||||
git_oid onto_id;
|
||||
char *onto_name;
|
||||
|
||||
git_array_t(git_rebase_operation) operations;
|
||||
size_t current;
|
||||
};
|
||||
|
||||
#define GIT_REBASE_STATE_INIT {0}
|
||||
@ -393,6 +399,9 @@ done:
|
||||
|
||||
static int rebase_cleanup(git_rebase *rebase)
|
||||
{
|
||||
if (!rebase || rebase->inmemory)
|
||||
return 0;
|
||||
|
||||
return git_path_isdir(rebase->state_path) ?
|
||||
git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) :
|
||||
0;
|
||||
@ -601,59 +610,63 @@ static int rebase_init_merge(
|
||||
const git_annotated_commit *upstream,
|
||||
const git_annotated_commit *onto)
|
||||
{
|
||||
if (rebase_init_operations(rebase, repo, branch, upstream, onto) < 0)
|
||||
return -1;
|
||||
git_reference *head_ref = NULL;
|
||||
git_commit *onto_commit = NULL;
|
||||
git_buf reflog = GIT_BUF_INIT;
|
||||
git_buf state_path = GIT_BUF_INIT;
|
||||
int error;
|
||||
|
||||
GIT_UNUSED(upstream);
|
||||
|
||||
if ((error = git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR)) < 0)
|
||||
goto done;
|
||||
|
||||
rebase->state_path = git_buf_detach(&state_path);
|
||||
GITERR_CHECK_ALLOC(rebase->state_path);
|
||||
|
||||
rebase->orig_head_name = git__strdup(branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD);
|
||||
GITERR_CHECK_ALLOC(rebase->orig_head_name);
|
||||
|
||||
rebase->onto_name = git__strdup(rebase_onto_name(onto));
|
||||
GITERR_CHECK_ALLOC(rebase->onto_name);
|
||||
|
||||
return 0;
|
||||
rebase->quiet = rebase->options.quiet;
|
||||
|
||||
git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch));
|
||||
git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto));
|
||||
|
||||
if ((error = rebase_setupfiles(rebase)) < 0 ||
|
||||
(error = git_buf_printf(&reflog,
|
||||
"rebase: checkout %s", rebase_onto_name(onto))) < 0 ||
|
||||
(error = git_commit_lookup(
|
||||
&onto_commit, repo, git_annotated_commit_id(onto))) < 0 ||
|
||||
(error = git_checkout_tree(repo,
|
||||
(git_object *)onto_commit, &rebase->options.checkout_options)) < 0 ||
|
||||
(error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE,
|
||||
git_annotated_commit_id(onto), 1, reflog.ptr)) < 0)
|
||||
goto done;
|
||||
|
||||
done:
|
||||
git_reference_free(head_ref);
|
||||
git_commit_free(onto_commit);
|
||||
git_buf_free(&reflog);
|
||||
git_buf_free(&state_path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int rebase_init(
|
||||
static int rebase_init_inmemory(
|
||||
git_rebase *rebase,
|
||||
git_repository *repo,
|
||||
const git_annotated_commit *branch,
|
||||
const git_annotated_commit *upstream,
|
||||
const git_annotated_commit *onto)
|
||||
{
|
||||
git_reference *head_ref = NULL;
|
||||
git_annotated_commit *head_branch = NULL;
|
||||
git_buf state_path = GIT_BUF_INIT;
|
||||
int error;
|
||||
GIT_UNUSED(branch);
|
||||
GIT_UNUSED(upstream);
|
||||
|
||||
if ((error = git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR)) < 0)
|
||||
goto done;
|
||||
|
||||
if (!branch) {
|
||||
if ((error = git_repository_head(&head_ref, repo)) < 0 ||
|
||||
(error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0)
|
||||
goto done;
|
||||
|
||||
branch = head_branch;
|
||||
}
|
||||
|
||||
rebase->repo = repo;
|
||||
rebase->type = GIT_REBASE_TYPE_MERGE;
|
||||
rebase->state_path = git_buf_detach(&state_path);
|
||||
rebase->orig_head_name = git__strdup(branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD);
|
||||
rebase->quiet = rebase->options.quiet;
|
||||
|
||||
git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch));
|
||||
git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto));
|
||||
|
||||
if (!rebase->orig_head_name || !rebase->state_path)
|
||||
return -1;
|
||||
|
||||
error = rebase_init_merge(rebase, repo, branch, upstream, onto);
|
||||
|
||||
git_buf_free(&state_path);
|
||||
|
||||
done:
|
||||
git_reference_free(head_ref);
|
||||
git_annotated_commit_free(head_branch);
|
||||
|
||||
return error;
|
||||
return git_commit_lookup(
|
||||
&rebase->last_commit, repo, git_annotated_commit_id(onto));
|
||||
}
|
||||
|
||||
int git_rebase_init(
|
||||
@ -665,9 +678,9 @@ int git_rebase_init(
|
||||
const git_rebase_options *given_opts)
|
||||
{
|
||||
git_rebase *rebase = NULL;
|
||||
git_buf reflog = GIT_BUF_INIT;
|
||||
git_commit *onto_commit = NULL;
|
||||
git_annotated_commit *head_branch = NULL;
|
||||
git_reference *head_ref = NULL;
|
||||
bool inmemory = (given_opts && given_opts->inmemory);
|
||||
int error;
|
||||
|
||||
assert(repo && (upstream || onto));
|
||||
@ -677,39 +690,51 @@ int git_rebase_init(
|
||||
if (!onto)
|
||||
onto = upstream;
|
||||
|
||||
if ((error = rebase_check_versions(given_opts)) < 0 ||
|
||||
(error = git_repository__ensure_not_bare(repo, "rebase")) < 0 ||
|
||||
(error = rebase_ensure_not_in_progress(repo)) < 0 ||
|
||||
(error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0 ||
|
||||
(error = git_commit_lookup(
|
||||
&onto_commit, repo, git_annotated_commit_id(onto))) < 0)
|
||||
return error;
|
||||
|
||||
rebase = rebase_alloc(given_opts);
|
||||
|
||||
if ((error = rebase_init(
|
||||
rebase, repo, branch, upstream, onto)) < 0 ||
|
||||
(error = rebase_setupfiles(rebase)) < 0 ||
|
||||
(error = git_buf_printf(&reflog,
|
||||
"rebase: checkout %s", rebase_onto_name(onto))) < 0 ||
|
||||
(error = git_checkout_tree(
|
||||
repo, (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 ||
|
||||
(error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE,
|
||||
git_annotated_commit_id(onto), 1, reflog.ptr)) < 0)
|
||||
if ((error = rebase_check_versions(given_opts)) < 0)
|
||||
goto done;
|
||||
|
||||
*out = rebase;
|
||||
if (!inmemory) {
|
||||
if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 ||
|
||||
(error = rebase_ensure_not_in_progress(repo)) < 0 ||
|
||||
(error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!branch) {
|
||||
if ((error = git_repository_head(&head_ref, repo)) < 0 ||
|
||||
(error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0)
|
||||
goto done;
|
||||
|
||||
branch = head_branch;
|
||||
}
|
||||
|
||||
rebase = rebase_alloc(given_opts);
|
||||
GITERR_CHECK_ALLOC(rebase);
|
||||
|
||||
rebase->repo = repo;
|
||||
rebase->inmemory = inmemory;
|
||||
rebase->type = GIT_REBASE_TYPE_MERGE;
|
||||
|
||||
if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0)
|
||||
goto done;
|
||||
|
||||
if (inmemory)
|
||||
error = rebase_init_inmemory(rebase, repo, branch, upstream, onto);
|
||||
else
|
||||
rebase_init_merge(rebase, repo, branch ,upstream, onto);
|
||||
|
||||
if (error == 0)
|
||||
*out = rebase;
|
||||
|
||||
done:
|
||||
git_reference_free(head_ref);
|
||||
git_annotated_commit_free(head_branch);
|
||||
|
||||
if (error < 0) {
|
||||
rebase_cleanup(rebase);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
git_commit_free(onto_commit);
|
||||
git_buf_free(&reflog);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -764,9 +789,6 @@ static int rebase_next_merge(
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if ((error = rebase_movenext(rebase)) < 0)
|
||||
goto done;
|
||||
|
||||
operation = git_array_get(rebase->operations, rebase->current);
|
||||
|
||||
if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 ||
|
||||
@ -791,7 +813,7 @@ static int rebase_next_merge(
|
||||
if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 ||
|
||||
(error = rebase_setupfile(rebase, MSGNUM_FILE, -1, "%" PRIuZ "\n", rebase->current+1)) < 0 ||
|
||||
(error = rebase_setupfile(rebase, CURRENT_FILE, -1, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 ||
|
||||
(error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, NULL)) < 0 ||
|
||||
(error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 ||
|
||||
(error = git_merge__check_result(rebase->repo, index)) < 0 ||
|
||||
(error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 ||
|
||||
(error = git_indexwriter_commit(&indexwriter)) < 0)
|
||||
@ -812,6 +834,46 @@ done:
|
||||
return error;
|
||||
}
|
||||
|
||||
static int rebase_next_inmemory(
|
||||
git_rebase_operation **out,
|
||||
git_rebase *rebase)
|
||||
{
|
||||
git_commit *current_commit = NULL, *parent_commit = NULL;
|
||||
git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL;
|
||||
git_rebase_operation *operation;
|
||||
git_index *index = NULL;
|
||||
int error;
|
||||
|
||||
*out = NULL;
|
||||
|
||||
operation = git_array_get(rebase->operations, rebase->current);
|
||||
|
||||
if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 ||
|
||||
(error = git_commit_tree(¤t_tree, current_commit)) < 0 ||
|
||||
(error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 ||
|
||||
(error = git_commit_tree(&parent_tree, parent_commit)) < 0 ||
|
||||
(error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 ||
|
||||
(error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0)
|
||||
goto done;
|
||||
|
||||
git_index_free(rebase->last_index);
|
||||
rebase->last_index = index;
|
||||
operation->index = index;
|
||||
index = NULL;
|
||||
|
||||
*out = operation;
|
||||
|
||||
done:
|
||||
git_commit_free(current_commit);
|
||||
git_commit_free(parent_commit);
|
||||
git_tree_free(current_tree);
|
||||
git_tree_free(head_tree);
|
||||
git_tree_free(parent_tree);
|
||||
git_index_free(index);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_rebase_next(
|
||||
git_rebase_operation **out,
|
||||
git_rebase *rebase)
|
||||
@ -820,14 +882,79 @@ int git_rebase_next(
|
||||
|
||||
assert(out && rebase);
|
||||
|
||||
switch (rebase->type) {
|
||||
case GIT_REBASE_TYPE_MERGE:
|
||||
if ((error = rebase_movenext(rebase)) < 0)
|
||||
return error;
|
||||
|
||||
if (rebase->inmemory)
|
||||
error = rebase_next_inmemory(out, rebase);
|
||||
else if (rebase->type == GIT_REBASE_TYPE_MERGE)
|
||||
error = rebase_next_merge(out, rebase);
|
||||
break;
|
||||
default:
|
||||
else
|
||||
abort();
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int rebase_commit__create(
|
||||
git_commit **out,
|
||||
git_rebase *rebase,
|
||||
git_index *index,
|
||||
git_commit *parent_commit,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message_encoding,
|
||||
const char *message)
|
||||
{
|
||||
git_rebase_operation *operation;
|
||||
git_commit *current_commit = NULL, *commit = NULL;
|
||||
git_tree *parent_tree = NULL, *tree = NULL;
|
||||
git_oid tree_id, commit_id;
|
||||
int error;
|
||||
|
||||
operation = git_array_get(rebase->operations, rebase->current);
|
||||
|
||||
if (git_index_has_conflicts(index)) {
|
||||
giterr_set(GITERR_REBASE, "conflicts have not been resolved");
|
||||
error = GIT_EUNMERGED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 ||
|
||||
(error = git_commit_tree(&parent_tree, parent_commit)) < 0 ||
|
||||
(error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 ||
|
||||
(error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0)
|
||||
goto done;
|
||||
|
||||
if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) {
|
||||
giterr_set(GITERR_REBASE, "this patch has already been applied");
|
||||
error = GIT_EAPPLIED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!author)
|
||||
author = git_commit_author(current_commit);
|
||||
|
||||
if (!message) {
|
||||
message_encoding = git_commit_message_encoding(current_commit);
|
||||
message = git_commit_message(current_commit);
|
||||
}
|
||||
|
||||
if ((error = git_commit_create(&commit_id, rebase->repo, NULL, author,
|
||||
committer, message_encoding, message, tree, 1,
|
||||
(const git_commit **)&parent_commit)) < 0 ||
|
||||
(error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0)
|
||||
goto done;
|
||||
|
||||
*out = commit;
|
||||
|
||||
done:
|
||||
if (error < 0)
|
||||
git_commit_free(commit);
|
||||
|
||||
git_commit_free(current_commit);
|
||||
git_tree_free(parent_tree);
|
||||
git_tree_free(tree);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -839,79 +966,77 @@ static int rebase_commit_merge(
|
||||
const char *message_encoding,
|
||||
const char *message)
|
||||
{
|
||||
git_index *index = NULL;
|
||||
git_reference *head = NULL;
|
||||
git_commit *current_commit = NULL, *head_commit = NULL, *commit = NULL;
|
||||
git_rebase_operation *operation;
|
||||
git_tree *head_tree = NULL, *tree = NULL;
|
||||
git_diff *diff = NULL;
|
||||
git_oid tree_id;
|
||||
git_buf reflog_msg = GIT_BUF_INIT;
|
||||
git_reference *head = NULL;
|
||||
git_commit *head_commit = NULL, *commit = NULL;
|
||||
git_index *index = NULL;
|
||||
char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ];
|
||||
int error;
|
||||
|
||||
operation = git_array_get(rebase->operations, rebase->current);
|
||||
assert(operation);
|
||||
|
||||
if ((error = git_repository_index(&index, rebase->repo)) < 0)
|
||||
goto done;
|
||||
|
||||
if (git_index_has_conflicts(index)) {
|
||||
giterr_set(GITERR_REBASE, "Conflicts have not been resolved");
|
||||
error = GIT_EUNMERGED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 ||
|
||||
(error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 ||
|
||||
(error = git_repository_head(&head, rebase->repo)) < 0 ||
|
||||
(error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)) < 0 ||
|
||||
(error = git_commit_tree(&head_tree, head_commit)) < 0 ||
|
||||
(error = git_diff_tree_to_index(&diff, rebase->repo, head_tree, index, NULL)) < 0)
|
||||
goto done;
|
||||
|
||||
if (git_diff_num_deltas(diff) == 0) {
|
||||
giterr_set(GITERR_REBASE, "This patch has already been applied");
|
||||
error = GIT_EAPPLIED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((error = git_index_write_tree(&tree_id, index)) < 0 ||
|
||||
(error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0)
|
||||
goto done;
|
||||
|
||||
if (!author)
|
||||
author = git_commit_author(current_commit);
|
||||
|
||||
if (!message) {
|
||||
message_encoding = git_commit_message_encoding(current_commit);
|
||||
message = git_commit_message(current_commit);
|
||||
}
|
||||
|
||||
if ((error = git_commit_create(commit_id, rebase->repo, NULL, author,
|
||||
committer, message_encoding, message, tree, 1,
|
||||
(const git_commit **)&head_commit)) < 0 ||
|
||||
(error = git_commit_lookup(&commit, rebase->repo, commit_id)) < 0 ||
|
||||
(error = git_repository_index(&index, rebase->repo)) < 0 ||
|
||||
(error = rebase_commit__create(&commit, rebase, index, head_commit,
|
||||
author, committer, message_encoding, message)) < 0 ||
|
||||
(error = git_reference__update_for_commit(
|
||||
rebase->repo, NULL, "HEAD", commit_id, "rebase")) < 0)
|
||||
rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0)
|
||||
goto done;
|
||||
|
||||
git_oid_fmt(old_idstr, git_commit_id(current_commit));
|
||||
git_oid_fmt(new_idstr, commit_id);
|
||||
git_oid_fmt(old_idstr, &operation->id);
|
||||
git_oid_fmt(new_idstr, git_commit_id(commit));
|
||||
|
||||
error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND,
|
||||
"%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr);
|
||||
if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND,
|
||||
"%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0)
|
||||
goto done;
|
||||
|
||||
git_oid_cpy(commit_id, git_commit_id(commit));
|
||||
|
||||
done:
|
||||
git_buf_free(&reflog_msg);
|
||||
git_commit_free(commit);
|
||||
git_diff_free(diff);
|
||||
git_tree_free(tree);
|
||||
git_tree_free(head_tree);
|
||||
git_commit_free(head_commit);
|
||||
git_commit_free(current_commit);
|
||||
git_reference_free(head);
|
||||
git_index_free(index);
|
||||
git_reference_free(head);
|
||||
git_commit_free(head_commit);
|
||||
git_commit_free(commit);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int rebase_commit_inmemory(
|
||||
git_oid *commit_id,
|
||||
git_rebase *rebase,
|
||||
const git_signature *author,
|
||||
const git_signature *committer,
|
||||
const char *message_encoding,
|
||||
const char *message)
|
||||
{
|
||||
git_rebase_operation *operation;
|
||||
git_commit *commit = NULL;
|
||||
int error = 0;
|
||||
|
||||
operation = git_array_get(rebase->operations, rebase->current);
|
||||
|
||||
assert(operation);
|
||||
assert(operation->index);
|
||||
assert(rebase->last_commit);
|
||||
|
||||
if ((error = rebase_commit__create(&commit, rebase, operation->index,
|
||||
rebase->last_commit, author, committer, message_encoding, message)) < 0)
|
||||
goto done;
|
||||
|
||||
git_index_free(rebase->last_index);
|
||||
operation->index = NULL;
|
||||
rebase->last_index = NULL;
|
||||
|
||||
git_commit_free(rebase->last_commit);
|
||||
rebase->last_commit = commit;
|
||||
|
||||
git_oid_cpy(commit_id, git_commit_id(commit));
|
||||
|
||||
done:
|
||||
if (error < 0)
|
||||
git_commit_free(commit);
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -928,14 +1053,14 @@ int git_rebase_commit(
|
||||
|
||||
assert(rebase && committer);
|
||||
|
||||
switch (rebase->type) {
|
||||
case GIT_REBASE_TYPE_MERGE:
|
||||
if (rebase->inmemory)
|
||||
error = rebase_commit_inmemory(
|
||||
id, rebase, author, committer, message_encoding, message);
|
||||
else if (rebase->type == GIT_REBASE_TYPE_MERGE)
|
||||
error = rebase_commit_merge(
|
||||
id, rebase, author, committer, message_encoding, message);
|
||||
break;
|
||||
default:
|
||||
else
|
||||
abort();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -948,6 +1073,9 @@ int git_rebase_abort(git_rebase *rebase)
|
||||
|
||||
assert(rebase);
|
||||
|
||||
if (rebase->inmemory)
|
||||
return 0;
|
||||
|
||||
error = rebase->head_detached ?
|
||||
git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE,
|
||||
&rebase->orig_head_id, 1, "rebase: aborting") :
|
||||
@ -1125,6 +1253,9 @@ int git_rebase_finish(
|
||||
|
||||
assert(rebase);
|
||||
|
||||
if (rebase->inmemory)
|
||||
return 0;
|
||||
|
||||
git_oid_fmt(onto, &rebase->onto_id);
|
||||
|
||||
if ((error = git_buf_printf(&branch_msg, "rebase finished: %s onto %.*s",
|
||||
@ -1182,6 +1313,8 @@ void git_rebase_free(git_rebase *rebase)
|
||||
if (rebase == NULL)
|
||||
return;
|
||||
|
||||
git_index_free(rebase->last_index);
|
||||
git_commit_free(rebase->last_commit);
|
||||
git__free(rebase->onto_name);
|
||||
git__free(rebase->orig_head_name);
|
||||
git__free(rebase->state_path);
|
||||
|
114
tests/rebase/inmemory.c
Normal file
114
tests/rebase/inmemory.c
Normal file
@ -0,0 +1,114 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "git2/rebase.h"
|
||||
#include "posix.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
static git_repository *repo;
|
||||
|
||||
// Fixture setup and teardown
|
||||
void test_rebase_inmemory__initialize(void)
|
||||
{
|
||||
repo = cl_git_sandbox_init("rebase");
|
||||
}
|
||||
|
||||
void test_rebase_inmemory__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_rebase_inmemory__not_in_rebase_state(void)
|
||||
{
|
||||
git_rebase *rebase;
|
||||
git_reference *branch_ref, *upstream_ref;
|
||||
git_annotated_commit *branch_head, *upstream_head;
|
||||
git_rebase_options opts = GIT_REBASE_OPTIONS_INIT;
|
||||
|
||||
opts.inmemory = true;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef"));
|
||||
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master"));
|
||||
|
||||
cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
|
||||
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts));
|
||||
|
||||
cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo));
|
||||
|
||||
git_rebase_free(rebase);
|
||||
|
||||
git_annotated_commit_free(branch_head);
|
||||
git_annotated_commit_free(upstream_head);
|
||||
|
||||
git_reference_free(branch_ref);
|
||||
git_reference_free(upstream_ref);
|
||||
}
|
||||
|
||||
void test_rebase_inmemory__can_resolve_conflicts(void)
|
||||
{
|
||||
git_rebase *rebase;
|
||||
git_reference *branch_ref, *upstream_ref;
|
||||
git_annotated_commit *branch_head, *upstream_head;
|
||||
git_rebase_operation *rebase_operation;
|
||||
git_status_list *status_list;
|
||||
git_oid pick_id, commit_id, expected_commit_id;
|
||||
git_signature *signature;
|
||||
git_index *repo_index;
|
||||
git_index_entry resolution = {{0}};
|
||||
git_rebase_options opts = GIT_REBASE_OPTIONS_INIT;
|
||||
|
||||
cl_git_pass(git_signature_new(&signature,
|
||||
"Rebaser", "rebaser@rebaser.rb", 1405694510, 0));
|
||||
|
||||
opts.inmemory = true;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus"));
|
||||
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master"));
|
||||
|
||||
cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
|
||||
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts));
|
||||
|
||||
cl_git_pass(git_rebase_next(&rebase_operation, rebase));
|
||||
|
||||
git_oid_fromstr(&pick_id, "33f915f9e4dbd9f4b24430e48731a59b45b15500");
|
||||
|
||||
cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type);
|
||||
cl_assert_equal_oid(&pick_id, &rebase_operation->id);
|
||||
|
||||
/* ensure that we did not do anything stupid to the workdir or repo index */
|
||||
cl_git_pass(git_repository_index(&repo_index, repo));
|
||||
cl_assert(!git_index_has_conflicts(repo_index));
|
||||
|
||||
cl_git_pass(git_status_list_new(&status_list, repo, NULL));
|
||||
cl_assert_equal_i(0, git_status_list_entrycount(status_list));
|
||||
|
||||
/* but that the index returned from rebase does have conflicts */
|
||||
cl_assert(git_index_has_conflicts(rebase_operation->index));
|
||||
|
||||
cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL));
|
||||
|
||||
/* ensure that we can work with the in-memory index to resolve the conflict */
|
||||
resolution.path = "asparagus.txt";
|
||||
resolution.mode = GIT_FILEMODE_BLOB;
|
||||
git_oid_fromstr(&resolution.id, "414dfc71ead79c07acd4ea47fecf91f289afc4b9");
|
||||
cl_git_pass(git_index_conflict_remove(rebase_operation->index, "asparagus.txt"));
|
||||
cl_git_pass(git_index_add(rebase_operation->index, &resolution));
|
||||
|
||||
/* and finally create a commit for the resolved rebase operation */
|
||||
cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL));
|
||||
|
||||
cl_git_pass(git_oid_fromstr(&expected_commit_id, "db7af47222181e548810da2ab5fec0e9357c5637"));
|
||||
cl_assert_equal_oid(&commit_id, &expected_commit_id);
|
||||
|
||||
git_signature_free(signature);
|
||||
git_status_list_free(status_list);
|
||||
git_annotated_commit_free(branch_head);
|
||||
git_annotated_commit_free(upstream_head);
|
||||
git_reference_free(branch_ref);
|
||||
git_reference_free(upstream_ref);
|
||||
git_index_free(repo_index);
|
||||
git_rebase_free(rebase);
|
||||
}
|
@ -46,26 +46,32 @@ static void test_operations(git_rebase *rebase, size_t expected_current)
|
||||
}
|
||||
}
|
||||
|
||||
void test_rebase_iterator__iterates(void)
|
||||
void test_iterator(bool inmemory)
|
||||
{
|
||||
git_rebase *rebase;
|
||||
git_rebase_options opts = GIT_REBASE_OPTIONS_INIT;
|
||||
git_reference *branch_ref, *upstream_ref;
|
||||
git_annotated_commit *branch_head, *upstream_head;
|
||||
git_rebase_operation *rebase_operation;
|
||||
git_oid commit_id;
|
||||
int error;
|
||||
|
||||
opts.inmemory = inmemory;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef"));
|
||||
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master"));
|
||||
|
||||
cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
|
||||
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL));
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts));
|
||||
test_operations(rebase, GIT_REBASE_NO_OPERATION);
|
||||
git_rebase_free(rebase);
|
||||
|
||||
cl_git_pass(git_rebase_open(&rebase, repo, NULL));
|
||||
if (!inmemory) {
|
||||
git_rebase_free(rebase);
|
||||
cl_git_pass(git_rebase_open(&rebase, repo, NULL));
|
||||
}
|
||||
|
||||
cl_git_pass(git_rebase_next(&rebase_operation, rebase));
|
||||
cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature,
|
||||
NULL, NULL));
|
||||
@ -81,8 +87,10 @@ void test_rebase_iterator__iterates(void)
|
||||
NULL, NULL));
|
||||
test_operations(rebase, 2);
|
||||
|
||||
git_rebase_free(rebase);
|
||||
cl_git_pass(git_rebase_open(&rebase, repo, NULL));
|
||||
if (!inmemory) {
|
||||
git_rebase_free(rebase);
|
||||
cl_git_pass(git_rebase_open(&rebase, repo, NULL));
|
||||
}
|
||||
|
||||
cl_git_pass(git_rebase_next(&rebase_operation, rebase));
|
||||
cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature,
|
||||
@ -104,3 +112,13 @@ void test_rebase_iterator__iterates(void)
|
||||
git_reference_free(upstream_ref);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
void test_rebase_iterator__iterates(void)
|
||||
{
|
||||
test_iterator(false);
|
||||
}
|
||||
|
||||
void test_rebase_iterator__iterates_inmemory(void)
|
||||
{
|
||||
test_iterator(true);
|
||||
}
|
||||
|
@ -565,3 +565,33 @@ void test_rebase_merge__custom_checkout_options(void)
|
||||
git_reference_free(upstream_ref);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
void test_rebase_merge__custom_merge_options(void)
|
||||
{
|
||||
git_rebase *rebase;
|
||||
git_reference *branch_ref, *upstream_ref;
|
||||
git_annotated_commit *branch_head, *upstream_head;
|
||||
git_rebase_options rebase_options = GIT_REBASE_OPTIONS_INIT;
|
||||
git_rebase_operation *rebase_operation;
|
||||
|
||||
rebase_options.merge_options.flags |=
|
||||
GIT_MERGE_FAIL_ON_CONFLICT |
|
||||
GIT_MERGE_SKIP_REUC;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus"));
|
||||
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master"));
|
||||
|
||||
cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
|
||||
|
||||
cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_options));
|
||||
|
||||
cl_git_fail_with(GIT_EMERGECONFLICT, git_rebase_next(&rebase_operation, rebase));
|
||||
|
||||
git_annotated_commit_free(branch_head);
|
||||
git_annotated_commit_free(upstream_head);
|
||||
git_reference_free(branch_ref);
|
||||
git_reference_free(upstream_ref);
|
||||
git_rebase_free(rebase);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user