Skip to content

Commit 385f6e2

Browse files
dschogitster
authored andcommitted
# This is a combination of 2 commits. # This is the 1st commit message:
sequencer: introduce the `merge` command This patch is part of the effort to reimplement `--preserve-merges` with a substantially improved design, a design that has been developed in the Git for Windows project to maintain the dozens of Windows-specific patch series on top of upstream Git. The previous patch implemented the `label` and `reset` commands to label commits and to reset to labeled commits. This patch adds the `merge` command, with the following syntax: merge [-C <commit>] <rev> # <oneline> The <commit> parameter in this instance is the *original* merge commit, whose author and message will be used for the merge commit that is about to be created. The <rev> parameter refers to the (possibly rewritten) revision to merge. Let's see an example of a todo list: label onto # Branch abc reset onto pick deadbee Hello, world! label abc reset onto pick cafecafe And now for something completely different merge -C baaabaaa abc # Merge the branch 'abc' into master To edit the merge commit's message (a "reword" for merges, if you will), use `-c` (lower-case) instead of `-C`; this convention was borrowed from `git commit` that also supports `-c` and `-C` with similar meanings. To create *new* merges, i.e. without copying the commit message from an existing commit, simply omit the `-C <commit>` parameter (which will open an editor for the merge message): merge abc This comes in handy when splitting a branch into two or more branches. Note: this patch only adds support for recursive merges, to keep things simple. Support for octopus merges will be added later in a separate patch series, support for merges using strategies other than the recursive merge is left for the future. Signed-off-by: Johannes Schindelin <[email protected]> # The commit message #2 will be skipped: # fixup! sequencer: introduce the `merge` command Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3b53182 commit 385f6e2

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

git-rebase--interactive.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ x, exec <commit> = run command (the rest of the line) using shell
164164
d, drop <commit> = remove commit
165165
l, label <label> = label current HEAD with a name
166166
t, reset <label> = reset HEAD to a label
167+
m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
168+
. create a merge commit using the original merge commit's
169+
. message (or the oneline, if no original merge commit was
170+
. specified). Use -c <commit> to reword the commit message.
167171
168172
These lines can be re-ordered; they are executed from top to bottom.
169173
" | git stripspace --comment-lines >>"$todo"

sequencer.c

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,7 @@ enum todo_command {
13051305
TODO_EXEC,
13061306
TODO_LABEL,
13071307
TODO_RESET,
1308+
TODO_MERGE,
13081309
/* commands that do nothing but are counted for reporting progress */
13091310
TODO_NOOP,
13101311
TODO_DROP,
@@ -1325,6 +1326,7 @@ static struct {
13251326
{ 'x', "exec" },
13261327
{ 'l', "label" },
13271328
{ 't', "reset" },
1329+
{ 'm', "merge" },
13281330
{ 0, "noop" },
13291331
{ 'd', "drop" },
13301332
{ 0, NULL }
@@ -1752,9 +1754,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)
17521754
return 0;
17531755
}
17541756

1757+
enum todo_item_flags {
1758+
TODO_EDIT_MERGE_MSG = 1
1759+
};
1760+
17551761
struct todo_item {
17561762
enum todo_command command;
17571763
struct commit *commit;
1764+
unsigned int flags;
17581765
const char *arg;
17591766
int arg_len;
17601767
size_t offset_in_buf;
@@ -1789,6 +1796,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
17891796
char *end_of_object_name;
17901797
int i, saved, status, padding;
17911798

1799+
item->flags = 0;
1800+
17921801
/* left-trim */
17931802
bol += strspn(bol, " \t");
17941803

@@ -1838,6 +1847,21 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
18381847
return 0;
18391848
}
18401849

1850+
if (item->command == TODO_MERGE) {
1851+
if (skip_prefix(bol, "-C", &bol))
1852+
bol += strspn(bol, " \t");
1853+
else if (skip_prefix(bol, "-c", &bol)) {
1854+
bol += strspn(bol, " \t");
1855+
item->flags |= TODO_EDIT_MERGE_MSG;
1856+
} else {
1857+
item->flags |= TODO_EDIT_MERGE_MSG;
1858+
item->commit = NULL;
1859+
item->arg = bol;
1860+
item->arg_len = (int)(eol - bol);
1861+
return 0;
1862+
}
1863+
}
1864+
18411865
end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
18421866
saved = *end_of_object_name;
18431867
*end_of_object_name = '\0';
@@ -2646,6 +2670,153 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)
26462670
return ret;
26472671
}
26482672

