builtin rebase: support --committer-date-is-author-date
[gitweb.git] / builtin / rebase.c
index f112d91d678b0556ea97cd1ac946ff4116dccdbe..6ce50eae0f2ea7da89348f40ed5046c18ab8ab29 100644 (file)
@@ -21,6 +21,7 @@
 #include "diff.h"
 #include "wt-status.h"
 #include "revision.h"
+#include "rerere.h"
 
 static char const * const builtin_rebase_usage[] = {
        N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
@@ -92,6 +93,8 @@ struct rebase_options {
        } flags;
        struct strbuf git_am_opt;
        const char *action;
+       int signoff;
+       int allow_rerere_autoupdate;
 };
 
 static int is_interactive(struct rebase_options *opts)
@@ -167,6 +170,26 @@ static int read_basic_state(struct rebase_options *opts)
        if (file_exists(state_dir_path("verbose", opts)))
                opts->flags |= REBASE_VERBOSE;
 
+       if (file_exists(state_dir_path("signoff", opts))) {
+               opts->signoff = 1;
+               opts->flags |= REBASE_FORCE;
+       }
+
+       if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) {
+               strbuf_reset(&buf);
+               if (read_one(state_dir_path("allow_rerere_autoupdate", opts),
+                           &buf))
+                       return -1;
+               if (!strcmp(buf.buf, "--rerere-autoupdate"))
+                       opts->allow_rerere_autoupdate = 1;
+               else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
+                       opts->allow_rerere_autoupdate = 0;
+               else
+                       warning(_("ignoring invalid allow_rerere_autoupdate: "
+                                 "'%s'"), buf.buf);
+       } else
+               opts->allow_rerere_autoupdate = -1;
+
        strbuf_release(&buf);
 
        return 0;
@@ -248,6 +271,11 @@ static int run_specific_rebase(struct rebase_options *opts)
        if (opts->switch_to)
                add_var(&script_snippet, "switch_to", opts->switch_to);
        add_var(&script_snippet, "action", opts->action ? opts->action : "");
+       add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
+       add_var(&script_snippet, "allow_rerere_autoupdate",
+               opts->allow_rerere_autoupdate < 0 ? "" :
+               opts->allow_rerere_autoupdate ?
+               "--rerere-autoupdate" : "--no-rerere-autoupdate");
 
        switch (opts->type) {
        case REBASE_AM:
@@ -451,12 +479,36 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
        return res && is_linear_history(onto, head);
 }
 
+/* -i followed by -m is still -i */
+static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
+{
+       struct rebase_options *opts = opt->value;
+
+       if (!is_interactive(opts))
+               opts->type = REBASE_MERGE;
+
+       return 0;
+}
+
+/* -i followed by -p is still explicitly interactive, but -p alone is not */
+static int parse_opt_interactive(const struct option *opt, const char *arg,
+                                int unset)
+{
+       struct rebase_options *opts = opt->value;
+
+       opts->type = REBASE_INTERACTIVE;
+       opts->flags |= REBASE_INTERACTIVE_EXPLICIT;
+
+       return 0;
+}
+
 int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
        struct rebase_options options = {
                .type = REBASE_UNSPECIFIED,
                .flags = REBASE_NO_QUIET,
                .git_am_opt = STRBUF_INIT,
+               .allow_rerere_autoupdate  = -1,
        };
        const char *branch_name;
        int ret, flags, total_argc, in_progress = 0;
@@ -468,7 +520,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        enum {
                NO_ACTION,
                ACTION_CONTINUE,
+               ACTION_SKIP,
+               ACTION_ABORT,
+               ACTION_QUIT,
+               ACTION_EDIT_TODO,
+               ACTION_SHOW_CURRENT_PATCH,
        } action = NO_ACTION;
