diff options
Diffstat (limited to 'builtin/rebase.c')
-rw-r--r-- | builtin/rebase.c | 168 |
1 files changed, 108 insertions, 60 deletions
diff --git a/builtin/rebase.c b/builtin/rebase.c index 670096c065..a11e15b86f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -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"), @@ -62,7 +62,7 @@ struct rebase_options { const char *onto_name; const char *revisions; const char *switch_to; - int root; + int root, root_with_onto; struct object_id *squash_onto; struct commit *restrict_revision; int dont_finish_rebase; @@ -79,8 +79,11 @@ struct rebase_options { int allow_rerere_autoupdate; int keep_empty; int autosquash; + int ignore_whitespace; char *gpg_sign_opt; int autostash; + int committer_date_is_author_date; + int ignore_date; char *cmd; int allow_empty_message; int rebase_merges, rebase_cousins; @@ -99,6 +102,7 @@ struct rebase_options { static struct replay_opts get_replay_opts(const struct rebase_options *opts) { + struct strbuf strategy_buf = STRBUF_INIT; struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; @@ -112,11 +116,20 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_empty_message = opts->allow_empty_message; replay.verbose = opts->flags & REBASE_VERBOSE; replay.reschedule_failed_exec = opts->reschedule_failed_exec; + replay.committer_date_is_author_date = + opts->committer_date_is_author_date; + replay.ignore_date = opts->ignore_date; replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); replay.strategy = opts->strategy; + if (opts->strategy_opts) - parse_strategy_opts(&replay, opts->strategy_opts); + strbuf_addstr(&strategy_buf, opts->strategy_opts); + if (opts->ignore_whitespace) + strbuf_addstr(&strategy_buf, " --ignore-space-change"); + if (strategy_buf.len) + parse_strategy_opts(&replay, strategy_buf.buf); + strbuf_release(&strategy_buf); return replay; } @@ -374,6 +387,7 @@ static int run_rebase_interactive(struct rebase_options *opts, flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; + flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0; flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; switch (command) { @@ -511,6 +525,8 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0); + opts.strategy_opts = xstrdup_or_null(opts.strategy_opts); + if (!is_null_oid(&squash_onto)) opts.squash_onto = &squash_onto; @@ -964,6 +980,12 @@ static int run_am(struct rebase_options *opts) am.git_cmd = 1; argv_array_push(&am.args, "am"); + if (opts->ignore_whitespace) + argv_array_push(&am.args, "--ignore-whitespace"); + if (opts->committer_date_is_author_date) + argv_array_push(&opts->git_am_opts, "--committer-date-is-author-date"); + if (opts->ignore_date) + argv_array_push(&opts->git_am_opts, "--ignore-date"); if (opts->action && !strcmp("continue", opts->action)) { argv_array_push(&am.args, "--resolved"); argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); @@ -1260,24 +1282,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); } @@ -1376,6 +1418,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; @@ -1394,6 +1437,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, @@ -1407,16 +1452,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), - OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &options.git_am_opts, - NULL, N_("passed to 'git am'"), - PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date", - &options.git_am_opts, NULL, - N_("passed to 'git am'"), PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL, - N_("passed to 'git am'"), PARSE_OPT_NOARG), + OPT_BOOL(0, "committer-date-is-author-date", + &options.committer_date_is_author_date, + N_("make committer date match author date")), + OPT_BOOL(0, "reset-author-date", &options.ignore_date, + N_("ignore author date and use current date")), + OPT_HIDDEN_BOOL(0, "ignore-date", &options.ignore_date, + N_("synonym of --reset-author-date")), OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"), N_("passed to 'git apply'"), 0), + OPT_BOOL(0, "ignore-whitespace", &options.ignore_whitespace, + N_("ignore changes in whitespace")), OPT_PASSTHRU_ARGV(0, "whitespace", &options.git_am_opts, N_("action"), N_("passed to 'git apply'"), 0), OPT_BIT('f', "force-rebase", &options.flags, @@ -1447,9 +1493,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) 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_("(DEPRECATED) try to recreate merges instead of " - "ignoring them"), REBASE_PRESERVE_MERGES), + OPT_SET_INT_F('p', "preserve-merges", &options.type, + N_("(DEPRECATED) try to recreate merges instead of " + "ignoring them"), + REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), OPT_BOOL('k', "keep-empty", &options.keep_empty, N_("preserve empty commits during rebase")), @@ -1547,6 +1594,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); @@ -1681,11 +1735,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) state_dir_base, cmd_live_rebase, buf.buf); } + if (options.committer_date_is_author_date || + options.ignore_date) + options.flags |= REBASE_FORCE; + for (i = 0; i < options.git_am_opts.argc; i++) { const char *option = options.git_am_opts.argv[i], *p; - if (!strcmp(option, "--committer-date-is-author-date") || - !strcmp(option, "--ignore-date") || - !strcmp(option, "--whitespace=fix") || + if (!strcmp(option, "--whitespace=fix") || !strcmp(option, "--whitespace=strip")) options.flags |= REBASE_FORCE; else if (skip_prefix(option, "-C", &p)) { @@ -1833,15 +1889,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "'--reschedule-failed-exec'")); } - if (options.rebase_merges) { - if (strategy_options.nr) - die(_("cannot combine '--rebase-merges' with " - "'--strategy-option'")); - if (options.strategy) - die(_("cannot combine '--rebase-merges' with " - "'--strategy'")); - } - if (!options.root) { if (argc < 1) { struct branch *branch; @@ -1872,7 +1919,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.squash_onto = &squash_onto; options.onto_name = squash_onto_name = xstrdup(oid_to_hex(&squash_onto)); - } + } else + options.root_with_onto = 1; + options.upstream_name = NULL; options.upstream = NULL; if (argc > 1) @@ -1882,12 +1931,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 { @@ -1968,9 +2027,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) state_dir_path("autostash", &options); struct child_process stash = CHILD_PROCESS_INIT; struct object_id oid; - struct commit *head = - lookup_commit_reference(the_repository, - &options.orig_head); argv_array_pushl(&stash.args, "stash", "create", "autostash", NULL); @@ -1991,17 +2047,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.state_dir); write_file(autostash, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(&head->object.oid, "reset --hard", + if (reset_head(NULL, "reset --hard", NULL, RESET_HEAD_HARD, NULL, NULL) < 0) die(_("could not reset --hard")); - printf(_("HEAD is now at %s"), - find_unique_abbrev(&head->object.oid, - DEFAULT_ABBREV)); - strbuf_reset(&buf); - pp_commit_easy(CMIT_FMT_ONELINE, head, &buf); - if (buf.len > 0) - printf(" %s", buf.buf); - putchar('\n'); if (discard_index(the_repository->index) < 0 || repo_read_index(the_repository) < 0) @@ -2022,13 +2070,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)) { |