@matheusbsbs / matheus-bagliano / commits / 0fcb4f6b62
Fork 0

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>

Jonathan Tan committed Apr 11, 2020 at 02:44 UTC 0fcb4f6b62c87a73971890070dea847b56176338
5 files changed +110 -4
Documentation/git-rebase.txt
+23 -2
@@ -279,7 +279,8 @@ See also INCOMPATIBLE OPTIONS below.
279 +
280 Note that commits which start empty are kept (unless --no-keep-empty
281 is specified), and commits which are clean cherry-picks (as determined
282 -by `git log --cherry-mark ...`) are always dropped.
282 +by `git log --cherry-mark ...`) are detected and dropped as a
283 +preliminary step (unless --reapply-cherry-picks is passed).
284 +
285 See also INCOMPATIBLE OPTIONS below.
286
@@ -304,6 +305,24 @@ see the --empty flag.
305 +
306 See also INCOMPATIBLE OPTIONS below.
307
308 +--reapply-cherry-picks::
309 +--no-reapply-cherry-picks::
310 + Reapply all clean cherry-picks of any upstream commit instead
311 + of preemptively dropping them. (If these commits then become
312 + empty after rebasing, because they contain a subset of already
313 + upstream changes, the behavior towards them is controlled by
314 + the `--empty` flag.)
315 ++
316 +By default (or if `--no-reapply-cherry-picks` is given), these commits
317 +will be automatically dropped. Because this necessitates reading all
318 +upstream commits, this can be expensive in repos with a large number
319 +of upstream commits that need to be read.
320 ++
321 +`--reapply-cherry-picks` allows rebase to forgo reading all upstream
322 +commits, potentially improving performance.
323 ++
324 +See also INCOMPATIBLE OPTIONS below.
325 +
326 --allow-empty-message::
327 No-op. Rebasing commits with an empty message used to fail
328 and this option would override that behavior, allowing commits
@@ -601,6 +620,7 @@ are incompatible with the following options:
620 * --exec
621 * --no-keep-empty
622 * --empty=
623 + * --reapply-cherry-picks
624 * --edit-todo
625 * --root when used in combination with --onto
626
@@ -1017,7 +1037,8 @@ Only works if the changes (patch IDs based on the diff contents) on
1037 'subsystem' did.
1038
1039 In that case, the fix is easy because 'git rebase' knows to skip
1020 -changes that are already present in the new upstream. So if you say
1040 +changes that are already present in the new upstream (unless
1041 +`--reapply-cherry-picks` is given). So if you say
1042 (assuming you're on 'topic')
1043 ------------
1044 $ git rebase subsystem
builtin/rebase.c
+7
@@ -96,6 +96,7 @@ struct rebase_options {
96 struct strbuf git_format_patch_opt;
97 int reschedule_failed_exec;
98 int use_legacy_rebase;
99 + int reapply_cherry_picks;
100 };
101
102 #define REBASE_OPTIONS_INIT { \
@@ -387,6 +388,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
388 flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
389 flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
390 flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
391 + flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
392
393 switch (command) {
394 case ACTION_NONE: {
@@ -1585,6 +1587,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1587 OPT_BOOL(0, "reschedule-failed-exec",
1588 &reschedule_failed_exec,
1589 N_("automatically re-schedule any `exec` that fails")),
1590 + OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks,
1591 + N_("apply all changes, even those already present upstream")),
1592 OPT_END(),
1593 };
1594 int i;
@@ -1825,6 +1829,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1829 if (options.empty != EMPTY_UNSPECIFIED)
1830 imply_merge(&options, "--empty");
1831
1832 + if (options.reapply_cherry_picks)
1833 + imply_merge(&options, "--reapply-cherry-picks");
1834 +
1835 if (gpg_sign) {
1836 free(options.gpg_sign_opt);
1837 options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
sequencer.c
+2 -1
@@ -4828,12 +4828,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
4828 int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
4829 const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
4830 int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
4831 + int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
4832
4833 repo_init_revisions(r, &revs, NULL);
4834 revs.verbose_header = 1;
4835 if (!rebase_merges)
4836 revs.max_parents = 1;
4836 - revs.cherry_mark = 1;
4837 + revs.cherry_mark = !reapply_cherry_picks;
4838 revs.limited = 1;
4839 revs.reverse = 1;
4840 revs.right_only = 1;
sequencer.h
+1 -1
@@ -150,7 +150,7 @@ int sequencer_remove_state(struct replay_opts *opts);
150 * `--onto`, we do not want to re-generate the root commits.
151 */
152 #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
153 -
153 +#define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
154
155 int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
156 const char **argv, unsigned flags);
t/t3402-rebase-merge.sh
+77
@@ -162,4 +162,81 @@ test_expect_success 'rebase --skip works with two conflicts in a row' '
162 git rebase --skip
163 '
164
165 +test_expect_success '--reapply-cherry-picks' '
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 --reapply-cherry-picks, it works
195 + git -C repo rebase --merge --reapply-cherry-picks master
196 +'
197 +
198 +test_expect_success '--reapply-cherry-picks 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 --reapply-cherry-picks 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 +
242 test_done