diff options
author | Edward Thomson <ethomson@microsoft.com> | 2014-07-15 10:23:10 -0400 |
---|---|---|
committer | Edward Thomson <ethomson@microsoft.com> | 2014-10-26 22:59:14 -0400 |
commit | 950a70915930342d18286c6d6350929662a978e2 (patch) | |
tree | ad7ce6126e0f1098cb4903605716f5b65ec74083 | |
parent | 4fe84d624b42a1ef1b9b392483ddc43064b1180e (diff) | |
download | libgit2-950a70915930342d18286c6d6350929662a978e2.tar.gz |
Introduce git_rebase_next
`git_rebase_next` will apply the next patch (or cherry-pick)
operation, leaving the results checked out in the index / working
directory so that consumers can resolve any conflicts, as appropriate.
-rw-r--r-- | include/git2/rebase.h | 13 | ||||
-rw-r--r-- | src/rebase.c | 156 | ||||
-rw-r--r-- | tests/rebase/merge.c | 59 |
3 files changed, 228 insertions, 0 deletions
diff --git a/include/git2/rebase.h b/include/git2/rebase.h index c760fbe74..e4300ebf6 100644 --- a/include/git2/rebase.h +++ b/include/git2/rebase.h @@ -69,6 +69,19 @@ GIT_EXTERN(int) git_rebase( const git_rebase_options *opts); /** + * Applies the next patch, updating the index and working directory with the + * changes. If there are conflicts, you will need to address those before + * committing the changes. + * + * @param repo The repository with a rebase in progress + * @param checkout_opts Options to specify how the patch should be checked out + * @return Zero on success; -1 on failure. + */ +GIT_EXTERN(int) git_rebase_next( + git_repository *repo, + git_checkout_options *checkout_opts); + +/** * Aborts a rebase that is currently in progress, resetting the repository * and working directory to their state before rebase began. * diff --git a/src/rebase.c b/src/rebase.c index 38ced24bb..1062b2a2b 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -32,6 +32,7 @@ #define MSGNUM_FILE "msgnum" #define END_FILE "end" #define CMT_FILE_FMT "cmt.%d" +#define CURRENT_FILE "current" #define ORIG_DETACHED_HEAD "detached HEAD" @@ -42,8 +43,14 @@ typedef enum { GIT_REBASE_TYPE_NONE = 0, GIT_REBASE_TYPE_APPLY = 1, GIT_REBASE_TYPE_MERGE = 2, + GIT_REBASE_TYPE_INTERACTIVE = 3, } git_rebase_type_t; +struct git_rebase_state_merge { + int32_t msgnum; + int32_t end; +}; + typedef struct { git_rebase_type_t type; char *state_path; @@ -52,6 +59,10 @@ typedef struct { char *orig_head_name; git_oid orig_head_id; + + union { + struct git_rebase_state_merge merge; + }; } git_rebase_state; #define GIT_REBASE_STATE_INIT {0} @@ -92,6 +103,50 @@ done: return 0; } +static int rebase_state_merge(git_rebase_state *state, git_repository *repo) +{ + git_buf path = GIT_BUF_INIT, msgnum = GIT_BUF_INIT, end = GIT_BUF_INIT; + int state_path_len, error; + + GIT_UNUSED(repo); + + if ((error = git_buf_puts(&path, state->state_path)) < 0) + goto done; + + state_path_len = git_buf_len(&path); + + if ((error = git_buf_joinpath(&path, path.ptr, MSGNUM_FILE)) < 0) + goto done; + + if (git_path_isfile(path.ptr)) { + if ((error = git_futils_readbuffer(&msgnum, path.ptr)) < 0) + goto done; + + git_buf_rtrim(&msgnum); + + if ((error = git__strtol32(&state->merge.msgnum, msgnum.ptr, NULL, 10)) < 0) + goto done; + } + + git_buf_truncate(&path, state_path_len); + + if ((error = git_buf_joinpath(&path, path.ptr, END_FILE)) < 0 || + (error = git_futils_readbuffer(&end, path.ptr)) < 0) + goto done; + + git_buf_rtrim(&end); + + if ((error = git__strtol32(&state->merge.end, end.ptr, NULL, 10)) < 0) + goto done; + +done: + git_buf_free(&path); + git_buf_free(&msgnum); + git_buf_free(&end); + + return error; +} + static int rebase_state(git_rebase_state *state, git_repository *repo) { git_buf path = GIT_BUF_INIT, orig_head_name = GIT_BUF_INIT, @@ -146,6 +201,22 @@ static int rebase_state(git_rebase_state *state, git_repository *repo) if (!state->head_detached) state->orig_head_name = git_buf_detach(&orig_head_name); + switch (state->type) { + case GIT_REBASE_TYPE_INTERACTIVE: + giterr_set(GITERR_REBASE, "Interactive rebase is not supported"); + error = -1; + break; + case GIT_REBASE_TYPE_MERGE: + error = rebase_state_merge(state, repo); + break; + case GIT_REBASE_TYPE_APPLY: + giterr_set(GITERR_REBASE, "Patch application rebase is not supported"); + error = -1; + break; + default: + abort(); + } + done: git_buf_free(&path); git_buf_free(&orig_head_name); @@ -421,6 +492,91 @@ done: return error; } +static int rebase_next_merge( + git_repository *repo, + git_rebase_state *state, + git_checkout_options *checkout_opts) +{ + git_buf path = GIT_BUF_INIT, current = GIT_BUF_INIT; + git_oid current_id; + git_commit *current_commit = NULL, *parent_commit = NULL; + git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; + git_index *index = NULL; + unsigned int parent_count; + int error; + + if (state->merge.msgnum == state->merge.end) + return GIT_ITEROVER; + + state->merge.msgnum++; + + if ((error = git_buf_joinpath(&path, state->state_path, "cmt.")) < 0 || + (error = git_buf_printf(&path, "%d", state->merge.msgnum)) < 0 || + (error = git_futils_readbuffer(¤t, path.ptr)) < 0) + goto done; + + git_buf_rtrim(¤t); + + if ((error = git_oid_fromstr(¤t_id, current.ptr)) < 0 || + (error = git_commit_lookup(¤t_commit, repo, ¤t_id)) < 0 || + (error = git_commit_tree(¤t_tree, current_commit)) < 0 || + (error = git_repository_head_tree(&head_tree, repo)) < 0) + goto done; + + if ((parent_count = git_commit_parentcount(current_commit)) > 1) { + giterr_set(GITERR_REBASE, "Cannot rebase a merge commit"); + error = -1; + goto done; + } else if (parent_count) { + if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0) + goto done; + } + + if ((error = rebase_setupfile(repo, MSGNUM_FILE, "%d\n", state->merge.msgnum)) < 0 || + (error = rebase_setupfile(repo, CURRENT_FILE, "%s\n", current.ptr)) < 0) + goto done; + + if ((error = git_merge_trees(&index, repo, parent_tree, head_tree, current_tree, NULL)) < 0 || + (error = git_merge__check_result(repo, index)) < 0 || + (error = git_checkout_index(repo, index, checkout_opts)) < 0) + goto done; + +done: + git_index_free(index); + git_tree_free(current_tree); + git_tree_free(head_tree); + git_tree_free(parent_tree); + git_commit_free(current_commit); + git_commit_free(parent_commit); + git_buf_free(&path); + git_buf_free(¤t); + + return error; +} + +int git_rebase_next(git_repository *repo, git_checkout_options *opts) +{ + git_rebase_state state = GIT_REBASE_STATE_INIT; + int error; + + assert(repo); + + if ((error = rebase_state(&state, repo)) < 0) + return -1; + + switch (state.type) { + case GIT_REBASE_TYPE_MERGE: + error = rebase_next_merge(repo, &state, opts); + break; + default: + abort(); + } + + rebase_state_free(&state); + return error; +} + int git_rebase_abort(git_repository *repo, const git_signature *signature) { git_rebase_state state = GIT_REBASE_STATE_INIT; diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c new file mode 100644 index 000000000..e44fdd3f6 --- /dev/null +++ b/tests/rebase/merge.c @@ -0,0 +1,59 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" +#include "posix.h" + +#include <fcntl.h> + +static git_repository *repo; +static git_signature *signature; + +// Fixture setup and teardown +void test_rebase_merge__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); +} + +void test_rebase_merge__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_rebase_merge__next(void) +{ + git_reference *branch_ref, *upstream_ref; + git_merge_head *branch_head, *upstream_head; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_status_list *status_list; + const git_status_entry *status_entry; + git_oid file1_id; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + 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_merge_head_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_merge_head_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase(repo, branch_head, upstream_head, NULL, signature, NULL)); + + cl_git_pass(git_rebase_next(repo, &checkout_opts)); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/current"); + cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/msgnum"); + + cl_git_pass(git_status_list_new(&status_list, repo, NULL)); + cl_assert_equal_i(1, git_status_list_entrycount(status_list)); + cl_assert(status_entry = git_status_byindex(status_list, 0)); + + cl_assert_equal_s("beef.txt", status_entry->head_to_index->new_file.path); + + git_oid_fromstr(&file1_id, "8d95ea62e621f1d38d230d9e7d206e41096d76af"); + cl_assert_equal_oid(&file1_id, &status_entry->head_to_index->new_file.id); + + git_status_list_free(status_list); + git_merge_head_free(branch_head); + git_merge_head_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); +} |