rebase --keep-base: imply --reapply-cherry-picks

As --keep-base does not rebase the branch it is confusing if it
removes commits that have been cherry-picked to the upstream branch.
As --reapply-cherry-picks is not supported by the "apply" backend this
commit ensures that cherry-picks are reapplied by forcing the upstream
commit to match the onto commit unless --no-reapply-cherry-picks is
given.

Reported-by: Philippe Blain <levraiphilippeblain@gmail.com>
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Phillip Wood 2022-10-17 13:17:44 +00:00 committed by Junio C Hamano
parent d42c9ffa0f
commit ce5238a690
3 changed files with 52 additions and 11 deletions

View File

@ -218,12 +218,14 @@ leave out at most one of A and B, in which case it defaults to HEAD.
merge base of `<upstream>` and `<branch>`. Running
`git rebase --keep-base <upstream> <branch>` is equivalent to
running
`git rebase --onto <upstream>...<branch> <upstream> <branch>`.
`git rebase --reapply-cherry-picks --onto <upstream>...<branch> <upstream> <branch>`.
+
This option is useful in the case where one is developing a feature on
top of an upstream branch. While the feature is being worked on, the
upstream branch may advance and it may not be the best idea to keep
rebasing on top of the upstream but to keep the base commit as-is.
rebasing on top of the upstream but to keep the base commit as-is. As
the base commit is unchanged this option implies `--reapply-cherry-picks`
to avoid losing commits.
+
Although both this option and `--fork-point` find the merge base between
`<upstream>` and `<branch>`, this option uses the merge base as the _starting
@ -278,7 +280,8 @@ See also INCOMPATIBLE OPTIONS below.
Note that commits which start empty are kept (unless `--no-keep-empty`
is specified), and commits which are clean cherry-picks (as determined
by `git log --cherry-mark ...`) are detected and dropped as a
preliminary step (unless `--reapply-cherry-picks` is passed).
preliminary step (unless `--reapply-cherry-picks` or `--keep-base` is
passed).
+
See also INCOMPATIBLE OPTIONS below.
@ -311,13 +314,16 @@ See also INCOMPATIBLE OPTIONS below.
upstream changes, the behavior towards them is controlled by
the `--empty` flag.)
+
By default (or if `--no-reapply-cherry-picks` is given), these commits
will be automatically dropped. Because this necessitates reading all
upstream commits, this can be expensive in repos with a large number
of upstream commits that need to be read. When using the 'merge'
backend, warnings will be issued for each dropped commit (unless
`--quiet` is given). Advice will also be issued unless
`advice.skippedCherryPicks` is set to false (see linkgit:git-config[1]).
In the absence of `--keep-base` (or if `--no-reapply-cherry-picks` is
given), these commits will be automatically dropped. Because this
necessitates reading all upstream commits, this can be expensive in
repositories with a large number of upstream commits that need to be
read. When using the 'merge' backend, warnings will be issued for each
dropped commit (unless `--quiet` is given). Advice will also be issued
unless `advice.skippedCherryPicks` is set to false (see
linkgit:git-config[1]).
+
`--reapply-cherry-picks` allows rebase to forgo reading all upstream
commits, potentially improving performance.

View File

@ -1171,6 +1171,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
options.reapply_cherry_picks = -1;
options.allow_empty_message = 1;
git_config(rebase_config, &options);
/* options.gpg_sign_opt will be either "-S" or NULL */
@ -1230,6 +1231,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.root)
die(_("options '%s' and '%s' cannot be used together"), "--keep-base", "--root");
}
/*
* --keep-base defaults to --reapply-cherry-picks to avoid losing
* commits when using this option.
*/
if (options.reapply_cherry_picks < 0)
options.reapply_cherry_picks = keep_base;
if (options.root && options.fork_point > 0)
die(_("options '%s' and '%s' cannot be used together"), "--root", "--fork-point");
@ -1406,7 +1413,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.empty != EMPTY_UNSPECIFIED)
imply_merge(&options, "--empty");
if (options.reapply_cherry_picks)
/*
* --keep-base implements --reapply-cherry-picks by altering upstream so
* it works with both backends.
*/
if (options.reapply_cherry_picks && !keep_base)
imply_merge(&options, "--reapply-cherry-picks");
if (gpg_sign)
@ -1672,6 +1683,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
fill_branch_base(&options, &branch_base);
}
if (keep_base && options.reapply_cherry_picks)
options.upstream = options.onto;
if (options.fork_point > 0)
options.restrict_revision =
get_fork_point(options.upstream_name, options.orig_head);

View File

@ -199,6 +199,27 @@ test_expect_success 'rebase --keep-base requires a single merge base' '
grep "need exactly one merge base with branch" err
'
test_expect_success 'rebase --keep-base keeps cherry picks' '
git checkout -f -B main E &&
git cherry-pick F &&
(
set_fake_editor &&
EXPECT_COUNT=2 git rebase -i --keep-base HEAD G
) &&
test_cmp_rev HEAD G
'
test_expect_success 'rebase --keep-base --no-reapply-cherry-picks' '
git checkout -f -B main E &&
git cherry-pick F &&
(
set_fake_editor &&
EXPECT_COUNT=1 git rebase -i --keep-base \
--no-reapply-cherry-picks HEAD G
) &&
test_cmp_rev HEAD^ C
'
# This must be the last test in this file
test_expect_success '$EDITOR and friends are unchanged' '
test_editor_unchanged