Web lists-archives.com

Re: [PATCH 4/4] built-in rebase: call `git am` directly




"Johannes Schindelin via GitGitGadget" <gitgitgadget@xxxxxxxxx>
writes:

> +static int write_basic_state(struct rebase_options *opts)
> +{
> +	write_file(state_dir_path("head-name", opts), "%s",
> +		   opts->head_name ? opts->head_name : "detached HEAD");
> +	write_file(state_dir_path("onto", opts), "%s",
> +		   opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
> +	write_file(state_dir_path("orig-head", opts), "%s",
> +		   oid_to_hex(&opts->orig_head));
> +	write_file(state_dir_path("quiet", opts), "%s",
> +		   opts->flags & REBASE_NO_QUIET ? "" : "t");
> +	if (opts->flags & REBASE_VERBOSE)
> +		write_file(state_dir_path("verbose", opts), "%s", "");
> +	if (opts->strategy)
> +		write_file(state_dir_path("strategy", opts), "%s",
> +			   opts->strategy);
> +	if (opts->strategy_opts)
> +		write_file(state_dir_path("strategy_opts", opts), "%s",
> +			   opts->strategy_opts);
> +	if (opts->allow_rerere_autoupdate >= 0)
> +		write_file(state_dir_path("allow_rerere_autoupdate", opts),
> +			   "-%s-rerere-autoupdate",
> +			   opts->allow_rerere_autoupdate ? "" : "-no");

Inside rebase, allow-rerere-autoupdate can be -1 (unspecified), 0
(declined) or 1 (requested), and this code is being consistent with
that convention.

The "--[no-]rerere-autoupdate" option that is parsed via
OPT_RERERE_AUTOUPDATE (used in builtin/rebase--interactive.c among
other built-in commands) on the other hand is tertially that uses 0
(unspecified), 1 (requested) and 2 (declined).  This might be a
ticking timebomb to confuse us in the future that may be worth
fixing but probably outside this series.

> +	if (opts->gpg_sign_opt)
> +		write_file(state_dir_path("gpg_sign_opt", opts), "%s",
> +			   opts->gpg_sign_opt);
> +	if (opts->signoff)
> +		write_file(state_dir_path("strategy", opts), "--signoff");
> +
> +	return 0;
> +}

The above looks like a literal translation of a helper function with
the same name in git-rebase--common.sh.  Good.

> @@ -459,6 +490,30 @@ static int reset_head(struct object_id *oid, const char *action,
>  	return ret;
>  }
>  
> +static int move_to_original_branch(struct rebase_options *opts)
> +{
> +	struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
> +	int ret;
> +
> +	if (!opts->head_name)
> +		return 0; /* nothing to move back to */
> +
> +	if (!opts->onto)
> +		BUG("move_to_original_branch without onto");

This check is absent in the scripted version, but from the message
we generate here, it is clear that the caller must not call this
when there is no "onto" commit.  Good.

> +	strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
> +		    opts->head_name, oid_to_hex(&opts->onto->object.oid));
> +	strbuf_addf(&head_reflog, "rebase finished: returning to %s",
> +		    opts->head_name);
> +	ret = reset_head(NULL, "checkout", opts->head_name,
> +			 RESET_HEAD_REFS_ONLY,
> +			 orig_head_reflog.buf, head_reflog.buf);

The *action given to reset_head() here is "checkout".  Makes me
wonder about two things:

 - The only real use of the parameter in the callee is to prepare
   the error and advice messages from the unpack_trees machinery,
   but because we are using it in REFS_ONLY mode, it does not
   matter.  In fact it might even be misleading; perhaps pass NULL
   or something, so that a mistaken update to reset_head() later
   that lets REFS_ONLY request to go to unpack_trees machinery will
   catch it as a bug?

 - Another topic in flight wants to make sure that the post-checkout
   hook gets called when the RESET_HEAD_RUN_POST_CHECKOUT_HOOK flag
   is given by the caller, and IIRC, the use of the flag is strongly
   correlated to *action being "checkout".  Do we want to pass
   REFS_ONLY and RUN_POST_CHECKOUT_HOOK flag for this call, or do we
   rather keep it silent?  As the original scripted version did not
   use "checkout" here and never triggered post-checkout hook, I am
   inclined to say that we should not pass that other bit.  That
   then leads me to suspect that we do not want *action to be
   "checkout" here.

> +	strbuf_release(&orig_head_reflog);
> +	strbuf_release(&head_reflog);
> +	return ret;
> +}

Unlike the scripted version, this does not die() upon failure, so
the caller needs to be careful about the returned status.

> @@ -466,6 +521,129 @@ N_("Resolve all conflicts manually, mark them as resolved with\n"
>  "To abort and get back to the state before \"git rebase\", run "
>  "\"git rebase --abort\".");
>  
> +static int run_am(struct rebase_options *opts)
> +{
> +	struct child_process am = CHILD_PROCESS_INIT;
> +	struct child_process format_patch = CHILD_PROCESS_INIT;
> +	struct strbuf revisions = STRBUF_INIT;
> +	int status;
> +	char *rebased_patches;
> +
> +	am.git_cmd = 1;
> +	argv_array_push(&am.args, "am");
> +
> +	if (opts->action && !strcmp("continue", opts->action)) {
> +		argv_array_push(&am.args, "--resolved");
> +		argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
> +		if (opts->gpg_sign_opt)
> +			argv_array_push(&am.args, opts->gpg_sign_opt);
> +		status = run_command(&am);
> +		if (status)
> +			return status;
> +
> +		discard_cache();
> +		return move_to_original_branch(opts);

It is curious why discard_cache() is placed exacly here, as if we
want to preserve the contents of the in-core index when
run_command() failed.  But I do not think we care about the in-core
index as the only thing that happen after "return status" is to
return the control to run_specific_rebase(), let it jump to
finished_rebase label to clean things up and rturn control to
cmd_rebase() and exit based on the status value.

It's not like move_to_original_branch() wants to call read_cache()
and get the result from the "am" that run_command() executed,
either.

Puzzled.  Care to explain a bit more in the in-code comment?

> +	}
> +	if (opts->action && !strcmp("skip", opts->action)) {
> +		argv_array_push(&am.args, "--skip");
> +		argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
> +		status = run_command(&am);
> +		if (status)
> +			return status;
> +
> +		discard_cache();
> +		return move_to_original_branch(opts);

Ditto.

> +	}
> +	if (opts->action && !strcmp("show-current-patch", opts->action)) {
> +		argv_array_push(&am.args, "--show-current-patch");
> +		return run_command(&am);
> +	}

Up to this point, it is a faithful conversion of the first case/esac
statement.  Good.

> +	strbuf_addf(&revisions, "%s...%s",
> +		    oid_to_hex(opts->root ?
> +			       /* this is now equivalent to ! -z "$upstream" */

Does "this" refer to the "opts->root being true" check?

Because you are flipping the polarity of the test from scripted
version, shouldn't the comment be updated to "-z $upstream"?

> +			       &opts->onto->object.oid :
> +			       &opts->upstream->object.oid),
> +		    oid_to_hex(&opts->orig_head));

> +	rebased_patches = xstrdup(git_path("rebased-patches"));
> +	format_patch.out = open(rebased_patches,
> +				O_WRONLY | O_CREAT | O_TRUNC, 0666);

Unlike scripted version, we do not remove a (possibly) existing file.
We give CREAT in case there is no existing one, and TRUNC in case
there is an existing one.  Makes sense.  A more faithful translation
would have unlink(2)ed a (possibly) existing one, and then because
we can afford to, passed O_EXCL to avoid stomping on somebody else
racing with us, but I do not think it is worth it.

> +	if (format_patch.out < 0) {
> +		status = error_errno(_("could not write '%s'"),
> +				     rebased_patches);

s/write '%s'/open '%s' for writing/?  I dunno.

> +		free(rebased_patches);
> +		argv_array_clear(&am.args);
> +		return status;
> +	}
> +
> +	format_patch.git_cmd = 1;
> +	argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
> +			 "--full-index", "--cherry-pick", "--right-only",
> +			 "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
> +			 "--no-cover-letter", "--pretty=mboxrd", NULL);
> +	if (opts->git_format_patch_opt.len)
> +		argv_array_split(&format_patch.args,
> +				 opts->git_format_patch_opt.buf);
> +	argv_array_push(&format_patch.args, revisions.buf);
> +	if (opts->restrict_revision)
> +		argv_array_pushf(&format_patch.args, "^%s",
> +				 oid_to_hex(&opts->restrict_revision->object.oid));

It is kinda surprising to see that we have learned quite a lot of
fringe "configurations" we need to explicitly override like this.

Looks like a quite faithful conversion, anyway.

> +	status = run_command(&format_patch);
> +	if (status) {
> +		unlink(rebased_patches);
> +		free(rebased_patches);
> +		argv_array_clear(&am.args);
> +
> +		reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
> +			   "HEAD", NULL);

This one may need to trigger post-checkout hook.  The scripted
version does two different things depending on the value of
$head_name, but we can just use the same code without conditional?

> +		error(_("\ngit encountered an error while preparing the "
> +			"patches to replay\n"
> +			"these revisions:\n"
> +			"\n    %s\n\n"
> +			"As a result, git cannot rebase them."),
> +		      opts->revisions);
> +
> +		strbuf_release(&revisions);
> +		return status;
> +	}
> +	strbuf_release(&revisions);
> +
> +	am.in = open(rebased_patches, O_RDONLY);
> +	if (am.in < 0) {
> +		status = error_errno(_("could not read '%s'"),
> +				     rebased_patches);

s/write '%s'/open '%s' for reading/?  I dunno.

> +		free(rebased_patches);
> +		argv_array_clear(&am.args);
> +		return status;
> +	}
> +
> +	argv_array_pushv(&am.args, opts->git_am_opts.argv);
> +	argv_array_push(&am.args, "--rebasing");
> +	argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
> +	argv_array_push(&am.args, "--patch-format=mboxrd");
> +	if (opts->allow_rerere_autoupdate > 0)
> +		argv_array_push(&am.args, "--rerere-autoupdate");
> +	else if (opts->allow_rerere_autoupdate == 0)
> +		argv_array_push(&am.args, "--no-rerere-autoupdate");
> +	if (opts->gpg_sign_opt)
> +		argv_array_push(&am.args, opts->gpg_sign_opt);
> +	status = run_command(&am);
> +	unlink(rebased_patches);
> +	free(rebased_patches);
> +
> +	if (!status) {
> +		discard_cache();
> +		return move_to_original_branch(opts);
> +	}
> +
> +	if (is_directory(opts->state_dir))
> +		write_basic_state(opts);
> +
> +	return status;
> +}
> +
>  static int run_specific_rebase(struct rebase_options *opts)
>  {
>  	const char *argv[] = { NULL, NULL };
> @@ -546,6 +724,11 @@ static int run_specific_rebase(struct rebase_options *opts)
>  		goto finished_rebase;
>  	}
>  
> +	if (opts->type == REBASE_AM) {
> +		status = run_am(opts);
> +		goto finished_rebase;
> +	}
> +
>  	add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
>  	add_var(&script_snippet, "state_dir", opts->state_dir);


Overall, this was quite a pleasant read and a well constructed
series.  Other than two minor points (i.e. interaction with the
'post-checkout hook' topic, and discard_cache() before calling
move_to_original_branch) I did not quite understand, looks good to
me.

When merged to 'pu', I seem to be getting failure from t3425.5, .8
and .11, by the way.  I haven't dug into the actual breakages any
further than that.

Thanks.