]> git.ipfire.org Git - thirdparty/git.git/blobdiff - merge-recursive.c
merge-recursive: i18n submodule merge output and respect verbosity
[thirdparty/git.git] / merge-recursive.c
index 869092f7b9bc0ce1104a1c43c7201acdee2eb4f8..0571919ee0afd63679210bcfdbc3da656576d6c0 100644 (file)
@@ -23,6 +23,7 @@
 #include "merge-recursive.h"
 #include "dir.h"
 #include "submodule.h"
+#include "revision.h"
 
 struct path_hashmap_entry {
        struct hashmap_entry e;
@@ -228,7 +229,7 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
                strbuf_addf(&o->obuf, "virtual %s\n",
                        merge_remote_util(commit)->name);
        else {
-               strbuf_add_unique_abbrev(&o->obuf, commit->object.oid.hash,
+               strbuf_add_unique_abbrev(&o->obuf, &commit->object.oid,
                                         DEFAULT_ABBREV);
                strbuf_addch(&o->obuf, ' ');
                if (parse_commit(commit) != 0)
@@ -335,7 +336,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
        return result;
 }
 
-static int save_files_dirs(const unsigned char *sha1,
+static int save_files_dirs(const struct object_id *oid,
                struct strbuf *base, const char *path,
                unsigned int mode, int stage, void *context)
 {
@@ -370,12 +371,12 @@ static struct stage_data *insert_stage_data(const char *path,
 {
        struct string_list_item *item;
        struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
-       get_tree_entry(o->object.oid.hash, path,
-                       e->stages[1].oid.hash, &e->stages[1].mode);
-       get_tree_entry(a->object.oid.hash, path,
-                       e->stages[2].oid.hash, &e->stages[2].mode);
-       get_tree_entry(b->object.oid.hash, path,
-                       e->stages[3].oid.hash, &e->stages[3].mode);
+       get_tree_entry(&o->object.oid, path,
+                       &e->stages[1].oid, &e->stages[1].mode);
+       get_tree_entry(&a->object.oid, path,
+                       &e->stages[2].oid, &e->stages[2].mode);
+       get_tree_entry(&b->object.oid, path,
+                       &e->stages[3].oid, &e->stages[3].mode);
        item = string_list_insert(entries, path);
        item->util = e;
        return e;
@@ -842,7 +843,7 @@ static int update_file_flags(struct merge_options *o,
                        goto update_index;
                }
 
-               buf = read_sha1_file(oid->hash, &type, &size);
+               buf = read_object_file(oid, &type, &size);
                if (!buf)
                        return err(o, _("cannot read object %s '%s'"), oid_to_hex(oid), path);
                if (type != OBJ_BLOB) {
@@ -977,6 +978,169 @@ static int merge_3way(struct merge_options *o,
        return merge_status;
 }
 
+static int find_first_merges(struct object_array *result, const char *path,
+               struct commit *a, struct commit *b)
+{
+       int i, j;
+       struct object_array merges = OBJECT_ARRAY_INIT;
+       struct commit *commit;
+       int contains_another;
+
+       char merged_revision[42];
+       const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
+                                  "--all", merged_revision, NULL };
+       struct rev_info revs;
+       struct setup_revision_opt rev_opts;
+
+       memset(result, 0, sizeof(struct object_array));
+       memset(&rev_opts, 0, sizeof(rev_opts));
+
+       /* get all revisions that merge commit a */
+       xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
+                       oid_to_hex(&a->object.oid));
+       init_revisions(&revs, NULL);
+       rev_opts.submodule = path;
+       /* FIXME: can't handle linked worktrees in submodules yet */
+       revs.single_worktree = path != NULL;
+       setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
+
+       /* save all revisions from the above list that contain b */
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       while ((commit = get_revision(&revs)) != NULL) {
+               struct object *o = &(commit->object);
+               if (in_merge_bases(b, commit))
+                       add_object_array(o, NULL, &merges);
+       }
+       reset_revision_walk();
+
+       /* Now we've got all merges that contain a and b. Prune all
+        * merges that contain another found merge and save them in
+        * result.
+        */
+       for (i = 0; i < merges.nr; i++) {
+               struct commit *m1 = (struct commit *) merges.objects[i].item;
+
+               contains_another = 0;
+               for (j = 0; j < merges.nr; j++) {
+                       struct commit *m2 = (struct commit *) merges.objects[j].item;
+                       if (i != j && in_merge_bases(m2, m1)) {
+                               contains_another = 1;
+                               break;
+                       }
+               }
+
+               if (!contains_another)
+                       add_object_array(merges.objects[i].item, NULL, result);
+       }
+
+       object_array_clear(&merges);
+       return result->nr;
+}
+
+static void print_commit(struct commit *commit)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
+       ctx.date_mode.type = DATE_NORMAL;
+       format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+       fprintf(stderr, "%s\n", sb.buf);
+       strbuf_release(&sb);
+}
+
+static int merge_submodule(struct merge_options *o,
+                          struct object_id *result, const char *path,
+                          const struct object_id *base, const struct object_id *a,
+                          const struct object_id *b)
+{
+       struct commit *commit_base, *commit_a, *commit_b;
+       int parent_count;
+       struct object_array merges;
+
+       int i;
+       int search = !o->call_depth;
+
+       /* store a in result in case we fail */
+       oidcpy(result, a);
+
+       /* we can not handle deletion conflicts */
+       if (is_null_oid(base))
+               return 0;
+       if (is_null_oid(a))
+               return 0;
+       if (is_null_oid(b))
+               return 0;
+
+       if (add_submodule_odb(path)) {
+               output(o, 1, _("Failed to merge submodule %s (not checked out)"), path);
+               return 0;
+       }
+
+       if (!(commit_base = lookup_commit_reference(base)) ||
+           !(commit_a = lookup_commit_reference(a)) ||
+           !(commit_b = lookup_commit_reference(b))) {
+               output(o, 1, _("Failed to merge submodule %s (commits not present)"), path);
+               return 0;
+       }
+
+       /* check whether both changes are forward */
+       if (!in_merge_bases(commit_base, commit_a) ||
+           !in_merge_bases(commit_base, commit_b)) {
+               output(o, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path);
+               return 0;
+       }
+
+       /* Case #1: a is contained in b or vice versa */
+       if (in_merge_bases(commit_a, commit_b)) {
+               oidcpy(result, b);
+               return 1;
+       }
+       if (in_merge_bases(commit_b, commit_a)) {
+               oidcpy(result, a);
+               return 1;
+       }
+
+       /*
+        * Case #2: There are one or more merges that contain a and b in
+        * the submodule. If there is only one, then present it as a
+        * suggestion to the user, but leave it marked unmerged so the
+        * user needs to confirm the resolution.
+        */
+
+       /* Skip the search if makes no sense to the calling context.  */
+       if (!search)
+               return 0;
+
+       /* find commit which merges them */
+       parent_count = find_first_merges(&merges, path, commit_a, commit_b);
+       switch (parent_count) {
+       case 0:
+               output(o, 1, _("Failed to merge submodule %s (merge following commits not found)"), path);
+               break;
+
+       case 1:
+               output(o, 1, _("Failed to merge submodule %s (not fast-forward)"), path);
+               output(o, 2, _("Found a possible merge resolution for the submodule:\n"));
+               print_commit((struct commit *) merges.objects[0].item);
+               output(o, 2, _(
+                       "If this is correct simply add it to the index "
+                       "for example\n"
+                       "by using:\n\n"
+                       "  git update-index --cacheinfo 160000 %s \"%s\"\n\n"
+                       "which will accept this suggestion.\n"),
+                       oid_to_hex(&merges.objects[0].item->oid), path);
+               break;
+
+       default:
+               output(o, 1, _("Failed to merge submodule %s (multiple merges found)"), path);
+               for (i = 0; i < merges.nr; i++)
+                       print_commit((struct commit *) merges.objects[i].item);
+       }
+
+       object_array_clear(&merges);
+       return 0;
+}
+
 static int merge_file_1(struct merge_options *o,
                                           const struct diff_filespec *one,
                                           const struct diff_filespec *a,
@@ -1039,12 +1203,11 @@ static int merge_file_1(struct merge_options *o,
                                return ret;
                        result->clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
-                       result->clean = merge_submodule(&result->oid,
+                       result->clean = merge_submodule(o, &result->oid,
                                                       one->path,
                                                       &one->oid,
                                                       &a->oid,
-                                                      &b->oid,
-                                                      !o->call_depth);
+                                                      &b->oid);
                } else if (S_ISLNK(a->mode)) {
                        switch (o->recursive_variant) {
                        case MERGE_RECURSIVE_NORMAL:
@@ -1656,7 +1819,7 @@ static int read_oid_strbuf(struct merge_options *o,
        void *buf;
        enum object_type type;
        unsigned long size;
-       buf = read_sha1_file(oid->hash, &type, &size);
+       buf = read_object_file(oid, &type, &size);
        if (!buf)
                return err(o, _("cannot read object %s"), oid_to_hex(oid));
        if (type != OBJ_BLOB) {