Merge branch 'dl/rebase-i-keep-base'
[git/git.git] / builtin / rebase.c
index e8319d5..f730b15 100644 (file)
@@ -29,8 +29,8 @@
 #include "rebase-interactive.h"
 
 static char const * const builtin_rebase_usage[] = {
-       N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
-               "[<upstream>] [<branch>]"),
+       N_("git rebase [-i] [options] [--exec <cmd>] "
+               "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
        N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
                "--root [<branch>]"),
        N_("git rebase --continue | --abort | --skip | --edit-todo"),
@@ -1261,24 +1261,44 @@ 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 commit *restrict_revision,
+                           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 (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))
+               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);
 }
@@ -1377,6 +1397,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        struct rebase_options options = REBASE_OPTIONS_INIT;
        const char *branch_name;
        int ret, flags, total_argc, in_progress = 0;
+       int keep_base = 0;
        int ok_to_skip_pre_rebase = 0;
        struct strbuf msg = STRBUF_INIT;
        struct strbuf revisions = STRBUF_INIT;
@@ -1395,6 +1416,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                OPT_STRING(0, "onto", &options.onto_name,
                           N_("revision"),
                           N_("rebase onto given branch instead of upstream")),
+               OPT_BOOL(0, "keep-base", &keep_base,
+                        N_("use the merge-base of upstream and branch as the current base")),
                OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
                         N_("allow pre-rebase hook to run")),
                OPT_NEGBIT('q', "quiet", &options.flags,
@@ -1548,6 +1571,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                warning(_("git rebase --preserve-merges is deprecated. "
                          "Use --rebase-merges instead."));
 
+       if (keep_base) {
+               if (options.onto_name)
+                       die(_("cannot combine '--keep-base' with '--onto'"));
+               if (options.root)
+                       die(_("cannot combine '--keep-base' with '--root'"));
+       }
+
        if (action != ACTION_NONE && !in_progress)
                die(_("No rebase in progress?"));
        setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
@@ -1876,12 +1906,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        }
 
        /* Make sure the branch to rebase onto is valid. */
-       if (!options.onto_name)
+       if (keep_base) {
+               strbuf_reset(&buf);
+               strbuf_addstr(&buf, options.upstream_name);
+               strbuf_addstr(&buf, "...");
+               options.onto_name = xstrdup(buf.buf);
+       } else if (!options.onto_name)
                options.onto_name = options.upstream_name;
        if (strstr(options.onto_name, "...")) {
-               if (get_oid_mb(options.onto_name, &merge_base) < 0)
-                       die(_("'%s': need exactly one merge base"),
-                           options.onto_name);
+               if (get_oid_mb(options.onto_name, &merge_base) < 0) {
+                       if (keep_base)
+                               die(_("'%s': need exactly one merge base with branch"),
+                                   options.upstream_name);
+                       else
+                               die(_("'%s': need exactly one merge base"),
+                                   options.onto_name);
+               }
                options.onto = lookup_commit_or_die(&merge_base,
                                                    options.onto_name);
        } else {
@@ -2016,13 +2056,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.restrict_revision,
+                   &options.orig_head, &merge_base) &&
+           !is_interactive(&options)) {
                int flag;
 
                if (!(options.flags & REBASE_FORCE)) {