Merge branch 'gr/rebase-i-drop-warn'
authorJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:22 +0000 (11:01 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:22 +0000 (11:01 -0700)
Add "drop commit-object-name subject" command as another way to
skip replaying of a commit in "rebase -i", and then punish those
who do not use it (and instead just remove the lines) by throwing
a warning.

* gr/rebase-i-drop-warn:
git rebase -i: add static check for commands and SHA-1
git rebase -i: warn about removed commits
git-rebase -i: add command "drop" to remove a commit

1  2 
Documentation/config.txt
Documentation/git-rebase.txt
git-rebase--interactive.sh
t/t3404-rebase-interactive.sh
diff --combined Documentation/config.txt
index 9358f4c16b0b53592e7a19456bd399ab0da4f42d,8169308aadf89739fd8a67dcfb273d9e63b7fe2a..315f2710af68edaa4c1703ec039245a93344d6a4
@@@ -914,8 -914,7 +914,8 @@@ command line with the `--color[=<when>]
  color.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
 -      of `plain` (context text), `meta` (metainformation), `frag`
 +      of `context` (context text - `plain` is a historical synonym),
 +      `meta` (metainformation), `frag`
        (hunk header), 'func' (function in hunk header), `old` (removed lines),
        `new` (added lines), `commit` (commit headers), or `whitespace`
        (highlighting whitespace errors).
@@@ -1242,25 -1241,6 +1242,25 @@@ filter.<driver>.smudge:
        object to a worktree file upon checkout.  See
        linkgit:gitattributes[5] for details.
  
 +fsck.<msg-id>::
 +      Allows overriding the message type (error, warn or ignore) of a
 +      specific message ID such as `missingEmail`.
 ++
 +For convenience, fsck prefixes the error/warning with the message ID,
 +e.g.  "missingEmail: invalid author/committer line - missing email" means
 +that setting `fsck.missingEmail = ignore` will hide that issue.
 ++
 +This feature is intended to support working with legacy repositories
 +which cannot be repaired without disruptive changes.
 +
 +fsck.skipList::
 +      The path to a sorted list of object names (i.e. one SHA-1 per
 +      line) that are known to be broken in a non-fatal way and should
 +      be ignored. This feature is useful when an established project
 +      should be accepted despite early commits containing errors that
 +      can be safely ignored such as invalid committer email addresses.
 +      Note: corrupt objects cannot be skipped with this setting.
 +
  gc.aggressiveDepth::
        The depth parameter used in the delta compression
        algorithm used by 'git gc --aggressive'.  This defaults
@@@ -2180,11 -2160,17 +2180,22 @@@ rebase.autoStash:
        successful rebase might result in non-trivial conflicts.
        Defaults to false.
  
+ rebase.missingCommitsCheck::
+       If set to "warn", git rebase -i will print a warning if some
+       commits are removed (e.g. a line was deleted), however the
+       rebase will still proceed. If set to "error", it will print
+       the previous warning and stop the rebase, 'git rebase
+       --edit-todo' can then be used to correct the error. If set to
+       "ignore", no checking is done.
+       To drop a commit without warning or error, use the `drop`
+       command in the todo-list.
+       Defaults to "ignore".
 +rebase.instructionFormat
 +      A format string, as specified in linkgit:git-log[1], to be used for
 +      the instruction list during an interactive rebase.  The format will automatically
 +      have the long commit hash prepended to the format.
 +
  receive.advertiseAtomic::
        By default, git-receive-pack will advertise the atomic push
        capability to its clients. If you don't want to this capability
@@@ -2221,28 -2207,6 +2232,28 @@@ receive.fsckObjects:
        Defaults to false. If not set, the value of `transfer.fsckObjects`
        is used instead.
  
 +receive.fsck.<msg-id>::
 +      When `receive.fsckObjects` is set to true, errors can be switched
 +      to warnings and vice versa by configuring the `receive.fsck.<msg-id>`
 +      setting where the `<msg-id>` is the fsck message ID and the value
 +      is one of `error`, `warn` or `ignore`. For convenience, fsck prefixes
 +      the error/warning with the message ID, e.g. "missingEmail: invalid
 +      author/committer line - missing email" means that setting
 +      `receive.fsck.missingEmail = ignore` will hide that issue.
 ++
 +This feature is intended to support working with legacy repositories
 +which would not pass pushing when `receive.fsckObjects = true`, allowing
 +the host to accept repositories with certain known issues but still catch
 +other issues.
 +
 +receive.fsck.skipList::
 +      The path to a sorted list of object names (i.e. one SHA-1 per
 +      line) that are known to be broken in a non-fatal way and should
 +      be ignored. This feature is useful when an established project
 +      should be accepted despite early commits containing errors that
 +      can be safely ignored such as invalid committer email addresses.
 +      Note: corrupt objects cannot be skipped with this setting.
 +
  receive.unpackLimit::
        If the number of objects received in a push is below this
        limit then the objects will be unpacked into loose object
@@@ -2605,20 -2569,14 +2616,20 @@@ uploadpack.hideRefs:
        are under the hierarchies listed on the value of this
        variable is excluded, and is hidden from `git ls-remote`,
        `git fetch`, etc.  An attempt to fetch a hidden ref by `git
 -      fetch` will fail.  See also `uploadpack.allowtipsha1inwant`.
 +      fetch` will fail.  See also `uploadpack.allowTipSHA1InWant`.
  
 -uploadpack.allowtipsha1inwant::
 +uploadpack.allowTipSHA1InWant::
        When `uploadpack.hideRefs` is in effect, allow `upload-pack`
        to accept a fetch request that asks for an object at the tip
        of a hidden ref (by default, such a request is rejected).
        see also `uploadpack.hideRefs`.
  
 +uploadpack.allowReachableSHA1InWant::
 +      Allow `upload-pack` to accept a fetch request that asks for an
 +      object that is reachable from any ref tip. However, note that
 +      calculating object reachability is computationally expensive.
 +      Defaults to `false`.
 +
  uploadpack.keepAlive::
        When `upload-pack` has started `pack-objects`, there may be a
        quiet period while `pack-objects` prepares the pack. Normally
index 7dc613cf3e5cdd04ed1c0725696ed3a11e6e14f1,2ca3b8d599382a77b2ff46f3e1bbb2527244d5b8..ca039546a463bee511ca3a1c7d301689fb56a412
@@@ -213,9 -213,12 +213,15 @@@ rebase.autoSquash:
  rebase.autoStash::
        If set to true enable '--autostash' option by default.
  
+ rebase.missingCommitsCheck::
+       If set to "warn", print warnings about removed commits in
+       interactive mode. If set to "error", print the warnings and
+       stop the rebase. If set to "ignore", no checking is
+       done. "ignore" by default.
 +rebase.instructionFormat::
 +      Custom commit list format to use during an '--interactive' rebase.
 +
  OPTIONS
  -------
  --onto <newbase>::
@@@ -362,10 -365,6 +368,10 @@@ default is `--no-fork-point`, otherwis
        Make a list of the commits which are about to be rebased.  Let the
        user edit that list before rebasing.  This mode can also be used to
        split commits (see SPLITTING COMMITS below).
 ++
 +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.
  
  -p::
  --preserve-merges::
@@@ -521,6 -520,9 +527,9 @@@ rebasing
  If you just want to edit the commit message for a commit, replace the
  command "pick" with the command "reword".
  
+ To drop a commit, replace the command "pick" with "drop", or just
+ delete the matching line.
  If you want to fold two or more commits into one, replace the command
  "pick" for the second and subsequent commits with "squash" or "fixup".
  If the commits had different authors, the folded commit will be
index 2f6ce55b586d7e71cd954409c30e6574dfcf301e,dcc3401b5a8c45fd9c5ba474416eb4a6c3c9a29e..f01637b1fdeb7f021b74171135ac0e7dba566542
@@@ -152,11 -152,21 +152,21 @@@ Commands
   s, squash = use commit, but meld into previous commit
   f, fixup = like "squash", but discard this commit's log message
   x, exec = run command (the rest of the line) using shell
+  d, drop = remove commit
  
  These lines can be re-ordered; they are executed from top to bottom.
  
+ EOF
+       if test $(get_missing_commit_check_level) = error
+       then
+               git stripspace --comment-lines >>"$todo" <<\EOF
+ Do not remove any line. Use 'drop' explicitly to remove a commit.
+ EOF
+       else
+               git stripspace --comment-lines >>"$todo" <<\EOF
  If you remove a line here THAT COMMIT WILL BE LOST.
  EOF
+       fi
  }
  
  make_patch () {
@@@ -502,10 -512,10 +512,10 @@@ do_pick () 
  }
  
  do_next () {
 -      rm -f "$msg" "$author_script" "$amend" || exit
 +      rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
        read -r command sha1 rest < "$todo"
        case "$command" in
-       "$comment_char"*|''|noop)
+       "$comment_char"*|''|noop|drop|d)
                mark_action_done
                ;;
        pick|p)
                read -r command rest < "$todo"
                mark_action_done
                printf 'Executing: %s\n' "$rest"
 -              # "exec" command doesn't take a sha1 in the todo-list.
 -              # => can't just use $sha1 here.
 -              git rev-parse --verify HEAD > "$state_dir"/stopped-sha
                ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
                status=$?
                # Run in subshell because require_clean_work_tree can die.
