Merge branch 'ag/rebase-i-in-c' into js/rebase-in-c-5.5-work-with-rebase-i-in-c
authorJunio C Hamano <gitster@pobox.com>
Thu, 11 Oct 2018 05:18:19 +0000 (14:18 +0900)
committerJunio C Hamano <gitster@pobox.com>
Thu, 11 Oct 2018 05:18:19 +0000 (14:18 +0900)
* ag/rebase-i-in-c:
rebase -i: move rebase--helper modes to rebase--interactive
rebase -i: remove git-rebase--interactive.sh
rebase--interactive2: rewrite the submodes of interactive rebase in C
rebase -i: implement the main part of interactive rebase as a builtin
rebase -i: rewrite init_basic_state() in C
rebase -i: rewrite write_basic_state() in C
rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C
rebase -i: implement the logic to initialize $revisions in C
rebase -i: remove unused modes and functions
rebase -i: rewrite complete_action() in C
t3404: todo list with commented-out commands only aborts
sequencer: change the way skip_unnecessary_picks() returns its result
sequencer: refactor append_todo_help() to write its message to a buffer
rebase -i: rewrite checkout_onto() in C
rebase -i: rewrite setup_reflog_action() in C
sequencer: add a new function to silence a command, except if it fails
rebase -i: rewrite the edit-todo functionality in C
editor: add a function to launch the sequence editor
rebase -i: rewrite append_todo_help() in C
sequencer: make three functions and an enum from sequencer.c public

1  2 
.gitignore
Makefile
builtin.h
cache.h
git-legacy-rebase.sh
git-rebase--preserve-merges.sh
git.c
sequencer.c
strbuf.h
t/t3404-rebase-interactive.sh
diff --combined .gitignore
index 824141cba1188ba28f0d753701ecaddd85d5ed7f,406f26d0507961c9e39da636bb87ff62fae6a90b..b94053f46bb51c700879e12d41117c1983c9d908
@@@ -78,7 -78,6 +78,7 @@@
  /git-init-db
  /git-interpret-trailers
  /git-instaweb
 +/git-legacy-rebase
  /git-log
  /git-ls-files
  /git-ls-remote
  /git-read-tree
  /git-rebase
  /git-rebase--am
- /git-rebase--helper
 +/git-rebase--common
  /git-rebase--interactive
  /git-rebase--merge
  /git-rebase--preserve-merges
diff --combined Makefile
index c210681a1dbc4320e59f5b4b1e57aedabaa3f35d,ca3a0888ddfdb48e85ed5d785d34d9194ff3c8a1..7414f79b42759a5b392aa9abee22c081a03ed83e
+++ b/Makefile
@@@ -609,7 -609,7 +609,7 @@@ SCRIPT_SH += git-merge-one-file.s
  SCRIPT_SH += git-merge-resolve.sh
  SCRIPT_SH += git-mergetool.sh
  SCRIPT_SH += git-quiltimport.sh
 -SCRIPT_SH += git-rebase.sh
 +SCRIPT_SH += git-legacy-rebase.sh
  SCRIPT_SH += git-remote-testgit.sh
  SCRIPT_SH += git-request-pull.sh
  SCRIPT_SH += git-stash.sh
@@@ -619,8 -619,6 +619,7 @@@ SCRIPT_SH += git-web--browse.s
  SCRIPT_LIB += git-mergetool--lib
  SCRIPT_LIB += git-parse-remote
  SCRIPT_LIB += git-rebase--am
- SCRIPT_LIB += git-rebase--interactive
 +SCRIPT_LIB += git-rebase--common
  SCRIPT_LIB += git-rebase--preserve-merges
  SCRIPT_LIB += git-rebase--merge
  SCRIPT_LIB += git-sh-setup
@@@ -720,7 -718,6 +719,7 @@@ TEST_BUILTINS_OBJS += test-prio-queue.
  TEST_BUILTINS_OBJS += test-read-cache.o
  TEST_BUILTINS_OBJS += test-ref-store.o
  TEST_BUILTINS_OBJS += test-regex.o
 +TEST_BUILTINS_OBJS += test-repository.o
  TEST_BUILTINS_OBJS += test-revision-walking.o
  TEST_BUILTINS_OBJS += test-run-command.o
  TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
@@@ -861,7 -858,6 +860,7 @@@ LIB_OBJS += ewah/ewah_bitmap.
  LIB_OBJS += ewah/ewah_io.o
  LIB_OBJS += ewah/ewah_rlw.o
  LIB_OBJS += exec-cmd.o
 +LIB_OBJS += fetch-negotiator.o
  LIB_OBJS += fetch-object.o
  LIB_OBJS += fetch-pack.o
  LIB_OBJS += fsck.o
@@@ -894,8 -890,6 +893,8 @@@ LIB_OBJS += merge-blobs.
  LIB_OBJS += merge-recursive.o
  LIB_OBJS += mergesort.o
  LIB_OBJS += name-hash.o
 +LIB_OBJS += negotiator/default.o
 +LIB_OBJS += negotiator/skipping.o
  LIB_OBJS += notes.o
  LIB_OBJS += notes-cache.o
  LIB_OBJS += notes-merge.o
@@@ -927,6 -921,7 +926,7 @@@ LIB_OBJS += protocol.
  LIB_OBJS += quote.o
  LIB_OBJS += reachable.o
  LIB_OBJS += read-cache.o
+ LIB_OBJS += rebase-interactive.o
  LIB_OBJS += reflog-walk.o
  LIB_OBJS += refs.o
  LIB_OBJS += refs/files-backend.o
@@@ -1064,8 -1059,7 +1064,8 @@@ BUILTIN_OBJS += builtin/prune.
  BUILTIN_OBJS += builtin/pull.o
  BUILTIN_OBJS += builtin/push.o
  BUILTIN_OBJS += builtin/read-tree.o
- BUILTIN_OBJS += builtin/rebase--helper.o
 +BUILTIN_OBJS += builtin/rebase.o
+ BUILTIN_OBJS += builtin/rebase--interactive.o
  BUILTIN_OBJS += builtin/receive-pack.o
  BUILTIN_OBJS += builtin/reflog.o
  BUILTIN_OBJS += builtin/remote.o
@@@ -2404,7 -2398,6 +2404,6 @@@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS
  LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
  LOCALIZED_SH = $(SCRIPT_SH)
  LOCALIZED_SH += git-parse-remote.sh
- LOCALIZED_SH += git-rebase--interactive.sh
  LOCALIZED_SH += git-rebase--preserve-merges.sh
  LOCALIZED_SH += git-sh-setup.sh
  LOCALIZED_PERL = $(SCRIPT_PERL)