+       int committer_date_is_author_date = 0;
        struct option builtin_rebase_options[] = {
                OPT_STRING(0, "onto", &options.onto_name,
                           N_("revision"),
@@ -484,6 +542,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL,
                        N_("do not show diffstat of what changed upstream"),
                        PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
+               OPT_BOOL(0, "signoff", &options.signoff,
+                        N_("add a Signed-off-by: line to each commit")),
+               OPT_BOOL(0, "committer-date-is-author-date",
+                        &committer_date_is_author_date,
+                        N_("passed to 'git am'")),
                OPT_BIT('f', "force-rebase", &options.flags,
                        N_("cherry-pick all commits, even if unchanged"),
                        REBASE_FORCE),
@@ -492,6 +555,33 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        REBASE_FORCE),
                OPT_CMDMODE(0, "continue", &action, N_("continue"),
                            ACTION_CONTINUE),
+               OPT_CMDMODE(0, "skip", &action,
+                           N_("skip current patch and continue"), ACTION_SKIP),
+               OPT_CMDMODE(0, "abort", &action,
+                           N_("abort and check out the original branch"),
+                           ACTION_ABORT),
+               OPT_CMDMODE(0, "quit", &action,
+                           N_("abort but keep HEAD where it is"), ACTION_QUIT),
+               OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list "
+                           "during an interactive rebase"), ACTION_EDIT_TODO),
+               OPT_CMDMODE(0, "show-current-patch", &action,
+                           N_("show the patch file being applied or merged"),
+                           ACTION_SHOW_CURRENT_PATCH),
+               { OPTION_CALLBACK, 'm', "merge", &options, NULL,
+                       N_("use merging strategies to rebase"),
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+                       parse_opt_merge },
+               { OPTION_CALLBACK, 'i', "interactive", &options, NULL,
+                       N_("let the user edit the list of commits to rebase"),
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+                       parse_opt_interactive },
+               OPT_SET_INT('p', "preserve-merges", &options.type,
+                           N_("try to recreate merges instead of ignoring "
+                              "them"), REBASE_PRESERVE_MERGES),
+               OPT_BOOL(0, "rerere-autoupdate",
+                        &options.allow_rerere_autoupdate,
+                        N_("allow rerere to update index  with resolved "
+                           "conflict")),
                OPT_END(),
        };
 
@@ -521,6 +611,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        git_config(rebase_config, &options);
 
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "%s/applying", apply_dir());
+       if(file_exists(buf.buf))
+               die(_("It looks like 'git am' is in progress. Cannot rebase."));
+
        if (is_directory(apply_dir())) {
                options.type = REBASE_AM;
                options.state_dir = apply_dir();
@@ -559,6 +654,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
 
+       if (action != NO_ACTION && !in_progress)
+               die(_("No rebase in progress?"));
+
+       if (action == ACTION_EDIT_TODO && !is_interactive(&options))
+               die(_("The --edit-todo action can only be used during "
+                     "interactive rebase."));
+
        switch (action) {
        case ACTION_CONTINUE: {
                struct object_id head;
@@ -590,8 +692,56 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        exit(1);
                goto run_rebase;
        }
+       case ACTION_SKIP: {
+               struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+               options.action = "skip";
+
+               rerere_clear(&merge_rr);
+               string_list_clear(&merge_rr, 1);
+
+               if (reset_head(NULL, "reset", NULL, 0) < 0)
+                       die(_("could not discard worktree changes"));
+               if (read_basic_state(&options))
+                       exit(1);
+               goto run_rebase;
+       }
+       case ACTION_ABORT: {
+               struct string_list merge_rr = STRING_LIST_INIT_DUP;
+               options.action = "abort";
+
+               rerere_clear(&merge_rr);
+               string_list_clear(&merge_rr, 1);
+
+               if (read_basic_state(&options))
+                       exit(1);
+               if (reset_head(&options.orig_head, "reset",
+                              options.head_name, 0) < 0)
+                       die(_("could not move back to %s"),
+                           oid_to_hex(&options.orig_head));
+               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);
+               goto cleanup;
+       }
+       case ACTION_EDIT_TODO:
+               options.action = "edit-todo";
+               options.dont_finish_rebase = 1;
+               goto run_rebase;
+       case ACTION_SHOW_CURRENT_PATCH:
+               options.action = "show-current-patch";
+               options.dont_finish_rebase = 1;
+               goto run_rebase;
+       case NO_ACTION:
+               break;
        default:
-               die("TODO");
+               BUG("action: %d", action);
        }
 
        /* Make sure no rebase is in progress */
@@ -617,6 +767,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        if (!(options.flags & REBASE_NO_QUIET))
                strbuf_addstr(&options.git_am_opt, " -q");
 
+       if (committer_date_is_author_date) {
+               strbuf_addstr(&options.git_am_opt,
+                             " --committer-date-is-author-date");
+               options.flags |= REBASE_FORCE;
+       }
+
        switch (options.type) {
        case REBASE_MERGE:
        case REBASE_INTERACTIVE:
@@ -633,6 +789,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                break;
        }
 
+       if (options.signoff) {
+               if (options.type == REBASE_PRESERVE_MERGES)
+                       die("cannot combine '--signoff' with "
+                           "'--preserve-merges'");
+               strbuf_addstr(&options.git_am_opt, " --signoff");
+               options.flags |= REBASE_FORCE;
+       }
+
        if (!options.root) {
                if (argc < 1)
                        die("TODO: handle @{upstream}");
@@ -806,6 +970,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                diff_flush(&opts);
        }
 
+       if (is_interactive(&options))
+               goto run_rebase;
+
        /* Detach HEAD and reset the tree */
        if (options.flags & REBASE_NO_QUIET)
                printf(_("First, rewinding head to replay your work on top of "