@@@ -740,15 -753,10 +750,15 @@@ collapse_todo_ids() 
  # "pick sha1 fixup!/squash! msg" appears in it so that the latter
  # comes immediately after the former, and change "pick" to
  # "fixup"/"squash".
 +#
 +# Note that if the config has specified a custom instruction format
 +# each log message will be re-retrieved in order to normalize the
 +# autosquash arrangement
  rearrange_squash () {
        # extract fixup!/squash! lines and resolve any referenced sha1's
        while read -r pick sha1 message
        do
 +              test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
                case "$message" in
                "squash! "*|"fixup! "*)
                        action="${message%%!*}"
                *" $sha1 "*) continue ;;
                esac
                printf '%s\n' "$pick $sha1 $message"
 +              test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
                used="$used$sha1 "
                while read -r squash action msg_prefix msg_content
                do
                                case "$message" in "$msg_content"*) emit=1;; esac ;;
                        esac
                        if test $emit = 1; then
 -                              real_prefix=$(echo "$msg_prefix" | sed "s/,/! /g")
 -                              printf '%s\n' "$action $squash ${real_prefix}$msg_content"
 +                              if test -n "${format}"
 +                              then
 +                                      msg_content=$(git log -n 1 --format="${format}" ${squash})
 +                              else
 +                                      msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content"
 +                              fi
 +                              printf '%s\n' "$action $squash $msg_content"
                                used="$used$squash "
                        fi
                done <"$1.sq"