diff --combined builtin.h
index 44651a447f5d52d9d36654d92a27a65f08d284b8,7feb689d87c765fdb59f1b8c0e368bcd5819712f..fbc76d30601831b4778a0224f5e4e7c81ed1f5f6
+++ b/builtin.h
@@@ -202,8 -202,7 +202,8 @@@ extern int cmd_prune_packed(int argc, c
  extern int cmd_pull(int argc, const char **argv, const char *prefix);
  extern int cmd_push(int argc, const char **argv, const char *prefix);
  extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
- extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
 +extern int cmd_rebase(int argc, const char **argv, const char *prefix);
+ extern int cmd_rebase__interactive(int argc, const char **argv, const char *prefix);
  extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
  extern int cmd_reflog(int argc, const char **argv, const char *prefix);
  extern int cmd_remote(int argc, const char **argv, const char *prefix);
diff --combined cache.h
index 8dc7134f002e0f8bfe693d2679cda6a95fc7c118,d70ae49ca23ab00c79b05bb75940f9fddc4eed5d..5b985591dc387f20e32db468a98f1f4991c4f53e
+++ b/cache.h
@@@ -15,7 -15,6 +15,7 @@@
  #include "path.h"
  #include "sha1-array.h"
  #include "repository.h"
 +#include "mem-pool.h"
  
  #include <zlib.h>
  typedef struct git_zstream {
@@@ -157,7 -156,6 +157,7 @@@ struct cache_entry 
        struct stat_data ce_stat_data;
        unsigned int ce_mode;
        unsigned int ce_flags;
 +      unsigned int mem_pool_allocated;
        unsigned int ce_namelen;
        unsigned int index;     /* for link extension */
        struct object_id oid;
  /* Forward structure decls */
  struct pathspec;
  struct child_process;
 +struct tree;
  
  /*
   * Copy the sha1 and stat state of a cache entry from one to
@@@ -230,7 -227,6 +230,7 @@@ static inline void copy_cache_entry(str
                                    const struct cache_entry *src)
  {
        unsigned int state = dst->ce_flags & CE_HASHED;
 +      int mem_pool_allocated = dst->mem_pool_allocated;
  
        /* Don't copy hash chain and name */
        memcpy(&dst->ce_stat_data, &src->ce_stat_data,
  
        /* Restore the hash state */
        dst->ce_flags = (dst->ce_flags & ~CE_HASHED) | state;
 +
 +      /* Restore the mem_pool_allocated flag */
 +      dst->mem_pool_allocated = mem_pool_allocated;
  }
  
  static inline unsigned create_ce_flags(unsigned stage)
@@@ -335,7 -328,6 +335,7 @@@ struct index_state 
        struct untracked_cache *untracked;
        uint64_t fsmonitor_last_update;
        struct ewah_bitmap *fsmonitor_dirty;
 +      struct mem_pool *ce_mem_pool;
  };
  
  extern struct index_state the_index;
@@@ -347,60 -339,6 +347,60 @@@ extern void remove_name_hash(struct ind
  extern void free_name_hash(struct index_state *istate);
  
  
 +/* Cache entry creation and cleanup */
 +
 +/*
 + * Create cache_entry intended for use in the specified index. Caller
 + * is responsible for discarding the cache_entry with
 + * `discard_cache_entry`.
 + */
 +struct cache_entry *make_cache_entry(struct index_state *istate,
 +                                   unsigned int mode,
 +                                   const struct object_id *oid,
 +                                   const char *path,
 +                                   int stage,
 +                                   unsigned int refresh_options);
 +
 +struct cache_entry *make_empty_cache_entry(struct index_state *istate,
 +                                         size_t name_len);
 +
 +/*
 + * Create a cache_entry that is not intended to be added to an index.
 + * Caller is responsible for discarding the cache_entry
 + * with `discard_cache_entry`.
 + */
 +struct cache_entry *make_transient_cache_entry(unsigned int mode,
 +                                             const struct object_id *oid,
 +                                             const char *path,
 +                                             int stage);
 +
 +struct cache_entry *make_empty_transient_cache_entry(size_t name_len);
 +
 +/*
 + * Discard cache entry.
 + */
 +void discard_cache_entry(struct cache_entry *ce);
 +
 +/*
 + * Check configuration if we should perform extra validation on cache
 + * entries.
 + */
 +int should_validate_cache_entries(void);
 +
 +/*
 + * Duplicate a cache_entry. Allocate memory for the new entry from a
 + * memory_pool. Takes into account cache_entry fields that are meant
 + * for managing the underlying memory allocation of the cache_entry.
 + */
 +struct cache_entry *dup_cache_entry(const struct cache_entry *ce, struct index_state *istate);
 +
 +/*
 + * Validate the cache entries in the index.  This is an internal
 + * consistency check that the cache_entry structs are allocated from
 + * the expected memory pool.
 + */
 +void validate_cache_entries(const struct index_state *istate);
 +
  #ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
  #define active_cache (the_index.cache)
  #define active_nr (the_index.cache_nr)
@@@ -697,15 -635,12 +697,15 @@@ extern void move_index_extensions(struc
  extern int unmerged_index(const struct index_state *);
  
  /**
 - * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn
 - * branch, returns 1 if there are entries in the index, 0 otherwise. If an
 - * strbuf is provided, the space-separated list of files that differ will be
 - * appended to it.
 + * Returns 1 if istate differs from tree, 0 otherwise.  If tree is NULL,
 + * compares istate to HEAD.  If tree is NULL and on an unborn branch,
 + * returns 1 if there are entries in istate, 0 otherwise.  If an strbuf is
 + * provided, the space-separated list of files that differ will be appended
 + * to it.
   */
 -extern int index_has_changes(struct strbuf *sb);
 +extern int index_has_changes(const struct index_state *istate,
 +                           struct tree *tree,
 +                           struct strbuf *sb);
  
  extern int verify_path(const char *path, unsigned mode);
  extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
@@@ -763,6 -698,7 +763,6 @@@ extern int remove_file_from_index(struc
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  
 -extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options);
  extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
  extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
@@@ -815,7 -751,7 +815,7 @@@ extern void fill_stat_cache_info(struc
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
  extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
 -extern struct cache_entry *refresh_cache_entry(struct cache_entry *, unsigned int);
 +extern struct cache_entry *refresh_cache_entry(struct index_state *, struct cache_entry *, unsigned int);
  
  /*
   * Opportunistically update the index but do not complain if we can't.
@@@ -877,6 -813,7 +877,6 @@@ extern char *git_replace_ref_base
  
  extern int fsync_object_files;
  extern int core_preload_index;
 -extern int core_commit_graph;
  extern int core_apply_sparse_checkout;
  extern int precomposed_unicode;
  extern int protect_hfs;
@@@ -1035,7 -972,7 +1035,7 @@@ extern const struct object_id null_oid
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
  {
 -      return memcmp(sha1, sha2, GIT_SHA1_RAWSZ);
 +      return memcmp(sha1, sha2, the_hash_algo->rawsz);
  }
  
  static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
@@@ -1055,7 -992,7 +1055,7 @@@ static inline int is_null_oid(const str
  
  static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
  {
 -      memcpy(sha_dst, sha_src, GIT_SHA1_RAWSZ);
 +      memcpy(sha_dst, sha_src, the_hash_algo->rawsz);
  }
  
  static inline void oidcpy(struct object_id *dst, const struct object_id *src)
@@@ -1072,7 -1009,7 +1072,7 @@@ static inline struct object_id *oiddup(
  
  static inline void hashclr(unsigned char *hash)
  {
 -      memset(hash, 0, GIT_SHA1_RAWSZ);
 +      memset(hash, 0, the_hash_algo->rawsz);
  }
  
  static inline void oidclr(struct object_id *oid)
@@@ -1472,6 -1409,7 +1472,7 @@@ extern const char *fmt_name(const char 
  extern const char *ident_default_name(void);
  extern const char *ident_default_email(void);
  extern const char *git_editor(void);
+ extern const char *git_sequence_editor(void);
  extern const char *git_pager(int stdout_is_tty);
  extern int is_terminal_dumb(void);
  extern int git_ident_config(const char *, const char *, void *);
diff --combined git-legacy-rebase.sh
index af2cdfef03d1a7566b1f305b7dc7e9c1ac6708a3,0000000000000000000000000000000000000000..7600765f541880af68896efcffe9626a10201f25
mode 100755,000000..100755
--- /dev/null
@@@ -1,708 -1,0 +1,745 @@@
-       . git-rebase--$type
 +#!/bin/sh
 +#
 +# Copyright (c) 2005 Junio C Hamano.
 +#
 +
 +SUBDIRECTORY_OK=Yes
 +OPTIONS_KEEPDASHDASH=
 +OPTIONS_STUCKLONG=t
 +OPTIONS_SPEC="\
 +git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
 +git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
 +git rebase --continue | --abort | --skip | --edit-todo
 +--
 + Available options are
 +v,verbose!         display a diffstat of what changed upstream
 +q,quiet!           be quiet. implies --no-stat
 +autostash          automatically stash/stash pop before and after
 +fork-point         use 'merge-base --fork-point' to refine upstream
 +onto=!             rebase onto given branch instead of upstream
 +r,rebase-merges?   try to rebase merges instead of skipping them
 +p,preserve-merges! try to recreate merges instead of ignoring them
 +s,strategy=!       use the given merge strategy
 +X,strategy-option=! pass the argument through to the merge strategy
 +no-ff!             cherry-pick all commits, even if unchanged
 +f,force-rebase!    cherry-pick all commits, even if unchanged
 +m,merge!           use merging strategies to rebase
 +i,interactive!     let the user edit the list of commits to rebase
 +x,exec=!           add exec lines after each commit of the editable list
 +k,keep-empty     preserve empty commits during rebase
 +allow-empty-message allow rebasing commits with empty messages
 +stat!              display a diffstat of what changed upstream
 +n,no-stat!         do not show diffstat of what changed upstream
 +verify             allow pre-rebase hook to run
 +rerere-autoupdate  allow rerere to update index with resolved conflicts
 +root!              rebase all reachable commits up to the root(s)
 +autosquash         move commits that begin with squash!/fixup! under -i
 +signoff            add a Signed-off-by: line to each commit
 +committer-date-is-author-date! passed to 'git am'
 +ignore-date!       passed to 'git am'
 +whitespace=!       passed to 'git apply'
 +ignore-whitespace! passed to 'git apply'
 +C=!                passed to 'git apply'
 +S,gpg-sign?        GPG-sign commits
 + Actions:
 +continue!          continue
 +abort!             abort and check out the original branch
 +skip!              skip current patch and continue
 +edit-todo!         edit the todo list during an interactive rebase
 +quit!              abort but keep HEAD where it is
 +show-current-patch! show the patch file being applied or merged
 +"
 +. git-sh-setup
 +set_reflog_action rebase
 +require_work_tree_exists
 +cd_to_toplevel
 +
 +LF='
 +'
 +ok_to_skip_pre_rebase=
 +
 +squash_onto=
 +unset onto
 +unset restrict_revision
 +cmd=
 +strategy=
 +strategy_opts=
 +do_merge=
 +merge_dir="$GIT_DIR"/rebase-merge
 +apply_dir="$GIT_DIR"/rebase-apply
 +verbose=
 +diffstat=
 +test "$(git config --bool rebase.stat)" = true && diffstat=t
 +autostash="$(git config --bool rebase.autostash || echo false)"
 +fork_point=auto
 +git_am_opt=
 +git_format_patch_opt=
 +rebase_root=
 +force_rebase=
 +allow_rerere_autoupdate=
 +# Non-empty if a rebase was in progress when 'git rebase' was invoked
 +in_progress=
 +# One of {am, merge, interactive}
 +type=
 +# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge}
 +state_dir=
 +# One of {'', continue, skip, abort}, as parsed from command line
 +action=
 +rebase_merges=
 +rebase_cousins=
 +preserve_merges=
 +autosquash=
 +keep_empty=
 +allow_empty_message=--allow-empty-message
 +signoff=
 +test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 +case "$(git config --bool commit.gpgsign)" in
 +true) gpg_sign_opt=-S ;;
 +*)    gpg_sign_opt= ;;
 +esac
 +. git-rebase--common
 +
 +read_basic_state () {
 +      test -f "$state_dir/head-name" &&
 +      test -f "$state_dir/onto" &&
 +      head_name=$(cat "$state_dir"/head-name) &&
 +      onto=$(cat "$state_dir"/onto) &&
 +      # We always write to orig-head, but interactive rebase used to write to
 +      # head. Fall back to reading from head to cover for the case that the
 +      # user upgraded git with an ongoing interactive rebase.
 +      if test -f "$state_dir"/orig-head
 +      then
 +              orig_head=$(cat "$state_dir"/orig-head)
 +      else
 +              orig_head=$(cat "$state_dir"/head)
 +      fi &&
 +      GIT_QUIET=$(cat "$state_dir"/quiet) &&
 +      test -f "$state_dir"/verbose && verbose=t
 +      test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)"
 +      test -f "$state_dir"/strategy_opts &&
 +              strategy_opts="$(cat "$state_dir"/strategy_opts)"
 +      test -f "$state_dir"/allow_rerere_autoupdate &&
 +              allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)"
 +      test -f "$state_dir"/gpg_sign_opt &&
 +              gpg_sign_opt="$(cat "$state_dir"/gpg_sign_opt)"
 +      test -f "$state_dir"/signoff && {
 +              signoff="$(cat "$state_dir"/signoff)"
 +              force_rebase=t
 +      }
 +}
 +
 +finish_rebase () {
 +      rm -f "$(git rev-parse --git-path REBASE_HEAD)"
 +      apply_autostash &&
 +      { git gc --auto || true; } &&
 +      rm -rf "$state_dir"
 +}
 +
++run_interactive () {
++      GIT_CHERRY_PICK_HELP="$resolvemsg"
++      export GIT_CHERRY_PICK_HELP
++
++      test -n "$keep_empty" && keep_empty="--keep-empty"
++      test -n "$rebase_merges" && rebase_merges="--rebase-merges"
++      test -n "$rebase_cousins" && rebase_cousins="--rebase-cousins"
++      test -n "$autosquash" && autosquash="--autosquash"
++      test -n "$verbose" && verbose="--verbose"
++      test -n "$force_rebase" && force_rebase="--no-ff"
++      test -n "$restrict_revision" && \
++              restrict_revision="--restrict-revision=^$restrict_revision"
++      test -n "$upstream" && upstream="--upstream=$upstream"
++      test -n "$onto" && onto="--onto=$onto"
++      test -n "$squash_onto" && squash_onto="--squash-onto=$squash_onto"
++      test -n "$onto_name" && onto_name="--onto-name=$onto_name"
++      test -n "$head_name" && head_name="--head-name=$head_name"
++      test -n "$strategy" && strategy="--strategy=$strategy"
++      test -n "$strategy_opts" && strategy_opts="--strategy-opts=$strategy_opts"
++      test -n "$switch_to" && switch_to="--switch-to=$switch_to"
++      test -n "$cmd" && cmd="--cmd=$cmd"
++      test -n "$action" && action="--$action"
++
++      exec git rebase--interactive "$action" "$keep_empty" "$rebase_merges" "$rebase_cousins" \
++              "$upstream" "$onto" "$squash_onto" "$restrict_revision" \
++              "$allow_empty_message" "$autosquash" "$verbose" \
++              "$force_rebase" "$onto_name" "$head_name" "$strategy" \
++              "$strategy_opts" "$cmd" "$switch_to" \
++              "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff"
++}
++
 +run_specific_rebase () {
 +      if [ "$interactive_rebase" = implied ]; then
 +              GIT_EDITOR=:
 +              export GIT_EDITOR
 +              autosquash=
 +      fi
-       if test -z "$preserve_merges"
 +
-               git_rebase__$type
++      if test -n "$interactive_rebase" -a -z "$preserve_merges"
 +      then
-               git_rebase__preserve_merges
++              run_interactive
 +      else
-       elif test $ret -eq 2 # special exit status for rebase -i
++              . git-rebase--$type
++
++              if test -z "$preserve_merges"
++              then
++                      git_rebase__$type
++              else
++                      git_rebase__preserve_merges
++              fi
 +      fi
 +
 +      ret=$?
 +      if test $ret -eq 0
 +      then
 +              finish_rebase
++      elif test $ret -eq 2 # special exit status for rebase -p
 +      then
 +              apply_autostash &&
 +              rm -rf "$state_dir" &&
 +              die "Nothing to do"
 +      fi
 +      exit $ret
 +}
 +
 +run_pre_rebase_hook () {
 +      if test -z "$ok_to_skip_pre_rebase" &&
 +         test -x "$(git rev-parse --git-path hooks/pre-rebase)"
 +      then
 +              "$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} ||
 +              die "$(gettext "The pre-rebase hook refused to rebase.")"
 +      fi
 +}
 +
 +test -f "$apply_dir"/applying &&
 +      die "$(gettext "It looks like 'git am' is in progress. Cannot rebase.")"
 +
 +if test -d "$apply_dir"
 +then
 +      type=am
 +      state_dir="$apply_dir"
 +elif test -d "$merge_dir"
 +then
 +      if test -d "$merge_dir"/rewritten
 +      then
 +              type=preserve-merges
 +              interactive_rebase=explicit
 +              preserve_merges=t
 +      elif test -f "$merge_dir"/interactive
 +      then
 +              type=interactive
 +              interactive_rebase=explicit
 +      else
 +              type=merge
 +      fi
 +      state_dir="$merge_dir"
 +fi
 +test -n "$type" && in_progress=t
 +
 +total_argc=$#
 +while test $# != 0
 +do
 +      case "$1" in
 +      --no-verify)
 +              ok_to_skip_pre_rebase=yes
 +              ;;
 +      --verify)
 +              ok_to_skip_pre_rebase=
 +              ;;
 +      --continue|--skip|--abort|--quit|--edit-todo|--show-current-patch)
 +              test $total_argc -eq 2 || usage
 +              action=${1##--}
 +              ;;
 +      --onto=*)
 +              onto="${1#--onto=}"
 +              ;;
 +      --exec=*)
 +              cmd="${cmd}exec ${1#--exec=}${LF}"
 +              test -z "$interactive_rebase" && interactive_rebase=implied
 +              ;;
 +      --interactive)
 +              interactive_rebase=explicit
 +              ;;
 +      --keep-empty)
 +              keep_empty=yes
 +              ;;
 +      --allow-empty-message)
 +              allow_empty_message=--allow-empty-message
 +              ;;
 +      --no-keep-empty)
 +              keep_empty=
 +              ;;
 +      --rebase-merges)
 +              rebase_merges=t
 +              test -z "$interactive_rebase" && interactive_rebase=implied
 +              ;;
 +      --rebase-merges=*)
 +              rebase_merges=t
 +              case "${1#*=}" in
 +              rebase-cousins) rebase_cousins=t;;
 +              no-rebase-cousins) rebase_cousins=;;
 +              *) die "Unknown mode: $1";;
 +              esac
 +              test -z "$interactive_rebase" && interactive_rebase=implied
 +              ;;
 +      --preserve-merges)
 +              preserve_merges=t
 +              test -z "$interactive_rebase" && interactive_rebase=implied
 +              ;;
 +      --autosquash)
 +              autosquash=t
 +              ;;
 +      --no-autosquash)
 +              autosquash=
 +              ;;
 +      --fork-point)
 +              fork_point=t
 +              ;;
 +      --no-fork-point)
 +              fork_point=
 +              ;;
 +      --merge)
 +              do_merge=t
 +              ;;
 +      --strategy-option=*)
 +              strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}" | sed -e s/^.//)"
 +              do_merge=t
 +              test -z "$strategy" && strategy=recursive
 +              ;;
 +      --strategy=*)
 +              strategy="${1#--strategy=}"
 +              do_merge=t
 +              ;;
 +      --no-stat)
 +              diffstat=
 +              ;;
 +      --stat)
 +              diffstat=t
 +              ;;
 +      --autostash)
 +              autostash=true
 +              ;;
 +      --no-autostash)
 +              autostash=false
 +              ;;
 +      --verbose)
 +              verbose=t
 +              diffstat=t
 +              GIT_QUIET=
 +              ;;
 +      --quiet)
 +              GIT_QUIET=t
 +              git_am_opt="$git_am_opt -q"
 +              verbose=
 +              diffstat=
 +              ;;
 +      --whitespace=*)
 +              git_am_opt="$git_am_opt --whitespace=${1#--whitespace=}"
 +              case "${1#--whitespace=}" in
 +              fix|strip)
 +                      force_rebase=t
 +                      ;;
 +              esac
 +              ;;
 +      --ignore-whitespace)
 +              git_am_opt="$git_am_opt $1"
 +              ;;
 +      --signoff)
 +              signoff=--signoff
 +              ;;
 +      --no-signoff)
 +              signoff=
 +              ;;
 +      --committer-date-is-author-date|--ignore-date)
 +              git_am_opt="$git_am_opt $1"
 +              force_rebase=t
 +              ;;
 +      -C*)
 +              git_am_opt="$git_am_opt $1"
 +              ;;
 +      --root)
 +              rebase_root=t
 +              ;;
 +      --force-rebase|--no-ff)
 +              force_rebase=t
 +              ;;
 +      --rerere-autoupdate|--no-rerere-autoupdate)
 +              allow_rerere_autoupdate="$1"
 +              ;;
 +      --gpg-sign)
 +              gpg_sign_opt=-S
 +              ;;
 +      --gpg-sign=*)
 +              gpg_sign_opt="-S${1#--gpg-sign=}"
 +              ;;
 +      --)
 +              shift
 +              break
 +              ;;
 +      *)
 +              usage
 +              ;;
 +      esac
 +      shift
 +done
 +test $# -gt 2 && usage
 +
 +if test -n "$action"
 +then
 +      test -z "$in_progress" && die "$(gettext "No rebase in progress?")"
 +      # Only interactive rebase uses detailed reflog messages
 +      if test -n "$interactive_rebase" && test "$GIT_REFLOG_ACTION" = rebase
 +      then
 +              GIT_REFLOG_ACTION="rebase -i ($action)"
 +              export GIT_REFLOG_ACTION
 +      fi
 +fi
 +
 +if test "$action" = "edit-todo" && test -z "$interactive_rebase"
 +then
 +      die "$(gettext "The --edit-todo action can only be used during interactive rebase.")"
 +fi
 +
 +case "$action" in
 +continue)
 +      # Sanity check
 +      git rev-parse --verify HEAD >/dev/null ||
 +              die "$(gettext "Cannot read HEAD")"
 +      git update-index --ignore-submodules --refresh &&
 +      git diff-files --quiet --ignore-submodules || {
 +              echo "$(gettext "You must edit all merge conflicts and then
 +mark them as resolved using git add")"
 +              exit 1
 +      }
 +      read_basic_state
 +      run_specific_rebase
 +      ;;
 +skip)
 +      output git reset --hard HEAD || exit $?
 +      read_basic_state
 +      run_specific_rebase
 +      ;;
 +abort)
 +      git rerere clear
 +      read_basic_state
 +      case "$head_name" in
 +      refs/*)
 +              git symbolic-ref -m "rebase: aborting" HEAD $head_name ||
 +              die "$(eval_gettext "Could not move back to \$head_name")"
 +              ;;
 +      esac
 +      output git reset --hard $orig_head
 +      finish_rebase
 +      exit
 +      ;;
 +quit)
 +      exec rm -rf "$state_dir"
 +      ;;
 +edit-todo)
 +      run_specific_rebase
 +      ;;
 +show-current-patch)
 +      run_specific_rebase
 +      die "BUG: run_specific_rebase is not supposed to return here"
 +      ;;
 +esac
 +
 +# Make sure no rebase is in progress
 +if test -n "$in_progress"
 +then
 +      state_dir_base=${state_dir##*/}
 +      cmd_live_rebase="git rebase (--continue | --abort | --skip)"
 +      cmd_clear_stale_rebase="rm -fr \"$state_dir\""
 +      die "
 +$(eval_gettext 'It seems that there is already a $state_dir_base directory, and
 +I wonder if you are in the middle of another rebase.  If that is the
 +case, please try
 +      $cmd_live_rebase
 +If that is not the case, please
 +      $cmd_clear_stale_rebase
 +and run me again.  I am stopping in case you still have something
 +valuable there.')"
 +fi
 +
 +if test -n "$rebase_root" && test -z "$onto"
 +then
 +      test -z "$interactive_rebase" && interactive_rebase=implied
 +fi
 +
 +if test -n "$keep_empty"
 +then
 +      test -z "$interactive_rebase" && interactive_rebase=implied
 +fi
 +
 +if test -n "$interactive_rebase"
 +then
 +      if test -z "$preserve_merges"
 +      then
 +              type=interactive
 +      else
 +              type=preserve-merges
 +      fi
 +
 +      state_dir="$merge_dir"
 +elif test -n "$do_merge"
 +then
 +      type=merge
 +      state_dir="$merge_dir"
 +else
 +      type=am
 +      state_dir="$apply_dir"
 +fi
 +
 +if test -t 2 && test -z "$GIT_QUIET"
 +then
 +      git_format_patch_opt="$git_format_patch_opt --progress"
 +fi
 +
 +if test -n "$git_am_opt"; then
 +      incompatible_opts=$(echo " $git_am_opt " | \
 +                          sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/')
 +      if test -n "$interactive_rebase"
 +      then
 +              if test -n "$incompatible_opts"
 +              then
 +                      die "$(gettext "error: cannot combine interactive options (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto) with am options ($incompatible_opts)")"
 +              fi
 +      fi
 +      if test -n "$do_merge"; then
 +              if test -n "$incompatible_opts"
 +              then
 +                      die "$(gettext "error: cannot combine merge options (--merge, --strategy, --strategy-option) with am options ($incompatible_opts)")"
 +              fi
 +      fi
 +fi
 +
 +if test -n "$signoff"
 +then
 +      test -n "$preserve_merges" &&
 +              die "$(gettext "error: cannot combine '--signoff' with '--preserve-merges'")"
 +      git_am_opt="$git_am_opt $signoff"
 +      force_rebase=t
 +fi
 +
 +if test -n "$preserve_merges"
 +then
 +      # Note: incompatibility with --signoff handled in signoff block above
 +      # Note: incompatibility with --interactive is just a strong warning;
 +      #       git-rebase.txt caveats with "unless you know what you are doing"
 +      test -n "$rebase_merges" &&
 +              die "$(gettext "error: cannot combine '--preserve_merges' with '--rebase-merges'")"
 +fi
 +
 +if test -n "$rebase_merges"
 +then
 +      test -n "$strategy_opts" &&
 +              die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy-option'")"
 +      test -n "$strategy" &&
 +              die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy'")"
 +fi
 +
 +if test -z "$rebase_root"
 +then
 +      case "$#" in
 +      0)
 +              if ! upstream_name=$(git rev-parse --symbolic-full-name \
 +                      --verify -q @{upstream} 2>/dev/null)
 +              then
 +                      . git-parse-remote
 +                      error_on_missing_default_upstream "rebase" "rebase" \
 +                              "against" "git rebase $(gettext '<branch>')"
 +              fi
 +
 +              test "$fork_point" = auto && fork_point=t
 +              ;;
 +      *)      upstream_name="$1"
 +              if test "$upstream_name" = "-"
 +              then
 +                      upstream_name="@{-1}"
 +              fi
 +              shift
 +              ;;
 +      esac
 +      upstream=$(peel_committish "${upstream_name}") ||
 +      die "$(eval_gettext "invalid upstream '\$upstream_name'")"
 +      upstream_arg="$upstream_name"
 +else
 +      if test -z "$onto"
 +      then
 +              empty_tree=$(git hash-object -t tree /dev/null)
 +              onto=$(git commit-tree $empty_tree </dev/null)
 +              squash_onto="$onto"
 +      fi
 +      unset upstream_name
 +      unset upstream
 +      test $# -gt 1 && usage
 +      upstream_arg=--root
 +fi
 +
 +# Make sure the branch to rebase onto is valid.
 +onto_name=${onto-"$upstream_name"}
 +case "$onto_name" in
 +*...*)
 +      if      left=${onto_name%...*} right=${onto_name#*...} &&
 +              onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
 +      then
 +              case "$onto" in
 +              ?*"$LF"?*)
 +                      die "$(eval_gettext "\$onto_name: there are more than one merge bases")"
 +                      ;;
 +              '')
 +                      die "$(eval_gettext "\$onto_name: there is no merge base")"
 +                      ;;
 +              esac
 +      else
 +              die "$(eval_gettext "\$onto_name: there is no merge base")"
 +      fi
 +      ;;
 +*)
 +      onto=$(peel_committish "$onto_name") ||
 +      die "$(eval_gettext "Does not point to a valid commit: \$onto_name")"
 +      ;;
 +esac
 +
 +# If the branch to rebase is given, that is the branch we will rebase
 +# $branch_name -- branch/commit being rebased, or HEAD (already detached)
 +# $orig_head -- commit object name of tip of the branch before rebasing
 +# $head_name -- refs/heads/<that-branch> or "detached HEAD"
 +switch_to=
 +case "$#" in
 +1)
 +      # Is it "rebase other $branchname" or "rebase other $commit"?
 +      branch_name="$1"
 +      switch_to="$1"
 +
 +      # Is it a local branch?
 +      if git show-ref --verify --quiet -- "refs/heads/$branch_name" &&
 +         orig_head=$(git rev-parse -q --verify "refs/heads/$branch_name")
 +      then
 +              head_name="refs/heads/$branch_name"
 +      # If not is it a valid ref (branch or commit)?
 +      elif orig_head=$(git rev-parse -q --verify "$branch_name")
 +      then
 +              head_name="detached HEAD"
 +
 +      else
 +              die "$(eval_gettext "fatal: no such branch/commit '\$branch_name'")"
 +      fi
 +      ;;
 +0)
 +      # Do not need to switch branches, we are already on it.
 +      if branch_name=$(git symbolic-ref -q HEAD)
 +      then
 +              head_name=$branch_name
 +              branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)')
 +      else
 +              head_name="detached HEAD"
 +              branch_name=HEAD
 +      fi
 +      orig_head=$(git rev-parse --verify HEAD) || exit
 +      ;;
 +*)
 +      die "BUG: unexpected number of arguments left to parse"
 +      ;;
 +esac
 +
 +if test "$fork_point" = t
 +then
 +      new_upstream=$(git merge-base --fork-point "$upstream_name" \
 +                      "${switch_to:-HEAD}")
 +      if test -n "$new_upstream"
 +      then
 +              restrict_revision=$new_upstream
 +      fi
 +fi
 +
 +if test "$autostash" = true && ! (require_clean_work_tree) 2>/dev/null
 +then
 +      stash_sha1=$(git stash create "autostash") ||
 +      die "$(gettext 'Cannot autostash')"
 +
 +      mkdir -p "$state_dir" &&
 +      echo $stash_sha1 >"$state_dir/autostash" &&
 +      stash_abbrev=$(git rev-parse --short $stash_sha1) &&
 +      echo "$(eval_gettext 'Created autostash: $stash_abbrev')" &&
 +      git reset --hard
 +fi
 +
 +require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")"
 +
 +# Now we are rebasing commits $upstream..$orig_head (or with --root,
 +# everything leading up to $orig_head) on top of $onto
 +
 +# Check if we are already based on $onto with linear history,
 +# but this should be done only when upstream and onto are the same
 +# and if this is not an interactive rebase.
 +mb=$(git merge-base "$onto" "$orig_head")
 +if test -z "$interactive_rebase" && test "$upstream" = "$onto" &&
 +      test "$mb" = "$onto" && test -z "$restrict_revision" &&
 +      # linear history?
 +      ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
 +then
 +      if test -z "$force_rebase"
 +      then
 +              # Lazily switch to the target branch if needed...
 +              test -z "$switch_to" ||
 +              GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \
 +                      git checkout -q "$switch_to" --
 +              if test "$branch_name" = "HEAD" &&
 +                       ! git symbolic-ref -q HEAD
 +              then
 +                      say "$(eval_gettext "HEAD is up to date.")"
 +              else
 +                      say "$(eval_gettext "Current branch \$branch_name is up to date.")"
 +              fi
 +              finish_rebase
 +              exit 0
 +      else
 +              if test "$branch_name" = "HEAD" &&
 +                       ! git symbolic-ref -q HEAD
 +              then
 +                      say "$(eval_gettext "HEAD is up to date, rebase forced.")"
 +              else
 +                      say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")"
 +              fi
 +      fi
 +fi
 +
 +# If a hook exists, give it a chance to interrupt
 +run_pre_rebase_hook "$upstream_arg" "$@"
 +
 +if test -n "$diffstat"
 +then
 +      if test -n "$verbose"
 +      then
 +              echo "$(eval_gettext "Changes from \$mb to \$onto:")"
 +      fi
 +      # We want color (if set), but no pager
 +      GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
 +fi
 +
 +test -n "$interactive_rebase" && run_specific_rebase
 +
 +# Detach HEAD and reset the tree
 +say "$(gettext "First, rewinding head to replay your work on top of it...")"
 +
 +GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
 +      git checkout -q "$onto^0" || die "could not detach HEAD"
 +git update-ref ORIG_HEAD $orig_head
 +
 +# If the $onto is a proper descendant of the tip of the branch, then
 +# we just fast-forwarded.
 +if test "$mb" = "$orig_head"
 +then
 +      say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
 +      move_to_original_branch
 +      finish_rebase
 +      exit 0
 +fi
 +
 +if test -n "$rebase_root"
 +then
 +      revisions="$onto..$orig_head"
 +else
 +      revisions="${restrict_revision-$upstream}..$orig_head"
 +fi
 +
 +run_specific_rebase
