rebase --rebase-merges: add support for octopus merges

Previously, we introduced the `merge` command for use in todo lists, to allow to recreate and modify branch topology. For ease of implementation, and to make review easier, the initial implementation only supported merge commits with exactly two parents. This patch adds support for octopus merges, making use of the just-introduced `-F <file>` option for the `git merge` command: to keep things simple, we spawn a new Git command instead of trying to call a library function, also opening an easier door to enhance `rebase --rebase-merges` to optionally use a merge strategy different from `recursive` for regular merges: this feature would use the same code path as octopus merges and simply spawn a `git merge`. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Dec 21, 2017 at 15:52 UTC 2b6ad0f4bca508a04e2b6e940e37f7d98c1a8696
2 files changed +159 -43
sequencer.c
+125 -43
@@ -2841,6 +2841,26 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)
2841 return ret;
2842 }
2843
2844 +static struct commit *lookup_label(const char *label, int len,
2845 + struct strbuf *buf)
2846 +{
2847 + struct commit *commit;
2848 +
2849 + strbuf_reset(buf);
2850 + strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
2851 + commit = lookup_commit_reference_by_name(buf->buf);
2852 + if (!commit) {
2853 + /* fall back to non-rewritten ref or commit */
2854 + strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
2855 + commit = lookup_commit_reference_by_name(buf->buf);
2856 + }
2857 +
2858 + if (!commit)
2859 + error(_("could not resolve '%s'"), buf->buf);
2860 +
2861 + return commit;
2862 +}
2863 +
2864 static int do_merge(struct commit *commit, const char *arg, int arg_len,
2865 int flags, struct replay_opts *opts)
2866 {
@@ -2849,8 +2869,9 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
2869 struct strbuf ref_name = STRBUF_INIT;
2870 struct commit *head_commit, *merge_commit, *i;
2871 struct commit_list *bases, *j, *reversed = NULL;
2872 + struct commit_list *to_merge = NULL, **tail = &to_merge;
2873 struct merge_options o;
2853 - int merge_arg_len, oneline_offset, can_fast_forward, ret;
2874 + int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
2875 static struct lock_file lock;
2876 const char *p;
2877
@@ -2865,26 +2886,34 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
2886 goto leave_merge;
2887 }
2888
2868 - oneline_offset = arg_len;
2869 - merge_arg_len = strcspn(arg, " \t\n");
2870 - p = arg + merge_arg_len;
2871 - p += strspn(p, " \t\n");
2872 - if (*p == '#' && (!p[1] || isspace(p[1]))) {
2873 - p += 1 + strspn(p + 1, " \t\n");
2874 - oneline_offset = p - arg;
2875 - } else if (p - arg < arg_len)
2876 - BUG("octopus merges are not supported yet: '%s'", p);
2877 -
2878 - strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
2879 - merge_commit = lookup_commit_reference_by_name(ref_name.buf);
2880 - if (!merge_commit) {
2881 - /* fall back to non-rewritten ref or commit */
2882 - strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
2883 - merge_commit = lookup_commit_reference_by_name(ref_name.buf);
2889 + /*
2890 + * For octopus merges, the arg starts with the list of revisions to be
2891 + * merged. The list is optionally followed by '#' and the oneline.
2892 + */
2893 + merge_arg_len = oneline_offset = arg_len;
2894 + for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) {
2895 + if (!*p)
2896 + break;
2897 + if (*p == '#' && (!p[1] || isspace(p[1]))) {
2898 + p += 1 + strspn(p + 1, " \t\n");
2899 + oneline_offset = p - arg;
2900 + break;
2901 + }
2902 + k = strcspn(p, " \t\n");
2903 + if (!k)
2904 + continue;
2905 + merge_commit = lookup_label(p, k, &ref_name);
2906 + if (!merge_commit) {
2907 + ret = error(_("unable to parse '%.*s'"), k, p);
2908 + goto leave_merge;
2909 + }
2910 + tail = &commit_list_insert(merge_commit, tail)->next;
2911 + p += k;
2912 + merge_arg_len = p - arg;
2913 }
2914
2886 - if (!merge_commit) {
2887 - ret = error(_("could not resolve '%s'"), ref_name.buf);
2915 + if (!to_merge) {
2916 + ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
2917 goto leave_merge;
2918 }
2919
@@ -2895,8 +2924,13 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
2924 * "[new root]", let's simply fast-forward to the merge head.
2925 */
2926 rollback_lock_file(&lock);
2898 - ret = fast_forward_to(&merge_commit->object.oid,
2899 - &head_commit->object.oid, 0, opts);
2927 + if (to_merge->next)
2928 + ret = error(_("octopus merge cannot be executed on "
2929 + "top of a [new root]"));
2930 + else
2931 + ret = fast_forward_to(&to_merge->item->object.oid,
2932 + &head_commit->object.oid, 0,
2933 + opts);
2934 goto leave_merge;
2935 }
2936
@@ -2932,7 +2966,8 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
2966 p = arg + oneline_offset;
2967 len = arg_len - oneline_offset;
2968 } else {
2935 - strbuf_addf(&buf, "Merge branch '%.*s'",
2969 + strbuf_addf(&buf, "Merge %s '%.*s'",
2970 + to_merge->next ? "branches" : "branch",
2971 merge_arg_len, arg);
2972 p = buf.buf;
2973 len = buf.len;
@@ -2956,28 +2991,76 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
2991 &head_commit->object.oid);
2992
2993 /*
2959 - * If the merge head is different from the original one, we cannot
2994 + * If any merge head is different from the original one, we cannot
2995 * fast-forward.
2996 */
2997 if (can_fast_forward) {
2963 - struct commit_list *second_parent = commit->parents->next;
2998 + struct commit_list *p = commit->parents->next;
2999
2965 - if (second_parent && !second_parent->next &&
2966 - oidcmp(&merge_commit->object.oid,
2967 - &second_parent->item->object.oid))
3000 + for (j = to_merge; j && p; j = j->next, p = p->next)
3001 + if (oidcmp(&j->item->object.oid,
3002 + &p->item->object.oid)) {
3003 + can_fast_forward = 0;
3004 + break;
3005 + }
3006 + /*
3007 + * If the number of merge heads differs from the original merge
3008 + * commit, we cannot fast-forward.
3009 + */
3010 + if (j || p)
3011 can_fast_forward = 0;
3012 }
3013
2971 - if (can_fast_forward && commit->parents->next &&
2972 - !commit->parents->next->next &&
2973 - !oidcmp(&commit->parents->next->item->object.oid,
2974 - &merge_commit->object.oid)) {
3014 + if (can_fast_forward) {
3015 rollback_lock_file(&lock);
3016 ret = fast_forward_to(&commit->object.oid,
3017 &head_commit->object.oid, 0, opts);
3018 goto leave_merge;
3019 }
3020
3021 + if (to_merge->next) {
3022 + /* Octopus merge */
3023 + struct child_process cmd = CHILD_PROCESS_INIT;
3024 +
3025 + if (read_env_script(&cmd.env_array)) {
3026 + const char *gpg_opt = gpg_sign_opt_quoted(opts);
3027 +
3028 + ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
3029 + goto leave_merge;
3030 + }
3031 +
3032 + cmd.git_cmd = 1;
3033 + argv_array_push(&cmd.args, "merge");
3034 + argv_array_push(&cmd.args, "-s");
3035 + argv_array_push(&cmd.args, "octopus");
3036 + argv_array_push(&cmd.args, "--no-edit");
3037 + argv_array_push(&cmd.args, "--no-ff");
3038 + argv_array_push(&cmd.args, "--no-log");
3039 + argv_array_push(&cmd.args, "--no-stat");
3040 + argv_array_push(&cmd.args, "-F");
3041 + argv_array_push(&cmd.args, git_path_merge_msg());
3042 + if (opts->gpg_sign)
3043 + argv_array_push(&cmd.args, opts->gpg_sign);
3044 +
3045 + /* Add the tips to be merged */
3046 + for (j = to_merge; j; j = j->next)
3047 + argv_array_push(&cmd.args,
3048 + oid_to_hex(&j->item->object.oid));
3049 +
3050 + strbuf_release(&ref_name);
3051 + unlink(git_path_cherry_pick_head());
3052 + rollback_lock_file(&lock);
3053 +
3054 + rollback_lock_file(&lock);
3055 + ret = run_command(&cmd);
3056 +
3057 + /* force re-reading of the cache */
3058 + if (!ret && (discard_cache() < 0 || read_cache() < 0))
3059 + ret = error(_("could not read index"));
3060 + goto leave_merge;
3061 + }
3062 +
3063 + merge_commit = to_merge->item;
3064 write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
3065 git_path_merge_head(), 0);
3066 write_message("no-ff", 5, git_path_merge_mode(), 0);
@@ -3040,6 +3123,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
3123 leave_merge:
3124 strbuf_release(&ref_name);
3125 rollback_lock_file(&lock);
3126 + free_commit_list(to_merge);
3127 return ret;
3128 }
3129
@@ -3877,7 +3961,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
3961 */
3962 while ((commit = get_revision(revs))) {
3963 struct commit_list *to_merge;
3880 - int is_octopus;
3964 const char *p1, *p2;
3965 struct object_id *oid;
3966 int is_empty;
@@ -3909,11 +3992,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
3992 continue;
3993 }
3994
3912 - is_octopus = to_merge && to_merge->next;
3913 -
3914 - if (is_octopus)
3915 - BUG("Octopus merges not yet supported");
3916 -
3995 /* Create a label */
3996 strbuf_reset(&label);
3997 if (skip_prefix(oneline.buf, "Merge ", &p1) &&
@@ -3935,13 +4013,17 @@ static int make_script_with_merges(struct pretty_print_context *pp,
4013 strbuf_addf(&buf, "%s -C %s",
4014 cmd_merge, oid_to_hex(&commit->object.oid));
4015
3938 - /* label the tip of merged branch */
3939 - oid = &to_merge->item->object.oid;
3940 - strbuf_addch(&buf, ' ');
4016 + /* label the tips of merged branches */
4017 + for (; to_merge; to_merge = to_merge->next) {
4018 + oid = &to_merge->item->object.oid;
4019 + strbuf_addch(&buf, ' ');
4020 +
4021 + if (!oidset_contains(&interesting, oid)) {
4022 + strbuf_addstr(&buf, label_oid(oid, NULL,
4023 + &state));
4024 + continue;
4025 + }
4026
3942 - if (!oidset_contains(&interesting, oid))
3943 - strbuf_addstr(&buf, label_oid(oid, NULL, &state));
3944 - else {
4027 tips_tail = &commit_list_insert(to_merge->item,
4028 tips_tail)->next;
4029
t/t3430-rebase-merges.sh
+34
@@ -329,4 +329,38 @@ test_expect_success 'labels that are object IDs are rewritten' '
329 ! grep "^label $third$" .git/ORIGINAL-TODO
330 '
331
332 +test_expect_success 'octopus merges' '
333 + git checkout -b three &&
334 + test_commit before-octopus &&
335 + test_commit three &&
336 + git checkout -b two HEAD^ &&
337 + test_commit two &&
338 + git checkout -b one HEAD^ &&
339 + test_commit one &&
340 + test_tick &&
341 + (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
342 + git merge -m "Tüntenfüsch" two three) &&
343 +
344 + : fast forward if possible &&
345 + before="$(git rev-parse --verify HEAD)" &&
346 + test_tick &&
347 + git rebase -i -r HEAD^^ &&
348 + test_cmp_rev HEAD $before &&
349 +
350 + test_tick &&
351 + git rebase -i --force -r HEAD^^ &&
352 + test "Hank" = "$(git show -s --format=%an HEAD)" &&
353 + test "$before" != $(git rev-parse HEAD) &&
354 + test_cmp_graph HEAD^^.. <<-\EOF
355 + *-. Tüntenfüsch
356 + |\ \
357 + | | * three
358 + | * | two
359 + | |/
360 + * | one
361 + |/
362 + o before-octopus
363 + EOF
364 +'
365 +
366 test_done