Merge branch 'js/rebase-merge-octopus'
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:44 +0000 (15:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:44 +0000 (15:30 -0700)
"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

1  2 
Documentation/git-rebase.txt
builtin/merge.c
sequencer.c
index a7850415b1a831eb3d1530dbc71e194b27bd8d59,c4bcd24bb5503d20d2f4c931412ed7504e0e20c9..1fbc6ebcde0945fe6bc8cc85e5fd31247c4879e4
@@@ -243,15 -243,11 +243,15 @@@ leave out at most one of A and B, in wh
  --keep-empty::
        Keep the commits that do not change anything from its
        parents in the result.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  --allow-empty-message::
        By default, rebasing commits with an empty message will fail.
        This option overrides that behavior, allowing commits with empty
        messages to be rebased.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  --skip::
        Restart the rebasing process by skipping the current patch.
@@@ -275,8 -271,6 +275,8 @@@ branch on top of the <upstream> branch
  conflict happens, the side reported as 'ours' is the so-far rebased
  series, starting with <upstream>, and 'theirs' is the working branch.  In
  other words, the sides are swapped.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  -s <strategy>::
  --strategy=<strategy>::
  +
  Because 'git rebase' replays each commit from the working branch
  on top of the <upstream> branch using the given strategy, using
 -the 'ours' strategy simply discards all patches from the <branch>,
 +the 'ours' strategy simply empties all patches from the <branch>,
  which makes little sense.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  -X <strategy-option>::
  --strategy-option=<strategy-option>::
        This implies `--merge` and, if no strategy has been
        specified, `-s recursive`.  Note the reversal of 'ours' and
        'theirs' as noted above for the `-m` option.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  -S[<keyid>]::
  --gpg-sign[=<keyid>]::
        and after each change.  When fewer lines of surrounding
        context exist they all must match.  By default no context is
        ever ignored.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
 --f::
 +--no-ff::
  --force-rebase::
 -      Force a rebase even if the current branch is up to date and
 -      the command without `--force` would return without doing anything.
 +-f::
 +      Individually replay all rebased commits instead of fast-forwarding
 +      over the unchanged ones.  This ensures that the entire history of
 +      the rebased branch is composed of new commits.
  +
 -You may find this (or --no-ff with an interactive rebase) helpful after
 -reverting a topic branch merge, as this option recreates the topic branch with
 -fresh commits so it can be remerged successfully without needing to "revert
 -the reversion" (see the
 -link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details).
 +You may find this helpful after reverting a topic branch merge, as this option
 +recreates the topic branch with fresh commits so it can be remerged
 +successfully without needing to "revert the reversion" (see the
 +link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for
 +details).
  
  --fork-point::
  --no-fork-point::
@@@ -369,22 -355,19 +369,22 @@@ default is `--no-fork-point`, otherwis
  --whitespace=<option>::
        These flag are passed to the 'git apply' program
        (see linkgit:git-apply[1]) that applies the patch.
 -      Incompatible with the --interactive option.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  --committer-date-is-author-date::
  --ignore-date::
        These flags are passed to 'git am' to easily change the dates
        of the rebased commits (see linkgit:git-am[1]).
 -      Incompatible with the --interactive option.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  --signoff::
        Add a Signed-off-by: trailer to all the rebased commits. Note
        that if `--interactive` is given then only commits marked to be
 -      picked, edited or reworded will have the trailer added. Incompatible
 -      with the `--preserve-merges` option.
 +      picked, edited or reworded will have the trailer added.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  -i::
  --interactive::
  The commit list format can be changed by setting the configuration option
  rebase.instructionFormat.  A customized instruction format will automatically
  have the long commit hash prepended to the format.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  -r::
  --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
@@@ -423,7 -404,7 +423,7 @@@ It is currently only possible to recrea
  `recursive` merge strategy; Different merge strategies can be used only via
  explicit `exec git merge -s <strategy> [...]` commands.
  +
 -See also REBASING MERGES below.
 +See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
  
  -p::
  --preserve-merges::
  This uses the `--interactive` machinery internally, but combining it
  with the `--interactive` option explicitly is generally not a good
  idea unless you know what you are doing (see BUGS below).
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  -x <cmd>::
  --exec <cmd>::