index c214c5e4d6ce21996f16031ec14dd624b75a9939,d43b4b582e907a6b2f78e8de19ff9176d0601869..afbb65765d46102339068e7e9aa397fcf88ee6a5
@@@ -711,11 -711,11 +711,11 @@@ do_rest () 
  }
  
  expand_todo_ids() {
-       git rebase--helper --expand-ids
+       git rebase--interactive --expand-ids
  }
  
  collapse_todo_ids() {
-       git rebase--helper --shorten-ids
+       git rebase--interactive --shorten-ids
  }
  
  # Switch to the branch in $into and notify it in the reflog
@@@ -876,8 -876,8 +876,8 @@@ init_revisions_and_shortrevisions () 
  
  complete_action() {
        test -s "$todo" || echo noop >> "$todo"
-       test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
-       test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
+       test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit
+       test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd"
  
        todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
        todocount=${todocount##* }
@@@ -891,9 -891,9 +891,9 @@@ $comment_char $(eval_ngettext 
  EOF
        append_todo_help
        gettext "
 -      However, if you remove everything, the rebase will be aborted.
 +However, if you remove everything, the rebase will be aborted.
  
 -      " | git stripspace --comment-lines >>"$todo"
 +" | git stripspace --comment-lines >>"$todo"
  
        if test -z "$keep_empty"
        then
        has_action "$todo" ||
                return 2
  
-       git rebase--helper --check-todo-list || {
+       git rebase--interactive --check-todo-list || {
                ret=$?
                checkout_onto
                exit $ret
diff --combined git.c
index 2c6b188c77db82a2f02ce44f0641573cd00f0a61,81aabd1423a99dad6ad17ffaedb6447de04fd51c..5cd2e708d7d79f57224a59c6af91d2e3b23a1497
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -414,10 -414,7 +414,10 @@@ static int run_builtin(struct cmd_struc
  
        trace_argv_printf(argv, "trace: built-in: git");
  
 +      validate_cache_entries(&the_index);
        status = p->fn(argc, argv, prefix);
 +      validate_cache_entries(&the_index);
 +
        if (status)
                return status;
  
@@@ -521,13 -518,7 +521,13 @@@ static struct cmd_struct commands[] = 
        { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
        { "push", cmd_push, RUN_SETUP },
        { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
-       { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
 +      /*
 +       * NEEDSWORK: Until the rebase is independent and needs no redirection
 +       * to rebase shell script this is kept as is, then should be changed to
 +       * RUN_SETUP | NEED_WORK_TREE
 +       */
 +      { "rebase", cmd_rebase },
+       { "rebase--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE },
        { "receive-pack", cmd_receive_pack },
        { "reflog", cmd_reflog, RUN_SETUP },
        { "remote", cmd_remote, RUN_SETUP },
diff --combined sequencer.c
index 31038472fdc0f13e820c43b32cf2e1448b54a543,8dd6db5a017030da8468a48fe868d2982394699d..dfb6ad2f5bd13328cdf0ca17470897dae8b43e8b
@@@ -30,6 -30,7 +30,7 @@@
  #include "oidset.h"
  #include "commit-slab.h"
  #include "alias.h"
+ #include "rebase-interactive.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
@@@ -52,7 -53,10 +53,10 @@@ static GIT_PATH_FUNC(rebase_path, "reba
   * the lines are processed, they are removed from the front of this
   * file and written to the tail of 'done'.
   */
- static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
+ GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
+ static GIT_PATH_FUNC(rebase_path_todo_backup,
+                    "rebase-merge/git-rebase-todo.backup")
  /*
   * The rebase command lines that have already been processed. A line
   * is moved here when it is first handled, before any associated user
@@@ -63,12 -67,12 +67,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.
@@@ -140,7 -144,7 +144,7 @@@ static GIT_PATH_FUNC(rebase_path_refs_t
  
  /*
   * The following files are written by git-rebase just after parsing the
-  * command-line (and are only consumed, not modified, by the sequencer).
+  * command-line.
   */
  static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
  static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
@@@ -152,6 -156,7 +156,7 @@@ static GIT_PATH_FUNC(rebase_path_autost
  static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
  static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
  static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
+ static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
  
  static int git_sequencer_config(const char *k, const char *v, void *cb)
  {
@@@ -373,8 -378,8 +378,8 @@@ static void print_advice(int show_hint
        }
  }
  
static int write_message(const void *buf, size_t len, const char *filename,
-                        int append_eol)
+ int write_message(const void *buf, size_t len, const char *filename,
+                 int append_eol)
  {
        struct lock_file msg_file = LOCK_INIT;
  
@@@ -433,7 -438,7 +438,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 -599,7 +599,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
@@@ -769,6 -774,23 +774,23 @@@ N_("you have staged changes in your wor
  #define VERIFY_MSG  (1<<4)
  #define CREATE_ROOT_COMMIT (1<<5)
  
+ static int run_command_silent_on_success(struct child_process *cmd)
+ {
+       struct strbuf buf = STRBUF_INIT;
+       int rc;
+       cmd->stdout_to_stderr = 1;
+       rc = pipe_command(cmd,
+                         NULL, 0,
+                         NULL, 0,
+                         &buf, 0);
+       if (rc)
+               fputs(buf.buf, stderr);
+       strbuf_release(&buf);
+       return rc;
+ }
  /*
   * If we are cherry-pick, and if the merge did not result in
   * hand-editing, we will hit this commit and inherit the original
@@@ -823,18 -845,11 +845,11 @@@ static int run_git_commit(const char *d
  
        cmd.git_cmd = 1;
  
-       if (is_rebase_i(opts)) {
-               if (!(flags & EDIT_MSG)) {
-                       cmd.stdout_to_stderr = 1;
-                       cmd.err = -1;
-               }
-               if (read_env_script(&cmd.env_array)) {
-                       const char *gpg_opt = gpg_sign_opt_quoted(opts);
+       if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
+               const char *gpg_opt = gpg_sign_opt_quoted(opts);
  
-                       return error(_(staged_changes_advice),
-                                    gpg_opt, gpg_opt);
-               }
+               return error(_(staged_changes_advice),
+                            gpg_opt, gpg_opt);
        }
  
        argv_array_push(&cmd.args, "commit");
        if (opts->allow_empty_message)
                argv_array_push(&cmd.args, "--allow-empty-message");
  
-       if (cmd.err == -1) {
-               /* hide stderr on success */
-               struct strbuf buf = STRBUF_INIT;
-               int rc = pipe_command(&cmd,
-                                     NULL, 0,
-                                     /* stdout is already redirected */
-                                     NULL, 0,
-                                     &buf, 0);
-               if (rc)
-                       fputs(buf.buf, stderr);
-               strbuf_release(&buf);
-               return rc;
-       }
-       return run_command(&cmd);
+       if (is_rebase_i(opts) && !(flags & EDIT_MSG))
+               return run_command_silent_on_success(&cmd);
+       else
+               return run_command(&cmd);
  }
  
  static int rest_is_empty(const struct strbuf *sb, int start)
@@@ -1101,7 -1105,7 +1105,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 -1180,7 +1180,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)) {
@@@ -1511,7 -1515,7 +1515,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"));
@@@ -1864,6 -1868,8 +1868,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 -2013,7 +2011,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;
  }
  
@@@ -2202,21 -2208,14 +2206,14 @@@ static int populate_opts_cb(const char 
        return 0;
  }
  
static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
  {
        int i;
-       char *strategy_opts_string;
-       strbuf_reset(buf);
-       if (!read_oneliner(buf, rebase_path_strategy(), 0))
-               return;
-       opts->strategy = strbuf_detach(buf, NULL);
-       if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
-               return;
+       char *strategy_opts_string = raw_opts;
  
-       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++) {
        }
  }
  
+ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
+ {
+       strbuf_reset(buf);
+       if (!read_oneliner(buf, rebase_path_strategy(), 0))
+               return;
+       opts->strategy = strbuf_detach(buf, NULL);
+       if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
+               return;
+       parse_strategy_opts(opts, buf->buf);
+ }
  static int read_populate_opts(struct replay_opts *opts)
  {
        if (is_rebase_i(opts)) {
        return 0;
  }
  
+ static void write_strategy_opts(struct replay_opts *opts)
+ {
+       int i;
+       struct strbuf buf = STRBUF_INIT;
+       for (i = 0; i < opts->xopts_nr; ++i)
+               strbuf_addf(&buf, " --%s", opts->xopts[i]);
+       write_file(rebase_path_strategy_opts(), "%s\n", buf.buf);
+       strbuf_release(&buf);
+ }
+ int write_basic_state(struct replay_opts *opts, const char *head_name,
+                     const char *onto, const char *orig_head)
+ {
+       const char *quiet = getenv("GIT_QUIET");
+       if (head_name)
+               write_file(rebase_path_head_name(), "%s\n", head_name);
+       if (onto)
+               write_file(rebase_path_onto(), "%s\n", onto);
+       if (orig_head)
+               write_file(rebase_path_orig_head(), "%s\n", orig_head);
+       if (quiet)
+               write_file(rebase_path_quiet(), "%s\n", quiet);
+       else
+               write_file(rebase_path_quiet(), "\n");
+       if (opts->verbose)
+               write_file(rebase_path_verbose(), "");
+       if (opts->strategy)
+               write_file(rebase_path_strategy(), "%s\n", opts->strategy);
+       if (opts->xopts_nr > 0)
+               write_strategy_opts(opts);
+       if (opts->allow_rerere_auto == RERERE_AUTOUPDATE)
+               write_file(rebase_path_allow_rerere_autoupdate(), "--rerere-autoupdate\n");
+       else if (opts->allow_rerere_auto == RERERE_NOAUTOUPDATE)
+               write_file(rebase_path_allow_rerere_autoupdate(), "--no-rerere-autoupdate\n");
+       if (opts->gpg_sign)
+               write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
+       if (opts->signoff)
+               write_file(rebase_path_signoff(), "--signoff\n");
+       return 0;
+ }
  static int walk_revs_populate_todo(struct todo_list *todo_list,
                                struct replay_opts *opts)
  {
                        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;
  }
  
@@@ -2645,8 -2701,6 +2703,8 @@@ static int do_exec(const char *command_
        fprintf(stderr, "Executing: %s\n", command_line);
        child_argv[0] = command_line;
        argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
 +      argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
 +                       absolute_path(get_git_work_tree()));
        status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
                                          child_env.argv);
  
@@@ -2852,26 -2906,6 +2910,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;
        }
  
                        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;
                        &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;
        }
  
 +      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");
 +              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(the_repository), 0);
        write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
  leave_merge:
        strbuf_release(&ref_name);
        rollback_lock_file(&lock);
 +      free_commit_list(to_merge);
        return ret;
  }
  
@@@ -3228,6 -3198,55 +3286,55 @@@ static const char *reflog_message(struc
        return buf.buf;
  }
  
+ static int run_git_checkout(struct replay_opts *opts, const char *commit,
+                           const char *action)
+ {
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       cmd.git_cmd = 1;
+       argv_array_push(&cmd.args, "checkout");
+       argv_array_push(&cmd.args, commit);
+       argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
+       if (opts->verbose)
+               return run_command(&cmd);
+       else
+               return run_command_silent_on_success(&cmd);
+ }
+ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
+ {
+       const char *action;
+       if (commit && *commit) {
+               action = reflog_message(opts, "start", "checkout %s", commit);
+               if (run_git_checkout(opts, commit, action))
+                       return error(_("could not checkout %s"), commit);
+       }
+       return 0;
+ }
+ static int checkout_onto(struct replay_opts *opts,
+                        const char *onto_name, const char *onto,
+                        const char *orig_head)
+ {
+       struct object_id oid;
+       const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
+       if (get_oid(orig_head, &oid))
+               return error(_("%s: not a valid OID"), orig_head);
+       if (run_git_checkout(opts, onto, action)) {
+               apply_autostash(opts);
+               sequencer_remove_state(opts);
+               return error(_("could not detach HEAD"));
+       }
+       return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+ }
  static const char rescheduled_advice[] =
  N_("Could not execute the todo command\n"
  "\n"
@@@ -3720,7 -3739,7 +3827,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);
        }
  
@@@ -3991,6 -4008,7 +4098,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");
  
@@@ -4333,24 -4352,20 +4440,20 @@@ int transform_todos(unsigned flags
        return i;
  }
  
- enum check_level {
-       CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
- };
- static enum check_level get_missing_commit_check_level(void)
+ enum missing_commit_check_level get_missing_commit_check_level(void)
  {
        const char *value;
  
        if (git_config_get_value("rebase.missingcommitscheck", &value) ||
                        !strcasecmp("ignore", value))
-               return CHECK_IGNORE;
+               return MISSING_COMMIT_CHECK_IGNORE;
        if (!strcasecmp("warn", value))
-               return CHECK_WARN;
+               return MISSING_COMMIT_CHECK_WARN;
        if (!strcasecmp("error", value))
-               return CHECK_ERROR;
+               return MISSING_COMMIT_CHECK_ERROR;
        warning(_("unrecognized setting %s for option "
                  "rebase.missingCommitsCheck. Ignoring."), value);
-       return CHECK_IGNORE;
+       return MISSING_COMMIT_CHECK_IGNORE;
  }
  
  define_commit_slab(commit_seen, unsigned char);
   */
  int check_todo_list(void)
  {
-       enum check_level check_level = get_missing_commit_check_level();
+       enum missing_commit_check_level check_level = get_missing_commit_check_level();
        struct strbuf todo_file = STRBUF_INIT;
        struct todo_list todo_list = TODO_LIST_INIT;
        struct strbuf missing = STRBUF_INIT;
        advise_to_edit_todo = res =
                parse_insn_buffer(todo_list.buf.buf, &todo_list);
  
-       if (res || check_level == CHECK_IGNORE)
+       if (res || check_level == MISSING_COMMIT_CHECK_IGNORE)
                goto leave_check;
  
        /* Mark the commits in git-rebase-todo as seen */
        if (!missing.len)
                goto leave_check;
  
-       if (check_level == CHECK_ERROR)
+       if (check_level == MISSING_COMMIT_CHECK_ERROR)
                advise_to_edit_todo = res = 1;
  
        fprintf(stderr,
@@@ -4460,17 -4475,17 +4563,17 @@@ static int rewrite_file(const char *pat
  }
  
  /* skip picking commits whose parents are unchanged */
int skip_unnecessary_picks(void)
static int skip_unnecessary_picks(struct object_id *output_oid)
  {
        const char *todo_file = rebase_path_todo();
        struct strbuf buf = STRBUF_INIT;
        struct todo_list todo_list = TODO_LIST_INIT;
-       struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
+       struct object_id *parent_oid;
        int fd, i;
  
        if (!read_oneliner(&buf, rebase_path_onto(), 0))
                return error(_("could not read 'onto'"));
-       if (get_oid(buf.buf, &onto_oid)) {
+       if (get_oid(buf.buf, output_oid)) {
                strbuf_release(&buf);
                return error(_("need a HEAD to fixup"));
        }
                if (item->commit->parents->next)
                        break; /* merge commit */
                parent_oid = &item->commit->parents->item->object.oid;
-               if (hashcmp(parent_oid->hash, oid->hash))
+               if (hashcmp(parent_oid->hash, output_oid->hash))
                        break;
-               oid = &item->commit->object.oid;
+               oidcpy(output_oid, &item->commit->object.oid);
        }
        if (i > 0) {
                int offset = get_item_line_offset(&todo_list, i);
  
                todo_list.current = i;
                if (is_fixup(peek_command(&todo_list, 0)))
-                       record_in_rewritten(oid, peek_command(&todo_list, 0));
+                       record_in_rewritten(output_oid, peek_command(&todo_list, 0));
        }
  
        todo_list_release(&todo_list);
-       printf("%s\n", oid_to_hex(oid));
  
        return 0;
  }
  
+ int complete_action(struct replay_opts *opts, unsigned flags,
+                   const char *shortrevisions, const char *onto_name,
+                   const char *onto, const char *orig_head, const char *cmd,
+                   unsigned autosquash)
+ {
+       const char *shortonto, *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       struct strbuf *buf = &(todo_list.buf);
+       struct object_id oid;
+       struct stat st;
+       get_oid(onto, &oid);
+       shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
+       if (!lstat(todo_file, &st) && st.st_size == 0 &&
+           write_message("noop\n", 5, todo_file, 0))
+               return -1;
+       if (autosquash && rearrange_squash())
+               return -1;
+       if (cmd && *cmd)
+               sequencer_add_exec_commands(cmd);
+       if (strbuf_read_file(buf, todo_file, 0) < 0)
+               return error_errno(_("could not read '%s'."), todo_file);
+       if (parse_insn_buffer(buf->buf, &todo_list)) {
+               todo_list_release(&todo_list);
+               return error(_("unusable todo list: '%s'"), todo_file);
+       }
+       if (count_commands(&todo_list) == 0) {
+               apply_autostash(opts);
+               sequencer_remove_state(opts);
+               todo_list_release(&todo_list);
+               return error(_("nothing to do"));
+       }
+       strbuf_addch(buf, '\n');
+       strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)",
+                                     "Rebase %s onto %s (%d commands)",
+                                     count_commands(&todo_list)),
+                             shortrevisions, shortonto, count_commands(&todo_list));
+       append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf);
+       if (write_message(buf->buf, buf->len, todo_file, 0)) {
+               todo_list_release(&todo_list);
+               return -1;
+       }
+       if (copy_file(rebase_path_todo_backup(), todo_file, 0666))
+               return error(_("could not copy '%s' to '%s'."), todo_file,
+                            rebase_path_todo_backup());
+       if (transform_todos(flags | TODO_LIST_SHORTEN_IDS))
+               return error(_("could not transform the todo list"));
+       strbuf_reset(buf);
+       if (launch_sequence_editor(todo_file, buf, NULL)) {
+               apply_autostash(opts);
+               sequencer_remove_state(opts);
+               todo_list_release(&todo_list);
+               return -1;
+       }
+       strbuf_stripspace(buf, 1);
+       if (buf->len == 0) {
+               apply_autostash(opts);
+               sequencer_remove_state(opts);
+               todo_list_release(&todo_list);
+               return error(_("nothing to do"));
+       }
+       todo_list_release(&todo_list);
+       if (check_todo_list()) {
+               checkout_onto(opts, onto_name, onto, orig_head);
+               return -1;
+       }
+       if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS)))
+               return error(_("could not transform the todo list"));
+       if (opts->allow_ff && skip_unnecessary_picks(&oid))
+               return error(_("could not skip unnecessary pick commands"));
+       if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head))
+               return -1;
+ ;
+       if (require_clean_work_tree("rebase", "", 1, 1))
+               return -1;
+       return sequencer_continue(opts);
+ }
  struct subject2item_entry {
        struct hashmap_entry entry;
        int i;
diff --combined strbuf.h
index b7aea8a9660160af9edf244fc0626009374b98c3,66da9822fd860cab6321505c368afa78496a4520..9043fa17aa4db378bee70a31411fdb2af2752cb9
+++ b/strbuf.h
@@@ -190,9 -190,6 +190,9 @@@ extern void strbuf_ltrim(struct strbuf 
  /* Strip trailing directory separators */
  extern void strbuf_trim_trailing_dir_sep(struct strbuf *);
  
 +/* Strip trailing LF or CR/LF */
 +extern void strbuf_trim_trailing_newline(struct strbuf *sb);
 +
  /**
   * Replace the contents of the strbuf with a reencoded form.  Returns -1
   * on error, 0 on success.
@@@ -578,6 -575,8 +578,8 @@@ extern void strbuf_add_unique_abbrev(st
   * file's contents are not read into the buffer upon completion.
   */
  extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
+ extern int launch_sequence_editor(const char *path, struct strbuf *buffer,
+                                 const char *const *env);
  
  extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
  
index 640fddc9c3903ba660d99b59a382d413706f7790,90c3ca70e6b9f6b4d8bfaa28db90c8ec19c4cf85..a147e2f1d4913ff307c02bc533c54d7672c6a310
@@@ -73,6 -73,16 +73,16 @@@ test_expect_success 'rebase --keep-empt
        git rebase --keep-empty -i HEAD~2 &&
        git log --oneline >actual &&
        test_line_count = 6 actual
+ '
+ cat > expect <<EOF
+ error: nothing to do
+ EOF
+ test_expect_success 'rebase -i with empty HEAD' '
+       set_fake_editor &&
+       test_must_fail env FAKE_LINES="1 exec_true" git rebase -i HEAD^ >actual 2>&1 &&
+       test_i18ncmp expect actual
  '
  
  test_expect_success 'rebase -i with the exec command' '
@@@ -119,15 -129,6 +129,15 @@@ test_expect_success 'rebase -i with exe
        )
  '
  
 +test_expect_success 'rebase -i sets work tree properly' '
 +      test_when_finished "rm -rf subdir" &&
 +      test_when_finished "test_might_fail git rebase --abort" &&
 +      mkdir subdir &&
 +      git rebase -x "(cd subdir && git rev-parse --show-toplevel)" HEAD^ \
 +              >actual &&
 +      ! grep "/subdir$" actual
 +'
 +
  test_expect_success 'rebase -i with the exec command checks tree cleanness' '
        git checkout master &&
        set_fake_editor &&
@@@ -273,18 -274,11 +283,18 @@@ test_expect_success 'retain authorship
  '
  
  test_expect_success 'retain authorship w/ conflicts' '
 +      oGIT_AUTHOR_NAME=$GIT_AUTHOR_NAME &&
 +      test_when_finished "GIT_AUTHOR_NAME=\$oGIT_AUTHOR_NAME" &&
 +
        git reset --hard twerp &&
        test_commit a conflict a conflict-a &&
        git reset --hard twerp &&
 -      GIT_AUTHOR_NAME=AttributeMe \
 +
 +      GIT_AUTHOR_NAME=AttributeMe &&
 +      export GIT_AUTHOR_NAME &&
        test_commit b conflict b conflict-b &&
 +      GIT_AUTHOR_NAME=$oGIT_AUTHOR_NAME &&
 +
        set_fake_editor &&
        test_must_fail git rebase -i conflict-a &&
        echo resolved >conflict &&
@@@ -525,7 -519,7 +535,7 @@@ test_expect_success 'interrupted squas
        one=$(git rev-parse HEAD~3) &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
 -      (echo one; echo two; echo four) > conflict &&
 +      test_write_lines one two four > conflict &&
        git add conflict &&
        test_must_fail git rebase --continue &&
        echo resolved > conflict &&
@@@ -539,10 -533,10 +549,10 @@@ test_expect_success 'interrupted squas
        one=$(git rev-parse HEAD~3) &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="3 squash 1 2" git rebase -i HEAD~3 &&
 -      (echo one; echo four) > conflict &&
 +      test_write_lines one four > conflict &&
        git add conflict &&
        test_must_fail git rebase --continue &&
 -      (echo one; echo two; echo four) > conflict &&
 +      test_write_lines one two four > conflict &&
        git add conflict &&
        test_must_fail git rebase --continue &&
        echo resolved > conflict &&
@@@ -569,16 -563,15 +579,16 @@@ test_expect_success '--continue tries t
  '
  
  test_expect_success 'aborted --continue does not squash commits after "edit"' '
 +      test_when_finished "git rebase --abort" &&
        old=$(git rev-parse HEAD) &&
        test_tick &&
        set_fake_editor &&
        FAKE_LINES="edit 1" git rebase -i HEAD^ &&
        echo "edited again" > file7 &&
        git add file7 &&
 -      test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue &&
 -      test $old = $(git rev-parse HEAD) &&
 -      git rebase --abort
 +      echo all the things >>conflict &&
 +      test_must_fail git rebase --continue &&
 +      test $old = $(git rev-parse HEAD)
  '
  
  test_expect_success 'auto-amend only edited commits after "edit"' '