diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2014-07-17 18:25:03 -0400 |
---|---|---|
committer | Edward Thomson <ethomson@microsoft.com> | 2014-10-26 22:59:19 -0400 |
commit | a35a9890b00b538cd0f3ef94a526c0dd2ec461bf (patch) | |
tree | 391dc91e8c3f872fe59f910fb06f422b3e6e26d7 | |
parent | 443d5674fe3f2edd4cb51c7657e7ad5063275bff (diff) | |
download | libgit2-a35a9890b00b538cd0f3ef94a526c0dd2ec461bf.tar.gz |
Introduce git_rebase_commit
Commit the current patch of a rebase process.
-rw-r--r-- | include/git2/rebase.h | 28 | ||||
-rw-r--r-- | src/rebase.c | 121 | ||||
-rw-r--r-- | src/signature.c | 11 | ||||
-rw-r--r-- | src/signature.h | 1 | ||||
-rw-r--r-- | tests/rebase/merge.c | 91 |
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); +} + |