Introduce git_rebase_finish to complete a rebase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
diff --git a/include/git2/rebase.h b/include/git2/rebase.h
index 32b4ff6..e123ae5 100644
--- a/include/git2/rebase.h
+++ b/include/git2/rebase.h
@@ -124,6 +124,18 @@ GIT_EXTERN(int) git_rebase_abort(
git_repository *repo,
const git_signature *signature);
+/**
+ * Finishes a rebase that is currently in progress once all patches have
+ * been applied.
+ *
+ * @param repo The repository with the in-progress rebase
+ * @param signature The identity that is finishing the rebase
+ * @param Zero on success; -1 on error
+ */
+GIT_EXTERN(int) git_rebase_finish(
+ git_repository *repo,
+ const git_signature *signature);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/rebase.c b/src/rebase.c
index a28a928..101dfb1 100644
--- a/src/rebase.c
+++ b/src/rebase.c
@@ -64,6 +64,8 @@ typedef struct {
char *orig_head_name;
git_oid orig_head_id;
+ git_oid onto_id;
+
union {
struct git_rebase_state_merge merge;
};
@@ -189,7 +191,7 @@ done:
static int rebase_state(git_rebase_state *state, git_repository *repo)
{
git_buf path = GIT_BUF_INIT, orig_head_name = GIT_BUF_INIT,
- orig_head_id = GIT_BUF_INIT;
+ orig_head_id = GIT_BUF_INIT, onto_id = GIT_BUF_INIT;
int state_path_len, error;
memset(state, 0x0, sizeof(git_rebase_state));
@@ -237,6 +239,17 @@ static int rebase_state(git_rebase_state *state, git_repository *repo)
if ((error = git_oid_fromstr(&state->orig_head_id, orig_head_id.ptr)) < 0)
goto done;
+ git_buf_truncate(&path, state_path_len);
+
+ if ((error = git_buf_joinpath(&path, path.ptr, ONTO_FILE)) < 0 ||
+ (error = git_futils_readbuffer(&onto_id, path.ptr)) < 0)
+ goto done;
+
+ git_buf_rtrim(&onto_id);
+
+ if ((error = git_oid_fromstr(&state->onto_id, onto_id.ptr)) < 0)
+ goto done;
+
if (!state->head_detached)
state->orig_head_name = git_buf_detach(&orig_head_name);
@@ -808,3 +821,51 @@ done:
return error;
}
+
+int git_rebase_finish(git_repository *repo, const git_signature *signature)
+{
+ git_rebase_state state = GIT_REBASE_STATE_INIT;
+ git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL;
+ git_commit *terminal_commit = NULL;
+ git_buf branch_msg = GIT_BUF_INIT, head_msg = GIT_BUF_INIT;
+ char onto[GIT_OID_HEXSZ];
+ int error;
+
+ assert(repo);
+
+ if ((error = rebase_state(&state, repo)) < 0)
+ goto done;
+
+ git_oid_fmt(onto, &state.onto_id);
+
+ if ((error = git_buf_printf(&branch_msg, "rebase finished: %s onto %.*s",
+ state.orig_head_name, GIT_OID_HEXSZ, onto)) < 0 ||
+ (error = git_buf_printf(&head_msg, "rebase finished: returning to %s",
+ state.orig_head_name)) < 0 ||
+ (error = git_repository_head(&terminal_ref, repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&terminal_commit,
+ terminal_ref, GIT_OBJ_COMMIT)) < 0)
+ goto done;
+
+ if ((error = git_reference_create_matching(&branch_ref,
+ repo, state.orig_head_name, git_commit_id(terminal_commit), 1,
+ &state.orig_head_id, signature, branch_msg.ptr)) < 0 ||
+ (error = git_reference_symbolic_create(&head_ref,
+ repo, GIT_HEAD_FILE, state.orig_head_name, 1,
+ signature, head_msg.ptr)) < 0)
+ goto done;
+
+ error = rebase_finish(&state);
+
+done:
+ git_buf_free(&head_msg);
+ git_buf_free(&branch_msg);
+ git_commit_free(terminal_commit);
+ git_reference_free(head_ref);
+ git_reference_free(branch_ref);
+ git_reference_free(terminal_ref);
+ rebase_state_free(&state);
+
+ return error;
+}
+
diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c
index 31fa6cc..fa37e89 100644
--- a/tests/rebase/merge.c
+++ b/tests/rebase/merge.c
@@ -295,3 +295,60 @@ void test_rebase_merge__commit_drops_already_applied(void)
git_reference_free(upstream_ref);
}
+void test_rebase_merge__finish(void)
+{
+ git_reference *branch_ref, *upstream_ref, *head_ref;
+ git_merge_head *branch_head, *upstream_head;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid commit_id;
+ git_reflog *reflog;
+ const git_reflog_entry *reflog_entry;
+ int error;
+
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy"));
+ cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal"));
+
+ 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_git_pass(git_rebase_commit(&commit_id, repo, NULL, signature,
+ NULL, NULL));
+
+ cl_git_fail(error = git_rebase_next(repo, &checkout_opts));
+ cl_assert_equal_i(GIT_ITEROVER, error);
+
+ cl_git_pass(git_rebase_finish(repo, signature));
+
+ cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo));
+
+ cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD"));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head_ref));
+ cl_assert_equal_s("refs/heads/gravy", git_reference_symbolic_target(head_ref));
+
+ /* Make sure the reflogs are updated appropriately */
+ cl_git_pass(git_reflog_read(&reflog, repo, "HEAD"));
+ cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0));
+ cl_assert_equal_oid(&commit_id, git_reflog_entry_id_old(reflog_entry));
+ cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry));
+ cl_assert_equal_s("rebase finished: returning to refs/heads/gravy", git_reflog_entry_message(reflog_entry));
+ git_reflog_free(reflog);
+
+ cl_git_pass(git_reflog_read(&reflog, repo, "refs/heads/gravy"));
+ cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0));
+ cl_assert_equal_oid(git_merge_head_id(branch_head), git_reflog_entry_id_old(reflog_entry));
+ cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry));
+ cl_assert_equal_s("rebase finished: refs/heads/gravy onto f87d14a4a236582a0278a916340a793714256864", git_reflog_entry_message(reflog_entry));
+
+ git_reflog_free(reflog);
+ git_merge_head_free(branch_head);
+ git_merge_head_free(upstream_head);
+ git_reference_free(head_ref);
+ git_reference_free(branch_ref);
+ git_reference_free(upstream_ref);
+}
+