Git fork

Merge branch 'cc/git-replay'

Introduce "git replay", a tool meant on the server side without
working tree to recreate a history.

* cc/git-replay:
replay: stop assuming replayed branches do not diverge
replay: add --contained to rebase contained branches
replay: add --advance or 'cherry-pick' mode
replay: use standard revision ranges
replay: make it a minimal server side command
replay: remove HEAD related sanity check
replay: remove progress and info output
replay: add an important FIXME comment about gpg signing
replay: change rev walking options
replay: introduce pick_regular_commit()
replay: die() instead of failing assert()
replay: start using parse_options API
replay: introduce new builtin
t6429: remove switching aspects of fast-rebase

+801 -264
+1
.gitignore
··· 135 /git-remote-ext 136 /git-repack 137 /git-replace 138 /git-request-pull 139 /git-rerere 140 /git-reset
··· 135 /git-remote-ext 136 /git-repack 137 /git-replace 138 + /git-replay 139 /git-request-pull 140 /git-rerere 141 /git-reset
+127
Documentation/git-replay.txt
···
··· 1 + git-replay(1) 2 + ============= 3 + 4 + NAME 5 + ---- 6 + git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too 7 + 8 + 9 + SYNOPSIS 10 + -------- 11 + [verse] 12 + (EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... 13 + 14 + DESCRIPTION 15 + ----------- 16 + 17 + Takes ranges of commits and replays them onto a new location. Leaves 18 + the working tree and the index untouched, and updates no references. 19 + The output of this command is meant to be used as input to 20 + `git update-ref --stdin`, which would update the relevant branches 21 + (see the OUTPUT section below). 22 + 23 + THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. 24 + 25 + OPTIONS 26 + ------- 27 + 28 + --onto <newbase>:: 29 + Starting point at which to create the new commits. May be any 30 + valid commit, and not just an existing branch name. 31 + + 32 + When `--onto` is specified, the update-ref command(s) in the output will 33 + update the branch(es) in the revision range to point at the new 34 + commits, similar to the way how `git rebase --update-refs` updates 35 + multiple branches in the affected range. 36 + 37 + --advance <branch>:: 38 + Starting point at which to create the new commits; must be a 39 + branch name. 40 + + 41 + When `--advance` is specified, the update-ref command(s) in the output 42 + will update the branch passed as an argument to `--advance` to point at 43 + the new commits (in other words, this mimics a cherry-pick operation). 44 + 45 + <revision-range>:: 46 + Range of commits to replay. More than one <revision-range> can 47 + be passed, but in `--advance <branch>` mode, they should have 48 + a single tip, so that it's clear where <branch> should point 49 + to. See "Specifying Ranges" in linkgit:git-rev-parse and the 50 + "Commit Limiting" options below. 51 + 52 + include::rev-list-options.txt[] 53 + 54 + OUTPUT 55 + ------ 56 + 57 + When there are no conflicts, the output of this command is usable as 58 + input to `git update-ref --stdin`. It is of the form: 59 + 60 + update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH} 61 + update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH} 62 + update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH} 63 + 64 + where the number of refs updated depends on the arguments passed and 65 + the shape of the history being replayed. When using `--advance`, the 66 + number of refs updated is always one, but for `--onto`, it can be one 67 + or more (rebasing multiple branches simultaneously is supported). 68 + 69 + EXIT STATUS 70 + ----------- 71 + 72 + For a successful, non-conflicted replay, the exit status is 0. When 73 + the replay has conflicts, the exit status is 1. If the replay is not 74 + able to complete (or start) due to some kind of error, the exit status 75 + is something other than 0 or 1. 76 + 77 + EXAMPLES 78 + -------- 79 + 80 + To simply rebase `mybranch` onto `target`: 81 + 82 + ------------ 83 + $ git replay --onto target origin/main..mybranch 84 + update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH} 85 + ------------ 86 + 87 + To cherry-pick the commits from mybranch onto target: 88 + 89 + ------------ 90 + $ git replay --advance target origin/main..mybranch 91 + update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH} 92 + ------------ 93 + 94 + Note that the first two examples replay the exact same commits and on 95 + top of the exact same new base, they only differ in that the first 96 + provides instructions to make mybranch point at the new commits and 97 + the second provides instructions to make target point at them. 98 + 99 + What if you have a stack of branches, one depending upon another, and 100 + you'd really like to rebase the whole set? 101 + 102 + ------------ 103 + $ git replay --contained --onto origin/main origin/main..tipbranch 104 + update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH} 105 + update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH} 106 + update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH} 107 + ------------ 108 + 109 + When calling `git replay`, one does not need to specify a range of 110 + commits to replay using the syntax `A..B`; any range expression will 111 + do: 112 + 113 + ------------ 114 + $ git replay --onto origin/main ^base branch1 branch2 branch3 115 + update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH} 116 + update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH} 117 + update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH} 118 + ------------ 119 + 120 + This will simultaneously rebase `branch1`, `branch2`, and `branch3`, 121 + all commits they have since `base`, playing them on top of 122 + `origin/main`. These three branches may have commits on top of `base` 123 + that they have in common, but that does not need to be the case. 124 + 125 + GIT 126 + --- 127 + Part of the linkgit:git[1] suite
+1 -1
Makefile
··· 803 TEST_BUILTINS_OBJS += test-dump-untracked-cache.o 804 TEST_BUILTINS_OBJS += test-env-helper.o 805 TEST_BUILTINS_OBJS += test-example-decorate.o 806 - TEST_BUILTINS_OBJS += test-fast-rebase.o 807 TEST_BUILTINS_OBJS += test-find-pack.o 808 TEST_BUILTINS_OBJS += test-fsmonitor-client.o 809 TEST_BUILTINS_OBJS += test-genrandom.o ··· 1294 BUILTIN_OBJS += builtin/remote.o 1295 BUILTIN_OBJS += builtin/repack.o 1296 BUILTIN_OBJS += builtin/replace.o 1297 BUILTIN_OBJS += builtin/rerere.o 1298 BUILTIN_OBJS += builtin/reset.o 1299 BUILTIN_OBJS += builtin/rev-list.o
··· 803 TEST_BUILTINS_OBJS += test-dump-untracked-cache.o 804 TEST_BUILTINS_OBJS += test-env-helper.o 805 TEST_BUILTINS_OBJS += test-example-decorate.o 806 TEST_BUILTINS_OBJS += test-find-pack.o 807 TEST_BUILTINS_OBJS += test-fsmonitor-client.o 808 TEST_BUILTINS_OBJS += test-genrandom.o ··· 1293 BUILTIN_OBJS += builtin/remote.o 1294 BUILTIN_OBJS += builtin/repack.o 1295 BUILTIN_OBJS += builtin/replace.o 1296 + BUILTIN_OBJS += builtin/replay.o 1297 BUILTIN_OBJS += builtin/rerere.o 1298 BUILTIN_OBJS += builtin/reset.o 1299 BUILTIN_OBJS += builtin/rev-list.o
+1
builtin.h
··· 211 int cmd_remote_ext(int argc, const char **argv, const char *prefix); 212 int cmd_remote_fd(int argc, const char **argv, const char *prefix); 213 int cmd_repack(int argc, const char **argv, const char *prefix); 214 int cmd_rerere(int argc, const char **argv, const char *prefix); 215 int cmd_reset(int argc, const char **argv, const char *prefix); 216 int cmd_restore(int argc, const char **argv, const char *prefix);
··· 211 int cmd_remote_ext(int argc, const char **argv, const char *prefix); 212 int cmd_remote_fd(int argc, const char **argv, const char *prefix); 213 int cmd_repack(int argc, const char **argv, const char *prefix); 214 + int cmd_replay(int argc, const char **argv, const char *prefix); 215 int cmd_rerere(int argc, const char **argv, const char *prefix); 216 int cmd_reset(int argc, const char **argv, const char *prefix); 217 int cmd_restore(int argc, const char **argv, const char *prefix);
+446
builtin/replay.c
···
··· 1 + /* 2 + * "git replay" builtin command 3 + */ 4 + 5 + #define USE_THE_INDEX_VARIABLE 6 + #include "git-compat-util.h" 7 + 8 + #include "builtin.h" 9 + #include "environment.h" 10 + #include "hex.h" 11 + #include "lockfile.h" 12 + #include "merge-ort.h" 13 + #include "object-name.h" 14 + #include "parse-options.h" 15 + #include "refs.h" 16 + #include "revision.h" 17 + #include "strmap.h" 18 + #include <oidset.h> 19 + #include <tree.h> 20 + 21 + static const char *short_commit_name(struct commit *commit) 22 + { 23 + return repo_find_unique_abbrev(the_repository, &commit->object.oid, 24 + DEFAULT_ABBREV); 25 + } 26 + 27 + static struct commit *peel_committish(const char *name) 28 + { 29 + struct object *obj; 30 + struct object_id oid; 31 + 32 + if (repo_get_oid(the_repository, name, &oid)) 33 + return NULL; 34 + obj = parse_object(the_repository, &oid); 35 + return (struct commit *)repo_peel_to_type(the_repository, name, 0, obj, 36 + OBJ_COMMIT); 37 + } 38 + 39 + static char *get_author(const char *message) 40 + { 41 + size_t len; 42 + const char *a; 43 + 44 + a = find_commit_header(message, "author", &len); 45 + if (a) 46 + return xmemdupz(a, len); 47 + 48 + return NULL; 49 + } 50 + 51 + static struct commit *create_commit(struct tree *tree, 52 + struct commit *based_on, 53 + struct commit *parent) 54 + { 55 + struct object_id ret; 56 + struct object *obj; 57 + struct commit_list *parents = NULL; 58 + char *author; 59 + char *sign_commit = NULL; /* FIXME: cli users might want to sign again */ 60 + struct commit_extra_header *extra; 61 + struct strbuf msg = STRBUF_INIT; 62 + const char *out_enc = get_commit_output_encoding(); 63 + const char *message = repo_logmsg_reencode(the_repository, based_on, 64 + NULL, out_enc); 65 + const char *orig_message = NULL; 66 + const char *exclude_gpgsig[] = { "gpgsig", NULL }; 67 + 68 + commit_list_insert(parent, &parents); 69 + extra = read_commit_extra_headers(based_on, exclude_gpgsig); 70 + find_commit_subject(message, &orig_message); 71 + strbuf_addstr(&msg, orig_message); 72 + author = get_author(message); 73 + reset_ident_date(); 74 + if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents, 75 + &ret, author, NULL, sign_commit, extra)) { 76 + error(_("failed to write commit object")); 77 + return NULL; 78 + } 79 + free(author); 80 + strbuf_release(&msg); 81 + 82 + obj = parse_object(the_repository, &ret); 83 + return (struct commit *)obj; 84 + } 85 + 86 + struct ref_info { 87 + struct commit *onto; 88 + struct strset positive_refs; 89 + struct strset negative_refs; 90 + int positive_refexprs; 91 + int negative_refexprs; 92 + }; 93 + 94 + static void get_ref_information(struct rev_cmdline_info *cmd_info, 95 + struct ref_info *ref_info) 96 + { 97 + int i; 98 + 99 + ref_info->onto = NULL; 100 + strset_init(&ref_info->positive_refs); 101 + strset_init(&ref_info->negative_refs); 102 + ref_info->positive_refexprs = 0; 103 + ref_info->negative_refexprs = 0; 104 + 105 + /* 106 + * When the user specifies e.g. 107 + * git replay origin/main..mybranch 108 + * git replay ^origin/next mybranch1 mybranch2 109 + * we want to be able to determine where to replay the commits. In 110 + * these examples, the branches are probably based on an old version 111 + * of either origin/main or origin/next, so we want to replay on the 112 + * newest version of that branch. In contrast we would want to error 113 + * out if they ran 114 + * git replay ^origin/master ^origin/next mybranch 115 + * git replay mybranch~2..mybranch 116 + * the first of those because there's no unique base to choose, and 117 + * the second because they'd likely just be replaying commits on top 118 + * of the same commit and not making any difference. 119 + */ 120 + for (i = 0; i < cmd_info->nr; i++) { 121 + struct rev_cmdline_entry *e = cmd_info->rev + i; 122 + struct object_id oid; 123 + const char *refexpr = e->name; 124 + char *fullname = NULL; 125 + int can_uniquely_dwim = 1; 126 + 127 + if (*refexpr == '^') 128 + refexpr++; 129 + if (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1) 130 + can_uniquely_dwim = 0; 131 + 132 + if (e->flags & BOTTOM) { 133 + if (can_uniquely_dwim) 134 + strset_add(&ref_info->negative_refs, fullname); 135 + if (!ref_info->negative_refexprs) 136 + ref_info->onto = lookup_commit_reference_gently(the_repository, 137 + &e->item->oid, 1); 138 + ref_info->negative_refexprs++; 139 + } else { 140 + if (can_uniquely_dwim) 141 + strset_add(&ref_info->positive_refs, fullname); 142 + ref_info->positive_refexprs++; 143 + } 144 + 145 + free(fullname); 146 + } 147 + } 148 + 149 + static void determine_replay_mode(struct rev_cmdline_info *cmd_info, 150 + const char *onto_name, 151 + const char **advance_name, 152 + struct commit **onto, 153 + struct strset **update_refs) 154 + { 155 + struct ref_info rinfo; 156 + 157 + get_ref_information(cmd_info, &rinfo); 158 + if (!rinfo.positive_refexprs) 159 + die(_("need some commits to replay")); 160 + if (onto_name && *advance_name) 161 + die(_("--onto and --advance are incompatible")); 162 + else if (onto_name) { 163 + *onto = peel_committish(onto_name); 164 + if (rinfo.positive_refexprs < 165 + strset_get_size(&rinfo.positive_refs)) 166 + die(_("all positive revisions given must be references")); 167 + } else if (*advance_name) { 168 + struct object_id oid; 169 + char *fullname = NULL; 170 + 171 + *onto = peel_committish(*advance_name); 172 + if (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name), 173 + &oid, &fullname, 0) == 1) { 174 + *advance_name = fullname; 175 + } else { 176 + die(_("argument to --advance must be a reference")); 177 + } 178 + if (rinfo.positive_refexprs > 1) 179 + die(_("cannot advance target with multiple sources because ordering would be ill-defined")); 180 + } else { 181 + int positive_refs_complete = ( 182 + rinfo.positive_refexprs == 183 + strset_get_size(&rinfo.positive_refs)); 184 + int negative_refs_complete = ( 185 + rinfo.negative_refexprs == 186 + strset_get_size(&rinfo.negative_refs)); 187 + /* 188 + * We need either positive_refs_complete or 189 + * negative_refs_complete, but not both. 190 + */ 191 + if (rinfo.negative_refexprs > 0 && 192 + positive_refs_complete == negative_refs_complete) 193 + die(_("cannot implicitly determine whether this is an --advance or --onto operation")); 194 + if (negative_refs_complete) { 195 + struct hashmap_iter iter; 196 + struct strmap_entry *entry; 197 + 198 + if (rinfo.negative_refexprs == 0) 199 + die(_("all positive revisions given must be references")); 200 + else if (rinfo.negative_refexprs > 1) 201 + die(_("cannot implicitly determine whether this is an --advance or --onto operation")); 202 + else if (rinfo.positive_refexprs > 1) 203 + die(_("cannot advance target with multiple source branches because ordering would be ill-defined")); 204 + 205 + /* Only one entry, but we have to loop to get it */ 206 + strset_for_each_entry(&rinfo.negative_refs, 207 + &iter, entry) { 208 + *advance_name = entry->key; 209 + } 210 + } else { /* positive_refs_complete */ 211 + if (rinfo.negative_refexprs > 1) 212 + die(_("cannot implicitly determine correct base for --onto")); 213 + if (rinfo.negative_refexprs == 1) 214 + *onto = rinfo.onto; 215 + } 216 + } 217 + if (!*advance_name) { 218 + *update_refs = xcalloc(1, sizeof(**update_refs)); 219 + **update_refs = rinfo.positive_refs; 220 + memset(&rinfo.positive_refs, 0, sizeof(**update_refs)); 221 + } 222 + strset_clear(&rinfo.negative_refs); 223 + strset_clear(&rinfo.positive_refs); 224 + } 225 + 226 + static struct commit *mapped_commit(kh_oid_map_t *replayed_commits, 227 + struct commit *commit, 228 + struct commit *fallback) 229 + { 230 + khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid); 231 + if (pos == kh_end(replayed_commits)) 232 + return fallback; 233 + return kh_value(replayed_commits, pos); 234 + } 235 + 236 + static struct commit *pick_regular_commit(struct commit *pickme, 237 + kh_oid_map_t *replayed_commits, 238 + struct commit *onto, 239 + struct merge_options *merge_opt, 240 + struct merge_result *result) 241 + { 242 + struct commit *base, *replayed_base; 243 + struct tree *pickme_tree, *base_tree; 244 + 245 + base = pickme->parents->item; 246 + replayed_base = mapped_commit(replayed_commits, base, onto); 247 + 248 + result->tree = repo_get_commit_tree(the_repository, replayed_base); 249 + pickme_tree = repo_get_commit_tree(the_repository, pickme); 250 + base_tree = repo_get_commit_tree(the_repository, base); 251 + 252 + merge_opt->branch1 = short_commit_name(replayed_base); 253 + merge_opt->branch2 = short_commit_name(pickme); 254 + merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2); 255 + 256 + merge_incore_nonrecursive(merge_opt, 257 + base_tree, 258 + result->tree, 259 + pickme_tree, 260 + result); 261 + 262 + free((char*)merge_opt->ancestor); 263 + merge_opt->ancestor = NULL; 264 + if (!result->clean) 265 + return NULL; 266 + return create_commit(result->tree, pickme, replayed_base); 267 + } 268 + 269 + int cmd_replay(int argc, const char **argv, const char *prefix) 270 + { 271 + const char *advance_name = NULL; 272 + struct commit *onto = NULL; 273 + const char *onto_name = NULL; 274 + int contained = 0; 275 + 276 + struct rev_info revs; 277 + struct commit *last_commit = NULL; 278 + struct commit *commit; 279 + struct merge_options merge_opt; 280 + struct merge_result result; 281 + struct strset *update_refs = NULL; 282 + kh_oid_map_t *replayed_commits; 283 + int ret = 0; 284 + 285 + const char * const replay_usage[] = { 286 + N_("(EXPERIMENTAL!) git replay " 287 + "([--contained] --onto <newbase> | --advance <branch>) " 288 + "<revision-range>..."), 289 + NULL 290 + }; 291 + struct option replay_options[] = { 292 + OPT_STRING(0, "advance", &advance_name, 293 + N_("branch"), 294 + N_("make replay advance given branch")), 295 + OPT_STRING(0, "onto", &onto_name, 296 + N_("revision"), 297 + N_("replay onto given commit")), 298 + OPT_BOOL(0, "contained", &contained, 299 + N_("advance all branches contained in revision-range")), 300 + OPT_END() 301 + }; 302 + 303 + argc = parse_options(argc, argv, prefix, replay_options, replay_usage, 304 + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); 305 + 306 + if (!onto_name && !advance_name) { 307 + error(_("option --onto or --advance is mandatory")); 308 + usage_with_options(replay_usage, replay_options); 309 + } 310 + 311 + if (advance_name && contained) 312 + die(_("options '%s' and '%s' cannot be used together"), 313 + "--advance", "--contained"); 314 + 315 + repo_init_revisions(the_repository, &revs, prefix); 316 + 317 + /* 318 + * Set desired values for rev walking options here. If they 319 + * are changed by some user specified option in setup_revisions() 320 + * below, we will detect that below and then warn. 321 + * 322 + * TODO: In the future we might want to either die(), or allow 323 + * some options changing these values if we think they could 324 + * be useful. 325 + */ 326 + revs.reverse = 1; 327 + revs.sort_order = REV_SORT_IN_GRAPH_ORDER; 328 + revs.topo_order = 1; 329 + revs.simplify_history = 0; 330 + 331 + argc = setup_revisions(argc, argv, &revs, NULL); 332 + if (argc > 1) { 333 + ret = error(_("unrecognized argument: %s"), argv[1]); 334 + goto cleanup; 335 + } 336 + 337 + /* 338 + * Detect and warn if we override some user specified rev 339 + * walking options. 340 + */ 341 + if (revs.reverse != 1) { 342 + warning(_("some rev walking options will be overridden as " 343 + "'%s' bit in 'struct rev_info' will be forced"), 344 + "reverse"); 345 + revs.reverse = 1; 346 + } 347 + if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) { 348 + warning(_("some rev walking options will be overridden as " 349 + "'%s' bit in 'struct rev_info' will be forced"), 350 + "sort_order"); 351 + revs.sort_order = REV_SORT_IN_GRAPH_ORDER; 352 + } 353 + if (revs.topo_order != 1) { 354 + warning(_("some rev walking options will be overridden as " 355 + "'%s' bit in 'struct rev_info' will be forced"), 356 + "topo_order"); 357 + revs.topo_order = 1; 358 + } 359 + if (revs.simplify_history != 0) { 360 + warning(_("some rev walking options will be overridden as " 361 + "'%s' bit in 'struct rev_info' will be forced"), 362 + "simplify_history"); 363 + revs.simplify_history = 0; 364 + } 365 + 366 + determine_replay_mode(&revs.cmdline, onto_name, &advance_name, 367 + &onto, &update_refs); 368 + 369 + if (!onto) /* FIXME: Should handle replaying down to root commit */ 370 + die("Replaying down to root commit is not supported yet!"); 371 + 372 + if (prepare_revision_walk(&revs) < 0) { 373 + ret = error(_("error preparing revisions")); 374 + goto cleanup; 375 + } 376 + 377 + init_merge_options(&merge_opt, the_repository); 378 + memset(&result, 0, sizeof(result)); 379 + merge_opt.show_rename_progress = 0; 380 + last_commit = onto; 381 + replayed_commits = kh_init_oid_map(); 382 + while ((commit = get_revision(&revs))) { 383 + const struct name_decoration *decoration; 384 + khint_t pos; 385 + int hr; 386 + 387 + if (!commit->parents) 388 + die(_("replaying down to root commit is not supported yet!")); 389 + if (commit->parents->next) 390 + die(_("replaying merge commits is not supported yet!")); 391 + 392 + last_commit = pick_regular_commit(commit, replayed_commits, onto, 393 + &merge_opt, &result); 394 + if (!last_commit) 395 + break; 396 + 397 + /* Record commit -> last_commit mapping */ 398 + pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr); 399 + if (hr == 0) 400 + BUG("Duplicate rewritten commit: %s\n", 401 + oid_to_hex(&commit->object.oid)); 402 + kh_value(replayed_commits, pos) = last_commit; 403 + 404 + /* Update any necessary branches */ 405 + if (advance_name) 406 + continue; 407 + decoration = get_name_decoration(&commit->object); 408 + if (!decoration) 409 + continue; 410 + while (decoration) { 411 + if (decoration->type == DECORATION_REF_LOCAL && 412 + (contained || strset_contains(update_refs, 413 + decoration->name))) { 414 + printf("update %s %s %s\n", 415 + decoration->name, 416 + oid_to_hex(&last_commit->object.oid), 417 + oid_to_hex(&commit->object.oid)); 418 + } 419 + decoration = decoration->next; 420 + } 421 + } 422 + 423 + /* In --advance mode, advance the target ref */ 424 + if (result.clean == 1 && advance_name) { 425 + printf("update %s %s %s\n", 426 + advance_name, 427 + oid_to_hex(&last_commit->object.oid), 428 + oid_to_hex(&onto->object.oid)); 429 + } 430 + 431 + merge_finalize(&merge_opt, &result); 432 + kh_destroy_oid_map(replayed_commits); 433 + if (update_refs) { 434 + strset_clear(update_refs); 435 + free(update_refs); 436 + } 437 + ret = result.clean; 438 + 439 + cleanup: 440 + release_revisions(&revs); 441 + 442 + /* Return */ 443 + if (ret < 0) 444 + exit(128); 445 + return ret ? 0 : 1; 446 + }
+1
command-list.txt
··· 160 git-remote ancillarymanipulators complete 161 git-repack ancillarymanipulators complete 162 git-replace ancillarymanipulators complete 163 git-request-pull foreignscminterface complete 164 git-rerere ancillaryinterrogators 165 git-reset mainporcelain history
··· 160 git-remote ancillarymanipulators complete 161 git-repack ancillarymanipulators complete 162 git-replace ancillarymanipulators complete 163 + git-replay plumbingmanipulators 164 git-request-pull foreignscminterface complete 165 git-rerere ancillaryinterrogators 166 git-reset mainporcelain history
+1
git.c
··· 594 { "remote-fd", cmd_remote_fd, NO_PARSEOPT }, 595 { "repack", cmd_repack, RUN_SETUP }, 596 { "replace", cmd_replace, RUN_SETUP }, 597 { "rerere", cmd_rerere, RUN_SETUP }, 598 { "reset", cmd_reset, RUN_SETUP }, 599 { "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE },
··· 594 { "remote-fd", cmd_remote_fd, NO_PARSEOPT }, 595 { "repack", cmd_repack, RUN_SETUP }, 596 { "replace", cmd_replace, RUN_SETUP }, 597 + { "replay", cmd_replay, RUN_SETUP }, 598 { "rerere", cmd_rerere, RUN_SETUP }, 599 { "reset", cmd_reset, RUN_SETUP }, 600 { "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE },
-241
t/helper/test-fast-rebase.c
··· 1 - /* 2 - * "git fast-rebase" builtin command 3 - * 4 - * FAST: Forking Any Subprocesses (is) Taboo 5 - * 6 - * This is meant SOLELY as a demo of what is possible. sequencer.c and 7 - * rebase.c should be refactored to use the ideas here, rather than attempting 8 - * to extend this file to replace those (unless Phillip or Dscho say that 9 - * refactoring is too hard and we need a clean slate, but I'm guessing that 10 - * refactoring is the better route). 11 - */ 12 - 13 - #define USE_THE_INDEX_VARIABLE 14 - #include "test-tool.h" 15 - #include "cache-tree.h" 16 - #include "commit.h" 17 - #include "environment.h" 18 - #include "gettext.h" 19 - #include "hash.h" 20 - #include "hex.h" 21 - #include "lockfile.h" 22 - #include "merge-ort.h" 23 - #include "object-name.h" 24 - #include "read-cache-ll.h" 25 - #include "refs.h" 26 - #include "revision.h" 27 - #include "sequencer.h" 28 - #include "setup.h" 29 - #include "strvec.h" 30 - #include "tree.h" 31 - 32 - static const char *short_commit_name(struct commit *commit) 33 - { 34 - return repo_find_unique_abbrev(the_repository, &commit->object.oid, 35 - DEFAULT_ABBREV); 36 - } 37 - 38 - static struct commit *peel_committish(const char *name) 39 - { 40 - struct object *obj; 41 - struct object_id oid; 42 - 43 - if (repo_get_oid(the_repository, name, &oid)) 44 - return NULL; 45 - obj = parse_object(the_repository, &oid); 46 - return (struct commit *)repo_peel_to_type(the_repository, name, 0, obj, 47 - OBJ_COMMIT); 48 - } 49 - 50 - static char *get_author(const char *message) 51 - { 52 - size_t len; 53 - const char *a; 54 - 55 - a = find_commit_header(message, "author", &len); 56 - if (a) 57 - return xmemdupz(a, len); 58 - 59 - return NULL; 60 - } 61 - 62 - static struct commit *create_commit(struct tree *tree, 63 - struct commit *based_on, 64 - struct commit *parent) 65 - { 66 - struct object_id ret; 67 - struct object *obj; 68 - struct commit_list *parents = NULL; 69 - char *author; 70 - char *sign_commit = NULL; 71 - struct commit_extra_header *extra; 72 - struct strbuf msg = STRBUF_INIT; 73 - const char *out_enc = get_commit_output_encoding(); 74 - const char *message = repo_logmsg_reencode(the_repository, based_on, 75 - NULL, out_enc); 76 - const char *orig_message = NULL; 77 - const char *exclude_gpgsig[] = { "gpgsig", NULL }; 78 - 79 - commit_list_insert(parent, &parents); 80 - extra = read_commit_extra_headers(based_on, exclude_gpgsig); 81 - find_commit_subject(message, &orig_message); 82 - strbuf_addstr(&msg, orig_message); 83 - author = get_author(message); 84 - reset_ident_date(); 85 - if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents, 86 - &ret, author, NULL, sign_commit, extra)) { 87 - error(_("failed to write commit object")); 88 - return NULL; 89 - } 90 - free(author); 91 - strbuf_release(&msg); 92 - 93 - obj = parse_object(the_repository, &ret); 94 - return (struct commit *)obj; 95 - } 96 - 97 - int cmd__fast_rebase(int argc, const char **argv) 98 - { 99 - struct commit *onto; 100 - struct commit *last_commit = NULL, *last_picked_commit = NULL; 101 - struct object_id head; 102 - struct lock_file lock = LOCK_INIT; 103 - struct strvec rev_walk_args = STRVEC_INIT; 104 - struct rev_info revs; 105 - struct commit *commit; 106 - struct merge_options merge_opt; 107 - struct tree *next_tree, *base_tree, *head_tree; 108 - struct merge_result result; 109 - struct strbuf reflog_msg = STRBUF_INIT; 110 - struct strbuf branch_name = STRBUF_INIT; 111 - int ret = 0; 112 - 113 - /* 114 - * test-tool stuff doesn't set up the git directory by default; need to 115 - * do that manually. 116 - */ 117 - setup_git_directory(); 118 - 119 - if (argc == 2 && !strcmp(argv[1], "-h")) { 120 - printf("Sorry, I am not a psychiatrist; I can not give you the help you need. Oh, you meant usage...\n"); 121 - exit(129); 122 - } 123 - 124 - if (argc != 5 || strcmp(argv[1], "--onto")) 125 - die("usage: read the code, figure out how to use it, then do so"); 126 - 127 - onto = peel_committish(argv[2]); 128 - strbuf_addf(&branch_name, "refs/heads/%s", argv[4]); 129 - 130 - /* Sanity check */ 131 - if (repo_get_oid(the_repository, "HEAD", &head)) 132 - die(_("Cannot read HEAD")); 133 - assert(oideq(&onto->object.oid, &head)); 134 - 135 - repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); 136 - if (repo_read_index(the_repository) < 0) 137 - BUG("Could not read index"); 138 - 139 - repo_init_revisions(the_repository, &revs, NULL); 140 - revs.verbose_header = 1; 141 - revs.max_parents = 1; 142 - revs.cherry_mark = 1; 143 - revs.limited = 1; 144 - revs.reverse = 1; 145 - revs.right_only = 1; 146 - revs.sort_order = REV_SORT_IN_GRAPH_ORDER; 147 - revs.topo_order = 1; 148 - strvec_pushl(&rev_walk_args, "", argv[4], "--not", argv[3], NULL); 149 - 150 - if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) { 151 - ret = error(_("unhandled options")); 152 - goto cleanup; 153 - } 154 - 155 - strvec_clear(&rev_walk_args); 156 - 157 - if (prepare_revision_walk(&revs) < 0) { 158 - ret = error(_("error preparing revisions")); 159 - goto cleanup; 160 - } 161 - 162 - init_merge_options(&merge_opt, the_repository); 163 - memset(&result, 0, sizeof(result)); 164 - merge_opt.show_rename_progress = 1; 165 - merge_opt.branch1 = "HEAD"; 166 - head_tree = repo_get_commit_tree(the_repository, onto); 167 - result.tree = head_tree; 168 - last_commit = onto; 169 - while ((commit = get_revision(&revs))) { 170 - struct commit *base; 171 - 172 - fprintf(stderr, "Rebasing %s...\r", 173 - oid_to_hex(&commit->object.oid)); 174 - assert(commit->parents && !commit->parents->next); 175 - base = commit->parents->item; 176 - 177 - next_tree = repo_get_commit_tree(the_repository, commit); 178 - base_tree = repo_get_commit_tree(the_repository, base); 179 - 180 - merge_opt.branch2 = short_commit_name(commit); 181 - merge_opt.ancestor = xstrfmt("parent of %s", merge_opt.branch2); 182 - 183 - merge_incore_nonrecursive(&merge_opt, 184 - base_tree, 185 - result.tree, 186 - next_tree, 187 - &result); 188 - 189 - free((char*)merge_opt.ancestor); 190 - merge_opt.ancestor = NULL; 191 - if (!result.clean) 192 - break; 193 - last_picked_commit = commit; 194 - last_commit = create_commit(result.tree, commit, last_commit); 195 - } 196 - 197 - merge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean); 198 - 199 - if (result.clean < 0) 200 - exit(128); 201 - 202 - if (result.clean) { 203 - fprintf(stderr, "\nDone.\n"); 204 - strbuf_addf(&reflog_msg, "finish rebase %s onto %s", 205 - oid_to_hex(&last_picked_commit->object.oid), 206 - oid_to_hex(&last_commit->object.oid)); 207 - if (update_ref(reflog_msg.buf, branch_name.buf, 208 - &last_commit->object.oid, 209 - &last_picked_commit->object.oid, 210 - REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) { 211 - error(_("could not update %s"), argv[4]); 212 - die("Failed to update %s", argv[4]); 213 - } 214 - if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0) 215 - die(_("unable to update HEAD")); 216 - 217 - prime_cache_tree(the_repository, the_repository->index, 218 - result.tree); 219 - } else { 220 - fprintf(stderr, "\nAborting: Hit a conflict.\n"); 221 - strbuf_addf(&reflog_msg, "rebase progress up to %s", 222 - oid_to_hex(&last_picked_commit->object.oid)); 223 - if (update_ref(reflog_msg.buf, "HEAD", 224 - &last_commit->object.oid, 225 - &head, 226 - REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) { 227 - error(_("could not update %s"), argv[4]); 228 - die("Failed to update %s", argv[4]); 229 - } 230 - } 231 - if (write_locked_index(&the_index, &lock, 232 - COMMIT_LOCK | SKIP_IF_UNCHANGED)) 233 - die(_("unable to write %s"), get_index_file()); 234 - 235 - ret = (result.clean == 0); 236 - cleanup: 237 - strbuf_release(&reflog_msg); 238 - strbuf_release(&branch_name); 239 - release_revisions(&revs); 240 - return ret; 241 - }
···
-1
t/helper/test-tool.c
··· 30 { "dump-untracked-cache", cmd__dump_untracked_cache }, 31 { "env-helper", cmd__env_helper }, 32 { "example-decorate", cmd__example_decorate }, 33 - { "fast-rebase", cmd__fast_rebase }, 34 { "find-pack", cmd__find_pack }, 35 { "fsmonitor-client", cmd__fsmonitor_client }, 36 { "genrandom", cmd__genrandom },
··· 30 { "dump-untracked-cache", cmd__dump_untracked_cache }, 31 { "env-helper", cmd__env_helper }, 32 { "example-decorate", cmd__example_decorate }, 33 { "find-pack", cmd__find_pack }, 34 { "fsmonitor-client", cmd__fsmonitor_client }, 35 { "genrandom", cmd__genrandom },
-1
t/helper/test-tool.h
··· 24 int cmd__dump_reftable(int argc, const char **argv); 25 int cmd__env_helper(int argc, const char **argv); 26 int cmd__example_decorate(int argc, const char **argv); 27 - int cmd__fast_rebase(int argc, const char **argv); 28 int cmd__find_pack(int argc, const char **argv); 29 int cmd__fsmonitor_client(int argc, const char **argv); 30 int cmd__genrandom(int argc, const char **argv);
··· 24 int cmd__dump_reftable(int argc, const char **argv); 25 int cmd__env_helper(int argc, const char **argv); 26 int cmd__example_decorate(int argc, const char **argv); 27 int cmd__find_pack(int argc, const char **argv); 28 int cmd__fsmonitor_client(int argc, const char **argv); 29 int cmd__genrandom(int argc, const char **argv);
+198
t/t3650-replay-basics.sh
···
··· 1 + #!/bin/sh 2 + 3 + test_description='basic git replay tests' 4 + 5 + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main 6 + export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 7 + 8 + . ./test-lib.sh 9 + 10 + GIT_AUTHOR_NAME=author@name 11 + GIT_AUTHOR_EMAIL=bogus@email@address 12 + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL 13 + 14 + test_expect_success 'setup' ' 15 + test_commit A && 16 + test_commit B && 17 + 18 + git switch -c topic1 && 19 + test_commit C && 20 + git switch -c topic2 && 21 + test_commit D && 22 + test_commit E && 23 + git switch topic1 && 24 + test_commit F && 25 + git switch -c topic3 && 26 + test_commit G && 27 + test_commit H && 28 + git switch -c topic4 main && 29 + test_commit I && 30 + test_commit J && 31 + 32 + git switch -c next main && 33 + test_commit K && 34 + git merge -m "Merge topic1" topic1 && 35 + git merge -m "Merge topic2" topic2 && 36 + git merge -m "Merge topic3" topic3 && 37 + >evil && 38 + git add evil && 39 + git commit --amend && 40 + git merge -m "Merge topic4" topic4 && 41 + 42 + git switch main && 43 + test_commit L && 44 + test_commit M && 45 + 46 + git switch -c conflict B && 47 + test_commit C.conflict C.t conflict 48 + ' 49 + 50 + test_expect_success 'setup bare' ' 51 + git clone --bare . bare 52 + ' 53 + 54 + test_expect_success 'using replay to rebase two branches, one on top of other' ' 55 + git replay --onto main topic1..topic2 >result && 56 + 57 + test_line_count = 1 result && 58 + 59 + git log --format=%s $(cut -f 3 -d " " result) >actual && 60 + test_write_lines E D M L B A >expect && 61 + test_cmp expect actual && 62 + 63 + printf "update refs/heads/topic2 " >expect && 64 + printf "%s " $(cut -f 3 -d " " result) >>expect && 65 + git rev-parse topic2 >>expect && 66 + 67 + test_cmp expect result 68 + ' 69 + 70 + test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' ' 71 + git -C bare replay --onto main topic1..topic2 >result-bare && 72 + test_cmp expect result-bare 73 + ' 74 + 75 + test_expect_success 'using replay to rebase with a conflict' ' 76 + test_expect_code 1 git replay --onto topic1 B..conflict 77 + ' 78 + 79 + test_expect_success 'using replay on bare repo to rebase with a conflict' ' 80 + test_expect_code 1 git -C bare replay --onto topic1 B..conflict 81 + ' 82 + 83 + test_expect_success 'using replay to perform basic cherry-pick' ' 84 + # The differences between this test and previous ones are: 85 + # --advance vs --onto 86 + # 2nd field of result is refs/heads/main vs. refs/heads/topic2 87 + # 4th field of result is hash for main instead of hash for topic2 88 + 89 + git replay --advance main topic1..topic2 >result && 90 + 91 + test_line_count = 1 result && 92 + 93 + git log --format=%s $(cut -f 3 -d " " result) >actual && 94 + test_write_lines E D M L B A >expect && 95 + test_cmp expect actual && 96 + 97 + printf "update refs/heads/main " >expect && 98 + printf "%s " $(cut -f 3 -d " " result) >>expect && 99 + git rev-parse main >>expect && 100 + 101 + test_cmp expect result 102 + ' 103 + 104 + test_expect_success 'using replay on bare repo to perform basic cherry-pick' ' 105 + git -C bare replay --advance main topic1..topic2 >result-bare && 106 + test_cmp expect result-bare 107 + ' 108 + 109 + test_expect_success 'replay on bare repo fails with both --advance and --onto' ' 110 + test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare 111 + ' 112 + 113 + test_expect_success 'replay fails when both --advance and --onto are omitted' ' 114 + test_must_fail git replay topic1..topic2 >result 115 + ' 116 + 117 + test_expect_success 'using replay to also rebase a contained branch' ' 118 + git replay --contained --onto main main..topic3 >result && 119 + 120 + test_line_count = 2 result && 121 + cut -f 3 -d " " result >new-branch-tips && 122 + 123 + git log --format=%s $(head -n 1 new-branch-tips) >actual && 124 + test_write_lines F C M L B A >expect && 125 + test_cmp expect actual && 126 + 127 + git log --format=%s $(tail -n 1 new-branch-tips) >actual && 128 + test_write_lines H G F C M L B A >expect && 129 + test_cmp expect actual && 130 + 131 + printf "update refs/heads/topic1 " >expect && 132 + printf "%s " $(head -n 1 new-branch-tips) >>expect && 133 + git rev-parse topic1 >>expect && 134 + printf "update refs/heads/topic3 " >>expect && 135 + printf "%s " $(tail -n 1 new-branch-tips) >>expect && 136 + git rev-parse topic3 >>expect && 137 + 138 + test_cmp expect result 139 + ' 140 + 141 + test_expect_success 'using replay on bare repo to also rebase a contained branch' ' 142 + git -C bare replay --contained --onto main main..topic3 >result-bare && 143 + test_cmp expect result-bare 144 + ' 145 + 146 + test_expect_success 'using replay to rebase multiple divergent branches' ' 147 + git replay --onto main ^topic1 topic2 topic4 >result && 148 + 149 + test_line_count = 2 result && 150 + cut -f 3 -d " " result >new-branch-tips && 151 + 152 + git log --format=%s $(head -n 1 new-branch-tips) >actual && 153 + test_write_lines E D M L B A >expect && 154 + test_cmp expect actual && 155 + 156 + git log --format=%s $(tail -n 1 new-branch-tips) >actual && 157 + test_write_lines J I M L B A >expect && 158 + test_cmp expect actual && 159 + 160 + printf "update refs/heads/topic2 " >expect && 161 + printf "%s " $(head -n 1 new-branch-tips) >>expect && 162 + git rev-parse topic2 >>expect && 163 + printf "update refs/heads/topic4 " >>expect && 164 + printf "%s " $(tail -n 1 new-branch-tips) >>expect && 165 + git rev-parse topic4 >>expect && 166 + 167 + test_cmp expect result 168 + ' 169 + 170 + test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' ' 171 + git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result && 172 + 173 + test_line_count = 4 result && 174 + cut -f 3 -d " " result >new-branch-tips && 175 + 176 + >expect && 177 + for i in 2 1 3 4 178 + do 179 + printf "update refs/heads/topic$i " >>expect && 180 + printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect && 181 + git -C bare rev-parse topic$i >>expect || return 1 182 + done && 183 + 184 + test_cmp expect result && 185 + 186 + test_write_lines F C M L B A >expect1 && 187 + test_write_lines E D C M L B A >expect2 && 188 + test_write_lines H G F C M L B A >expect3 && 189 + test_write_lines J I M L B A >expect4 && 190 + 191 + for i in 1 2 3 4 192 + do 193 + git -C bare log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual && 194 + test_cmp expect$i actual || return 1 195 + done 196 + ' 197 + 198 + test_done
+25 -20
t/t6429-merge-sequence-rename-caching.sh
··· 71 72 git switch upstream && 73 74 - test-tool fast-rebase --onto HEAD upstream~1 topic && 75 - #git cherry-pick upstream~1..topic 76 77 git ls-files >tracked-files && 78 test_line_count = 2 tracked-files && ··· 140 GIT_TRACE2_PERF="$(pwd)/trace.output" && 141 export GIT_TRACE2_PERF && 142 143 - test-tool fast-rebase --onto HEAD upstream~1 topic && 144 - #git cherry-pick upstream~1..topic && 145 146 grep region_enter.*diffcore_rename trace.output >calls && 147 test_line_count = 1 calls ··· 199 GIT_TRACE2_PERF="$(pwd)/trace.output" && 200 export GIT_TRACE2_PERF && 201 202 - test-tool fast-rebase --onto HEAD upstream~1 topic && 203 - #git cherry-pick upstream~1..topic && 204 205 git ls-files >tracked && 206 test_line_count = 2 tracked && ··· 276 GIT_TRACE2_PERF="$(pwd)/trace.output" && 277 export GIT_TRACE2_PERF && 278 279 - test-tool fast-rebase --onto HEAD upstream~1 topic && 280 - #git cherry-pick upstream~1..topic && 281 282 git ls-files >tracked && 283 test_line_count = 4 tracked && ··· 353 GIT_TRACE2_PERF="$(pwd)/trace.output" && 354 export GIT_TRACE2_PERF && 355 356 - test_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output && 357 - #git cherry-pick upstream..topic && 358 - 359 - grep CONFLICT..rename/rename output && 360 361 grep region_enter.*diffcore_rename trace.output >calls && 362 test_line_count = 2 calls ··· 455 GIT_TRACE2_PERF="$(pwd)/trace.output" && 456 export GIT_TRACE2_PERF && 457 458 - test-tool fast-rebase --onto HEAD upstream~1 topic && 459 - #git cherry-pick upstream..topic && 460 461 grep region_enter.*diffcore_rename trace.output >calls && 462 test_line_count = 2 calls && ··· 521 GIT_TRACE2_PERF="$(pwd)/trace.output" && 522 export GIT_TRACE2_PERF && 523 524 - test-tool fast-rebase --onto HEAD upstream~1 topic && 525 - #git cherry-pick upstream..topic && 526 527 grep region_enter.*diffcore_rename trace.output >calls && 528 test_line_count = 3 calls && ··· 623 GIT_TRACE2_PERF="$(pwd)/trace.output" && 624 export GIT_TRACE2_PERF && 625 626 - test-tool fast-rebase --onto HEAD upstream~1 topic && 627 - #git cherry-pick upstream..topic && 628 629 grep region_enter.*diffcore_rename trace.output >calls && 630 test_line_count = 1 calls && ··· 681 GIT_TRACE2_PERF="$(pwd)/trace.output" && 682 export GIT_TRACE2_PERF && 683 684 - test-tool fast-rebase --onto HEAD upstream~1 topic && 685 - #git cherry-pick upstream..topic && 686 687 grep region_enter.*diffcore_rename trace.output >calls && 688 test_line_count = 2 calls &&
··· 71 72 git switch upstream && 73 74 + git replay --onto HEAD upstream~1..topic >out && 75 + git update-ref --stdin <out && 76 + git checkout topic && 77 78 git ls-files >tracked-files && 79 test_line_count = 2 tracked-files && ··· 141 GIT_TRACE2_PERF="$(pwd)/trace.output" && 142 export GIT_TRACE2_PERF && 143 144 + git replay --onto HEAD upstream~1..topic >out && 145 + git update-ref --stdin <out && 146 + git checkout topic && 147 148 grep region_enter.*diffcore_rename trace.output >calls && 149 test_line_count = 1 calls ··· 201 GIT_TRACE2_PERF="$(pwd)/trace.output" && 202 export GIT_TRACE2_PERF && 203 204 + git replay --onto HEAD upstream~1..topic >out && 205 + git update-ref --stdin <out && 206 + git checkout topic && 207 208 git ls-files >tracked && 209 test_line_count = 2 tracked && ··· 279 GIT_TRACE2_PERF="$(pwd)/trace.output" && 280 export GIT_TRACE2_PERF && 281 282 + git replay --onto HEAD upstream~1..topic >out && 283 + git update-ref --stdin <out && 284 + git checkout topic && 285 286 git ls-files >tracked && 287 test_line_count = 4 tracked && ··· 357 GIT_TRACE2_PERF="$(pwd)/trace.output" && 358 export GIT_TRACE2_PERF && 359 360 + test_must_fail git replay --onto HEAD upstream~1..topic >output && 361 362 grep region_enter.*diffcore_rename trace.output >calls && 363 test_line_count = 2 calls ··· 456 GIT_TRACE2_PERF="$(pwd)/trace.output" && 457 export GIT_TRACE2_PERF && 458 459 + git replay --onto HEAD upstream~1..topic >out && 460 + git update-ref --stdin <out && 461 + git checkout topic && 462 463 grep region_enter.*diffcore_rename trace.output >calls && 464 test_line_count = 2 calls && ··· 523 GIT_TRACE2_PERF="$(pwd)/trace.output" && 524 export GIT_TRACE2_PERF && 525 526 + git replay --onto HEAD upstream~1..topic >out && 527 + git update-ref --stdin <out && 528 + git checkout topic && 529 530 grep region_enter.*diffcore_rename trace.output >calls && 531 test_line_count = 3 calls && ··· 626 GIT_TRACE2_PERF="$(pwd)/trace.output" && 627 export GIT_TRACE2_PERF && 628 629 + git replay --onto HEAD upstream~1..topic >out && 630 + git update-ref --stdin <out && 631 + git checkout topic && 632 633 grep region_enter.*diffcore_rename trace.output >calls && 634 test_line_count = 1 calls && ··· 685 GIT_TRACE2_PERF="$(pwd)/trace.output" && 686 export GIT_TRACE2_PERF && 687 688 + git replay --onto HEAD upstream~1..topic >out && 689 + git update-ref --stdin <out && 690 + git checkout topic && 691 692 grep region_enter.*diffcore_rename trace.output >calls && 693 test_line_count = 2 calls &&