]> git.ipfire.org Git - thirdparty/git.git/blobdiff - builtin/rebase.c
Merge branch 'hs/rebase-not-in-progress' into HEAD
[thirdparty/git.git] / builtin / rebase.c
index 7acb7f3559c7642500c709ac89e651ea3c35085d..6ead9465a42c6d3c6ff7a547bb42ed9dd919dd90 100644 (file)
@@ -6,18 +6,21 @@
 
 #define USE_THE_INDEX_VARIABLE
 #include "builtin.h"
+#include "abspath.h"
+#include "environment.h"
+#include "gettext.h"
+#include "hex.h"
 #include "run-command.h"
-#include "exec-cmd.h"
 #include "strvec.h"
 #include "dir.h"
-#include "packfile.h"
 #include "refs.h"
-#include "quote.h"
 #include "config.h"
-#include "cache-tree.h"
 #include "unpack-trees.h"
 #include "lockfile.h"
+#include "object-file.h"
+#include "object-name.h"
 #include "parse-options.h"
+#include "path.h"
 #include "commit.h"
 #include "diff.h"
 #include "wt-status.h"
@@ -28,6 +31,7 @@
 #include "sequencer.h"
 #include "rebase-interactive.h"
 #include "reset.h"
+#include "trace2.h"
 #include "hook.h"
 
 static char const * const builtin_rebase_usage[] = {
@@ -116,13 +120,15 @@ struct rebase_options {
        struct string_list exec;
        int allow_empty_message;
        int rebase_merges, rebase_cousins;
-       char *strategy, *strategy_opts;
+       char *strategy;
+       struct string_list strategy_opts;
        struct strbuf git_format_patch_opt;
        int reschedule_failed_exec;
        int reapply_cherry_picks;
        int fork_point;
        int update_refs;
        int config_autosquash;
+       int config_rebase_merges;
        int config_update_refs;
 };
 
@@ -139,9 +145,11 @@ struct rebase_options {
                .reapply_cherry_picks = -1,             \
                .allow_empty_message = 1,               \
                .autosquash = -1,                       \
-               .config_autosquash = -1,                \
+               .rebase_merges = -1,                    \
+               .config_rebase_merges = -1,             \
                .update_refs = -1,                      \
                .config_update_refs = -1,               \
+               .strategy_opts = STRING_LIST_INIT_NODUP,\
        }
 
 static struct replay_opts get_replay_opts(const struct rebase_options *opts)
@@ -175,8 +183,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
                replay.default_strategy = NULL;
        }
 
-       if (opts->strategy_opts)
-               parse_strategy_opts(&replay, opts->strategy_opts);
+       for (size_t i = 0; i < opts->strategy_opts.nr; i++)
+               strvec_push(&replay.xopts, opts->strategy_opts.items[i].string);
 
        if (opts->squash_onto) {
                oidcpy(&replay.squash_onto, opts->squash_onto);
@@ -196,7 +204,7 @@ static int edit_todo_file(unsigned flags)
        if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
                return error_errno(_("could not read '%s'."), todo_file);
 
-       strbuf_stripspace(&todo_list.buf, 1);
+       strbuf_stripspace(&todo_list.buf, comment_line_char);
        res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags);
        if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file,
                                            NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS)))
@@ -218,13 +226,15 @@ static int get_revision_ranges(struct commit *upstream, struct commit *onto,
        *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
                             oid_to_hex(orig_head));
 
-       shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV);
+       shorthead = repo_find_unique_abbrev(the_repository, orig_head,
+                                           DEFAULT_ABBREV);
 
        if (upstream) {
                const char *shortrev;
 
-               shortrev = find_unique_abbrev(&base_rev->object.oid,
-                                             DEFAULT_ABBREV);
+               shortrev = repo_find_unique_abbrev(the_repository,
+                                                  &base_rev->object.oid,
+                                                  DEFAULT_ABBREV);
 
                *shortrevisions = xstrfmt("%s..%s", shortrev, shorthead);
        } else
@@ -361,20 +371,6 @@ static int run_sequencer_rebase(struct rebase_options *opts)
        return ret;
 }
 