2673+
static int do_merge(struct commit *commit, const char *arg, int arg_len,
2674+
int flags, struct replay_opts *opts)
2675+
{
2676+
int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
2677+
EDIT_MSG | VERIFY_MSG : 0;
2678+
struct strbuf ref_name = STRBUF_INIT;
2679+
struct commit *head_commit, *merge_commit, *i;
2680+
struct commit_list *bases, *j, *reversed = NULL;
2681+
struct merge_options o;
2682+
int merge_arg_len, oneline_offset, ret;
2683+
static struct lock_file lock;
2684+
const char *p;
2685+
2686+
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
2687+
ret = -1;
2688+
goto leave_merge;
2689+
}
2690+
2691+
head_commit = lookup_commit_reference_by_name("HEAD");
2692+
if (!head_commit) {
2693+
ret = error(_("cannot merge without a current revision"));
2694+
goto leave_merge;
2695+
}
2696+
2697+
oneline_offset = arg_len;
2698+
merge_arg_len = strcspn(arg, " \t\n");
2699+
p = arg + merge_arg_len;
2700+
p += strspn(p, " \t\n");
2701+
if (*p == '#' && (!p[1] || isspace(p[1]))) {
2702+
p += 1 + strspn(p + 1, " \t\n");
2703+
oneline_offset = p - arg;
2704+
} else if (p - arg < arg_len)
2705+
BUG("octopus merges are not supported yet: '%s'", p);
2706+
2707+
strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
2708+
merge_commit = lookup_commit_reference_by_name(ref_name.buf);
2709+
if (!merge_commit) {
2710+
/* fall back to non-rewritten ref or commit */
2711+
strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
2712+
merge_commit = lookup_commit_reference_by_name(ref_name.buf);
2713+
}
2714+
2715+
if (!merge_commit) {
2716+
ret = error(_("could not resolve '%s'"), ref_name.buf);
2717+
goto leave_merge;
2718+
}
2719+
2720+
if (commit) {
2721+
const char *message = get_commit_buffer(commit, NULL);
2722+
const char *body;
2723+
int len;
2724+
2725+
if (!message) {
2726+
ret = error(_("could not get commit message of '%s'"),
2727+
oid_to_hex(&commit->object.oid));
2728+
goto leave_merge;
2729+
}
2730+
write_author_script(message);
2731+
find_commit_subject(message, &body);
2732+
len = strlen(body);
2733+
ret = write_message(body, len, git_path_merge_msg(), 0);
2734+
unuse_commit_buffer(commit, message);
2735+
if (ret) {
2736+
error_errno(_("could not write '%s'"),
2737+
git_path_merge_msg());
2738+
goto leave_merge;
2739+
}
2740+
} else {
2741+
struct strbuf buf = STRBUF_INIT;
2742+
int len;
2743+
2744+
strbuf_addf(&buf, "author %s", git_author_info(0));
2745+
write_author_script(buf.buf);
2746+
strbuf_reset(&buf);
2747+
2748+
if (oneline_offset < arg_len) {
2749+
p = arg + oneline_offset;
2750+
len = arg_len - oneline_offset;
2751+
} else {
2752+
strbuf_addf(&buf, "Merge branch '%.*s'",
2753+
merge_arg_len, arg);
2754+
p = buf.buf;
2755+
len = buf.len;
2756+
}
2757+
2758+
ret = write_message(p, len, git_path_merge_msg(), 0);
2759+
strbuf_release(&buf);
2760+
if (ret) {
2761+
error_errno(_("could not write '%s'"),
2762+
git_path_merge_msg());
2763+
goto leave_merge;
2764+
}
2765+
}
2766+
2767+
write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
2768+
git_path_merge_head(), 0);
2769+
write_message("no-ff", 5, git_path_merge_mode(), 0);
2770+
2771+
bases = get_merge_bases(head_commit, merge_commit);
2772+
for (j = bases; j; j = j->next)
2773+
commit_list_insert(j->item, &reversed);
2774+
free_commit_list(bases);
2775+
2776+
read_cache();
2777+
init_merge_options(&o);
2778+
o.branch1 = "HEAD";
2779+
o.branch2 = ref_name.buf;
2780+
o.buffer_output = 2;
2781+
2782+
ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);
2783+
if (ret <= 0)
2784+
fputs(o.obuf.buf, stdout);
2785+
strbuf_release(&o.obuf);
2786+
if (ret < 0) {
2787+
error(_("could not even attempt to merge '%.*s'"),
2788+
merge_arg_len, arg);
2789+
goto leave_merge;
2790+
}
2791+
/*
2792+
* The return value of merge_recursive() is 1 on clean, and 0 on
2793+
* unclean merge.
2794+
*
2795+
* Let's reverse that, so that do_merge() returns 0 upon success and
2796+
* 1 upon failed merge (keeping the return value -1 for the cases where
2797+
* we will want to reschedule the `merge` command).
2798+
*/
2799+
ret = !ret;
2800+
2801+
if (active_cache_changed &&
2802+
write_locked_index(&the_index, &lock, COMMIT_LOCK)) {
2803+
ret = error(_("merge: Unable to write new index file"));
2804+
goto leave_merge;
2805+
}
2806+
2807+
rollback_lock_file(&lock);
2808+
if (ret)
2809+
rerere(opts->allow_rerere_auto);
2810+
else
2811+
ret = run_git_commit(git_path_merge_msg(), opts,
2812+
run_commit_flags);
2813+
2814+
leave_merge:
2815+
strbuf_release(&ref_name);
2816+
rollback_lock_file(&lock);
2817+
return ret;
2818+
}
2819+
26492820
static int is_final_fixup(struct todo_list *todo_list)
26502821
{
26512822
int i = todo_list->current;
@@ -2852,6 +3023,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
28523023
} else if (item->command == TODO_RESET) {
28533024
if ((res = do_reset(item->arg, item->arg_len, opts)))
28543025
goto reschedule;
3026+
} else if (item->command == TODO_MERGE) {
3027+
res = do_merge(item->commit, item->arg, item->arg_len,
3028+
item->flags, opts);
3029+
if (res < 0)
3030+
goto reschedule;
28553031
} else if (!is_noop(item->command))
28563032
return error(_("unknown command %d"), item->command);
28573033

@@ -3334,8 +3510,16 @@ int transform_todos(unsigned flags)
33343510
short_commit_name(item->commit) :
33353511
oid_to_hex(&item->commit->object.oid);
33363512

3513+
if (item->command == TODO_MERGE) {
3514+
if (item->flags & TODO_EDIT_MERGE_MSG)
3515+
strbuf_addstr(&buf, " -c");
3516+
else
3517+
strbuf_addstr(&buf, " -C");
3518+
}
3519+
33373520
strbuf_addf(&buf, " %s", oid);
33383521
}
3522+
33393523
/* add all the rest */
33403524
if (!item->arg_len)
33413525
strbuf_addch(&buf, '\n');

0 commit comments

Comments
 (0)