|
20 | 20 | #include "commit.h"
|
21 | 21 | #include "diff.h"
|
22 | 22 | #include "wt-status.h"
|
| 23 | +#include "revision.h" |
23 | 24 |
|
24 | 25 | static char const * const builtin_rebase_usage[] = {
|
25 | 26 | N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
|
@@ -89,6 +90,12 @@ struct rebase_options {
|
89 | 90 | struct strbuf git_am_opt;
|
90 | 91 | };
|
91 | 92 |
|
| 93 | +static int is_interactive(struct rebase_options *opts) |
| 94 | +{ |
| 95 | + return opts->type == REBASE_INTERACTIVE || |
| 96 | + opts->type == REBASE_PRESERVE_MERGES; |
| 97 | +} |
| 98 | + |
92 | 99 | /* Returns the filename prefixed by the state_dir */
|
93 | 100 | static const char *state_dir_path(const char *filename, struct rebase_options *opts)
|
94 | 101 | {
|
@@ -334,6 +341,46 @@ static int rebase_config(const char *var, const char *value, void *data)
|
334 | 341 | return git_default_config(var, value, data);
|
335 | 342 | }
|
336 | 343 |
|
| 344 | +/* |
| 345 | + * Determines whether the commits in from..to are linear, i.e. contain |
| 346 | + * no merge commits. This function *expects* `from` to be an ancestor of |
| 347 | + * `to`. |
| 348 | + */ |
| 349 | +static int is_linear_history(struct commit *from, struct commit *to) |
| 350 | +{ |
| 351 | + while (to && to != from) { |
| 352 | + parse_commit(to); |
| 353 | + if (!to->parents) |
| 354 | + return 1; |
| 355 | + if (to->parents->next) |
| 356 | + return 0; |
| 357 | + to = to->parents->item; |
| 358 | + } |
| 359 | + return 1; |
| 360 | +} |
| 361 | + |
| 362 | +static int can_fast_forward(struct commit *onto, struct object_id *head_oid, |
| 363 | + struct object_id *merge_base) |
| 364 | +{ |
| 365 | + struct commit *head = lookup_commit(the_repository, head_oid); |
| 366 | + struct commit_list *merge_bases; |
| 367 | + int res; |
| 368 | + |
| 369 | + if (!head) |
| 370 | + return 0; |
| 371 | + |
| 372 | + merge_bases = get_merge_bases(onto, head); |
| 373 | + if (merge_bases && !merge_bases->next) { |
| 374 | + oidcpy(merge_base, &merge_bases->item->object.oid); |
| 375 | + res = !oidcmp(merge_base, &onto->object.oid); |
| 376 | + } else { |
| 377 | + oidcpy(merge_base, &null_oid); |
| 378 | + res = 0; |
| 379 | + } |
| 380 | + free_commit_list(merge_bases); |
| 381 | + return res && is_linear_history(onto, head); |
| 382 | +} |
| 383 | + |
337 | 384 | int cmd_rebase(int argc, const char **argv, const char *prefix)
|
338 | 385 | {
|
339 | 386 | struct rebase_options options = {
|
@@ -489,6 +536,31 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
489 | 536 | goto cleanup;
|
490 | 537 | }
|
491 | 538 |
|
| 539 | + /* |
| 540 | + * Now we are rebasing commits upstream..orig_head (or with --root, |
| 541 | + * everything leading up to orig_head) on top of onto. |
| 542 | + */ |
| 543 | + |
| 544 | + /* |
| 545 | + * Check if we are already based on onto with linear history, |
| 546 | + * but this should be done only when upstream and onto are the same |
| 547 | + * and if this is not an interactive rebase. |
| 548 | + */ |
| 549 | + if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && |
| 550 | + !is_interactive(&options) && !options.restrict_revision && |
| 551 | + !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { |
| 552 | + int flag; |
| 553 | + |
| 554 | + if (!(options.flags & REBASE_NO_QUIET)) |
| 555 | + ; /* be quiet */ |
| 556 | + else if (!strcmp(branch_name, "HEAD") && |
| 557 | + resolve_ref_unsafe("HEAD", 0, NULL, &flag)) |
| 558 | + puts(_("HEAD is up to date, rebase forced.")); |
| 559 | + else |
| 560 | + printf(_("Current branch %s is up to date, rebase " |
| 561 | + "forced.\n"), branch_name); |
| 562 | + } |
| 563 | + |
492 | 564 | /* If a hook exists, give it a chance to interrupt*/
|
493 | 565 | if (!ok_to_skip_pre_rebase &&
|
494 | 566 | run_hook_le(NULL, "pre-rebase", options.upstream_arg,
|
|
0 commit comments