@@@ -458,8 -437,6 +458,8 @@@ squash/fixup series
  +
  This uses the `--interactive` machinery internally, but it can be run
  without an explicit `--interactive`.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  --root::
        Rebase all commits reachable from <branch>, instead of
        When used together with both --onto and --preserve-merges,
        'all' root commits will be rewritten to have <newbase> as parent
        instead.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  --autosquash::
  --no-autosquash::
        too.  The recommended way to create fixup/squash commits is by using
        the `--fixup`/`--squash` options of linkgit:git-commit[1].
  +
 -This option is only valid when the `--interactive` option is used.
 -+
  If the `--autosquash` option is enabled by default using the
  configuration variable `rebase.autoSquash`, this option can be
  used to override and disable this setting.
 ++
 +See also INCOMPATIBLE OPTIONS below.
  
  --autostash::
  --no-autostash::
        with care: the final stash application after a successful
        rebase might result in non-trivial conflicts.
  
 ---no-ff::
 -      With --interactive, cherry-pick all rebased commits instead of
 -      fast-forwarding over the unchanged ones.  This ensures that the
 -      entire history of the rebased branch is composed of new commits.
 -+
 -Without --interactive, this is a synonym for --force-rebase.
 -+
 -You may find this helpful after reverting a topic branch merge, as this option
 -recreates the topic branch with fresh commits so it can be remerged
 -successfully without needing to "revert the reversion" (see the
 -link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details).
 +INCOMPATIBLE OPTIONS
 +--------------------
 +
 +git-rebase has many flags that are incompatible with each other,
 +predominantly due to the fact that it has three different underlying
 +implementations:
 +
 + * one based on linkgit:git-am[1] (the default)
 + * one based on git-merge-recursive (merge backend)
 + * one based on linkgit:git-cherry-pick[1] (interactive backend)
 +
 +Flags only understood by the am backend:
 +
 + * --committer-date-is-author-date
 + * --ignore-date
 + * --whitespace
 + * --ignore-whitespace
 + * -C
 +
 +Flags understood by both merge and interactive backends:
 +
 + * --merge
 + * --strategy
 + * --strategy-option
 + * --allow-empty-message
 +
 +Flags only understood by the interactive backend:
 +
 + * --[no-]autosquash
 + * --rebase-merges
 + * --preserve-merges
 + * --interactive
 + * --exec
 + * --keep-empty
 + * --autosquash
 + * --edit-todo
 + * --root when used in combination with --onto
 +
 +Other incompatible flag pairs:
 +
 + * --preserve-merges and --interactive
 + * --preserve-merges and --signoff
 + * --preserve-merges and --rebase-merges
 + * --rebase-merges and --strategy
 + * --rebase-merges and --strategy-option
 +
 +BEHAVIORAL DIFFERENCES
 +-----------------------
 +
 + * empty commits:
 +
 +    am-based rebase will drop any "empty" commits, whether the
 +    commit started empty (had no changes relative to its parent to
 +    start with) or ended empty (all changes were already applied
 +    upstream in other commits).
 +
 +    merge-based rebase does the same.
 +
 +    interactive-based rebase will by default drop commits that
 +    started empty and halt if it hits a commit that ended up empty.
 +    The `--keep-empty` option exists for interactive rebases to allow
 +    it to keep commits that started empty.
 +
 +  * directory rename detection:
 +
 +    merge-based and interactive-based rebases work fine with
 +    directory rename detection.  am-based rebases sometimes do not.
  
  include::merge-strategies.txt[]
  
@@@ -885,7 -804,7 +885,7 @@@ The ripple effect of a "hard case" reco
  case" recovery too!
  
  REBASING MERGES
 ------------------
 +---------------
  
  The interactive rebase command was originally designed to handle
  individual patch series. As such, it makes sense to exclude merge
@@@ -960,8 -879,8 +960,8 @@@ rescheduled immediately, with a helpfu
  (this typically happens when a `reset` command was inserted into the todo
  list manually and contains a typo).
  
- The `merge` command will merge the specified revision into whatever is
- HEAD at that time. With `-C <original-commit>`, the commit message of
+ The `merge` command will merge the specified revision(s) into whatever
is HEAD at that time. With `-C <original-commit>`, the commit message of
  the specified merge commit will be used. When the `-C` is changed to
  a lower-case `-c`, the message will be opened in an editor after a
  successful merge so that the user can edit the message.
@@@ -970,7 -889,8 +970,8 @@@ If a `merge` command fails for any reas
  when the merge operation did not even start), it is rescheduled immediately.
  
  At this time, the `merge` command will *always* use the `recursive`
- merge strategy, with no way to choose a different one. To work around
+ merge strategy for regular merges, and `octopus` for octopus merges,
+ strategy, with no way to choose a different one. To work around
  this, an `exec` command can be used to call `git merge` explicitly,
  using the fact that the labels are worktree-local refs (the ref
  `refs/rewritten/onto` would correspond to the label `onto`, for example).
diff --combined builtin/merge.c
index 299a75425900a10b16dcba1e1f3cadba50eb791b,b0e90775117f9ba1a0ccce039499bbcc61166150..77e1694a785f533f2953727897fee8a19fb74d4e
@@@ -111,6 -111,35 +111,35 @@@ static int option_parse_message(const s
        return 0;
  }
  