@@@ -844,6 -846,180 +854,180 @@@ add_exec_commands () 
        mv "$1.new" "$1"
  }
  
+ # Check if the SHA-1 passed as an argument is a
+ # correct one, if not then print $2 in "$todo".badsha
+ # $1: the SHA-1 to test
+ # $2: the line to display if incorrect SHA-1
+ check_commit_sha () {
+       badsha=0
+       if test -z $1
+       then
+               badsha=1
+       else
+               sha1_verif="$(git rev-parse --verify --quiet $1^{commit})"
+               if test -z $sha1_verif
+               then
+                       badsha=1
+               fi
+       fi
+       if test $badsha -ne 0
+       then
+               warn "Warning: the SHA-1 is missing or isn't" \
+                       "a commit in the following line:"
+               warn " - $2"
+               warn
+       fi
+       return $badsha
+ }
+ # prints the bad commits and bad commands
+ # from the todolist in stdin
+ check_bad_cmd_and_sha () {
+       retval=0
+       git stripspace --strip-comments |
+       (
+               while read -r line
+               do
+                       IFS=' '
+                       set -- $line
+                       command=$1
+                       sha1=$2
+                       case $command in
+                       ''|noop|x|"exec")
+                               # Doesn't expect a SHA-1
+                               ;;
+                       pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
+                               if ! check_commit_sha $sha1 "$line"
+                               then
+                                       retval=1
+                               fi
+                               ;;
+                       *)
+                               warn "Warning: the command isn't recognized" \
+                                       "in the following line:"
+                               warn " - $line"
+                               warn
+                               retval=1
+                               ;;
+                       esac
+               done
+               return $retval
+       )
+ }
+ # Print the list of the SHA-1 of the commits
+ # from stdin to stdout
+ todo_list_to_sha_list () {
+       git stripspace --strip-comments |
+       while read -r command sha1 rest
+       do
+               case $command in
+               "$comment_char"*|''|noop|x|"exec")
+                       ;;
+               *)
+                       long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null)
+                       printf "%s\n" "$long_sha"
+                       ;;
+               esac
+       done
+ }
+ # Use warn for each line in stdin
+ warn_lines () {
+       while read -r line
+       do
+               warn " - $line"
+       done
+ }
+ # Switch to the branch in $into and notify it in the reflog
+ checkout_onto () {
+       GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
+       output git checkout $onto || die_abort "could not detach HEAD"
+       git update-ref ORIG_HEAD $orig_head
+ }
+ get_missing_commit_check_level () {
+       check_level=$(git config --get rebase.missingCommitsCheck)
+       check_level=${check_level:-ignore}
+       # Don't be case sensitive
+       printf '%s' "$check_level" | tr 'A-Z' 'a-z'
+ }
+ # Check if the user dropped some commits by mistake
+ # Behaviour determined by rebase.missingCommitsCheck.
+ # Check if there is an unrecognized command or a
+ # bad SHA-1 in a command.
+ check_todo_list () {
+       raise_error=f
+       check_level=$(get_missing_commit_check_level)
+       case "$check_level" in
+       warn|error)
+               # Get the SHA-1 of the commits
+               todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1
+               todo_list_to_sha_list <"$todo" >"$todo".newsha1
+               # Sort the SHA-1 and compare them
+               sort -u "$todo".oldsha1 >"$todo".oldsha1+
+               mv "$todo".oldsha1+ "$todo".oldsha1
+               sort -u "$todo".newsha1 >"$todo".newsha1+
+               mv "$todo".newsha1+ "$todo".newsha1
+               comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss
+               # Warn about missing commits
+               if test -s "$todo".miss
+               then
+                       test "$check_level" = error && raise_error=t
+                       warn "Warning: some commits may have been dropped" \
+                               "accidentally."
+                       warn "Dropped commits (newer to older):"
+                       # Make the list user-friendly and display
+                       opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin"
+                       git rev-list $opt <"$todo".miss | warn_lines
+                       warn "To avoid this message, use \"drop\" to" \
+                               "explicitly remove a commit."
+                       warn
+                       warn "Use 'git config rebase.missingCommitsCheck' to change" \
+                               "the level of warnings."
+                       warn "The possible behaviours are: ignore, warn, error."
+                       warn
+               fi
+               ;;
+       ignore)
+               ;;
+       *)
+               warn "Unrecognized setting $check_level for option" \
+                       "rebase.missingCommitsCheck. Ignoring."
+               ;;
+       esac
+       if ! check_bad_cmd_and_sha <"$todo"
+       then
+               raise_error=t
+       fi
+       if test $raise_error = t
+       then
+               # Checkout before the first commit of the
+               # rebase: this way git rebase --continue
+               # will work correctly as it expects HEAD to be
+               # placed before the commit of the next action
+               checkout_onto
+               warn "You can fix this with 'git rebase --edit-todo'."
+               die "Or you can abort the rebase with 'git rebase --abort'."
+       fi
+ }
  # The whole contents of this file is run by dot-sourcing it from
  # inside a shell function.  It used to be that "return"s we see
  # below were not inside any function, and expected to return
