diff --git a/include/git2/errors.h b/include/git2/errors.h index 5dfa72ab8..b33118e02 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -43,6 +43,7 @@ typedef enum { GIT_EMODIFIED = -15, /**< Reference value does not match expected */ GIT_EAUTH = -16, /**< Authentication error */ GIT_ECERTIFICATE = -17, /**< Server certificate is invalid */ + GIT_EAPPLIED = -18, /**< Patch/merge has already been applied */ GIT_PASSTHROUGH = -30, /**< Internal only */ GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */ diff --git a/include/git2/rebase.h b/include/git2/rebase.h index 0fe2b263d..32b4ff614 100644 --- a/include/git2/rebase.h +++ b/include/git2/rebase.h @@ -99,7 +99,9 @@ GIT_EXTERN(int) git_rebase_next( * @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. + * the index, GIT_EAPPLIED if the current commit has already + * been applied to the upstream and there is nothing to commit, + * -1 on failure. */ GIT_EXTERN(int) git_rebase_commit( git_oid *id, diff --git a/src/rebase.c b/src/rebase.c index 9245dcada..a28a928fc 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -674,7 +674,8 @@ static int rebase_commit_merge( git_index *index = NULL; git_reference *head = NULL; git_commit *head_commit = NULL; - git_tree *tree = NULL; + git_tree *head_tree = NULL, *tree = NULL; + git_diff *diff = NULL; git_oid tree_id; char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ]; int error; @@ -694,11 +695,19 @@ static int rebase_commit_merge( 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_commit_tree(&head_tree, head_commit)) < 0 || + (error = git_diff_tree_to_index(&diff, repo, head_tree, index, NULL)) < 0) + goto done; + + if (git_diff_num_deltas(diff) == 0) { + 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, repo, &tree_id)) < 0) goto done; @@ -722,7 +731,9 @@ static int rebase_commit_merge( "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr); done: + git_diff_free(diff); git_tree_free(tree); + git_tree_free(head_tree); git_commit_free(head_commit); git_reference_free(head); git_index_free(index); diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c index e578bef9b..04d06d2e8 100644 --- a/tests/rebase/merge.c +++ b/tests/rebase/merge.c @@ -207,3 +207,41 @@ void test_rebase_merge__commit_updates_rewritten(void) git_reference_free(upstream_ref); } +void test_rebase_merge__commit_drops_already_applied(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; + int error; + + 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/green_pea")); + + 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_fail(error = git_rebase_commit(&commit_id, repo, NULL, signature, + NULL, NULL)); + + cl_assert_equal_i(GIT_EAPPLIED, error); + + 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( + "8d1f13f93c4995760ac07d129246ac1ff64c0be9 2ac4fb7b74c1287f6c792acad759e1ec01e18dae\n", + 82, "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); +} + diff --git a/tests/resources/rebase/.gitted/objects/05/3808a709cf91385985369159b296cf61a177ac b/tests/resources/rebase/.gitted/objects/05/3808a709cf91385985369159b296cf61a177ac new file mode 100644 index 000000000..c38c5b255 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/05/3808a709cf91385985369159b296cf61a177ac differ diff --git a/tests/resources/rebase/.gitted/objects/50/8be4ff49d38465ad3de58f66d38f70e59f881f b/tests/resources/rebase/.gitted/objects/50/8be4ff49d38465ad3de58f66d38f70e59f881f new file mode 100644 index 000000000..7ce4452b2 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/50/8be4ff49d38465ad3de58f66d38f70e59f881f differ diff --git a/tests/resources/rebase/.gitted/objects/61/139b9b40a3e489f4abbc6af14e10ae14006e47 b/tests/resources/rebase/.gitted/objects/61/139b9b40a3e489f4abbc6af14e10ae14006e47 new file mode 100644 index 000000000..b096a964c Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/61/139b9b40a3e489f4abbc6af14e10ae14006e47 differ diff --git a/tests/resources/rebase/.gitted/objects/ad/c97cfb874cdfb9d5ab17b54f3771dea6e02ccf b/tests/resources/rebase/.gitted/objects/ad/c97cfb874cdfb9d5ab17b54f3771dea6e02ccf new file mode 100644 index 000000000..cc7ccd086 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/ad/c97cfb874cdfb9d5ab17b54f3771dea6e02ccf differ diff --git a/tests/resources/rebase/.gitted/objects/cb/20a10406172afd6ca3138ce36ecaf8b1269e8e b/tests/resources/rebase/.gitted/objects/cb/20a10406172afd6ca3138ce36ecaf8b1269e8e new file mode 100644 index 000000000..acd6bdc56 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/cb/20a10406172afd6ca3138ce36ecaf8b1269e8e differ diff --git a/tests/resources/rebase/.gitted/objects/d4/82e77aecb8e07da43e4cad6e0dcb59219e12af b/tests/resources/rebase/.gitted/objects/d4/82e77aecb8e07da43e4cad6e0dcb59219e12af new file mode 100644 index 000000000..af3bd174a Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/d4/82e77aecb8e07da43e4cad6e0dcb59219e12af differ diff --git a/tests/resources/rebase/.gitted/objects/dc/12ac1e10f2be70e8ecd52132a08da98a309c3a b/tests/resources/rebase/.gitted/objects/dc/12ac1e10f2be70e8ecd52132a08da98a309c3a new file mode 100644 index 000000000..9907248f8 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/dc/12ac1e10f2be70e8ecd52132a08da98a309c3a differ diff --git a/tests/resources/rebase/.gitted/objects/e7/bb00c4eab291e08361fda376733a12b4150aa9 b/tests/resources/rebase/.gitted/objects/e7/bb00c4eab291e08361fda376733a12b4150aa9 new file mode 100644 index 000000000..da96e9d9c Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/e7/bb00c4eab291e08361fda376733a12b4150aa9 differ diff --git a/tests/resources/rebase/.gitted/objects/ed/f7b3ffde1624c60d2d6b1a2bb792d86de172e0 b/tests/resources/rebase/.gitted/objects/ed/f7b3ffde1624c60d2d6b1a2bb792d86de172e0 new file mode 100644 index 000000000..e2e98d6d8 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/ed/f7b3ffde1624c60d2d6b1a2bb792d86de172e0 differ diff --git a/tests/resources/rebase/.gitted/refs/heads/green_pea b/tests/resources/rebase/.gitted/refs/heads/green_pea new file mode 100644 index 000000000..3bffe27d1 Binary files /dev/null and b/tests/resources/rebase/.gitted/refs/heads/green_pea differ