Skip to content

Commit 2b9afea

Browse files
committed
Merge branch 'js/rebase-merge-octopus'
"git rebase --rebase-merges" mode now handles octopus merges as well. * js/rebase-merge-octopus: rebase --rebase-merges: adjust man page for octopus support rebase --rebase-merges: add support for octopus merges merge: allow reading the merge commit message from a file
2 parents 87ece7c + caafecf commit 2b9afea

File tree

5 files changed

+204
-47
lines changed

5 files changed

+204
-47
lines changed

Documentation/git-merge.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ SYNOPSIS
1212
'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
1313
[-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
1414
[--[no-]allow-unrelated-histories]
15-
[--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]
15+
[--[no-]rerere-autoupdate] [-m <msg>] [-F <file>] [<commit>...]
1616
'git merge' --abort
1717
'git merge' --continue
1818

@@ -75,6 +75,14 @@ The 'git fmt-merge-msg' command can be
7575
used to give a good default for automated 'git merge'
7676
invocations. The automated message can include the branch description.
7777

78+
-F <file>::
79+
--file=<file>::
80+
Read the commit message to be used for the merge commit (in
81+
case one is created).
82+
+
83+
If `--log` is specified, a shortlog of the commits being merged
84+
will be appended to the specified message.
85+
7886
--[no-]rerere-autoupdate::
7987
Allow the rerere mechanism to update the index with the
8088
result of auto-conflict resolution if possible.

Documentation/git-rebase.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -960,8 +960,8 @@ rescheduled immediately, with a helpful message how to edit the todo list
960960
(this typically happens when a `reset` command was inserted into the todo
961961
list manually and contains a typo).
962962

963-
The `merge` command will merge the specified revision into whatever is
964-
HEAD at that time. With `-C <original-commit>`, the commit message of
963+
The `merge` command will merge the specified revision(s) into whatever
964+
is HEAD at that time. With `-C <original-commit>`, the commit message of
965965
the specified merge commit will be used. When the `-C` is changed to
966966
a lower-case `-c`, the message will be opened in an editor after a
967967
successful merge so that the user can edit the message.
@@ -970,7 +970,8 @@ If a `merge` command fails for any reason other than merge conflicts (i.e.
970970
when the merge operation did not even start), it is rescheduled immediately.
971971

972972
At this time, the `merge` command will *always* use the `recursive`
973-
merge strategy, with no way to choose a different one. To work around
973+
merge strategy for regular merges, and `octopus` for octopus merges,
974+
strategy, with no way to choose a different one. To work around
974975
this, an `exec` command can be used to call `git merge` explicitly,
975976
using the fact that the labels are worktree-local refs (the ref
976977
`refs/rewritten/onto` would correspond to the label `onto`, for example).

builtin/merge.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,35 @@ static int option_parse_message(const struct option *opt,
111111
return 0;
112112
}
113113

114+
static int option_read_message(struct parse_opt_ctx_t *ctx,
115+
const struct option *opt, int unset)
116+
{
117+
struct strbuf *buf = opt->value;
118+
const char *arg;
119+
120+
if (unset)
121+
BUG("-F cannot be negated");
122+
123+
if (ctx->opt) {
124+
arg = ctx->opt;
125+
ctx->opt = NULL;
126+
} else if (ctx->argc > 1) {
127+
ctx->argc--;
128+
arg = *++ctx->argv;
129+
} else
130+
return opterror(opt, "requires a value", 0);
131+
132+
if (buf->len)
133+
strbuf_addch(buf, '\n');
134+
if (ctx->prefix && !is_absolute_path(arg))
135+
arg = prefix_filename(ctx->prefix, arg);
136+
if (strbuf_read_file(buf, arg, 0) < 0)
137+
return error(_("could not read file '%s'"), arg);
138+
have_message = 1;
139+
140+
return 0;
141+
}
142+
114143
static struct strategy *get_strategy(const char *name)
115144
{
116145
int i;
@@ -228,6 +257,9 @@ static struct option builtin_merge_options[] = {
228257
OPT_CALLBACK('m', "message", &merge_msg, N_("message"),
229258
N_("merge commit message (for a non-fast-forward merge)"),
230259
option_parse_message),
260+
{ OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"),
261+
N_("read message from file"), PARSE_OPT_NONEG,
262+
(parse_opt_cb *) option_read_message },
231263
OPT__VERBOSITY(&verbosity),
232264
OPT_BOOL(0, "abort", &abort_current_merge,
233265
N_("abort the current in-progress merge")),

sequencer.c

Lines changed: 125 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2850,6 +2850,26 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)
28502850
return ret;
28512851
}
28522852

2853+
static struct commit *lookup_label(const char *label, int len,
2854+
struct strbuf *buf)
2855+
{
2856+
struct commit *commit;
2857+
2858+
strbuf_reset(buf);
2859+
strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
2860+
commit = lookup_commit_reference_by_name(buf->buf);
2861+
if (!commit) {
2862+
/* fall back to non-rewritten ref or commit */
2863+
strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
2864+
commit = lookup_commit_reference_by_name(buf->buf);
2865+
}
2866+
2867+
if (!commit)
2868+
error(_("could not resolve '%s'"), buf->buf);
2869+
2870+
return commit;
2871+
}
2872+
28532873
static int do_merge(struct commit *commit, const char *arg, int arg_len,
28542874
int flags, struct replay_opts *opts)
28552875
{
@@ -2858,8 +2878,9 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
28582878
struct strbuf ref_name = STRBUF_INIT;
28592879
struct commit *head_commit, *merge_commit, *i;
28602880
struct commit_list *bases, *j, *reversed = NULL;
2881+
struct commit_list *to_merge = NULL, **tail = &to_merge;
28612882
struct merge_options o;
2862-
int merge_arg_len, oneline_offset, can_fast_forward, ret;
2883+
int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
28632884
static struct lock_file lock;
28642885
const char *p;
28652886

@@ -2874,26 +2895,34 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
28742895
goto leave_merge;
28752896
}
28762897

2877-
oneline_offset = arg_len;
2878-
merge_arg_len = strcspn(arg, " \t\n");
2879-
p = arg + merge_arg_len;
2880-
p += strspn(p, " \t\n");
2881-
if (*p == '#' && (!p[1] || isspace(p[1]))) {
2882-
p += 1 + strspn(p + 1, " \t\n");
2883-
oneline_offset = p - arg;
2884-
} else if (p - arg < arg_len)
2885-
BUG("octopus merges are not supported yet: '%s'", p);
2886-
2887-
strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
2888-
merge_commit = lookup_commit_reference_by_name(ref_name.buf);
2889-
if (!merge_commit) {
2890-
/* fall back to non-rewritten ref or commit */
2891-
strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
2892-
merge_commit = lookup_commit_reference_by_name(ref_name.buf);
2898+
/*
2899+
* For octopus merges, the arg starts with the list of revisions to be
2900+
* merged. The list is optionally followed by '#' and the oneline.
2901+
*/
2902+
merge_arg_len = oneline_offset = arg_len;
2903+
for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) {
2904+
if (!*p)
2905+
break;
2906+
if (*p == '#' && (!p[1] || isspace(p[1]))) {
2907+
p += 1 + strspn(p + 1, " \t\n");
2908+
oneline_offset = p - arg;
2909+
break;
2910+
}
2911+
k = strcspn(p, " \t\n");
2912+
if (!k)
2913+
continue;
2914+
merge_commit = lookup_label(p, k, &ref_name);
2915+
if (!merge_commit) {
2916+
ret = error(_("unable to parse '%.*s'"), k, p);
2917+
goto leave_merge;
2918+
}
2919+
tail = &commit_list_insert(merge_commit, tail)->next;
2920+
p += k;
2921+
merge_arg_len = p - arg;
28932922
}
28942923

2895-
if (!merge_commit) {
2896-
ret = error(_("could not resolve '%s'"), ref_name.buf);
2924+
if (!to_merge) {
2925+
ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
28972926
goto leave_merge;
28982927
}
28992928

@@ -2904,8 +2933,13 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
29042933
* "[new root]", let's simply fast-forward to the merge head.
29052934
*/
29062935
rollback_lock_file(&lock);
2907-
ret = fast_forward_to(&merge_commit->object.oid,
2908-
&head_commit->object.oid, 0, opts);
2936+
if (to_merge->next)
2937+
ret = error(_("octopus merge cannot be executed on "
2938+
"top of a [new root]"));
2939+
else
2940+
ret = fast_forward_to(&to_merge->item->object.oid,
2941+
&head_commit->object.oid, 0,
2942+
opts);
29092943
goto leave_merge;
29102944
}
29112945

@@ -2941,7 +2975,8 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
29412975
p = arg + oneline_offset;
29422976
len = arg_len - oneline_offset;
29432977
} else {
2944-
strbuf_addf(&buf, "Merge branch '%.*s'",
2978+
strbuf_addf(&buf, "Merge %s '%.*s'",
2979+
to_merge->next ? "branches" : "branch",
29452980
merge_arg_len, arg);
29462981
p = buf.buf;
29472982
len = buf.len;
@@ -2965,28 +3000,76 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
29653000
&head_commit->object.oid);
29663001

29673002
/*
2968-
* If the merge head is different from the original one, we cannot
3003+
* If any merge head is different from the original one, we cannot
29693004
* fast-forward.
29703005
*/
29713006
if (can_fast_forward) {
2972-
struct commit_list *second_parent = commit->parents->next;
3007+
struct commit_list *p = commit->parents->next;
29733008

2974-
if (second_parent && !second_parent->next &&
2975-
oidcmp(&merge_commit->object.oid,
2976-
&second_parent->item->object.oid))
3009+
for (j = to_merge; j && p; j = j->next, p = p->next)
3010+
if (oidcmp(&j->item->object.oid,
3011+
&p->item->object.oid)) {
3012+
can_fast_forward = 0;
3013+
break;
3014+
}
3015+
/*
3016+
* If the number of merge heads differs from the original merge
3017+
* commit, we cannot fast-forward.
3018+
*/
3019+
if (j || p)
29773020
can_fast_forward = 0;
29783021
}
29793022

2980-
if (can_fast_forward && commit->parents->next &&
2981-
!commit->parents->next->next &&
2982-
!oidcmp(&commit->parents->next->item->object.oid,
2983-
&merge_commit->object.oid)) {
3023+
if (can_fast_forward) {
29843024
rollback_lock_file(&lock);
29853025
ret = fast_forward_to(&commit->object.oid,
29863026
&head_commit->object.oid, 0, opts);
29873027
goto leave_merge;
29883028
}
29893029

3030+
if (to_merge->next) {
3031+
/* Octopus merge */
3032+
struct child_process cmd = CHILD_PROCESS_INIT;
3033+
3034+
if (read_env_script(&cmd.env_array)) {
3035+
const char *gpg_opt = gpg_sign_opt_quoted(opts);
3036+
3037+
ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
3038+
goto leave_merge;
3039+
}
3040+
3041+
cmd.git_cmd = 1;
3042+
argv_array_push(&cmd.args, "merge");
3043+
argv_array_push(&cmd.args, "-s");
3044+
argv_array_push(&cmd.args, "octopus");
3045+
argv_array_push(&cmd.args, "--no-edit");
3046+
argv_array_push(&cmd.args, "--no-ff");
3047+
argv_array_push(&cmd.args, "--no-log");
3048+
argv_array_push(&cmd.args, "--no-stat");
3049+
argv_array_push(&cmd.args, "-F");
3050+
argv_array_push(&cmd.args, git_path_merge_msg(the_repository));
3051+
if (opts->gpg_sign)
3052+
argv_array_push(&cmd.args, opts->gpg_sign);
3053+
3054+
/* Add the tips to be merged */
3055+
for (j = to_merge; j; j = j->next)
3056+
argv_array_push(&cmd.args,
3057+
oid_to_hex(&j->item->object.oid));
3058+
3059+
strbuf_release(&ref_name);
3060+
unlink(git_path_cherry_pick_head(the_repository));
3061+
rollback_lock_file(&lock);
3062+
3063+
rollback_lock_file(&lock);
3064+
ret = run_command(&cmd);
3065+
3066+
/* force re-reading of the cache */
3067+
if (!ret && (discard_cache() < 0 || read_cache() < 0))
3068+
ret = error(_("could not read index"));
3069+
goto leave_merge;
3070+
}
3071+
3072+
merge_commit = to_merge->item;
29903073
write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
29913074
git_path_merge_head(the_repository), 0);
29923075
write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
@@ -3049,6 +3132,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
30493132
leave_merge:
30503133
strbuf_release(&ref_name);
30513134
rollback_lock_file(&lock);
3135+
free_commit_list(to_merge);
30523136
return ret;
30533137
}
30543138

