Skip to content

Commit de8be84

Browse files
jonathantanmygitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d0654dc commit de8be84

File tree

5 files changed

+106
-3
lines changed

5 files changed

+106
-3
lines changed

Documentation/git-rebase.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,20 @@ See also INCOMPATIBLE OPTIONS below.
318318
+
319319
See also INCOMPATIBLE OPTIONS below.
320320

321+
--skip-cherry-pick-detection::
322+
--no-skip-cherry-pick-detection::
323+
Whether rebase tries to determine if commits are already present
324+
upstream, i.e. if there are commits which are cherry-picks. If such
325+
detection is done, any commits being rebased which are cherry-picks
326+
will be dropped, since those commits are already found upstream. If
327+
such detection is not done, those commits will be re-applied, which
328+
most likely will result in no new changes (as the changes are already
329+
upstream) and result in the commit being dropped anyway. cherry-pick
330+
detection is the default, but can be expensive in repos with a large
331+
number of upstream commits that need to be read.
332+
+
333+
See also INCOMPATIBLE OPTIONS below.
334+
321335
--rerere-autoupdate::
322336
--no-rerere-autoupdate::
323337
Allow the rerere mechanism to update the index with the
@@ -568,6 +582,9 @@ In addition, the following pairs of options are incompatible:
568582
* --keep-base and --onto
569583
* --keep-base and --root
570584

585+
Also, the --skip-cherry-pick-detection option requires the use of the merge
586+
backend (e.g., through --merge).
587+
571588
BEHAVIORAL DIFFERENCES
572589
-----------------------
573590

@@ -866,7 +883,8 @@ Only works if the changes (patch IDs based on the diff contents) on
866883
'subsystem' did.
867884

868885
In that case, the fix is easy because 'git rebase' knows to skip
869-
changes that are already present in the new upstream. So if you say
886+
changes that are already present in the new upstream (unless
887+
`--skip-cherry-pick-detection` is given). So if you say
870888
(assuming you're on 'topic')
871889
------------
872890
$ git rebase subsystem

builtin/rebase.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ struct rebase_options {
8888
struct strbuf git_format_patch_opt;
8989
int reschedule_failed_exec;
9090
int use_legacy_rebase;
91+
int skip_cherry_pick_detection;
9192
};
9293

9394
#define REBASE_OPTIONS_INIT { \
@@ -381,6 +382,7 @@ static int run_rebase_interactive(struct rebase_options *opts,
381382
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
382383
flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
383384
flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
385+
flags |= opts->skip_cherry_pick_detection ? TODO_LIST_SKIP_CHERRY_PICK_DETECTION : 0;
384386

385387
switch (command) {
386388
case ACTION_NONE: {
@@ -1515,6 +1517,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
15151517
OPT_BOOL(0, "reschedule-failed-exec",
15161518
&reschedule_failed_exec,
15171519
N_("automatically re-schedule any `exec` that fails")),
1520+
OPT_BOOL(0, "skip-cherry-pick-detection", &options.skip_cherry_pick_detection,
1521+
N_("skip changes that are already present in the new upstream")),
15181522
OPT_END(),
15191523
};
15201524
int i;
@@ -1848,6 +1852,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
18481852
"interactive or merge options"));
18491853
}
18501854

