rebase: fast-forward --onto in more cases
[gitweb.git] / builtin / rebase.c
index db6ca9bd7d4c4e9cdf58eb47fbfce2dda5b58497..1e1406c8ba59e6ff90392480de2b67fc67635049 100644 (file)
@@ -508,7 +508,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
        if (argc == 1)
                usage_with_options(builtin_rebase_interactive_usage, options);
 
-       argc = parse_options(argc, argv, NULL, options,
+       argc = parse_options(argc, argv, prefix, options,
                        builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
 
        if (!is_null_oid(&squash_onto))
@@ -738,20 +738,30 @@ static int finish_rebase(struct rebase_options *opts)
 {
        struct strbuf dir = STRBUF_INIT;
        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+       int ret = 0;
 
        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
        apply_autostash(opts);
-       close_all_packs(the_repository->objects);
+       close_object_store(the_repository->objects);
        /*
         * We ignore errors in 'gc --auto', since the
         * user should see them.
         */
        run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
-       strbuf_addstr(&dir, opts->state_dir);
-       remove_dir_recursively(&dir, 0);
-       strbuf_release(&dir);
+       if (opts->type == REBASE_INTERACTIVE) {
+               struct replay_opts replay = REPLAY_OPTS_INIT;
 
-       return 0;
+               replay.action = REPLAY_INTERACTIVE_REBASE;
+               ret = sequencer_remove_state(&replay);
+       } else {
+               strbuf_addstr(&dir, opts->state_dir);
+               if (remove_dir_recursively(&dir, 0))
+                       ret = error(_("could not remove '%s'"),
+                                   opts->state_dir);
+               strbuf_release(&dir);
+       }
+
+       return ret;
 }
 
 static struct commit *peel_committish(const char *name)
@@ -840,13 +850,13 @@ static int reset_head(struct object_id *oid, const char *action,
                goto leave_reset_head;
        }
 
-       if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) {
+       if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) {
                ret = error(_("failed to find tree of %s"),
                            oid_to_hex(&head_oid));
                goto leave_reset_head;
        }
 
-       if (!fill_tree_descriptor(&desc[nr++], oid)) {
+       if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) {
                ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
                goto leave_reset_head;
        }
@@ -1153,10 +1163,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
        }
 
        switch (opts->type) {
-       case REBASE_AM:
-               backend = "git-rebase--am";
-               backend_func = "git_rebase__am";
-               break;
        case REBASE_PRESERVE_MERGES:
                backend = "git-rebase--preserve-merges";
                backend_func = "git_rebase__preserve_merges";
@@ -1167,8 +1173,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
        }
 
        strbuf_addf(&script_snippet,
-                   ". git-sh-setup && . git-rebase--common &&"
-                   " . %s && %s", backend, backend_func);
+                   ". git-sh-setup && . %s && %s", backend, backend_func);
        argv[0] = script_snippet.buf;
 
        status = run_command_v_opt(argv, RUN_USING_SHELL);
@@ -1255,24 +1260,40 @@ static int is_linear_history(struct commit *from, struct commit *to)
        return 1;
 }
 
-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
-                           struct object_id *merge_base)
+static int can_fast_forward(struct commit *onto, struct commit *upstream,
+                           struct object_id *head_oid, struct object_id *merge_base)
 {
        struct commit *head = lookup_commit(the_repository, head_oid);
-       struct commit_list *merge_bases;
-       int res;
+       struct commit_list *merge_bases = NULL;
+       int res = 0;
 
        if (!head)
-               return 0;
+               goto done;
 
        merge_bases = get_merge_bases(onto, head);
-       if (merge_bases && !merge_bases->next) {
-               oidcpy(merge_base, &merge_bases->item->object.oid);
-               res = oideq(merge_base, &onto->object.oid);
-       } else {
+       if (!merge_bases || merge_bases->next) {
                oidcpy(merge_base, &null_oid);
-               res = 0;
+               goto done;
        }
+
+       oidcpy(merge_base, &merge_bases->item->object.oid);
+       if (!oideq(merge_base, &onto->object.oid))
+               goto done;
+
+       if (!upstream)
+               goto done;
+
+       free_commit_list(merge_bases);
+       merge_bases = get_merge_bases(upstream, head);
+       if (!merge_bases || merge_bases->next)
+               goto done;
+
+       if (!oideq(&onto->object.oid, &merge_bases->item->object.oid))
+               goto done;
+
+       res = 1;
+
+done:
        free_commit_list(merge_bases);
        return res && is_linear_history(onto, head);
 }
@@ -1384,6 +1405,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        struct string_list strategy_options = STRING_LIST_INIT_NODUP;
        struct object_id squash_onto;
        char *squash_onto_name = NULL;
+       int reschedule_failed_exec = -1;
        struct option builtin_rebase_options[] = {
                OPT_STRING(0, "onto", &options.onto_name,
                           N_("revision"),
@@ -1476,7 +1498,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "root", &options.root,
                         N_("rebase all reachable commits up to the root(s)")),
                OPT_BOOL(0, "reschedule-failed-exec",
-                        &options.reschedule_failed_exec,
+                        &reschedule_failed_exec,
                         N_("automatically re-schedule any `exec` that fails")),
                OPT_END(),
        };
@@ -1486,10 +1508,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
 
-       prefix = setup_git_directory();
-       trace_repo_setup(prefix);
-       setup_work_tree();
-
        options.allow_empty_message = 1;
        git_config(rebase_config, &options);
 
@@ -1605,7 +1623,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
                               NULL, NULL) < 0)
                        die(_("could not discard worktree changes"));