@@ -3905,7 +3989,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
39053989
*/
39063990
while ((commit = get_revision(revs))) {
39073991
struct commit_list *to_merge;
3908-
int is_octopus;
39093992
const char *p1, *p2;
39103993
struct object_id *oid;
39113994
int is_empty;
@@ -3937,11 +4020,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
39374020
continue;
39384021
}
39394022

3940-
is_octopus = to_merge && to_merge->next;
3941-
3942-
if (is_octopus)
3943-
BUG("Octopus merges not yet supported");
3944-
39454023
/* Create a label */
39464024
strbuf_reset(&label);
39474025
if (skip_prefix(oneline.buf, "Merge ", &p1) &&
@@ -3963,13 +4041,17 @@ static int make_script_with_merges(struct pretty_print_context *pp,
39634041
strbuf_addf(&buf, "%s -C %s",
39644042
cmd_merge, oid_to_hex(&commit->object.oid));
39654043

3966-
/* label the tip of merged branch */
3967-
oid = &to_merge->item->object.oid;
3968-
strbuf_addch(&buf, ' ');
4044+
/* label the tips of merged branches */
4045+
for (; to_merge; to_merge = to_merge->next) {
4046+
oid = &to_merge->item->object.oid;
4047+
strbuf_addch(&buf, ' ');
4048+
4049+
if (!oidset_contains(&interesting, oid)) {
4050+
strbuf_addstr(&buf, label_oid(oid, NULL,
4051+
&state));
4052+
continue;
4053+
}
39694054

3970-
if (!oidset_contains(&interesting, oid))
3971-
strbuf_addstr(&buf, label_oid(oid, NULL, &state));
3972-
else {
39734055
tips_tail = &commit_list_insert(to_merge->item,
39744056
tips_tail)->next;
39754057

t/t3430-rebase-merges.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,38 @@ test_expect_success 'labels that are object IDs are rewritten' '
329329
! grep "^label $third$" .git/ORIGINAL-TODO
330330
'
331331

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="[email protected]" \
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+
332366
test_done

0 commit comments

Comments
 (0)