Merge branch 'sb/submodule-merge-in-merge-recursive'
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 May 2018 12:51:27 +0000 (21:51 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 May 2018 12:51:27 +0000 (21:51 +0900)
By code restructuring of submodule merge in merge-recursive,
informational messages from the codepath are now given using the
same mechanism as other output, and honor the merge.verbosity
configuration. The code also learned to give a few new messages
when a submodule three-way merge resolves cleanly when one side
records a descendant of the commit chosen by the other side.

* sb/submodule-merge-in-merge-recursive:
merge-recursive: give notice when submodule commit gets fast-forwarded
merge-recursive: i18n submodule merge output and respect verbosity
submodule.c: move submodule merging to merge-recursive.c

merge-recursive.c
submodule.c
submodule.h
index e64b4b9d031afff282bf37c7c373d29e11157835..cf5341828f53e5dbb4d3221e4db85b1295165b95 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;
@@ -1082,6 +1083,185 @@ 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);
+               if (show(o, 3)) {
+                       output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
+                       output_commit_title(o, commit_b);
+               } else if (show(o, 2))
+                       output(o, 2, _("Fast-forwarding submodule %s to %s"), path, oid_to_hex(b));
+               else
+                       ; /* no output */
+
+               return 1;
+       }
+       if (in_merge_bases(commit_b, commit_a)) {
+               oidcpy(result, a);
+               if (show(o, 3)) {
+                       output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
+                       output_commit_title(o, commit_a);
+               } else if (show(o, 2))
+                       output(o, 2, _("Fast-forwarding submodule %s to %s"), path, oid_to_hex(a));
+               else
+                       ; /* no output */
+
+               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,
@@ -1145,12 +1325,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:
index 47d005797e3951ac1878d9bdb1ac8de99b392464..edc3a206d7cb3b23c7779ddd48595df6b927c2f2 100644 (file)
@@ -153,7 +153,8 @@ void stage_updated_gitmodules(struct index_state *istate)
                die(_("staging updated .gitmodules failed"));
 }
 
-static int add_submodule_odb(const char *path)
+/* TODO: remove this function, use repo_submodule_init instead. */
+int add_submodule_odb(const char *path)
 {
        struct strbuf objects_directory = STRBUF_INIT;
        int ret = 0;
@@ -1700,171 +1701,6 @@ int submodule_move_head(const char *path,
        return ret;
 }
 
-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);
-}
-
-#define MERGE_WARNING(path, msg) \
-       warning("Failed to merge submodule %s (%s)", path, msg);
-
-int merge_submodule(struct object_id *result, const char *path,
-                   const struct object_id *base, const struct object_id *a,
-                   const struct object_id *b, int search)
-{
-       struct commit *commit_base, *commit_a, *commit_b;
-       int parent_count;
-       struct object_array merges;
-
-       int i;
-
-       /* 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)) {
-               MERGE_WARNING(path, "not checked out");
-               return 0;
-       }
-
-       if (!(commit_base = lookup_commit_reference(base)) ||
-           !(commit_a = lookup_commit_reference(a)) ||
-           !(commit_b = lookup_commit_reference(b))) {
-               MERGE_WARNING(path, "commits not present");
-               return 0;
-       }
-
-       /* check whether both changes are forward */
-       if (!in_merge_bases(commit_base, commit_a) ||
-           !in_merge_bases(commit_base, commit_b)) {
-               MERGE_WARNING(path, "commits don't follow merge-base");
-               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:
-               MERGE_WARNING(path, "merge following commits not found");
-               break;
-
-       case 1:
-               MERGE_WARNING(path, "not fast-forward");
-               fprintf(stderr, "Found a possible merge resolution "
-                               "for the submodule:\n");
-               print_commit((struct commit *) merges.objects[0].item);
-               fprintf(stderr,
-                       "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:
-               MERGE_WARNING(path, "multiple merges found");
-               for (i = 0; i < merges.nr; i++)
-                       print_commit((struct commit *) merges.objects[i].item);
-       }
-
-       object_array_clear(&merges);
-       return 0;
-}
-
 /*
  * Embeds a single submodules git directory into the superprojects git dir,
  * non recursively.
index aae0c9c8fffebb5bc256f6d6bf2cef3590d80bfc..a2871d0312c6a3b0afa515a9d8c80a01ffc9e0d0 100644 (file)
@@ -89,10 +89,8 @@ extern int submodule_uses_gitfile(const char *path);
 #define SUBMODULE_REMOVAL_IGNORE_UNTRACKED (1<<1)
 #define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)
 extern int bad_to_remove_submodule(const char *path, unsigned flags);
-extern int merge_submodule(struct object_id *result, const char *path,
-                          const struct object_id *base,
-                          const struct object_id *a,
-                          const struct object_id *b, int search);
+
+int add_submodule_odb(const char *path);
 
 /* Checks if there are submodule changes in a..b. */
 extern int submodule_touches_in_range(struct object_id *a,