Re: [PATCH v8 08/14] merge-resolve: rewrite in C
- From
- Phillip Wood <phillip.wood123@gmail.com>
- Date
- Aug 10, 2022, 15:03 UTC
- Message-ID
- <08ea1eec-58fb-cbfa-d405-0d4159c99515@gmail.com>
- In-Reply-To
- <20220809185429.20098-9-alban.gruin@gmail.com>
Hi Alban
On 09/08/2022 19:54, Alban Gruin wrote:
Show 37 quoted lines
> This rewrites `git merge-resolve' from shell to C. As for `git > merge-one-file', this port is not completely straightforward and removes > calls to external processes to avoid reading and writing the index over > and over again. > > - The call to `update-index -q --refresh' is replaced by a call to > refresh_index(). > > - The call to `read-tree' is replaced by a call to unpack_trees() (and > all the setup needed). > > - The call to `write-tree' is replaced by a call to > cache_tree_update(). This call is wrapped in a new function, > write_tree(). It is made to mimick write_index_as_tree() with > WRITE_TREE_SILENT flag, but without locking the index; this is taken > care directly in merge_strategies_resolve(). > > - The call to `diff-index ...' is replaced by a call to > repo_index_has_changes(). > > - The call to `merge-index', needed to invoke `git merge-one-file', is > replaced by a call to the new merge_all_index() function. > > The index is read in cmd_merge_resolve(), and is wrote back by > merge_strategies_resolve(). This is to accomodate future applications: > in `git-merge', the index has already been read when the merge strategy > is called, so it would be redundant to read it again when the builtin > will be able to use merge_strategies_resolve() directly. > > The parameters of merge_strategies_resolve() will be surprising at first > glance: why using a commit list for `bases' and `remote', where we could > use an oid array, and a pointer to an oid? Because, in a later commit, > try_merge_strategy() will be able to call merge_strategies_resolve() > directly, and it already uses a commit list for `bases' (`common') and > `remote' (`remoteheads'), and a string for `head_arg'. To reduce > frictions later, merge_strategies_resolve() takes the same types of > parameters.
git-merge-resolve will happily merge three trees, unfortunately using lists of commits will break that.
Show 39 quoted lines
> merge_strategies_resolve() locks the index only once, at the beginning
> of the merge, and releases it when the merge has been completed.
>
> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
> ---
> diff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c
> new file mode 100644
> index 0000000000..a51158ebf8
> --- /dev/null
> +++ b/builtin/merge-resolve.c
> @@ -0,0 +1,63 @@
> +/*
> + * Builtin "git merge-resolve"
> + *
> + * Copyright (c) 2020 Alban Gruin
> + *
> + * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C
> + * Hamano.
> + *
> + * Resolve two trees, using enhanced multi-base read-tree.
> + */
> +
> +#include "cache.h"
> +#include "builtin.h"
> +#include "merge-strategies.h"
> +
> +static const char builtin_merge_resolve_usage[] =
> + "git merge-resolve <bases>... -- <head> <remote>";
> +
> +int cmd_merge_resolve(int argc, const char **argv, const char *prefix)
> +{
> + int i, sep_seen = 0;
> + const char *head = NULL;
> + struct commit_list *bases = NULL, *remote = NULL;
> + struct commit_list **next_base = &bases;
> + struct repository *r = the_repository;
> +
> + if (argc < 5)
> + usage(builtin_merge_resolve_usage);I think it would be better to call parse_options() and then check argc. That would give better error messages for unknown options and supports '-h' for free.
I think we also need to call git_config(). I see that read-tree respects submodule.recurse so I think we need the same here. I suspect we should also be reading the merge config to respect merge.conflictStyle.
> + setup_work_tree();
> + if (repo_read_index(r) < 0)
> + die("invalid index");This should probably be marked for translation.
Show 18 quoted lines
> +
> + /*
> + * The first parameters up to -- are merge bases; the rest are
> + * heads.
> + */
> + for (i = 1; i < argc; i++) {
> + if (!strcmp(argv[i], "--"))
> + sep_seen = 1;
> + else if (!strcmp(argv[i], "-h"))
> + usage(builtin_merge_resolve_usage);
> + else if (sep_seen && !head)
> + head = argv[i];
> + else {
> + struct object_id oid;
> + struct commit *commit;
> +
> + if (get_oid(argv[i], &oid))
> + die("object %s not found.", argv[i]);translation here as well.
> + commit = oideq(&oid, r->hash_algo->empty_tree) ? > + NULL : lookup_commit_or_die(&oid, argv[i]);
As I said above, git-merge-resolve should be callable with trees I think.
Show 34 quoted lines
> +
> + if (sep_seen)
> + commit_list_insert(commit, &remote);
> + else
> + next_base = commit_list_append(commit, next_base);
> + }
> + }
> +
> + return merge_strategies_resolve(r, bases, head, remote);
> +}
> diff --git a/merge-strategies.c b/merge-strategies.c
> index 373b69c10b..30f225ae5f 100644
> --- a/merge-strategies.c
> +++ b/merge-strategies.c
> @@ -1,9 +1,34 @@
> #include "cache.h"
> +#include "cache-tree.h"
> #include "dir.h"
> #include "entry.h"
> +#include "lockfile.h"
> #include "merge-strategies.h"
> +#include "unpack-trees.h"
> #include "xdiff-interface.h"
>
> +static int check_index_is_head(struct repository *r, const char *head_arg)
> +{
> + struct commit *head_commit;
> + struct tree *head_tree;
> + struct object_id head;
> + struct strbuf sb = STRBUF_INIT;
> +
> + get_oid(head_arg, &head);
> + head_commit = lookup_commit_reference(r, &head);
> + head_tree = repo_get_commit_tree(r, head_commit);Can this all be replaced by a call to parse_tree_indirect(), we should also handle an invalid HEAD.
Show 5 quoted lines
> +
> + if (repo_index_has_changes(r, head_tree, &sb)) {
> + error(_("Your local changes to the following files "
> + "would be overwritten by merge:\n %s"),
> + sb.buf);This matches the script but I wonder why that did not check for unstaged changes.
> +int merge_strategies_resolve(struct repository *r, > + struct commit_list *bases, const char *head_arg,
As well as the commit vs tree comments above I think that we should be getting the callers to parse head rather than passing a string. Both builtin/merge.c and sequencer.c have a struct commit we can use.
Show 26 quoted lines
> + struct commit_list *remote)
> +{
> + struct tree_desc t[MAX_UNPACK_TREES];
> + struct commit_list *i;
> + struct lock_file lock = LOCK_INIT;
> + int nr = 0, ret = 0;
> +
> + /* Abort if index does not match head */
> + if (check_index_is_head(r, head_arg))
> + return 2;
> +
> + /*
> + * Give up if we are given two or more remotes. Not handling
> + * octopus.
> + */
> + if (remote && remote->next)
> + return 2;
> +
> + /* Give up if this is a baseless merge. */
> + if (!bases)
> + return 2;
> +
> + puts(_("Trying simple merge."));
> +
> + for (i = bases; i && i->item; i = i->next) {
> + if (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))This needs to check that we're not overrunning the end of t as builtin/read-tree.c:list_trees() does.
Except for the tree issue the conversion of the script looks correct to me and you have been careful to preserve the exit values.
Best Wishes
Phillip
Show 54 quoted lines
> + return 2;
> + }
> +
> + if (head_arg) {
> + struct object_id head;
> + struct tree *tree;
> +
> + get_oid(head_arg, &head);
> + tree = parse_tree_indirect(&head);
> +
> + if (add_tree(tree, t + (nr++)))
> + return 2;
> + }
> +
> + if (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))
> + return 2;
> +
> + repo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);
> +
> + if (merge_trees(r, t, nr, 1)) {
> + rollback_lock_file(&lock);
> + return 2;
> + }
> +
> + if (write_tree(r)) {
> + puts(_("Simple merge failed, trying Automatic merge."));
> + ret = merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);
> + }
> +
> + if (write_locked_index(r->index, &lock, COMMIT_LOCK))
> + return !!error(_("unable to write new index file"));
> + return !!ret;
> +}
> diff --git a/merge-strategies.h b/merge-strategies.h
> index 8705a550ca..bba4bf999c 100644
> --- a/merge-strategies.h
> +++ b/merge-strategies.h
> @@ -1,6 +1,7 @@
> #ifndef MERGE_STRATEGIES_H
> #define MERGE_STRATEGIES_H
>
> +#include "commit.h"
> #include "object.h"
>
> int merge_three_way(struct index_state *istate,
> @@ -28,4 +29,8 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,
> int merge_all_index(struct index_state *istate, int oneshot, int quiet,
> merge_fn fn, void *data);
>
> +int merge_strategies_resolve(struct repository *r,
> + struct commit_list *bases, const char *head_arg,
> + struct commit_list *remote);
> +
> #endif /* MERGE_STRATEGIES_H */