-static void imply_merge(struct rebase_options *opts, const char *option);
-static int parse_opt_keep_empty(const struct option *opt, const char *arg,
-                               int unset)
-{
-       struct rebase_options *opts = opt->value;
-
-       BUG_ON_OPT_ARG(arg);
-
-       imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty");
-       opts->keep_empty = !unset;
-       opts->type = REBASE_MERGE;
-       return 0;
-}
-
 static int is_merge(struct rebase_options *opts)
 {
        return opts->type == REBASE_MERGE;
@@ -482,24 +478,6 @@ static int read_basic_state(struct rebase_options *opts)
                opts->gpg_sign_opt = xstrdup(buf.buf);
        }
 
-       if (file_exists(state_dir_path("strategy", opts))) {
-               strbuf_reset(&buf);
-               if (!read_oneliner(&buf, state_dir_path("strategy", opts),
-                                  READ_ONELINER_WARN_MISSING))
-                       return -1;
-               free(opts->strategy);
-               opts->strategy = xstrdup(buf.buf);
-       }
-
-       if (file_exists(state_dir_path("strategy_opts", opts))) {
-               strbuf_reset(&buf);
-               if (!read_oneliner(&buf, state_dir_path("strategy_opts", opts),
-                                  READ_ONELINER_WARN_MISSING))
-                       return -1;
-               free(opts->strategy_opts);
-               opts->strategy_opts = xstrdup(buf.buf);
-       }
-
        strbuf_release(&buf);
 
        return 0;
@@ -517,12 +495,6 @@ static int rebase_write_basic_state(struct rebase_options *opts)
                write_file(state_dir_path("quiet", opts), "%s", "");
        if (opts->flags & REBASE_VERBOSE)
                write_file(state_dir_path("verbose", opts), "%s", "");
-       if (opts->strategy)
-               write_file(state_dir_path("strategy", opts), "%s",
-                          opts->strategy);
-       if (opts->strategy_opts)
-               write_file(state_dir_path("strategy_opts", opts), "%s",
-                          opts->strategy_opts);
        if (opts->allow_rerere_autoupdate > 0)
                write_file(state_dir_path("allow_rerere_autoupdate", opts),
                           "-%s-rerere-autoupdate",
@@ -543,7 +515,7 @@ static int finish_rebase(struct rebase_options *opts)
        int ret = 0;
 
        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
-       unlink(git_path_auto_merge(the_repository));
+       delete_ref(NULL, "AUTO_MERGE", NULL, REF_NO_DEREF);
        apply_autostash(state_dir_path("autostash", opts));
        /*
         * We ignore errors in 'git maintenance run --auto', since the
@@ -606,7 +578,6 @@ static int run_am(struct rebase_options *opts)
 {
        struct child_process am = CHILD_PROCESS_INIT;
        struct child_process format_patch = CHILD_PROCESS_INIT;
-       struct strbuf revisions = STRBUF_INIT;
        int status;
        char *rebased_patches;
 
@@ -639,13 +610,6 @@ static int run_am(struct rebase_options *opts)
                return run_command(&am);
        }
 
-       strbuf_addf(&revisions, "%s...%s",
-                   oid_to_hex(opts->root ?
-                              /* this is now equivalent to !opts->upstream */
-                              &opts->onto->object.oid :
-                              &opts->upstream->object.oid),
-                   oid_to_hex(&opts->orig_head->object.oid));
-
        rebased_patches = xstrdup(git_path("rebased-patches"));
        format_patch.out = open(rebased_patches,
                                O_WRONLY | O_CREAT | O_TRUNC, 0666);
@@ -660,13 +624,18 @@ static int run_am(struct rebase_options *opts)
        format_patch.git_cmd = 1;
        strvec_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
                     "--full-index", "--cherry-pick", "--right-only",
-                    "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
+                    "--default-prefix", "--no-renames",
                     "--no-cover-letter", "--pretty=mboxrd", "--topo-order",
                     "--no-base", NULL);
        if (opts->git_format_patch_opt.len)
                strvec_split(&format_patch.args,
                             opts->git_format_patch_opt.buf);
