rebase --merge: optionally skip upstreamed commits

When rebasing against an upstream that has had many commits since the
original branch was created:

 O -- O -- ... -- O -- O (upstream)
  \
   -- O (my-dev-branch)

it must read the contents of every novel upstream commit, in addition to
the tip of the upstream and the merge base, because "git rebase"
attempts to exclude commits that are duplicates of upstream ones. This
can be a significant performance hit, especially in a partial clone,
wherein a read of an object may end up being a fetch.

Add a flag to "git rebase" to allow suppression of this feature. This
flag only works when using the "merge" backend.

This flag changes the behavior of sequencer_make_script(), called from
do_interactive_rebase() <- run_rebase_interactive() <-
run_specific_rebase() <- cmd_rebase(). With this flag, limit_list()
(indirectly called from sequencer_make_script() through
prepare_revision_walk()) will no longer call cherry_pick_list(), and
thus PATCHSAME is no longer set. Refraining from setting PATCHSAME both
means that the intermediate commits in upstream are no longer read (as
shown by the test) and means that no PATCHSAME-caused skipping of
commits is done by sequencer_make_script(), either directly or through
make_script_with_merges().

Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jonathan Tan 2020-04-11 02:44:27 +00:00 коммит произвёл Junio C Hamano
Родитель 50ed76148a
Коммит 0fcb4f6b62
5 изменённых файлов: 110 добавлений и 4 удалений

Просмотреть файл

@ -279,7 +279,8 @@ See also INCOMPATIBLE OPTIONS below.
+ +
Note that commits which start empty are kept (unless --no-keep-empty Note that commits which start empty are kept (unless --no-keep-empty
is specified), and commits which are clean cherry-picks (as determined is specified), and commits which are clean cherry-picks (as determined
by `git log --cherry-mark ...`) are always dropped. by `git log --cherry-mark ...`) are detected and dropped as a
preliminary step (unless --reapply-cherry-picks is passed).
+ +
See also INCOMPATIBLE OPTIONS below. See also INCOMPATIBLE OPTIONS below.
@ -304,6 +305,24 @@ see the --empty flag.
+ +
See also INCOMPATIBLE OPTIONS below. See also INCOMPATIBLE OPTIONS below.
--reapply-cherry-picks::
--no-reapply-cherry-picks::
Reapply all clean cherry-picks of any upstream commit instead
of preemptively dropping them. (If these commits then become
empty after rebasing, because they contain a subset of already
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.
+
`--reapply-cherry-picks` allows rebase to forgo reading all upstream
commits, potentially improving performance.
+
See also INCOMPATIBLE OPTIONS below.
--allow-empty-message:: --allow-empty-message::
No-op. Rebasing commits with an empty message used to fail No-op. Rebasing commits with an empty message used to fail
and this option would override that behavior, allowing commits and this option would override that behavior, allowing commits
@ -601,6 +620,7 @@ are incompatible with the following options:
* --exec * --exec
* --no-keep-empty * --no-keep-empty
* --empty= * --empty=
* --reapply-cherry-picks
* --edit-todo * --edit-todo
* --root when used in combination with --onto * --root when used in combination with --onto
@ -1017,7 +1037,8 @@ Only works if the changes (patch IDs based on the diff contents) on
'subsystem' did. 'subsystem' did.
In that case, the fix is easy because 'git rebase' knows to skip In that case, the fix is easy because 'git rebase' knows to skip
changes that are already present in the new upstream. So if you say changes that are already present in the new upstream (unless
`--reapply-cherry-picks` is given). So if you say
(assuming you're on 'topic') (assuming you're on 'topic')
------------ ------------
$ git rebase subsystem $ git rebase subsystem

Просмотреть файл

@ -96,6 +96,7 @@ struct rebase_options {
struct strbuf git_format_patch_opt; struct strbuf git_format_patch_opt;
int reschedule_failed_exec; int reschedule_failed_exec;
int use_legacy_rebase; int use_legacy_rebase;
int reapply_cherry_picks;
}; };
#define REBASE_OPTIONS_INIT { \ #define REBASE_OPTIONS_INIT { \
@ -387,6 +388,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0; flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
switch (command) { switch (command) {
case ACTION_NONE: { case ACTION_NONE: {
@ -1585,6 +1587,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "reschedule-failed-exec", OPT_BOOL(0, "reschedule-failed-exec",
&reschedule_failed_exec, &reschedule_failed_exec,
N_("automatically re-schedule any `exec` that fails")), N_("automatically re-schedule any `exec` that fails")),
OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks,
N_("apply all changes, even those already present upstream")),
OPT_END(), OPT_END(),
}; };
int i; int i;
@ -1825,6 +1829,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.empty != EMPTY_UNSPECIFIED) if (options.empty != EMPTY_UNSPECIFIED)
imply_merge(&options, "--empty"); imply_merge(&options, "--empty");
if (options.reapply_cherry_picks)
imply_merge(&options, "--reapply-cherry-picks");
if (gpg_sign) { if (gpg_sign) {
free(options.gpg_sign_opt); free(options.gpg_sign_opt);
options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);

Просмотреть файл

@ -4828,12 +4828,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
int keep_empty = flags & TODO_LIST_KEEP_EMPTY; int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES; int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
repo_init_revisions(r, &revs, NULL); repo_init_revisions(r, &revs, NULL);
revs.verbose_header = 1; revs.verbose_header = 1;
if (!rebase_merges) if (!rebase_merges)
revs.max_parents = 1; revs.max_parents = 1;
revs.cherry_mark = 1; revs.cherry_mark = !reapply_cherry_picks;
revs.limited = 1; revs.limited = 1;
revs.reverse = 1; revs.reverse = 1;
revs.right_only = 1; revs.right_only = 1;

Просмотреть файл

@ -150,7 +150,7 @@ int sequencer_remove_state(struct replay_opts *opts);
* `--onto`, we do not want to re-generate the root commits. * `--onto`, we do not want to re-generate the root commits.
*/ */
#define TODO_LIST_ROOT_WITH_ONTO (1U << 6) #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
#define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
const char **argv, unsigned flags); const char **argv, unsigned flags);

