summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2014-07-17 18:25:03 -0400
committerEdward Thomson <ethomson@microsoft.com>2014-10-26 22:59:19 -0400
commita35a9890b00b538cd0f3ef94a526c0dd2ec461bf (patch)
tree391dc91e8c3f872fe59f910fb06f422b3e6e26d7
parent443d5674fe3f2edd4cb51c7657e7ad5063275bff (diff)
downloadlibgit2-a35a9890b00b538cd0f3ef94a526c0dd2ec461bf.tar.gz
Introduce git_rebase_commit
Commit the current patch of a rebase process.
-rw-r--r--include/git2/rebase.h28
-rw-r--r--src/rebase.c121
-rw-r--r--src/signature.c11
-rw-r--r--src/signature.h1
-rw-r--r--tests/rebase/merge.c91
5 files changed, 241 insertions, 11 deletions
diff --git a/include/git2/rebase.h b/include/git2/rebase.h
index e4300ebf6..0fe2b263d 100644
--- a/include/git2/rebase.h
+++ b/include/git2/rebase.h
@@ -82,6 +82,34 @@ GIT_EXTERN(int) git_rebase_next(
git_checkout_options *checkout_opts);
/**
+ * Commits the current patch. You must have resolved any conflicts that
+ * were introduced during the patch application from the `git_rebase_next`
+ * invocation.
+ *
+ * @param id Pointer in which to store the OID of the newly created commit
+ * @param repo The repository with the in-progress rebase
+ * @param author The author of the updated commit, or NULL to keep the
+ * author from the original commit
+ * @param committer The committer of the rebase
+ * @param message_encoding The encoding for the message in the commit,
+ * represented with a standard encoding name. If message is NULL,
+ * this should also be NULL, and the encoding from the original
+ * commit will be maintained. If message is specified, this may be
+ * NULL to indicate that "UTF-8" is to be used.
+ * @param message The message for this commit, or NULL to keep the message
+ * from the original commit
+ * @return Zero on success, GIT_EUNMERGED if there are unmerged changes in
+ * the index, -1 on failure.
+ */
+GIT_EXTERN(int) git_rebase_commit(
+ git_oid *id,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message);
+
+/**
* 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 676a803cc..9245dcada 100644
--- a/src/rebase.c
+++ b/src/rebase.c
@@ -33,6 +33,7 @@
#define END_FILE "end"
#define CMT_FILE_FMT "cmt.%d"
#define CURRENT_FILE "current"
+#define REWRITTEN_FILE "rewritten"
#define ORIG_DETACHED_HEAD "detached HEAD"
@@ -284,7 +285,7 @@ static int rebase_finish(git_rebase_state *state)
0;
}
-static int rebase_setupfile(git_repository *repo, const char *filename, const char *fmt, ...)
+static int rebase_setupfile(git_repository *repo, const char *filename, int flags, const char *fmt, ...)
{
git_buf path = GIT_BUF_INIT,
contents = GIT_BUF_INIT;
@@ -297,7 +298,7 @@ static int rebase_setupfile(git_repository *repo, const char *filename, const ch
if ((error = git_buf_joinpath(&path, repo->path_repository, REBASE_MERGE_DIR)) == 0 &&
(error = git_buf_joinpath(&path, path.ptr, filename)) == 0)
- error = git_futils_writebuffer(&contents, path.ptr, O_RDWR|O_CREAT, REBASE_FILE_MODE);
+ error = git_futils_writebuffer(&contents, path.ptr, flags, REBASE_FILE_MODE);
git_buf_free(&path);
git_buf_free(&contents);
@@ -361,16 +362,16 @@ static int rebase_setup_merge(
git_buf_printf(&commit_filename, CMT_FILE_FMT, commit_cnt);
git_oid_fmt(id_str, &id);
- if ((error = rebase_setupfile(repo, commit_filename.ptr,
+ if ((error = rebase_setupfile(repo, commit_filename.ptr, -1,
"%.*s\n", GIT_OID_HEXSZ, id_str)) < 0)
goto done;
}
if (error != GIT_ITEROVER ||
- (error = rebase_setupfile(repo, END_FILE, "%d\n", commit_cnt)) < 0)
+ (error = rebase_setupfile(repo, END_FILE, -1, "%d\n", commit_cnt)) < 0)
goto done;
- error = rebase_setupfile(repo, ONTO_NAME_FILE, "%s\n",
+ error = rebase_setupfile(repo, ONTO_NAME_FILE, -1, "%s\n",
rebase_onto_name(onto));
done:
@@ -405,10 +406,10 @@ static int rebase_setup(
orig_head_name = branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD;
- if ((error = rebase_setupfile(repo, HEAD_NAME_FILE, "%s\n", orig_head_name)) < 0 ||
- (error = rebase_setupfile(repo, ONTO_FILE, "%s\n", onto->oid_str)) < 0 ||
- (error = rebase_setupfile(repo, ORIG_HEAD_FILE, "%s\n", branch->oid_str)) < 0 ||
- (error = rebase_setupfile(repo, QUIET_FILE, opts->quiet ? "t\n" : "\n")) < 0)
+ if ((error = rebase_setupfile(repo, HEAD_NAME_FILE, -1, "%s\n", orig_head_name)) < 0 ||
+ (error = rebase_setupfile(repo, ONTO_FILE, -1, "%s\n", onto->oid_str)) < 0 ||
+ (error = rebase_setupfile(repo, ORIG_HEAD_FILE, -1, "%s\n", branch->oid_str)) < 0 ||
+ (error = rebase_setupfile(repo, QUIET_FILE, -1, opts->quiet ? "t\n" : "\n")) < 0)
goto done;
error = rebase_setup_merge(repo, branch, upstream, onto, opts);
@@ -615,8 +616,8 @@ static int rebase_next_merge(
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)
+ if ((error = rebase_setupfile(repo, MSGNUM_FILE, -1, "%d\n", state->merge.msgnum)) < 0 ||
+ (error = rebase_setupfile(repo, CURRENT_FILE, -1, "%s\n", current.ptr)) < 0)
goto done;
if ((error = normalize_checkout_opts(repo, &checkout_opts, given_checkout_opts, state)) < 0 ||
@@ -661,6 +662,104 @@ int git_rebase_next(
return error;
}
+static int rebase_commit_merge(
+ git_oid *commit_id,
+ git_repository *repo,
+ git_rebase_state *state,
+ 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 *head_commit = NULL;
+ git_tree *tree = NULL;
+ git_oid tree_id;
+ char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ];
+ int error;
+
+ if (!state->merge.msgnum || !state->merge.current) {
+ giterr_set(GITERR_REBASE, "No rebase-merge state files exist");
+ error = -1;
+ goto done;
+ }
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ goto done;
+
+ if (git_index_has_conflicts(index)) {
+ giterr_set(GITERR_REBASE, "Conflicts have not been resolved");
+ error = GIT_EMERGECONFLICT;
+ goto done;
+ }
+
+ /* TODO: if there are no changes, error with a useful code */
+
+ if ((error = git_repository_head(&head, repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_index_write_tree(&tree_id, index)) < 0 ||
+ (error = git_tree_lookup(&tree, repo, &tree_id)) < 0)
+ goto done;
+
+ if (!author)
+ author = git_commit_author(state->merge.current);
+
+ if (!message) {
+ message_encoding = git_commit_message_encoding(state->merge.current);
+ message = git_commit_message(state->merge.current);
+ }
+
+ if ((error = git_commit_create(commit_id, repo, "HEAD", author,
+ committer, message_encoding, message, tree, 1,
+ (const git_commit **)&head_commit)) < 0)
+ goto done;
+
+ git_oid_fmt(old_idstr, git_commit_id(state->merge.current));
+ git_oid_fmt(new_idstr, commit_id);
+
+ error = rebase_setupfile(repo, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND,
+ "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr);
+
+done:
+ git_tree_free(tree);
+ git_commit_free(head_commit);
+ git_reference_free(head);
+ git_index_free(index);
+
+ return error;
+}
+
+int git_rebase_commit(
+ git_oid *id,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message)
+{
+ git_rebase_state state = GIT_REBASE_STATE_INIT;
+ int error;
+
+ assert(repo && committer);
+
+ if ((error = rebase_state(&state, repo)) < 0)
+ goto done;
+
+ switch (state.type) {
+ case GIT_REBASE_TYPE_MERGE:
+ error = rebase_commit_merge(
+ id, repo, &state, author, committer, message_encoding, message);
+ break;
+ default:
+ abort();
+ }
+
+done:
+ 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/src/signature.c b/src/signature.c
index 514b153ac..818cd300e 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -270,3 +270,14 @@ void git_signature__writebuf(git_buf *buf, const char *header, const git_signatu
(unsigned)sig->when.time, sign, hours, mins);
}
+bool git_signature__equal(const git_signature *one, const git_signature *two)
+{
+ assert(one && two);
+
+ return
+ git__strcmp(one->name, two->name) == 0 &&
+ git__strcmp(one->email, two->email) == 0 &&
+ one->when.time == two->when.time &&
+ one->when.offset == two->when.offset;
+}
+
diff --git a/src/signature.h b/src/signature.h
index eb71db7db..75265df52 100644
--- a/src/signature.h
+++ b/src/signature.h
@@ -14,6 +14,7 @@
int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender);
void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig);
+bool git_signature__equal(const git_signature *one, const git_signature *two);
int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool);
diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c
index 506c411ed..e578bef9b 100644
--- a/tests/rebase/merge.c
+++ b/tests/rebase/merge.c
@@ -1,6 +1,7 @@
#include "clar_libgit2.h"
#include "git2/rebase.h"
#include "posix.h"
+#include "signature.h"
#include <fcntl.h>
@@ -11,10 +12,13 @@ static git_signature *signature;
void test_rebase_merge__initialize(void)
{
repo = cl_git_sandbox_init("rebase");
+ cl_git_pass(git_signature_new(&signature,
+ "Rebaser", "rebaser@rebaser.rb", 1405694510, 0));
}
void test_rebase_merge__cleanup(void)
{
+ git_signature_free(signature);
cl_git_sandbox_cleanup();
}
@@ -116,3 +120,90 @@ void test_rebase_merge__next_with_conflicts(void)
git_reference_free(upstream_ref);
}
+void test_rebase_merge__commit(void)
+{
+ git_reference *branch_ref, *upstream_ref;
+ git_merge_head *branch_head, *upstream_head;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid commit_id, tree_id, parent_id;
+ git_signature *author;
+ git_commit *commit;
+
+ 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_git_pass(git_rebase_commit(&commit_id, repo, NULL, signature,
+ NULL, NULL));
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
+
+ git_oid_fromstr(&parent_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00");
+ cl_assert_equal_i(1, git_commit_parentcount(commit));
+ cl_assert_equal_oid(&parent_id, git_commit_parent_id(commit, 0));
+
+ git_oid_fromstr(&tree_id, "4461379789c777d2a6c1f2ee0e9d6c86731b9992");
+ cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit));
+
+ cl_assert_equal_s(NULL, git_commit_message_encoding(commit));
+ cl_assert_equal_s("Modification 1 to beef\n", git_commit_message(commit));
+
+ cl_git_pass(git_signature_new(&author,
+ "Edward Thomson", "ethomson@edwardthomson.com", 1405621769, 0-(4*60)));
+ cl_assert(git_signature__equal(author, git_commit_author(commit)));
+
+ cl_assert(git_signature__equal(signature, git_commit_committer(commit)));
+
+ cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94 776e4c48922799f903f03f5f6e51da8b01e4cce0\n", 82, "rebase/.git/rebase-merge/rewritten");
+
+ git_signature_free(author);
+ git_commit_free(commit);
+ git_merge_head_free(branch_head);
+ git_merge_head_free(upstream_head);
+ git_reference_free(branch_ref);
+ git_reference_free(upstream_ref);
+}
+
+void test_rebase_merge__commit_updates_rewritten(void)
+{
+ git_reference *branch_ref, *upstream_ref;
+ git_merge_head *branch_head, *upstream_head;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid commit_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_git_pass(git_rebase_commit(&commit_id, repo, NULL, signature,
+ NULL, NULL));
+
+ cl_git_pass(git_rebase_next(repo, &checkout_opts));
+ cl_git_pass(git_rebase_commit(&commit_id, repo, NULL, signature,
+ NULL, NULL));
+
+ cl_assert_equal_file(
+ "da9c51a23d02d931a486f45ad18cda05cf5d2b94 776e4c48922799f903f03f5f6e51da8b01e4cce0\n"
+ "8d1f13f93c4995760ac07d129246ac1ff64c0be9 ba1f9b4fd5cf8151f7818be2111cc0869f1eb95a\n",
+ 164, "rebase/.git/rebase-merge/rewritten");
+
+ git_merge_head_free(branch_head);
+ git_merge_head_free(upstream_head);
+ git_reference_free(branch_ref);
+ git_reference_free(upstream_ref);
+}
+