summaryrefslogtreecommitdiff
path: root/src/rebase.c
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@github.com>2016-02-11 10:48:48 -0800
committerEdward Thomson <ethomson@github.com>2016-02-11 10:48:48 -0800
commitee6673070a6134f0a1743c866583b14a305b6a43 (patch)
tree44e7b14e9ed28baa15c763332ec8909052970e47 /src/rebase.c
parent488e2b85053a45dd2ce6a37b7d5cf70870be5ed8 (diff)
downloadlibgit2-ee6673070a6134f0a1743c866583b14a305b6a43.tar.gz
rebase: introduce inmemory rebasing
Introduce the ability to rebase in-memory or in a bare repository. When `rebase_options.inmemory` is specified, the resultant `git_rebase` session will not be persisted to disk. Callers may still analyze the rebase operations, resolve any conflicts against the in-memory index and create the commits. Neither `HEAD` nor the working directory will be updated during this process.
Diffstat (limited to 'src/rebase.c')
-rw-r--r--src/rebase.c367
1 files changed, 250 insertions, 117 deletions
diff --git a/src/rebase.c b/src/rebase.c
index 17536c030..54affd6e2 100644
--- a/src/rebase.c
+++ b/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,61 +610,65 @@ 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;
-
- rebase->onto_name = git__strdup(rebase_onto_name(onto));
- GITERR_CHECK_ALLOC(rebase->onto_name);
-
- return 0;
-}
-
-static int rebase_init(
- 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_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;
- 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);
+ 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);
+
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);
+ 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_annotated_commit_free(head_branch);
+ git_commit_free(onto_commit);
+ git_buf_free(&reflog);
+ git_buf_free(&state_path);
return error;
}
+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_UNUSED(branch);
+ GIT_UNUSED(upstream);
+
+ return git_commit_lookup(
+ &rebase->last_commit, repo, git_annotated_commit_id(onto));
+}
+
int git_rebase_init(
git_rebase **out,
git_repository *repo,
@@ -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;
+ if ((error = rebase_check_versions(given_opts)) < 0)
+ goto done;
+
+ 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);
- 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)
+ 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;
- *out = rebase;
+ 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(&current_commit, rebase->repo, &operation->id)) < 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(&current_commit, rebase->repo, &operation->id)) < 0 ||
+ (error = git_commit_tree(&current_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, NULL)) < 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,66 +882,55 @@ 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_merge(
- git_oid *commit_id,
+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_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;
- char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ];
+ 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);
- 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");
+ 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(&current_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)
+ if ((error = git_commit_lookup(&current_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_diff_num_deltas(diff) == 0) {
- giterr_set(GITERR_REBASE, "This patch has already been applied");
+ 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 ((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);
@@ -888,30 +939,104 @@ static int rebase_commit_merge(
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 ||
+ 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;
+}
+
+static int rebase_commit_merge(
+ 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_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 = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 ||
+ (error = git_repository_head(&head, rebase->repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)) < 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));
+
+ 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;
- 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);
+ 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);