Просмотреть файл

@ -162,4 +162,81 @@ test_expect_success 'rebase --skip works with two conflicts in a row' '
git rebase --skip git rebase --skip
' '
test_expect_success '--reapply-cherry-picks' '
git init repo &&
# O(1-10) -- O(1-11) -- O(0-10) master
# \
# -- O(1-11) -- O(1-12) otherbranch
printf "Line %d\n" $(test_seq 1 10) >repo/file.txt &&
git -C repo add file.txt &&
git -C repo commit -m "base commit" &&
printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
git -C repo commit -a -m "add 11" &&
printf "Line %d\n" $(test_seq 0 10) >repo/file.txt &&
git -C repo commit -a -m "add 0 delete 11" &&
git -C repo checkout -b otherbranch HEAD^^ &&
printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
git -C repo commit -a -m "add 11 in another branch" &&
printf "Line %d\n" $(test_seq 1 12) >repo/file.txt &&
git -C repo commit -a -m "add 12 in another branch" &&
# Regular rebase fails, because the 1-11 commit is deduplicated
test_must_fail git -C repo rebase --merge master 2> err &&
test_i18ngrep "error: could not apply.*add 12 in another branch" err &&
git -C repo rebase --abort &&
# With --reapply-cherry-picks, it works
git -C repo rebase --merge --reapply-cherry-picks master
'
test_expect_success '--reapply-cherry-picks refrains from reading unneeded blobs' '
git init server &&
# O(1-10) -- O(1-11) -- O(1-12) master
# \
# -- O(0-10) otherbranch
printf "Line %d\n" $(test_seq 1 10) >server/file.txt &&
git -C server add file.txt &&
git -C server commit -m "merge base" &&
printf "Line %d\n" $(test_seq 1 11) >server/file.txt &&
git -C server commit -a -m "add 11" &&
printf "Line %d\n" $(test_seq 1 12) >server/file.txt &&
git -C server commit -a -m "add 12" &&
git -C server checkout -b otherbranch HEAD^^ &&
printf "Line %d\n" $(test_seq 0 10) >server/file.txt &&
git -C server commit -a -m "add 0" &&
test_config -C server uploadpack.allowfilter 1 &&
test_config -C server uploadpack.allowanysha1inwant 1 &&
git clone --filter=blob:none "file://$(pwd)/server" client &&
git -C client checkout origin/master &&
git -C client checkout origin/otherbranch &&
# Sanity check to ensure that the blobs from the merge base and "add
# 11" are missing
git -C client rev-list --objects --all --missing=print >missing_list &&
MERGE_BASE_BLOB=$(git -C server rev-parse master^^:file.txt) &&
ADD_11_BLOB=$(git -C server rev-parse master^:file.txt) &&
grep "[?]$MERGE_BASE_BLOB" missing_list &&
grep "[?]$ADD_11_BLOB" missing_list &&
git -C client rebase --merge --reapply-cherry-picks origin/master &&
# The blob from the merge base had to be fetched, but not "add 11"
git -C client rev-list --objects --all --missing=print >missing_list &&
! grep "[?]$MERGE_BASE_BLOB" missing_list &&
grep "[?]$ADD_11_BLOB" missing_list
'
test_done test_done