-       strvec_push(&format_patch.args, revisions.buf);
+       strvec_pushf(&format_patch.args, "%s...%s",
+                    oid_to_hex(opts->root ?
+                               /* this is now equivalent to !opts->upstream */
+                               &opts->onto->object.oid :
+                               &opts->upstream->object.oid),
+                    oid_to_hex(&opts->orig_head->object.oid));
        if (opts->restrict_revision)
                strvec_pushf(&format_patch.args, "^%s",
                             oid_to_hex(&opts->restrict_revision->object.oid));
@@ -689,10 +658,8 @@ static int run_am(struct rebase_options *opts)
                        "As a result, git cannot rebase them."),
                      opts->revisions);
 
-               strbuf_release(&revisions);
                return status;
        }
-       strbuf_release(&revisions);
 
        am.in = open(rebased_patches, O_RDONLY);
        if (am.in < 0) {
@@ -734,10 +701,8 @@ static int run_specific_rebase(struct rebase_options *opts)
        if (opts->type == REBASE_MERGE) {
                /* Run sequencer-based rebase */
                setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
-               if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
+               if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT))
                        setenv("GIT_SEQUENCE_EDITOR", ":", 1);
-                       opts->autosquash = 0;
-               }
                if (opts->gpg_sign_opt) {
                        /* remove the leading "-S" */
                        char *tmp = xstrdup(opts->gpg_sign_opt + 2);
@@ -771,7 +736,18 @@ static int run_specific_rebase(struct rebase_options *opts)
        return status ? -1 : 0;
 }
 
-static int rebase_config(const char *var, const char *value, void *data)
+static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
+{
+       if (!strcmp("no-rebase-cousins", value))
+               options->rebase_cousins = 0;
+       else if (!strcmp("rebase-cousins", value))
+               options->rebase_cousins = 1;
+       else
+               die(_("Unknown rebase-merges mode: %s"), value);
+}
+
+static int rebase_config(const char *var, const char *value,
+                        const struct config_context *ctx, void *data)
 {
        struct rebase_options *opts = data;
 
@@ -800,6 +776,17 @@ static int rebase_config(const char *var, const char *value, void *data)
                return 0;
        }
 
+       if (!strcmp(var, "rebase.rebasemerges")) {
+               opts->config_rebase_merges = git_parse_maybe_bool(value);
+               if (opts->config_rebase_merges < 0) {
+                       opts->config_rebase_merges = 1;
+                       parse_rebase_merges_value(opts, value);
+               } else {
+                       opts->rebase_cousins = 0;
+               }
+               return 0;
+       }
+
        if (!strcmp(var, "rebase.updaterefs")) {
                opts->config_update_refs = git_config_bool(var, value);
                return 0;
@@ -819,7 +806,7 @@ static int rebase_config(const char *var, const char *value, void *data)
                return git_config_string(&opts->default_backend, var, value);
        }
 
-       return git_default_config(var, value, data);
+       return git_default_config(var, value, ctx, data);
 }
 
 static int checkout_up_to_date(struct rebase_options *options)