+ static int option_read_message(struct parse_opt_ctx_t *ctx,
+                              const struct option *opt, int unset)
+ {
+       struct strbuf *buf = opt->value;
+       const char *arg;
+       if (unset)
+               BUG("-F cannot be negated");
+       if (ctx->opt) {
+               arg = ctx->opt;
+               ctx->opt = NULL;
+       } else if (ctx->argc > 1) {
+               ctx->argc--;
+               arg = *++ctx->argv;
+       } else
+               return opterror(opt, "requires a value", 0);
+       if (buf->len)
+               strbuf_addch(buf, '\n');
+       if (ctx->prefix && !is_absolute_path(arg))
+               arg = prefix_filename(ctx->prefix, arg);
+       if (strbuf_read_file(buf, arg, 0) < 0)
+               return error(_("could not read file '%s'"), arg);
+       have_message = 1;
+       return 0;
+ }
  static struct strategy *get_strategy(const char *name)
  {
        int i;
@@@ -228,6 -257,9 +257,9 @@@ static struct option builtin_merge_opti
        OPT_CALLBACK('m', "message", &merge_msg, N_("message"),
                N_("merge commit message (for a non-fast-forward merge)"),
                option_parse_message),
+       { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"),
+               N_("read message from file"), PARSE_OPT_NONEG,
+               (parse_opt_cb *) option_read_message },
        OPT__VERBOSITY(&verbosity),
        OPT_BOOL(0, "abort", &abort_current_merge,
                N_("abort the current in-progress merge")),
  /* Cleans up metadata that is uninteresting after a succeeded merge. */
  static void drop_save(void)
  {
 -      unlink(git_path_merge_head());
 -      unlink(git_path_merge_msg());
 -      unlink(git_path_merge_mode());
 +      unlink(git_path_merge_head(the_repository));
 +      unlink(git_path_merge_msg(the_repository));
 +      unlink(git_path_merge_mode(the_repository));
  }
  
  static int save_state(struct object_id *stash)
@@@ -382,7 -414,7 +414,7 @@@ static void squash_message(struct commi
                        oid_to_hex(&commit->object.oid));
                pretty_print_commit(&ctx, commit, &out);
        }
 -      write_file_buf(git_path_squash_msg(), out.buf, out.len);
 +      write_file_buf(git_path_squash_msg(the_repository), out.buf, out.len);
        strbuf_release(&out);
  }
  
@@@ -741,7 -773,7 +773,7 @@@ static void add_strategies(const char *
  
  static void read_merge_msg(struct strbuf *msg)
  {
 -      const char *filename = git_path_merge_msg();
 +      const char *filename = git_path_merge_msg(the_repository);
        strbuf_reset(msg);
        if (strbuf_read_file(msg, filename, 0) < 0)
                die_errno(_("Could not read from '%s'"), filename);
@@@ -778,18 -810,18 +810,18 @@@ static void prepare_to_commit(struct co
        if (signoff)
                append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);
        write_merge_heads(remoteheads);
 -      write_file_buf(git_path_merge_msg(), msg.buf, msg.len);
 +      write_file_buf(git_path_merge_msg(the_repository), msg.buf, msg.len);
        if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg",
 -                          git_path_merge_msg(), "merge", NULL))
 +                          git_path_merge_msg(the_repository), "merge", NULL))
                abort_commit(remoteheads, NULL);
        if (0 < option_edit) {
 -              if (launch_editor(git_path_merge_msg(), NULL, NULL))
 +              if (launch_editor(git_path_merge_msg(the_repository), NULL, NULL))
                        abort_commit(remoteheads, NULL);
        }
  
        if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(),
                                          "commit-msg",
 -                                        git_path_merge_msg(), NULL))
 +                                        git_path_merge_msg(the_repository), NULL))
                abort_commit(remoteheads, NULL);
  
        read_merge_msg(&msg);
@@@ -859,7 -891,7 +891,7 @@@ static int suggest_conflicts(void
        FILE *fp;
        struct strbuf msgbuf = STRBUF_INIT;
  
 -      filename = git_path_merge_msg();
 +      filename = git_path_merge_msg(the_repository);
        fp = xfopen(filename, "a");
  
        append_conflicts_hint(&msgbuf);
@@@ -942,12 -974,12 +974,12 @@@ static void write_merge_heads(struct co
                }
                strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
        }
 -      write_file_buf(git_path_merge_head(), buf.buf, buf.len);
 +      write_file_buf(git_path_merge_head(the_repository), buf.buf, buf.len);
  
        strbuf_reset(&buf);
        if (fast_forward == FF_NO)
                strbuf_addstr(&buf, "no-ff");
 -      write_file_buf(git_path_merge_mode(), buf.buf, buf.len);
 +      write_file_buf(git_path_merge_mode(the_repository), buf.buf, buf.len);
        strbuf_release(&buf);
  }
  