@@@ -860,11 -1036,7 +1044,11 @@@ continue
        # do we have anything to commit?
        if git diff-index --cached --quiet HEAD --
        then
 -              : Nothing to commit -- skip this
 +              # Nothing to commit -- skip this commit
 +
 +              test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD ||
 +              rm "$GIT_DIR"/CHERRY_PICK_HEAD ||
 +              die "Could not remove CHERRY_PICK_HEAD"
        else
                if ! test -f "$author_script"
                then
@@@ -902,10 -1074,7 +1086,10 @@@ first and then run 'git rebase --contin
                fi
        fi
  
 -      record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
 +      if test -r "$state_dir"/stopped-sha
 +      then
 +              record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
 +      fi
  
        require_clean_work_tree "rebase"
        do_rest
@@@ -992,10 -1161,7 +1176,10 @@@ els
        revisions=$onto...$orig_head
        shortrevisions=$shorthead
  fi
 -git rev-list $merges_option --pretty=oneline --reverse --left-right --topo-order \
 +format=$(git config --get rebase.instructionFormat)
 +# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
 +git rev-list $merges_option --format="%m%H ${format:-%s}" \
 +      --reverse --left-right --topo-order \
        $revisions ${restrict_revision+^$restrict_revision} | \
        sed -n "s/^>//p" |
  while read -r sha1 rest
@@@ -1094,13 -1260,13 +1278,13 @@@ git_sequence_editor "$todo" |
  has_action "$todo" ||
        return 2
  
+ check_todo_list
  expand_todo_ids
  
  test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
  
- GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
- output git checkout $onto || die_abort "could not detach HEAD"
- git update-ref ORIG_HEAD $orig_head
+ checkout_onto
  do_rest
  
  }