@@ -851,7 +838,7 @@ static int checkout_up_to_date(struct rebase_options *options)
 static int is_linear_history(struct commit *from, struct commit *to)
 {
        while (to && to != from) {
-               parse_commit(to);
+               repo_parse_commit(the_repository, to);
                if (!to->parents)
                        return 1;
                if (to->parents->next)
@@ -880,7 +867,7 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
        if (!upstream)
                goto done;
 
-       merge_bases = get_merge_bases(upstream, head);
+       merge_bases = repo_get_merge_bases(the_repository, upstream, head);
        if (!merge_bases || merge_bases->next)
                goto done;
 
@@ -899,7 +886,8 @@ static void fill_branch_base(struct rebase_options *options,
 {
        struct commit_list *merge_bases = NULL;
 
-       merge_bases = get_merge_bases(options->onto, options->orig_head);
+       merge_bases = repo_get_merge_bases(the_repository, options->onto,
+                                          options->orig_head);
        if (!merge_bases || merge_bases->next)
                oidcpy(branch_base, null_oid());
        else
@@ -969,6 +957,18 @@ static enum empty_type parse_empty_value(const char *value)
        die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
 }
 
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+                               int unset)
+{
+       struct rebase_options *opts = opt->value;
+
+       BUG_ON_OPT_ARG(arg);
+
+       imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty");
+       opts->keep_empty = !unset;
+       return 0;
+}
+
 static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
 {
        struct rebase_options *options = opt->value;
@@ -980,6 +980,28 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
+{
+       struct rebase_options *options = opt->value;
+
+       options->rebase_merges = !unset;
+       options->rebase_cousins = 0;
+
+       if (arg) {
+               if (!*arg) {
+                       warning(_("--rebase-merges with an empty string "
+                                 "argument is deprecated and will stop "
+                                 "working in a future version of Git. Use "
+                                 "--rebase-merges without an argument "
+                                 "instead, which does the same thing."));
+                       return 0;
+               }
+               parse_rebase_merges_value(options, arg);
+       }
+
+       return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
        struct branch *current_branch = branch_get(NULL);
@@ -1035,8 +1057,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        struct object_id branch_base;
        int ignore_whitespace = 0;
        const char *gpg_sign = NULL;
-       const char *rebase_merges = NULL;
-       struct string_list strategy_options = STRING_LIST_INIT_NODUP;
        struct object_id squash_onto;
        char *squash_onto_name = NULL;
        char *keep_base_onto_name = NULL;
@@ -1113,7 +1133,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                                 "instead of ignoring them"),
                              1, PARSE_OPT_HIDDEN),
                OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
-               OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}",
+               OPT_CALLBACK_F(0, "empty", &options, "(drop|keep|ask)",
                               N_("how to handle commits that become empty"),
                               PARSE_OPT_NONEG, parse_opt_empty),
                OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
@@ -1137,15 +1157,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                           &options.allow_empty_message,
                           N_("allow rebasing commits with empty messages"),
                           PARSE_OPT_HIDDEN),
-               {OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
-                       N_("mode"),
+               OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
                        N_("try to rebase merges instead of skipping them"),
-                       PARSE_OPT_OPTARG, NULL, (intptr_t)""},
+                       PARSE_OPT_OPTARG, parse_opt_rebase_merges),
                OPT_BOOL(0, "fork-point", &options.fork_point,
                         N_("use 'merge-base --fork-point' to refine upstream")),
                OPT_STRING('s', "strategy", &options.strategy,
                           N_("strategy"), N_("use the given merge strategy")),
-               OPT_STRING_LIST('X', "strategy-option", &strategy_options,
+               OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
                                N_("option"),
                                N_("pass the argument through to the merge "
                                   "strategy")),
@@ -1261,7 +1280,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                int fd;
 
                /* Sanity check */
-               if (get_oid("HEAD", &head))
+               if (repo_get_oid(the_repository, "HEAD", &head))
                        die(_("Cannot read HEAD"));
 
                fd = repo_hold_locked_index(the_repository, &lock_file, 0);
@@ -1374,7 +1393,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
            (options.action != ACTION_NONE) ||
            (options.exec.nr > 0) ||
-           (options.autosquash == -1 && options.config_autosquash == 1) ||
            options.autosquash == 1) {
                allow_preemptive_ff = 0;
        }
@@ -1436,17 +1454,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        if (options.exec.nr)
                imply_merge(&options, "--exec");
 
