diff options
| author | Edward Thomson <ethomson@github.com> | 2016-02-11 10:48:48 -0800 |
|---|---|---|
| committer | Edward Thomson <ethomson@github.com> | 2016-02-11 10:48:48 -0800 |
| commit | ee6673070a6134f0a1743c866583b14a305b6a43 (patch) | |
| tree | 44e7b14e9ed28baa15c763332ec8909052970e47 /src/rebase.c | |
| parent | 488e2b85053a45dd2ce6a37b7d5cf70870be5ed8 (diff) | |
| download | libgit2-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.c | 367 |
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(¤t_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(¤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, 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(¤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) + 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_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); |