@@@ -955,8 -987,7 +987,8 @@@ static void write_merge_state(struct co
  {
        write_merge_heads(remoteheads);
        strbuf_addch(&merge_msg, '\n');
 -      write_file_buf(git_path_merge_msg(), merge_msg.buf, merge_msg.len);
 +      write_file_buf(git_path_merge_msg(the_repository), merge_msg.buf,
 +                     merge_msg.len);
  }
  
  static int default_edit_option(void)
@@@ -1035,12 -1066,11 +1067,12 @@@ static void handle_fetch_head(struct co
        const char *filename;
        int fd, pos, npos;
        struct strbuf fetch_head_file = STRBUF_INIT;
 +      const unsigned hexsz = the_hash_algo->hexsz;
  
        if (!merge_names)
                merge_names = &fetch_head_file;
  
 -      filename = git_path_fetch_head();
 +      filename = git_path_fetch_head(the_repository);
        fd = open(filename, O_RDONLY);
        if (fd < 0)
                die_errno(_("could not open '%s' for reading"), filename);
                else
                        npos = merge_names->len;
  
 -              if (npos - pos < GIT_SHA1_HEXSZ + 2 ||
 +              if (npos - pos < hexsz + 2 ||
                    get_oid_hex(merge_names->buf + pos, &oid))
                        commit = NULL; /* bad */
 -              else if (memcmp(merge_names->buf + pos + GIT_SHA1_HEXSZ, "\t\t", 2))
 +              else if (memcmp(merge_names->buf + pos + hexsz, "\t\t", 2))
                        continue; /* not-for-merge */
                else {
 -                      char saved = merge_names->buf[pos + GIT_SHA1_HEXSZ];
 -                      merge_names->buf[pos + GIT_SHA1_HEXSZ] = '\0';
 +                      char saved = merge_names->buf[pos + hexsz];
 +                      merge_names->buf[pos + hexsz] = '\0';
                        commit = get_merge_parent(merge_names->buf + pos);
 -                      merge_names->buf[pos + GIT_SHA1_HEXSZ] = saved;
 +                      merge_names->buf[pos + hexsz] = saved;
                }
                if (!commit) {
                        if (ptr)
@@@ -1215,7 -1245,7 +1247,7 @@@ int cmd_merge(int argc, const char **ar
                        usage_msg_opt(_("--abort expects no arguments"),
                              builtin_merge_usage, builtin_merge_options);
  
 -              if (!file_exists(git_path_merge_head()))
 +              if (!file_exists(git_path_merge_head(the_repository)))
                        die(_("There is no merge to abort (MERGE_HEAD missing)."));
  
                /* Invoke 'git reset --merge' */
                        usage_msg_opt(_("--continue expects no arguments"),
                              builtin_merge_usage, builtin_merge_options);
  
 -              if (!file_exists(git_path_merge_head()))
 +              if (!file_exists(git_path_merge_head(the_repository)))
                        die(_("There is no merge in progress (MERGE_HEAD missing)."));
  
                /* Invoke 'git commit' */
        if (read_cache_unmerged())
                die_resolve_conflict("merge");
  
 -      if (file_exists(git_path_merge_head())) {
 +      if (file_exists(git_path_merge_head(the_repository))) {
                /*
                 * There is no unmerged entry, don't advise 'git
                 * add/rm <file>', just 'git commit'.
                else
                        die(_("You have not concluded your merge (MERGE_HEAD exists)."));
        }
 -      if (file_exists(git_path_cherry_pick_head())) {
 +      if (file_exists(git_path_cherry_pick_head(the_repository))) {
                if (advice_resolve_conflict)
                        die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
                            "Please, commit your changes before you merge."));
diff --combined sequencer.c
index 5a068fd35116a1e664d7cefbd363e984be6cc0b6,bcc43699cd92fa517b06746a833e1181ff5e275d..f12b61fc935c9f85f47738fa922bad26a89d4043
@@@ -2,7 -2,6 +2,7 @@@
  #include "config.h"
  #include "lockfile.h"
  #include "dir.h"
 +#include "object-store.h"
  #include "object.h"
  #include "commit.h"
  #include "sequencer.h"
@@@ -63,12 -62,12 +63,12 @@@ static GIT_PATH_FUNC(rebase_path_done, 
   * The file to keep track of how many commands were already processed (e.g.
   * for the prompt).
   */
 -static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum");
 +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum")
  /*
   * The file to keep track of how many commands are to be processed in total
   * (e.g. for the prompt).
   */
 -static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end");
 +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end")
  /*
   * The commit message that is planned to be used for any changes that
   * need to be committed following a user interaction.
@@@ -358,7 -357,7 +358,7 @@@ static void print_advice(int show_hint
                 * (typically rebase --interactive) wants to take care
                 * of the commit itself so remove CHERRY_PICK_HEAD
                 */
 -              unlink(git_path_cherry_pick_head());
 +              unlink(git_path_cherry_pick_head(the_repository));
                return;
        }
  
@@@ -433,7 -432,7 +433,7 @@@ static int read_oneliner(struct strbuf 
  
  static struct tree *empty_tree(void)
  {
 -      return lookup_tree(the_hash_algo->empty_tree);
 +      return lookup_tree(the_repository, the_repository->hash_algo->empty_tree);
  }
  
  static int error_dirty_index(struct replay_opts *opts)
@@@ -594,7 -593,7 +594,7 @@@ static int is_index_unchanged(void
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
                return error(_("could not resolve HEAD commit"));
  
 -      head_commit = lookup_commit(&head_oid);
 +      head_commit = lookup_commit(the_repository, &head_oid);
  
        /*
         * If head_commit is NULL, check_commit, called from
@@@ -1101,7 -1100,7 +1101,7 @@@ void print_commit_summary(const char *p
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
  
 -      commit = lookup_commit(oid);
 +      commit = lookup_commit(the_repository, oid);
        if (!commit)
                die(_("couldn't look up newly created commit"));
        if (parse_commit(commit))
@@@ -1176,7 -1175,7 +1176,7 @@@ static int parse_head(struct commit **h
        if (get_oid("HEAD", &oid)) {
                current_head = NULL;
        } else {
 -              current_head = lookup_commit_reference(&oid);
 +              current_head = lookup_commit_reference(the_repository, &oid);
                if (!current_head)
                        return error(_("could not parse HEAD"));
                if (oidcmp(&oid, &current_head->object.oid)) {
@@@ -1325,8 -1324,8 +1325,8 @@@ static int do_commit(const char *msg_fi
                                    &oid);
                strbuf_release(&sb);
                if (!res) {
 -                      unlink(git_path_cherry_pick_head());
 -                      unlink(git_path_merge_msg());
 +                      unlink(git_path_cherry_pick_head(the_repository));
 +                      unlink(git_path_merge_msg(the_repository));
                        if (!is_rebase_i(opts))
                                print_commit_summary(NULL, &oid,
                                                SUMMARY_SHOW_AUTHOR_DATE);
@@@ -1511,7 -1510,7 +1511,7 @@@ static int update_squash_messages(enum 
  
                if (get_oid("HEAD", &head))
                        return error(_("need a HEAD to fixup"));
 -              if (!(head_commit = lookup_commit_reference(&head)))
 +              if (!(head_commit = lookup_commit_reference(the_repository, &head)))
                        return error(_("could not read HEAD"));
                if (!(head_message = get_commit_buffer(head_commit, NULL)))
                        return error(_("could not read HEAD's commit message"));
@@@ -1614,7 -1613,7 +1614,7 @@@ static int do_pick_commit(enum todo_com
                struct replay_opts *opts, int final_fixup)
  {
        unsigned int flags = opts->edit ? EDIT_MSG : 0;
 -      const char *msg_file = opts->edit ? NULL : git_path_merge_msg();
 +      const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository);
        struct object_id head;
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
                        flags |= CLEANUP_MSG;
                        msg_file = rebase_path_fixup_msg();
                } else {
 -                      const char *dest = git_path_squash_msg();
 +                      const char *dest = git_path_squash_msg(the_repository);
                        unlink(dest);
                        if (copy_file(dest, rebase_path_squash_msg(), 0666))
                                return error(_("could not rename '%s' to '%s'"),
                                             rebase_path_squash_msg(), dest);
 -                      unlink(git_path_merge_msg());
 +                      unlink(git_path_merge_msg(the_repository));
                        msg_file = dest;
                        flags |= EDIT_MSG;
                }
                        goto leave;
  
                res |= write_message(msgbuf.buf, msgbuf.len,
 -                                   git_path_merge_msg(), 0);
 +                                   git_path_merge_msg(the_repository), 0);
        } else {
                struct commit_list *common = NULL;
                struct commit_list *remotes = NULL;
  
                res = write_message(msgbuf.buf, msgbuf.len,
 -                                  git_path_merge_msg(), 0);
 +                                  git_path_merge_msg(the_repository), 0);
  
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
@@@ -1864,6 -1863,8 +1864,6 @@@ static int prepare_revs(struct replay_o
        if (prepare_revision_walk(opts->revs))
                return error(_("revision walk setup failed"));
  
 -      if (!opts->revs->commits)
 -              return error(_("empty commit set passed"));
        return 0;
  }
  
@@@ -2007,7 -2008,7 +2007,7 @@@ static int parse_insn_line(struct todo_
        if (status < 0)
                return -1;
  
 -      item->commit = lookup_commit_reference(&commit_oid);
 +      item->commit = lookup_commit_reference(the_repository, &commit_oid);
        return !item->commit;
  }
  
@@@ -2205,7 -2206,6 +2205,7 @@@ static int populate_opts_cb(const char 
  static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
  {
        int i;
 +      char *strategy_opts_string;
  
        strbuf_reset(buf);
        if (!read_oneliner(buf, rebase_path_strategy(), 0))
        if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
                return;
  
 -      opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts);
 +      strategy_opts_string = buf->buf;
 +      if (*strategy_opts_string == ' ')
 +              strategy_opts_string++;
 +      opts->xopts_nr = split_cmdline(strategy_opts_string,
 +                                     (const char ***)&opts->xopts);
        for (i = 0; i < opts->xopts_nr; i++) {
                const char *arg = opts->xopts[i];
  
@@@ -2321,10 -2317,6 +2321,10 @@@ static int walk_revs_populate_todo(stru
                        short_commit_name(commit), subject_len, subject);
                unuse_commit_buffer(commit, commit_buffer);
        }
 +
 +      if (!todo_list->nr)
 +              return error(_("empty commit set passed"));
 +
        return 0;
  }
  
@@@ -2402,8 -2394,8 +2402,8 @@@ static int rollback_single_pick(void
  {
        struct object_id head_oid;
  
 -      if (!file_exists(git_path_cherry_pick_head()) &&
 -          !file_exists(git_path_revert_head()))
 +      if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
 +          !file_exists(git_path_revert_head(the_repository)))
                return error(_("no cherry-pick or revert in progress"));
        if (read_ref_full("HEAD", 0, &head_oid, NULL))
                return error(_("cannot resolve HEAD"));
@@@ -2628,11 -2620,10 +2628,11 @@@ static int error_failed_squash(struct c
        if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
                return error(_("could not copy '%s' to '%s'"),
                        rebase_path_squash_msg(), rebase_path_message());
 -      unlink(git_path_merge_msg());
 -      if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
 +      unlink(git_path_merge_msg(the_repository));
 +      if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666))
                return error(_("could not copy '%s' to '%s'"),
 -                           rebase_path_message(), git_path_merge_msg());
 +                           rebase_path_message(),
 +                           git_path_merge_msg(the_repository));
        return error_with_patch(commit, subject, subject_len, opts, 1, 0);
  }
  
@@@ -2850,6 -2841,26 +2850,26 @@@ static int do_reset(const char *name, i
        return ret;
  }
  
+ static struct commit *lookup_label(const char *label, int len,
+                                  struct strbuf *buf)
+ {
+       struct commit *commit;
+       strbuf_reset(buf);
+       strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
+       commit = lookup_commit_reference_by_name(buf->buf);
+       if (!commit) {
+               /* fall back to non-rewritten ref or commit */
+               strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
+               commit = lookup_commit_reference_by_name(buf->buf);
+       }
+       if (!commit)
+               error(_("could not resolve '%s'"), buf->buf);
+       return commit;
+ }
  static int do_merge(struct commit *commit, const char *arg, int arg_len,
                    int flags, struct replay_opts *opts)
  {
        struct strbuf ref_name = STRBUF_INIT;
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j, *reversed = NULL;
+       struct commit_list *to_merge = NULL, **tail = &to_merge;
        struct merge_options o;
-       int merge_arg_len, oneline_offset, can_fast_forward, ret;
+       int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
        static struct lock_file lock;
        const char *p;
  
                goto leave_merge;
        }
  
-       oneline_offset = arg_len;
-       merge_arg_len = strcspn(arg, " \t\n");
-       p = arg + merge_arg_len;
-       p += strspn(p, " \t\n");
-       if (*p == '#' && (!p[1] || isspace(p[1]))) {
-               p += 1 + strspn(p + 1, " \t\n");
-               oneline_offset = p - arg;
-       } else if (p - arg < arg_len)
-               BUG("octopus merges are not supported yet: '%s'", p);
-       strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
-       merge_commit = lookup_commit_reference_by_name(ref_name.buf);
-       if (!merge_commit) {
-               /* fall back to non-rewritten ref or commit */
-               strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
-               merge_commit = lookup_commit_reference_by_name(ref_name.buf);
+       /*
+        * For octopus merges, the arg starts with the list of revisions to be
+        * merged. The list is optionally followed by '#' and the oneline.
+        */
+       merge_arg_len = oneline_offset = arg_len;
+       for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) {
+               if (!*p)
+                       break;
+               if (*p == '#' && (!p[1] || isspace(p[1]))) {
+                       p += 1 + strspn(p + 1, " \t\n");
+                       oneline_offset = p - arg;
+                       break;
+               }
+               k = strcspn(p, " \t\n");
+               if (!k)
+                       continue;
+               merge_commit = lookup_label(p, k, &ref_name);
+               if (!merge_commit) {
+                       ret = error(_("unable to parse '%.*s'"), k, p);
+                       goto leave_merge;
+               }
+               tail = &commit_list_insert(merge_commit, tail)->next;
+               p += k;
+               merge_arg_len = p - arg;
        }
  
-       if (!merge_commit) {
-               ret = error(_("could not resolve '%s'"), ref_name.buf);
+       if (!to_merge) {
+               ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
                goto leave_merge;
        }
  
                 * "[new root]", let's simply fast-forward to the merge head.
                 */
                rollback_lock_file(&lock);
-               ret = fast_forward_to(&merge_commit->object.oid,
-                                      &head_commit->object.oid, 0, opts);
+               if (to_merge->next)
+                       ret = error(_("octopus merge cannot be executed on "
+                                     "top of a [new root]"));
+               else
+                       ret = fast_forward_to(&to_merge->item->object.oid,
+                                             &head_commit->object.oid, 0,
+                                             opts);
                goto leave_merge;
        }
  
                write_author_script(message);
                find_commit_subject(message, &body);
                len = strlen(body);
 -              ret = write_message(body, len, git_path_merge_msg(), 0);
 +              ret = write_message(body, len, git_path_merge_msg(the_repository), 0);
                unuse_commit_buffer(commit, message);
                if (ret) {
                        error_errno(_("could not write '%s'"),
 -                                  git_path_merge_msg());
 +                                  git_path_merge_msg(the_repository));
                        goto leave_merge;
                }
        } else {
                        p = arg + oneline_offset;
                        len = arg_len - oneline_offset;
                } else {
-                       strbuf_addf(&buf, "Merge branch '%.*s'",
+                       strbuf_addf(&buf, "Merge %s '%.*s'",
+                                   to_merge->next ? "branches" : "branch",
                                    merge_arg_len, arg);
                        p = buf.buf;
                        len = buf.len;
                }
  
 -              ret = write_message(p, len, git_path_merge_msg(), 0);
 +              ret = write_message(p, len, git_path_merge_msg(the_repository), 0);
                strbuf_release(&buf);
                if (ret) {
                        error_errno(_("could not write '%s'"),
 -                                  git_path_merge_msg());
 +                                  git_path_merge_msg(the_repository));
                        goto leave_merge;
                }
        }
                        &head_commit->object.oid);
  
        /*
-        * If the merge head is different from the original one, we cannot
+        * If any merge head is different from the original one, we cannot
         * fast-forward.
         */
        if (can_fast_forward) {
-               struct commit_list *second_parent = commit->parents->next;
+               struct commit_list *p = commit->parents->next;
  
-               if (second_parent && !second_parent->next &&
-                   oidcmp(&merge_commit->object.oid,
-                          &second_parent->item->object.oid))
+               for (j = to_merge; j && p; j = j->next, p = p->next)
+                       if (oidcmp(&j->item->object.oid,
+                                  &p->item->object.oid)) {
+                               can_fast_forward = 0;
+                               break;
+                       }
+               /*
+                * If the number of merge heads differs from the original merge
+                * commit, we cannot fast-forward.
+                */
+               if (j || p)
                        can_fast_forward = 0;
        }
  
-       if (can_fast_forward && commit->parents->next &&
-           !commit->parents->next->next &&
-           !oidcmp(&commit->parents->next->item->object.oid,
-                   &merge_commit->object.oid)) {
+       if (can_fast_forward) {
                rollback_lock_file(&lock);
                ret = fast_forward_to(&commit->object.oid,
                                      &head_commit->object.oid, 0, opts);
                goto leave_merge;
        }
  
 -              argv_array_push(&cmd.args, git_path_merge_msg());
+       if (to_merge->next) {
+               /* Octopus merge */
+               struct child_process cmd = CHILD_PROCESS_INIT;
+               if (read_env_script(&cmd.env_array)) {
+                       const char *gpg_opt = gpg_sign_opt_quoted(opts);
+                       ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
+                       goto leave_merge;
+               }
+               cmd.git_cmd = 1;
+               argv_array_push(&cmd.args, "merge");
+               argv_array_push(&cmd.args, "-s");
+               argv_array_push(&cmd.args, "octopus");
+               argv_array_push(&cmd.args, "--no-edit");
+               argv_array_push(&cmd.args, "--no-ff");
+               argv_array_push(&cmd.args, "--no-log");
+               argv_array_push(&cmd.args, "--no-stat");
+               argv_array_push(&cmd.args, "-F");
 -              unlink(git_path_cherry_pick_head());
++              argv_array_push(&cmd.args, git_path_merge_msg(the_repository));
+               if (opts->gpg_sign)
+                       argv_array_push(&cmd.args, opts->gpg_sign);
+               /* Add the tips to be merged */
+               for (j = to_merge; j; j = j->next)
+                       argv_array_push(&cmd.args,
+                                       oid_to_hex(&j->item->object.oid));
+               strbuf_release(&ref_name);
++              unlink(git_path_cherry_pick_head(the_repository));
+               rollback_lock_file(&lock);
+               rollback_lock_file(&lock);
+               ret = run_command(&cmd);
+               /* force re-reading of the cache */
+               if (!ret && (discard_cache() < 0 || read_cache() < 0))
+                       ret = error(_("could not read index"));
+               goto leave_merge;
+       }
+       merge_commit = to_merge->item;
        write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
 -                    git_path_merge_head(), 0);
 -      write_message("no-ff", 5, git_path_merge_mode(), 0);
 +                    git_path_merge_head(the_repository), 0);
 +      write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
  
        bases = get_merge_bases(head_commit, merge_commit);
        if (bases && !oidcmp(&merge_commit->object.oid,
                 * value (a negative one would indicate that the `merge`
                 * command needs to be rescheduled).
                 */
 -              ret = !!run_git_commit(git_path_merge_msg(), opts,
 +              ret = !!run_git_commit(git_path_merge_msg(the_repository), opts,
                                     run_commit_flags);
  
  leave_merge:
        strbuf_release(&ref_name);
        rollback_lock_file(&lock);
+       free_commit_list(to_merge);
        return ret;
  }
  
@@@ -3226,27 -3301,10 +3310,27 @@@ static int pick_commits(struct todo_lis
                                        intend_to_amend();
                                return error_failed_squash(item->commit, opts,
                                        item->arg_len, item->arg);
 -                      } else if (res && is_rebase_i(opts) && item->commit)
 +                      } else if (res && is_rebase_i(opts) && item->commit) {
 +                              int to_amend = 0;
 +                              struct object_id oid;
 +
 +                              /*
 +                               * If we are rewording and have either
 +                               * fast-forwarded already, or are about to
 +                               * create a new root commit, we want to amend,
 +                               * otherwise we do not.
 +                               */
 +                              if (item->command == TODO_REWORD &&
 +                                  !get_oid("HEAD", &oid) &&
 +                                  (!oidcmp(&item->commit->object.oid, &oid) ||
 +                                   (opts->have_squash_onto &&
 +                                    !oidcmp(&opts->squash_onto, &oid))))
 +                                      to_amend = 1;
 +
                                return res | error_with_patch(item->commit,
 -                                      item->arg, item->arg_len, opts, res,
 -                                      item->command == TODO_REWORD);
 +                                              item->arg, item->arg_len, opts,
 +                                              res, to_amend);
 +                      }
                } else if (item->command == TODO_EXEC) {
                        char *end_of_arg = (char *)(item->arg + item->arg_len);
                        int saved = *end_of_arg;
@@@ -3427,8 -3485,8 +3511,8 @@@ static int continue_single_pick(void
  {
        const char *argv[] = { "commit", NULL };
  
 -      if (!file_exists(git_path_cherry_pick_head()) &&
 -          !file_exists(git_path_revert_head()))
 +      if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
 +          !file_exists(git_path_revert_head(the_repository)))
                return error(_("no cherry-pick or revert in progress"));
        return run_command_v_opt(argv, RUN_GIT_CMD);
  }
@@@ -3531,7 -3589,7 +3615,7 @@@ static int commit_staged_changes(struc
        }
  
        if (is_clean) {
 -              const char *cherry_pick_head = git_path_cherry_pick_head();
 +              const char *cherry_pick_head = git_path_cherry_pick_head(the_repository);
  
                if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
                        return error(_("could not remove CHERRY_PICK_HEAD"));
@@@ -3581,8 -3639,8 +3665,8 @@@ int sequencer_continue(struct replay_op
  
        if (!is_rebase_i(opts)) {
                /* Verify that the conflict has been resolved */
 -              if (file_exists(git_path_cherry_pick_head()) ||
 -                  file_exists(git_path_revert_head())) {
 +              if (file_exists(git_path_cherry_pick_head(the_repository)) ||
 +                  file_exists(git_path_revert_head(the_repository))) {
                        res = continue_single_pick();
                        if (res)
                                goto release_todo_list;
@@@ -3634,7 -3692,7 +3718,7 @@@ int sequencer_pick_revisions(struct rep
                        continue;
  
                if (!get_oid(name, &oid)) {
 -                      if (!lookup_commit_reference_gently(&oid, 1)) {
 +                      if (!lookup_commit_reference_gently(the_repository, &oid, 1)) {
                                enum object_type type = oid_object_info(the_repository,
                                                                        &oid,
                                                                        NULL);
                if (prepare_revision_walk(opts->revs))
                        return error(_("revision walk setup failed"));
                cmit = get_revision(opts->revs);
 -              if (!cmit || get_revision(opts->revs))
 -                      return error("BUG: expected exactly one commit from walk");
 +              if (!cmit)
 +                      return error(_("empty commit set passed"));
 +              if (get_revision(opts->revs))
 +                      BUG("unexpected extra commit from walk");
                return single_pick(cmit, opts);
        }
  
@@@ -3905,7 -3961,6 +3989,6 @@@ static int make_script_with_merges(stru
         */
        while ((commit = get_revision(revs))) {
                struct commit_list *to_merge;
-               int is_octopus;
                const char *p1, *p2;
                struct object_id *oid;
                int is_empty;
                        continue;
                }
  
-               is_octopus = to_merge && to_merge->next;
-               if (is_octopus)
-                       BUG("Octopus merges not yet supported");
                /* Create a label */
                strbuf_reset(&label);
                if (skip_prefix(oneline.buf, "Merge ", &p1) &&
                strbuf_addf(&buf, "%s -C %s",
                            cmd_merge, oid_to_hex(&commit->object.oid));
  
-               /* label the tip of merged branch */
-               oid = &to_merge->item->object.oid;
-               strbuf_addch(&buf, ' ');
+               /* label the tips of merged branches */
+               for (; to_merge; to_merge = to_merge->next) {
+                       oid = &to_merge->item->object.oid;
+                       strbuf_addch(&buf, ' ');
+                       if (!oidset_contains(&interesting, oid)) {
+                               strbuf_addstr(&buf, label_oid(oid, NULL,
+                                                             &state));
+                               continue;
+                       }
  
-               if (!oidset_contains(&interesting, oid))
-                       strbuf_addstr(&buf, label_oid(oid, NULL, &state));
-               else {
                        tips_tail = &commit_list_insert(to_merge->item,
                                                        tips_tail)->next;
  
                entry = oidmap_get(&state.commit2label, &commit->object.oid);
  
                if (entry)
 -                      fprintf(out, "\n# Branch %s\n", entry->string);
 +                      fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
                else
                        fprintf(out, "\n");