diff --git a/CHANGELOG.md b/CHANGELOG.md index be8e92400..12c60532c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ v0.22 + 1 allow for specifying the expression from the user to be put into the reflog. +* `git_rebase_commit` now returns `GIT_EUNMERGED` when you attempt to + commit with unstaged changes. + ### API additions * The `git_merge_options` gained a `file_flags` member. @@ -101,6 +104,18 @@ v0.22 + 1 * `git_note_default_ref()` now uses a `git_buf` to return the string, as the string is otherwise not guaranteed to stay allocated. +* `git_rebase_operation_current()` will return `GIT_REBASE_NO_OPERATION` + if it is called immediately after creating a rebase session but before + you have applied the first patch. + +* `git_rebase_options` now contains an optional pointer to + `git_checkout_options` that will be used for functions that modify + the working directory, namely `git_checkout_init`, `git_checkout_next` + and `git_checkout_abort`. As a result, `git_rebase_open` now also + takes a `git_rebase_options` and only the `git_rebase_init` and + `git_rebase_open` functions take a `git_rebase_options`, where they + will persist the options to subsequent `git_rebase` calls. + v0.22 ------ diff --git a/include/git2/rebase.h b/include/git2/rebase.h index 58b66b7fa..d9aa175c7 100644 --- a/include/git2/rebase.h +++ b/include/git2/rebase.h @@ -30,19 +30,32 @@ typedef struct { unsigned int version; /** - * Provide a quiet rebase experience; unused by libgit2 but provided for - * interoperability with other clients. + * Used by `git_rebase_init`, this will instruct other clients working + * on this rebase that you want a quiet rebase experience, which they + * may choose to provide in an application-specific manner. This has no + * effect upon libgit2 directly, but is provided for interoperability + * between Git tools. */ int quiet; /** - * Canonical name of the notes reference used to rewrite notes for - * rebased commits when finishing the rebase; if NULL, the contents of - * the coniguration option `notes.rewriteRef` is examined, unless the - * configuration option `notes.rewrite.rebase` is set to false. If - * `notes.rewriteRef` is NULL, notes will not be rewritten. + * Used by `git_rebase_finish`, this is the name of the notes reference + * used to rewrite notes for rebased commits when finishing the rebase; + * if NULL, the contents of the coniguration option `notes.rewriteRef` + * is examined, unless the configuration option `notes.rewrite.rebase` + * is set to false. If `notes.rewriteRef` is also NULL, notes will + * not be rewritten. */ const char *rewrite_notes_ref; + + /** + * Options to control how files are written during `git_rebase_init`, + * `git_checkout_next` and `git_checkout_abort`. Note that a minimum + * strategy of `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`, + * and a minimum strategy of `GIT_CHECKOUT_FORCE` is defaulted in + * `abort` to match git semantics. + */ + git_checkout_options checkout_options; } git_rebase_options; /** @@ -87,7 +100,11 @@ typedef enum { } git_rebase_operation_t; #define GIT_REBASE_OPTIONS_VERSION 1 -#define GIT_REBASE_OPTIONS_INIT {GIT_REBASE_OPTIONS_VERSION} +#define GIT_REBASE_OPTIONS_INIT \ + {GIT_REBASE_OPTIONS_VERSION, 0, NULL, GIT_CHECKOUT_OPTIONS_INIT} + +/** Indicates that a rebase operation is not (yet) in progress. */ +#define GIT_REBASE_NO_OPERATION SIZE_MAX /** * A rebase operation @@ -139,7 +156,7 @@ GIT_EXTERN(int) git_rebase_init_options( * reachable commits * @param onto The branch to rebase onto, or NULL to rebase onto the given * upstream - * @param opts Options to specify how rebase is performed + * @param opts Options to specify how rebase is performed, or NULL * @return Zero on success; -1 on failure. */ GIT_EXTERN(int) git_rebase_init( @@ -156,9 +173,13 @@ GIT_EXTERN(int) git_rebase_init( * * @param out Pointer to store the rebase object * @param repo The repository that has a rebase in-progress + * @param opts Options to specify how rebase is performed * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_rebase_open(git_rebase **out, git_repository *repo); +GIT_EXTERN(int) git_rebase_open( + git_rebase **out, + git_repository *repo, + const git_rebase_options *opts); /** * Gets the count of rebase operations that are to be applied. @@ -170,6 +191,9 @@ GIT_EXTERN(size_t) git_rebase_operation_entrycount(git_rebase *rebase); /** * Gets the index of the rebase operation that is currently being applied. + * If the first operation has not yet been applied (because you have + * called `init` but not yet `next`) then this returns + * `GIT_REBASE_NO_OPERATION`. * * @param rebase The in-progress rebase * @return The index of the rebase operation currently being applied. @@ -196,13 +220,11 @@ GIT_EXTERN(git_rebase_operation *) git_rebase_operation_byindex( * * @param operation Pointer to store the rebase operation that is to be performed next * @param rebase The 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_rebase_operation **operation, - git_rebase *rebase, - git_checkout_options *checkout_opts); + git_rebase *rebase); /** * Commits the current patch. You must have resolved any conflicts that @@ -250,13 +272,11 @@ GIT_EXTERN(int) git_rebase_abort(git_rebase *rebase); * * @param rebase The rebase that is in-progress * @param signature The identity that is finishing the rebase (optional) - * @param opts Options to specify how rebase is finished * @return Zero on success; -1 on error */ GIT_EXTERN(int) git_rebase_finish( git_rebase *rebase, - const git_signature *signature, - const git_rebase_options *opts); + const git_signature *signature); /** * Frees the `git_rebase` object. diff --git a/include/git2/reset.h b/include/git2/reset.h index c03dbed8c..37e578e58 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -62,7 +62,7 @@ GIT_EXTERN(int) git_reset( git_repository *repo, git_object *target, git_reset_t reset_type, - git_checkout_options *checkout_opts); + const git_checkout_options *checkout_opts); /** * Sets the current head to the specified commit oid and optionally @@ -80,7 +80,7 @@ GIT_EXTERN(int) git_reset_from_annotated( git_repository *repo, git_annotated_commit *commit, git_reset_t reset_type, - git_checkout_options *checkout_opts); + const git_checkout_options *checkout_opts); /** * Updates some entries in the index from the target commit tree. diff --git a/src/rebase.c b/src/rebase.c index eb25d4c3a..b636e7951 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -57,6 +57,8 @@ typedef enum { struct git_rebase { git_repository *repo; + git_rebase_options options; + git_rebase_type_t type; char *state_path; @@ -249,7 +251,43 @@ done: return error; } -int git_rebase_open(git_rebase **out, git_repository *repo) +static git_rebase *rebase_alloc(const git_rebase_options *rebase_opts) +{ + git_rebase *rebase = git__calloc(1, sizeof(git_rebase)); + + if (!rebase) + return NULL; + + if (rebase_opts) + memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); + else + git_rebase_init_options(&rebase->options, GIT_REBASE_OPTIONS_VERSION); + + if (rebase_opts && rebase_opts->rewrite_notes_ref) { + if ((rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref)) == NULL) + return NULL; + } + + if ((rebase->options.checkout_options.checkout_strategy & (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_FORCE)) == 0) + rebase->options.checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; + + return rebase; +} + +static int rebase_check_versions(const git_rebase_options *given_opts) +{ + GITERR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); + + if (given_opts) + GITERR_CHECK_VERSION(&given_opts->checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); + + return 0; +} + +int git_rebase_open( + git_rebase **out, + git_repository *repo, + const git_rebase_options *given_opts) { git_rebase *rebase; git_buf path = GIT_BUF_INIT, orig_head_name = GIT_BUF_INIT, @@ -258,7 +296,10 @@ int git_rebase_open(git_rebase **out, git_repository *repo) assert(repo); - rebase = git__calloc(1, sizeof(git_rebase)); + if ((error = rebase_check_versions(given_opts)) < 0) + return error; + + rebase = rebase_alloc(given_opts); GITERR_CHECK_ALLOC(rebase); rebase->repo = repo; @@ -446,48 +487,6 @@ int git_rebase_init_options(git_rebase_options *opts, unsigned int version) return 0; } -static int rebase_normalize_opts( - git_repository *repo, - git_rebase_options *opts, - const git_rebase_options *given_opts) -{ - git_rebase_options default_opts = GIT_REBASE_OPTIONS_INIT; - git_config *config; - - if (given_opts) - memcpy(opts, given_opts, sizeof(git_rebase_options)); - else - memcpy(opts, &default_opts, sizeof(git_rebase_options)); - - if (git_repository_config(&config, repo) < 0) - return -1; - - if (given_opts && given_opts->rewrite_notes_ref) { - opts->rewrite_notes_ref = git__strdup(given_opts->rewrite_notes_ref); - GITERR_CHECK_ALLOC(opts->rewrite_notes_ref); - } else if (git_config__get_bool_force(config, "notes.rewrite.rebase", 1)) { - char *rewrite_ref = git_config__get_string_force( - config, "notes.rewriteref", NOTES_DEFAULT_REF); - - if (rewrite_ref) { - opts->rewrite_notes_ref = rewrite_ref; - GITERR_CHECK_ALLOC(opts->rewrite_notes_ref); - } - } - - git_config_free(config); - - return 0; -} - -static void rebase_opts_free(git_rebase_options *opts) -{ - if (!opts) - return; - - git__free((char *)opts->rewrite_notes_ref); -} - static int rebase_ensure_not_in_progress(git_repository *repo) { int error; @@ -504,33 +503,42 @@ static int rebase_ensure_not_in_progress(git_repository *repo) return 0; } -static int rebase_ensure_not_dirty(git_repository *repo) +static int rebase_ensure_not_dirty( + git_repository *repo, + bool check_index, + bool check_workdir, + int fail_with) { git_tree *head = NULL; git_index *index = NULL; git_diff *diff = NULL; int error; - if ((error = git_repository_head_tree(&head, repo)) < 0 || - (error = git_repository_index(&index, repo)) < 0 || - (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0) - goto done; + if (check_index) { + if ((error = git_repository_head_tree(&head, repo)) < 0 || + (error = git_repository_index(&index, repo)) < 0 || + (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0) + goto done; - if (git_diff_num_deltas(diff) > 0) { - giterr_set(GITERR_REBASE, "Uncommitted changes exist in index"); - error = -1; - goto done; + if (git_diff_num_deltas(diff) > 0) { + giterr_set(GITERR_REBASE, "Uncommitted changes exist in index"); + error = fail_with; + goto done; + } + + git_diff_free(diff); + diff = NULL; } - git_diff_free(diff); - diff = NULL; + if (check_workdir) { + if ((error = git_diff_index_to_workdir(&diff, repo, index, NULL)) < 0) + goto done; - if ((error = git_diff_index_to_workdir(&diff, repo, index, NULL)) < 0) - goto done; - - if (git_diff_num_deltas(diff) > 0) { - giterr_set(GITERR_REBASE, "Unstaged changes exist in workdir"); - error = -1; + if (git_diff_num_deltas(diff) > 0) { + giterr_set(GITERR_REBASE, "Unstaged changes exist in workdir"); + error = fail_with; + goto done; + } } done: @@ -607,8 +615,7 @@ static int rebase_init( git_repository *repo, const git_annotated_commit *branch, const git_annotated_commit *upstream, - const git_annotated_commit *onto, - const git_rebase_options *opts) + const git_annotated_commit *onto) { git_reference *head_ref = NULL; git_annotated_commit *head_branch = NULL; @@ -630,7 +637,7 @@ static int rebase_init( rebase->type = GIT_REBASE_TYPE_MERGE; rebase->state_path = git_buf_detach(&state_path); rebase->orig_head_name = git__strdup(branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD); - rebase->quiet = opts->quiet; + rebase->quiet = rebase->options.quiet; git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); @@ -658,10 +665,8 @@ int git_rebase_init( const git_rebase_options *given_opts) { git_rebase *rebase = NULL; - git_rebase_options opts; git_buf reflog = GIT_BUF_INIT; git_commit *onto_commit = NULL; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; git_reference *head_ref = NULL; int error; @@ -669,31 +674,26 @@ int git_rebase_init( *out = NULL; - GITERR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); - if (!onto) onto = upstream; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - if ((error = rebase_normalize_opts(repo, &opts, given_opts)) < 0 || + if ((error = rebase_check_versions(given_opts)) < 0 || (error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || (error = rebase_ensure_not_in_progress(repo)) < 0 || - (error = rebase_ensure_not_dirty(repo)) < 0 || + (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0 || (error = git_commit_lookup( &onto_commit, repo, git_annotated_commit_id(onto))) < 0) return error; - rebase = git__calloc(1, sizeof(git_rebase)); - GITERR_CHECK_ALLOC(rebase); + rebase = rebase_alloc(given_opts); if ((error = rebase_init( - rebase, repo, branch, upstream, onto, &opts)) < 0 || + rebase, repo, branch, upstream, onto)) < 0 || (error = rebase_setupfiles(rebase)) < 0 || (error = git_buf_printf(&reflog, "rebase: checkout %s", rebase_onto_name(onto))) < 0 || (error = git_checkout_tree( - repo, (git_object *)onto_commit, &checkout_opts)) < 0 || + repo, (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) goto done; @@ -709,25 +709,16 @@ done: git_commit_free(onto_commit); git_buf_free(&reflog); - rebase_opts_free(&opts); return error; } -static void normalize_checkout_opts( - git_rebase *rebase, - git_commit *current_commit, +static void normalize_checkout_options_for_apply( git_checkout_options *checkout_opts, - const git_checkout_options *given_checkout_opts) + git_rebase *rebase, + git_commit *current_commit) { - if (given_checkout_opts != NULL) - memcpy(checkout_opts, given_checkout_opts, sizeof(git_checkout_options)); - else { - git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - memcpy(checkout_opts, &default_checkout_opts, sizeof(git_checkout_options)); - } + memcpy(checkout_opts, &rebase->options.checkout_options, sizeof(git_checkout_options)); if (!checkout_opts->ancestor_label) checkout_opts->ancestor_label = "ancestor"; @@ -758,16 +749,15 @@ GIT_INLINE(int) rebase_movenext(git_rebase *rebase) static int rebase_next_merge( git_rebase_operation **out, - git_rebase *rebase, - git_checkout_options *given_checkout_opts) + git_rebase *rebase) { git_buf path = GIT_BUF_INIT; - git_checkout_options checkout_opts = {0}; git_commit *current_commit = NULL, *parent_commit = NULL; git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; git_index *index = NULL; git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; git_rebase_operation *operation; + git_checkout_options checkout_opts; char current_idstr[GIT_OID_HEXSZ]; unsigned int parent_count; int error; @@ -796,7 +786,7 @@ static int rebase_next_merge( git_oid_fmt(current_idstr, &operation->id); - normalize_checkout_opts(rebase, current_commit, &checkout_opts, given_checkout_opts); + normalize_checkout_options_for_apply(&checkout_opts, rebase, current_commit); if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 || (error = rebase_setupfile(rebase, MSGNUM_FILE, -1, "%d\n", rebase->current+1)) < 0 || @@ -824,8 +814,7 @@ done: int git_rebase_next( git_rebase_operation **out, - git_rebase *rebase, - git_checkout_options *checkout_opts) + git_rebase *rebase) { int error; @@ -833,7 +822,7 @@ int git_rebase_next( switch (rebase->type) { case GIT_REBASE_TYPE_MERGE: - error = rebase_next_merge(out, rebase, checkout_opts); + error = rebase_next_merge(out, rebase); break; default: abort(); @@ -869,11 +858,12 @@ static int rebase_commit_merge( if (git_index_has_conflicts(index)) { giterr_set(GITERR_REBASE, "Conflicts have not been resolved"); - error = GIT_EMERGECONFLICT; + error = GIT_EUNMERGED; goto done; } - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 || + (error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || (error = git_repository_head(&head, rebase->repo)) < 0 || (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)) < 0 || (error = git_commit_tree(&head_tree, head_commit)) < 0 || @@ -971,7 +961,7 @@ int git_rebase_abort(git_rebase *rebase) if ((error = git_commit_lookup( &orig_head_commit, rebase->repo, &rebase->orig_head_id)) < 0 || (error = git_reset(rebase->repo, (git_object *)orig_head_commit, - GIT_RESET_HARD, NULL)) < 0) + GIT_RESET_HARD, &rebase->options.checkout_options)) < 0) goto done; error = rebase_cleanup(rebase); @@ -983,19 +973,50 @@ done: return error; } +static int notes_ref_lookup(git_buf *out, git_rebase *rebase) +{ + git_config *config = NULL; + int do_rewrite, error; + + if (rebase->options.rewrite_notes_ref) { + git_buf_attach_notowned(out, + rebase->options.rewrite_notes_ref, + strlen(rebase->options.rewrite_notes_ref)); + return 0; + } + + if ((error = git_repository_config(&config, rebase->repo)) < 0 || + (error = git_config_get_bool(&do_rewrite, config, "notes.rewrite.rebase")) < 0) { + + if (error != GIT_ENOTFOUND) + goto done; + + giterr_clear(); + do_rewrite = 1; + } + + error = do_rewrite ? + git_config_get_string_buf(out, config, "notes.rewriteref") : + GIT_ENOTFOUND; + +done: + git_config_free(config); + return error; +} + static int rebase_copy_note( git_rebase *rebase, + const char *notes_ref, git_oid *from, git_oid *to, - const git_signature *committer, - const git_rebase_options *opts) + const git_signature *committer) { git_note *note = NULL; git_oid note_id; git_signature *who = NULL; int error; - if ((error = git_note_read(¬e, rebase->repo, opts->rewrite_notes_ref, from)) < 0) { + if ((error = git_note_read(¬e, rebase->repo, notes_ref, from)) < 0) { if (error == GIT_ENOTFOUND) { giterr_clear(); error = 0; @@ -1016,7 +1037,7 @@ static int rebase_copy_note( committer = who; } - error = git_note_create(¬e_id, rebase->repo, opts->rewrite_notes_ref, + error = git_note_create(¬e_id, rebase->repo, notes_ref, git_note_author(note), committer, to, git_note_message(note), 0); done: @@ -1028,17 +1049,22 @@ done: static int rebase_copy_notes( git_rebase *rebase, - const git_signature *committer, - const git_rebase_options *opts) + const git_signature *committer) { - git_buf path = GIT_BUF_INIT, rewritten = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT, rewritten = GIT_BUF_INIT, notes_ref = GIT_BUF_INIT; char *pair_list, *fromstr, *tostr, *end; git_oid from, to; unsigned int linenum = 1; int error = 0; - if (!opts->rewrite_notes_ref) + if ((error = notes_ref_lookup(¬es_ref, rebase)) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + goto done; + } if ((error = git_buf_joinpath(&path, rebase->state_path, REWRITTEN_FILE)) < 0 || (error = git_futils_readbuffer(&rewritten, path.ptr)) < 0) @@ -1067,7 +1093,7 @@ static int rebase_copy_notes( git_oid_fromstr(&to, tostr) < 0) goto on_error; - if ((error = rebase_copy_note(rebase, &from, &to, committer, opts)) < 0) + if ((error = rebase_copy_note(rebase, notes_ref.ptr, &from, &to, committer)) < 0) goto done; linenum++; @@ -1082,16 +1108,15 @@ on_error: done: git_buf_free(&rewritten); git_buf_free(&path); + git_buf_free(¬es_ref); return error; } int git_rebase_finish( git_rebase *rebase, - const git_signature *signature, - const git_rebase_options *given_opts) + const git_signature *signature) { - git_rebase_options opts; 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; @@ -1100,11 +1125,6 @@ int git_rebase_finish( assert(rebase); - GITERR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); - - if ((error = rebase_normalize_opts(rebase->repo, &opts, given_opts)) < 0) - goto done; - git_oid_fmt(onto, &rebase->onto_id); if ((error = git_buf_printf(&branch_msg, "rebase finished: %s onto %.*s", @@ -1120,7 +1140,7 @@ int git_rebase_finish( (error = git_reference_symbolic_create(&head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, head_msg.ptr)) < 0 || - (error = rebase_copy_notes(rebase, signature, &opts)) < 0) + (error = rebase_copy_notes(rebase, signature)) < 0) goto done; error = rebase_cleanup(rebase); @@ -1132,7 +1152,6 @@ done: git_reference_free(head_ref); git_reference_free(branch_ref); git_reference_free(terminal_ref); - rebase_opts_free(&opts); return error; } @@ -1148,7 +1167,7 @@ size_t git_rebase_operation_current(git_rebase *rebase) { assert(rebase); - return rebase->current; + return rebase->started ? rebase->current : GIT_REBASE_NO_OPERATION; } git_rebase_operation *git_rebase_operation_byindex(git_rebase *rebase, size_t idx) @@ -1167,5 +1186,6 @@ void git_rebase_free(git_rebase *rebase) git__free(rebase->orig_head_name); git__free(rebase->state_path); git_array_clear(rebase->operations); + git__free((char *)rebase->options.rewrite_notes_ref); git__free(rebase); } diff --git a/src/reset.c b/src/reset.c index aaebf4198..d08e48cd7 100644 --- a/src/reset.c +++ b/src/reset.c @@ -102,7 +102,7 @@ static int reset( git_object *target, const char *to, git_reset_t reset_type, - git_checkout_options *checkout_opts) + const git_checkout_options *checkout_opts) { git_object *commit = NULL; git_index *index = NULL; @@ -183,7 +183,7 @@ int git_reset( git_repository *repo, git_object *target, git_reset_t reset_type, - git_checkout_options *checkout_opts) + const git_checkout_options *checkout_opts) { return reset(repo, target, git_oid_tostr_s(git_object_id(target)), reset_type, checkout_opts); } @@ -192,7 +192,7 @@ int git_reset_from_annotated( git_repository *repo, git_annotated_commit *commit, git_reset_t reset_type, - git_checkout_options *checkout_opts) + const git_checkout_options *checkout_opts) { return reset(repo, (git_object *) commit->commit, commit->ref_name, reset_type, checkout_opts); } diff --git a/tests/rebase/abort.c b/tests/rebase/abort.c index 24af2d140..c4b3890bc 100644 --- a/tests/rebase/abort.c +++ b/tests/rebase/abort.c @@ -27,7 +27,7 @@ static void test_abort(git_annotated_commit *branch, git_annotated_commit *onto) git_reflog *reflog; const git_reflog_entry *reflog_entry; - cl_git_pass(git_rebase_open(&rebase, repo)); + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); cl_git_pass(git_rebase_abort(rebase)); cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); diff --git a/tests/rebase/iterator.c b/tests/rebase/iterator.c index 2cff82ced..acf2a92db 100644 --- a/tests/rebase/iterator.c +++ b/tests/rebase/iterator.c @@ -52,12 +52,9 @@ void test_rebase_iterator__iterates(void) git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - 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/master")); @@ -65,39 +62,39 @@ void test_rebase_iterator__iterates(void) cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - test_operations(rebase, 0); + test_operations(rebase, GIT_REBASE_NO_OPERATION); git_rebase_free(rebase); - cl_git_pass(git_rebase_open(&rebase, repo)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 0); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 1); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 2); git_rebase_free(rebase); - cl_git_pass(git_rebase_open(&rebase, repo)); + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 3); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 4); - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); cl_assert_equal_i(GIT_ITEROVER, error); test_operations(rebase, 4); diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c index f820e96c6..12f0de5a0 100644 --- a/tests/rebase/merge.c +++ b/tests/rebase/merge.c @@ -40,13 +40,10 @@ void test_rebase_merge__next(void) git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; git_status_list *status_list; const git_status_entry *status_entry; git_oid pick_id, 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")); @@ -55,7 +52,7 @@ void test_rebase_merge__next(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); git_oid_fromstr(&pick_id, "da9c51a23d02d931a486f45ad18cda05cf5d2b94"); @@ -87,10 +84,9 @@ void test_rebase_merge__next_with_conflicts(void) git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; git_status_list *status_list; const git_status_entry *status_entry; - git_oid pick_id; + git_oid pick_id, commit_id; const char *expected_merge = "ASPARAGUS SOUP.\n" @@ -112,8 +108,6 @@ void test_rebase_merge__next_with_conflicts(void) "asparagus which had been laid by, boil it until these last articles are\n" "sufficiently done, thicken with flour, butter and milk, and serve it up.\n"; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); @@ -122,7 +116,7 @@ void test_rebase_merge__next_with_conflicts(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); git_oid_fromstr(&pick_id, "33f915f9e4dbd9f4b24430e48731a59b45b15500"); @@ -139,6 +133,8 @@ void test_rebase_merge__next_with_conflicts(void) cl_assert_equal_file(expected_merge, strlen(expected_merge), "rebase/asparagus.txt"); + cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + git_status_list_free(status_list); git_annotated_commit_free(branch_head); git_annotated_commit_free(upstream_head); @@ -153,12 +149,9 @@ void test_rebase_merge__next_stops_with_iterover(void) git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - 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/master")); @@ -167,27 +160,27 @@ void test_rebase_merge__next_stops_with_iterover(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); cl_assert_equal_i(GIT_ITEROVER, error); cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); @@ -206,15 +199,12 @@ void test_rebase_merge__commit(void) git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; git_oid commit_id, tree_id, parent_id; git_signature *author; git_commit *commit; git_reflog *reflog; const git_reflog_entry *reflog_entry; - 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")); @@ -223,7 +213,7 @@ void test_rebase_merge__commit(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); @@ -262,17 +252,14 @@ void test_rebase_merge__commit(void) git_rebase_free(rebase); } -void test_rebase_merge__commit_updates_rewritten(void) +void test_rebase_merge__blocked_when_dirty(void) { git_rebase *rebase; git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - 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")); @@ -281,11 +268,46 @@ void test_rebase_merge__commit_updates_rewritten(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + /* Allow untracked files */ + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_mkfile("rebase/untracked_file.txt", "This is untracked\n"); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + /* Do not allow unstaged */ + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_mkfile("rebase/veal.txt", "This is an unstaged change\n"); + cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__commit_updates_rewritten(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + + 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_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); @@ -307,12 +329,9 @@ void test_rebase_merge__commit_drops_already_applied(void) git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - 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")); @@ -321,13 +340,13 @@ void test_rebase_merge__commit_drops_already_applied(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_fail(error = git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); cl_assert_equal_i(GIT_EAPPLIED, error); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); @@ -348,14 +367,11 @@ void test_rebase_merge__finish(void) git_reference *branch_ref, *upstream_ref, *head_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - 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")); @@ -364,14 +380,14 @@ void test_rebase_merge__finish(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); cl_assert_equal_i(GIT_ITEROVER, error); - cl_git_pass(git_rebase_finish(rebase, signature, NULL)); + cl_git_pass(git_rebase_finish(rebase, signature)); cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); @@ -411,13 +427,10 @@ static void test_copy_note( git_annotated_commit *branch_head, *upstream_head; git_commit *branch_commit; git_rebase_operation *rebase_operation; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; git_oid note_id, commit_id; git_note *note = NULL; 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")); @@ -435,11 +448,11 @@ static void test_copy_note( cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, opts)); - cl_git_pass(git_rebase_next(&rebase_operation, rebase, &checkout_opts)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - cl_git_pass(git_rebase_finish(rebase, signature, opts)); + cl_git_pass(git_rebase_finish(rebase, signature)); cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); @@ -497,3 +510,53 @@ void test_rebase_merge__copy_notes_disabled_in_config(void) test_copy_note(NULL, 0); } +void rebase_checkout_progress_cb( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + int *called = payload; + *called = 1; +} + +void test_rebase_merge__custom_checkout_options(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_options rebase_options = GIT_REBASE_OPTIONS_INIT; + git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + git_rebase_operation *rebase_operation; + int called = 0; + + checkout_options.progress_cb = rebase_checkout_progress_cb; + checkout_options.progress_payload = &called; + + memcpy(&rebase_options.checkout_options, &checkout_options, + sizeof(git_checkout_options)); + + 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_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + called = 0; + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_options)); + cl_assert_equal_i(1, called); + + called = 0; + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(1, called); + + called = 0; + cl_git_pass(git_rebase_abort(rebase)); + cl_assert_equal_i(1, called); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +}