Hi Elijah,
On Thu, 25 Jul 2019, Elijah Newren wrote:
> We had a rule to enforce that the index matches head, but it was found
> at the beginning of merge_trees() and would only trigger when
> opt->call_depth was 0. Since merge_recursive() doesn't call
> merge_trees() until after returning from recursing, this meant that the
> check wasn't triggered by merge_recursive() until it had first finished
> all the intermediate merges to create virtual merge bases. That is a
> potentially huge amount of computation (and writing of intermediate
> merge results into the .git/objects directory) before it errors out and
> says, in effect, "Sorry, I can't do any merging because you have some
> local changes that would be overwritten."
>
> Further, not enforcing this requirement earlier allowed other bugs (such
> as an unintentional unconditional dropping and reloading of the index in
> merge_recursive() even when no recursion was necessary), to mask bugs in
> other callers (which were fixed in the commit prior to this one).
>
> Make sure we do the index == head check at the beginning of the merge,
> and error out immediately if it fails.
Very clear commit message.
> diff --git a/merge-recursive.c b/merge-recursive.c
> index 37bb94fb4d..b762ecd7bd 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -3381,21 +3381,14 @@ static int process_entry(struct merge_options *opt,
> return clean_merge;
> }
>
> -int merge_trees(struct merge_options *opt,
> - struct tree *head,
> - struct tree *merge,
> - struct tree *common,
> - struct tree **result)
> +static int merge_trees_internal(struct merge_options *opt,
In other, similar cases, we seem to use the suffix `_1`. Not sure
whether you want to change that here.
> + struct tree *head,
> + struct tree *merge,
> + struct tree *common,
> + struct tree **result)
> {
> struct index_state *istate = opt->repo->index;
> int code, clean;
> - struct strbuf sb = STRBUF_INIT;
> -
> - if (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {
> - err(opt, _("Your local changes to the following files would be
> overwritten by merge:\n %s"),
> - sb.buf);
> - return -1;
> - }
>
> if (opt->subtree_shift) {
> merge = shift_tree_object(opt->repo, head, merge,
> opt->subtree_shift);
> @@ -3499,11 +3492,11 @@ static struct commit_list *reverse_commit_list(struct
> commit_list *list)
> * Merge the commits h1 and h2, return the resulting virtual
> * commit object and a flag indicating the cleanness of the merge.
> */
> -int merge_recursive(struct merge_options *opt,
> - struct commit *h1,
> - struct commit *h2,
> - struct commit_list *ca,
> - struct commit **result)
> +static int merge_recursive_internal(struct merge_options *opt,
Same here.
> + struct commit *h1,
> + struct commit *h2,
> + struct commit_list *ca,
> + struct commit **result)
> {
> struct commit_list *iter;
> struct commit *merged_common_ancestors;
> [...]
> @@ -3596,6 +3589,58 @@ int merge_recursive(struct merge_options *opt,
> return clean;
> }
>
> +static int merge_start(struct merge_options *opt, struct tree *head)
I would prefer to call this something like `check_invariants()` or
something similar.
> +{
> + struct strbuf sb = STRBUF_INIT;
> +
> + assert(opt->branch1 && opt->branch2);
> +
> + if (repo_index_has_changes(opt->repo, head, &sb)) {
> + err(opt, _("Your local changes to the following files would be
> overwritten by merge:\n %s"),
> + sb.buf);
I know that you did not introduce this leak, but maybe we could slip an
`strbuf_release(&sb);` in at this point?
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static void merge_finalize(struct merge_options *opt)
> +{
> + /* Common code for wrapping up merges will be added here later */
> +}
I was about to comment that this complicates this here diff and that we
should do this when we need it, but I just peeked into the next patch,
and it uses it, so I leave this here paragraph only to show that I
actually reviewed this part, too.
And of course, if we have a `merge_finalize()`, then a `merge_start()`
does not sound all that bad, either.
The rest looks good to me.
Thanks,
Dscho
> +
> +int merge_trees(struct merge_options *opt,
> + struct tree *head,
> + struct tree *merge,
> + struct tree *common,
> + struct tree **result)
> +{
> + int ret;
> +
> + if (merge_start(opt, head))
> + return -1;
> + ret = merge_trees_internal(opt, head, merge, common, result);
> + merge_finalize(opt);
> +
> + return ret;
> +}
> +
> +int merge_recursive(struct merge_options *opt,
> + struct commit *h1,
> + struct commit *h2,
> + struct commit_list *ca,
> + struct commit **result)
> +{
> + int ret;
> +
> + if (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))
> + return -1;
> + ret = merge_recursive_internal(opt, h1, h2, ca, result);
> + merge_finalize(opt);
> +
> + return ret;
> +}
> +
> static struct commit *get_ref(struct repository *repo, const struct
> object_id *oid,
> const char *name)
> {
> --
> 2.22.0.559.g28a8880890.dirty
>
>