1855+
if (options.skip_cherry_pick_detection && !is_interactive(&options))
1856+
die(_("--skip-cherry-pick-detection does not work with the 'apply' backend"));
1857+
18511858
if (options.signoff) {
18521859
if (options.type == REBASE_PRESERVE_MERGES)
18531860
die("cannot combine '--signoff' with "

sequencer.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4800,12 +4800,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
48004800
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
48014801
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
48024802
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
4803+
int skip_cherry_pick_detection = flags & TODO_LIST_SKIP_CHERRY_PICK_DETECTION;
48034804

48044805
repo_init_revisions(r, &revs, NULL);
48054806
revs.verbose_header = 1;
48064807
if (!rebase_merges)
48074808
revs.max_parents = 1;
4808-
revs.cherry_mark = 1;
4809+
revs.cherry_mark = !skip_cherry_pick_detection;
48094810
revs.limited = 1;
48104811
revs.reverse = 1;
48114812
revs.right_only = 1;

sequencer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ int sequencer_remove_state(struct replay_opts *opts);
148148
* `--onto`, we do not want to re-generate the root commits.
149149
*/
150150
#define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
151-
151+
#define TODO_LIST_SKIP_CHERRY_PICK_DETECTION (1U << 7)
152152

153153
int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
154154
const char **argv, unsigned flags);

t/t3402-rebase-merge.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,81 @@ test_expect_success 'rebase --skip works with two conflicts in a row' '
162162
git rebase --skip
163163
'
164164

165+
test_expect_success '--skip-cherry-pick-detection' '
166+
git init repo &&
167+
168+
# O(1-10) -- O(1-11) -- O(0-10) master
169+
# \
170+
# -- O(1-11) -- O(1-12) otherbranch
171+
172+
printf "Line %d\n" $(test_seq 1 10) >repo/file.txt &&
173+
git -C repo add file.txt &&
174+
git -C repo commit -m "base commit" &&
175+
176+
printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
177+
git -C repo commit -a -m "add 11" &&
178+
179+
printf "Line %d\n" $(test_seq 0 10) >repo/file.txt &&
180+
git -C repo commit -a -m "add 0 delete 11" &&
181+
182+
git -C repo checkout -b otherbranch HEAD^^ &&
183+
printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
184+
git -C repo commit -a -m "add 11 in another branch" &&
185+
186+
printf "Line %d\n" $(test_seq 1 12) >repo/file.txt &&
187+
git -C repo commit -a -m "add 12 in another branch" &&
188+
189+
# Regular rebase fails, because the 1-11 commit is deduplicated
190+
test_must_fail git -C repo rebase --merge master 2> err &&
191+
test_i18ngrep "error: could not apply.*add 12 in another branch" err &&
192+
git -C repo rebase --abort &&
193+
194+
# With --skip-cherry-pick-detection, it works
195+
git -C repo rebase --merge --skip-cherry-pick-detection master
196+
'
197+
198+
test_expect_success '--skip-cherry-pick-detection refrains from reading unneeded blobs' '
199+
git init server &&
200+
201+
# O(1-10) -- O(1-11) -- O(1-12) master
202+
# \
203+
# -- O(0-10) otherbranch
204+
205+
printf "Line %d\n" $(test_seq 1 10) >server/file.txt &&
206+
git -C server add file.txt &&
207+
git -C server commit -m "merge base" &&
208+
209+
printf "Line %d\n" $(test_seq 1 11) >server/file.txt &&
210+
git -C server commit -a -m "add 11" &&
211+
212+
printf "Line %d\n" $(test_seq 1 12) >server/file.txt &&
213+
git -C server commit -a -m "add 12" &&
214+
215+
git -C server checkout -b otherbranch HEAD^^ &&
216+
printf "Line %d\n" $(test_seq 0 10) >server/file.txt &&
217+
git -C server commit -a -m "add 0" &&
218+
219+
test_config -C server uploadpack.allowfilter 1 &&
220+
test_config -C server uploadpack.allowanysha1inwant 1 &&
221+
222+
git clone --filter=blob:none "file://$(pwd)/server" client &&
223+
git -C client checkout origin/master &&
224+
git -C client checkout origin/otherbranch &&
225+
226+
# Sanity check to ensure that the blobs from the merge base and "add
227+
# 11" are missing
228+
git -C client rev-list --objects --all --missing=print >missing_list &&
229+
MERGE_BASE_BLOB=$(git -C server rev-parse master^^:file.txt) &&
230+
ADD_11_BLOB=$(git -C server rev-parse master^:file.txt) &&
231+
grep "\\?$MERGE_BASE_BLOB" missing_list &&
232+
grep "\\?$ADD_11_BLOB" missing_list &&
233+
234+
git -C client rebase --merge --skip-cherry-pick-detection origin/master &&
235+
236+
# The blob from the merge base had to be fetched, but not "add 11"
237+
git -C client rev-list --objects --all --missing=print >missing_list &&
238+
! grep "\\?$MERGE_BASE_BLOB" missing_list &&
239+
grep "\\?$ADD_11_BLOB" missing_list
240+
'
241+
165242
test_done

0 commit comments

Comments
 (0)