-               remove_branch_state(the_repository);
+               remove_branch_state(the_repository, 0);
                if (read_basic_state(&options))
                        exit(1);
                goto run_rebase;
@@ -1625,16 +1643,24 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                               NULL, NULL) < 0)
                        die(_("could not move back to %s"),
                            oid_to_hex(&options.orig_head));
-               remove_branch_state(the_repository);
-               ret = finish_rebase(&options);
+               remove_branch_state(the_repository, 0);
+               ret = !!finish_rebase(&options);
                goto cleanup;
        }
        case ACTION_QUIT: {
-               strbuf_reset(&buf);
-               strbuf_addstr(&buf, options.state_dir);
-               ret = !!remove_dir_recursively(&buf, 0);
-               if (ret)
-                       die(_("could not remove '%s'"), options.state_dir);
+               if (options.type == REBASE_INTERACTIVE) {
+                       struct replay_opts replay = REPLAY_OPTS_INIT;
+
+                       replay.action = REPLAY_INTERACTIVE_REBASE;
+                       ret = !!sequencer_remove_state(&replay);
+               } else {
+                       strbuf_reset(&buf);
+                       strbuf_addstr(&buf, options.state_dir);
+                       ret = !!remove_dir_recursively(&buf, 0);
+                       if (ret)
+                               error(_("could not remove '%s'"),
+                                      options.state_dir);
+               }
                goto cleanup;
        }
        case ACTION_EDIT_TODO:
@@ -1783,8 +1809,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                break;
        }
 
-       if (options.reschedule_failed_exec && !is_interactive(&options))
-               die(_("%s requires an interactive rebase"), "--reschedule-failed-exec");
+       if (reschedule_failed_exec > 0 && !is_interactive(&options))
+               die(_("--reschedule-failed-exec requires "
+                     "--exec or --interactive"));
+       if (reschedule_failed_exec >= 0)
+               options.reschedule_failed_exec = reschedule_failed_exec;
 
        if (options.git_am_opts.argc) {
                /* all am options except -q are compatible only with --am */
@@ -2009,13 +2038,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        /*
         * Check if we are already based on onto with linear history,
-        * but this should be done only when upstream and onto are the same
-        * and if this is not an interactive rebase.
+        * in which case we could fast-forward without replacing the commits
+        * with new commits recreated by replaying their changes. This
+        * optimization must not be done if this is an interactive rebase.
         */
-       if (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&
-           !is_interactive(&options) && !options.restrict_revision &&
-           options.upstream &&
-           !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
+       if (can_fast_forward(options.onto, options.upstream, &options.orig_head,
+                   &merge_base) &&
+           !is_interactive(&options) && !options.restrict_revision) {
                int flag;
 
                if (!(options.flags & REBASE_FORCE)) {
@@ -2109,7 +2138,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        strbuf_addf(&msg, "%s: checkout %s",
                    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
        if (reset_head(&options.onto->object.oid, "checkout", NULL,
-                      RESET_HEAD_DETACH | RESET_ORIG_HEAD | 
+                      RESET_HEAD_DETACH | RESET_ORIG_HEAD |
                       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
                       NULL, msg.buf))
                die(_("Could not detach HEAD"));
@@ -2146,6 +2175,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        ret = !!run_specific_rebase(&options, action);
 
 cleanup:
+       strbuf_release(&buf);
        strbuf_release(&revisions);
        free(options.head_name);
        free(options.gpg_sign_opt);