index 467e6c1ed526d4bfffbab2e1479e1334058506de,ebdab4b95d0bfb4c91295f1461f9bd51bd76cf15..9d26064abaeb9134f70f3f011ac14f965a250f77
@@@ -1102,25 -1102,127 +1102,148 @@@ test_expect_success 'rebase -i commits 
        test $(git cat-file commit HEAD | sed -ne \$p) = I
  '
  
 +test_expect_success 'rebase --continue removes CHERRY_PICK_HEAD' '
 +      git checkout -b commit-to-skip &&
 +      for double in X 3 1
 +      do
 +              test_seq 5 | sed "s/$double/&&/" >seq &&
 +              git add seq &&
 +              test_tick &&
 +              git commit -m seq-$double
 +      done &&
 +      git tag seq-onto &&
 +      git reset --hard HEAD~2 &&
 +      git cherry-pick seq-onto &&
 +      set_fake_editor &&
 +      test_must_fail env FAKE_LINES= git rebase -i seq-onto &&
 +      test -d .git/rebase-merge &&
 +      git rebase --continue &&
 +      git diff --exit-code seq-onto &&
 +      test ! -d .git/rebase-merge &&
 +      test ! -f .git/CHERRY_PICK_HEAD
 +'
 +
+ rebase_setup_and_clean () {
+       test_when_finished "
+               git checkout master &&
+               test_might_fail git branch -D $1 &&
+               test_might_fail git rebase --abort
+       " &&
+       git checkout -b $1 master
+ }
+ test_expect_success 'drop' '
+       rebase_setup_and_clean drop-test &&
+       set_fake_editor &&
+       FAKE_LINES="1 drop 2 3 drop 4 5" git rebase -i --root &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test C = $(git cat-file commit HEAD^ | sed -ne \$p) &&
+       test A = $(git cat-file commit HEAD^^ | sed -ne \$p)
+ '
+ cat >expect <<EOF
+ Successfully rebased and updated refs/heads/missing-commit.
+ EOF
+ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
+       test_config rebase.missingCommitsCheck ignore &&
+       rebase_setup_and_clean missing-commit &&
+       set_fake_editor &&
+       FAKE_LINES="1 2 3 4" \
+               git rebase -i --root 2>actual &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test_cmp expect actual
+ '
+ cat >expect <<EOF
+ Warning: some commits may have been dropped accidentally.
+ Dropped commits (newer to older):
+  - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+ To avoid this message, use "drop" to explicitly remove a commit.
+ Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
+ The possible behaviours are: ignore, warn, error.
+ Successfully rebased and updated refs/heads/missing-commit.
+ EOF
+ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' '
+       test_config rebase.missingCommitsCheck warn &&
+       rebase_setup_and_clean missing-commit &&
+       set_fake_editor &&
+       FAKE_LINES="1 2 3 4" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p)
+ '
+ cat >expect <<EOF
+ Warning: some commits may have been dropped accidentally.
+ Dropped commits (newer to older):
+  - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+  - $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)
+ To avoid this message, use "drop" to explicitly remove a commit.
+ Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
+ The possible behaviours are: ignore, warn, error.
+ You can fix this with 'git rebase --edit-todo'.
+ Or you can abort the rebase with 'git rebase --abort'.
+ EOF
+ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
+       test_config rebase.missingCommitsCheck error &&
+       rebase_setup_and_clean missing-commit &&
+       set_fake_editor &&
+       test_must_fail env FAKE_LINES="1 2 4" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       cp .git/rebase-merge/git-rebase-todo.backup \
+               .git/rebase-merge/git-rebase-todo &&
+       FAKE_LINES="1 2 drop 3 4 drop 5" \
+               git rebase --edit-todo &&
+       git rebase --continue &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test B = $(git cat-file commit HEAD^ | sed -ne \$p)
+ '
+ cat >expect <<EOF
+ Warning: the command isn't recognized in the following line:
+  - badcmd $(git rev-list --oneline -1 master~1)
+ You can fix this with 'git rebase --edit-todo'.
+ Or you can abort the rebase with 'git rebase --abort'.
+ EOF
+ test_expect_success 'static check of bad command' '
+       rebase_setup_and_clean bad-cmd &&
+       set_fake_editor &&
+       test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
+       git rebase --continue &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test C = $(git cat-file commit HEAD^ | sed -ne \$p)
+ '
+ cat >expect <<EOF
+ Warning: the SHA-1 is missing or isn't a commit in the following line:
+  - edit XXXXXXX False commit
+ You can fix this with 'git rebase --edit-todo'.
+ Or you can abort the rebase with 'git rebase --abort'.
+ EOF
+ test_expect_success 'static check of bad SHA-1' '
+       rebase_setup_and_clean bad-sha &&
+       set_fake_editor &&
+       test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
+               git rebase -i --root 2>actual &&
+       test_cmp expect actual &&
+       FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
+       git rebase --continue &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p)
+ '
  test_done