-       if (rebase_merges) {
-               if (!*rebase_merges)
-                       ; /* default mode; do nothing */
-               else if (!strcmp("rebase-cousins", rebase_merges))
-                       options.rebase_cousins = 1;
-               else if (strcmp("no-rebase-cousins", rebase_merges))
-                       die(_("Unknown mode: %s"), rebase_merges);
-               options.rebase_merges = 1;
-               imply_merge(&options, "--rebase-merges");
-       }
-
        if (options.type == REBASE_APPLY) {
                if (ignore_whitespace)
                        strvec_push(&options.git_am_opts,
@@ -1459,43 +1466,19 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        } else {
                /* REBASE_MERGE */
                if (ignore_whitespace) {
-                       string_list_append(&strategy_options,
+                       string_list_append(&options.strategy_opts,
                                           "ignore-space-change");
                }
        }
 
-       if (strategy_options.nr) {
-               int i;
-
-               if (!options.strategy)
-                       options.strategy = "ort";
-
-               strbuf_reset(&buf);
-               for (i = 0; i < strategy_options.nr; i++)
-                       strbuf_addf(&buf, " --%s",
-                                   strategy_options.items[i].string);
-               options.strategy_opts = xstrdup(buf.buf);
-       }
+       if (options.strategy_opts.nr && !options.strategy)
+               options.strategy = "ort";
 
        if (options.strategy) {
                options.strategy = xstrdup(options.strategy);
-               switch (options.type) {
-               case REBASE_APPLY:
-                       die(_("--strategy requires --merge or --interactive"));
-               case REBASE_MERGE:
-                       /* compatible */
-                       break;
-               case REBASE_UNSPECIFIED:
-                       options.type = REBASE_MERGE;
-                       break;
-               default:
-                       BUG("unhandled rebase type (%d)", options.type);
-               }
+               imply_merge(&options, "--strategy");
        }
 
-       if (options.type == REBASE_MERGE)
-               imply_merge(&options, "--merge");
-
        if (options.root && !options.onto_name)
                imply_merge(&options, "--root without --onto");
 
@@ -1512,8 +1495,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        if (is_merge(&options))
                                die(_("apply options and merge options "
                                          "cannot be used together"));
-                       else if (options.autosquash == -1 && options.config_autosquash == 1)
-                               die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
+                       else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
+                               die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
                        else if (options.update_refs == -1 && options.config_update_refs == 1)
                                die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
                        else
@@ -1526,14 +1509,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        options.update_refs = (options.update_refs >= 0) ? options.update_refs :
                             ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
 
-       if (options.autosquash == 1)
+       if (options.rebase_merges == 1)
+               imply_merge(&options, "--rebase-merges");
+       options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
+                               ((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
+
+       if (options.autosquash == 1) {
                imply_merge(&options, "--autosquash");
-       options.autosquash = (options.autosquash >= 0) ? options.autosquash :
-                            ((options.config_autosquash >= 0) ? options.config_autosquash : 0);
+       } else if (options.autosquash == -1) {
+               options.autosquash =
+                       options.config_autosquash &&
+                       (options.flags & REBASE_INTERACTIVE_EXPLICIT);
+       }
 
        if (options.type == REBASE_UNSPECIFIED) {
                if (!strcmp(options.default_backend, "merge"))
-                       imply_merge(&options, "--merge");
+                       options.type = REBASE_MERGE;
                else if (!strcmp(options.default_backend, "apply"))
                        options.type = REBASE_APPLY;
                else
@@ -1680,7 +1671,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        } else if (!options.onto_name)
                options.onto_name = options.upstream_name;
        if (strstr(options.onto_name, "...")) {
-               if (get_oid_mb(options.onto_name, &branch_base) < 0) {
+               if (repo_get_oid_mb(the_repository, options.onto_name, &branch_base) < 0) {
                        if (keep_base)
                                die(_("'%s': need exactly one merge base with branch"),
                                    options.upstream_name);
@@ -1783,9 +1774,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                }
 
                /* We want color (if set), but no pager */
-               diff_setup(&opts);
-               opts.stat_width = -1; /* use full terminal width */
-               opts.stat_graph_width = -1; /* respect statGraphWidth config */
+               repo_diff_setup(the_repository, &opts);
+               init_diffstat_widths(&opts);
                opts.output_format |=
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
@@ -1850,10 +1840,9 @@ cleanup:
        free(options.gpg_sign_opt);
        string_list_clear(&options.exec, 0);
        free(options.strategy);
-       free(options.strategy_opts);
+       string_list_clear(&options.strategy_opts, 0);
        strbuf_release(&options.git_format_patch_opt);
        free(squash_onto_name);
        free(keep_base_onto_name);
-       string_list_clear(&strategy_options, 0);
        return !!ret;
 }