built-in rebase: call `git am` directly

While the scripted `git rebase` still has to rely on the `git-rebase--am.sh` script to implement the glue between the `rebase` and the `am` commands, we can go a more direct route in the built-in rebase and avoid using a shell script altogether. This patch represents a straight-forward port of `git-rebase--am.sh` to C, along with the glue code to call it directly from within `builtin/rebase.c`. This reduces the chances of Git for Windows running into trouble due to problems with the POSIX emulation layer (known as "MSYS2 runtime", itself a derivative of the Cygwin runtime): when no shell script is called, the POSIX emulation layer is avoided altogether. Note: we pass an empty action to `reset_head()` here when moving back to the original branch, as no other action is applicable, really. This parameter is used to initialize `unpack_trees()`' messages. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Jan 18, 2019 at 07:09 UTC 21853626eac565dd42572d90724b29863f61eb3b
1 file changed +179
builtin/rebase.c
+179
@@ -246,6 +246,37 @@ static int read_basic_state(struct rebase_options *opts)
246 return 0;
247 }
248
249 +static int write_basic_state(struct rebase_options *opts)
250 +{
251 + write_file(state_dir_path("head-name", opts), "%s",
252 + opts->head_name ? opts->head_name : "detached HEAD");
253 + write_file(state_dir_path("onto", opts), "%s",
254 + opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
255 + write_file(state_dir_path("orig-head", opts), "%s",
256 + oid_to_hex(&opts->orig_head));
257 + write_file(state_dir_path("quiet", opts), "%s",
258 + opts->flags & REBASE_NO_QUIET ? "" : "t");
259 + if (opts->flags & REBASE_VERBOSE)
260 + write_file(state_dir_path("verbose", opts), "%s", "");
261 + if (opts->strategy)
262 + write_file(state_dir_path("strategy", opts), "%s",
263 + opts->strategy);
264 + if (opts->strategy_opts)
265 + write_file(state_dir_path("strategy_opts", opts), "%s",
266 + opts->strategy_opts);
267 + if (opts->allow_rerere_autoupdate >= 0)
268 + write_file(state_dir_path("allow_rerere_autoupdate", opts),
269 + "-%s-rerere-autoupdate",
270 + opts->allow_rerere_autoupdate ? "" : "-no");
271 + if (opts->gpg_sign_opt)
272 + write_file(state_dir_path("gpg_sign_opt", opts), "%s",
273 + opts->gpg_sign_opt);
274 + if (opts->signoff)
275 + write_file(state_dir_path("strategy", opts), "--signoff");
276 +
277 + return 0;
278 +}
279 +
280 static int apply_autostash(struct rebase_options *opts)
281 {
282 const char *path = state_dir_path("autostash", opts);
@@ -459,6 +490,29 @@ leave_reset_head:
490 return ret;
491 }
492
493 +static int move_to_original_branch(struct rebase_options *opts)
494 +{
495 + struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
496 + int ret;
497 +
498 + if (!opts->head_name)
499 + return 0; /* nothing to move back to */
500 +
501 + if (!opts->onto)
502 + BUG("move_to_original_branch without onto");
503 +
504 + strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
505 + opts->head_name, oid_to_hex(&opts->onto->object.oid));
506 + strbuf_addf(&head_reflog, "rebase finished: returning to %s",
507 + opts->head_name);
508 + ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY,
509 + orig_head_reflog.buf, head_reflog.buf);
510 +
511 + strbuf_release(&orig_head_reflog);
512 + strbuf_release(&head_reflog);
513 + return ret;
514 +}
515 +
516 static const char *resolvemsg =
517 N_("Resolve all conflicts manually, mark them as resolved with\n"
518 "\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
@@ -466,6 +520,126 @@ N_("Resolve all conflicts manually, mark them as resolved with\n"
520 "To abort and get back to the state before \"git rebase\", run "
521 "\"git rebase --abort\".");
522
523 +static int run_am(struct rebase_options *opts)
524 +{
525 + struct child_process am = CHILD_PROCESS_INIT;
526 + struct child_process format_patch = CHILD_PROCESS_INIT;
527 + struct strbuf revisions = STRBUF_INIT;
528 + int status;
529 + char *rebased_patches;
530 +
531 + am.git_cmd = 1;
532 + argv_array_push(&am.args, "am");
533 +
534 + if (opts->action && !strcmp("continue", opts->action)) {
535 + argv_array_push(&am.args, "--resolved");
536 + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
537 + if (opts->gpg_sign_opt)
538 + argv_array_push(&am.args, opts->gpg_sign_opt);
539 + status = run_command(&am);
540 + if (status)
541 + return status;
542 +
543 + return move_to_original_branch(opts);
544 + }
545 + if (opts->action && !strcmp("skip", opts->action)) {
546 + argv_array_push(&am.args, "--skip");
547 + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
548 + status = run_command(&am);
549 + if (status)
550 + return status;
551 +
552 + return move_to_original_branch(opts);
553 + }
554 + if (opts->action && !strcmp("show-current-patch", opts->action)) {
555 + argv_array_push(&am.args, "--show-current-patch");
556 + return run_command(&am);
557 + }
558 +
559 + strbuf_addf(&revisions, "%s...%s",
560 + oid_to_hex(opts->root ?
561 + /* this is now equivalent to !opts->upstream */
562 + &opts->onto->object.oid :
563 + &opts->upstream->object.oid),
564 + oid_to_hex(&opts->orig_head));
565 +
566 + rebased_patches = xstrdup(git_path("rebased-patches"));
567 + format_patch.out = open(rebased_patches,
568 + O_WRONLY | O_CREAT | O_TRUNC, 0666);
569 + if (format_patch.out < 0) {
570 + status = error_errno(_("could not open '%s' for writing"),
571 + rebased_patches);
572 + free(rebased_patches);
573 + argv_array_clear(&am.args);
574 + return status;
575 + }
576 +
577 + format_patch.git_cmd = 1;
578 + argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
579 + "--full-index", "--cherry-pick", "--right-only",
580 + "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
581 + "--no-cover-letter", "--pretty=mboxrd", NULL);
582 + if (opts->git_format_patch_opt.len)
583 + argv_array_split(&format_patch.args,
584 + opts->git_format_patch_opt.buf);
585 + argv_array_push(&format_patch.args, revisions.buf);
586 + if (opts->restrict_revision)
587 + argv_array_pushf(&format_patch.args, "^%s",
588 + oid_to_hex(&opts->restrict_revision->object.oid));
589 +
590 + status = run_command(&format_patch);
591 + if (status) {
592 + unlink(rebased_patches);
593 + free(rebased_patches);
594 + argv_array_clear(&am.args);
595 +
596 + reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
597 + "HEAD", NULL);
598 + error(_("\ngit encountered an error while preparing the "
599 + "patches to replay\n"
600 + "these revisions:\n"
601 + "\n %s\n\n"
602 + "As a result, git cannot rebase them."),
603 + opts->revisions);
604 +
605 + strbuf_release(&revisions);
606 + return status;
607 + }
608 + strbuf_release(&revisions);
609 +
610 + am.in = open(rebased_patches, O_RDONLY);
611 + if (am.in < 0) {
612 + status = error_errno(_("could not open '%s' for reading"),
613 + rebased_patches);
614 + free(rebased_patches);
615 + argv_array_clear(&am.args);
616 + return status;
617 + }
618 +
619 + argv_array_pushv(&am.args, opts->git_am_opts.argv);
620 + argv_array_push(&am.args, "--rebasing");
621 + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
622 + argv_array_push(&am.args, "--patch-format=mboxrd");
623 + if (opts->allow_rerere_autoupdate > 0)
624 + argv_array_push(&am.args, "--rerere-autoupdate");
625 + else if (opts->allow_rerere_autoupdate == 0)
626 + argv_array_push(&am.args, "--no-rerere-autoupdate");
627 + if (opts->gpg_sign_opt)
628 + argv_array_push(&am.args, opts->gpg_sign_opt);
629 + status = run_command(&am);
630 + unlink(rebased_patches);
631 + free(rebased_patches);
632 +
633 + if (!status) {
634 + return move_to_original_branch(opts);
635 + }
636 +
637 + if (is_directory(opts->state_dir))
638 + write_basic_state(opts);
639 +
640 + return status;
641 +}
642 +
643 static int run_specific_rebase(struct rebase_options *opts)
644 {
645 const char *argv[] = { NULL, NULL };
@@ -546,6 +720,11 @@ static int run_specific_rebase(struct rebase_options *opts)
720 goto finished_rebase;
721 }
722
723 + if (opts->type == REBASE_AM) {
724 + status = run_am(opts);
725 + goto finished_rebase;
726 + }
727 +
728 add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
729 add_var(&script_snippet, "state_dir", opts->state_dir);
730