Re: [PATCH 04/19] merge-recursive: exit early if index != head
- From
Johannes Schindelin <johannes.schindelin@gmx.de>
- Date
- Jul 25, 2019, 19:51 UTC
- Message-ID
- <nycvar.QRO.7.76.6.1907252142470.21907@tvgsbejvaqbjf.bet>
- In-Reply-To
- <20190725174611.14802-5-newren@gmail.com>
Hi Elijah,
On Thu, 25 Jul 2019, Elijah Newren wrote:
Show 18 quoted lines
> 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.
Show 14 quoted lines
> 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.
Show 27 quoted lines
> + 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.
Show 13 quoted lines
> + 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.
Show 8 quoted lines
> +{
> + 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?
Show 10 quoted lines
> + 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
Show 40 quoted lines
> +
> +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
>
>