rebase: fast-forward --onto in more cases
[gitweb.git] / builtin / rebase.c
index 95d34223e93774edbede0c83d781587777d647f3..1e1406c8ba59e6ff90392480de2b67fc67635049 100644 (file)
@@ -1260,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);
 }
@@ -1492,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);
 
@@ -2026,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)) {
@@ -2126,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"));