Merge branch 'pw/rebase-signoff'
authorJunio C Hamano <gitster@pobox.com>
Wed, 25 Apr 2018 04:28:51 +0000 (13:28 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 25 Apr 2018 04:28:51 +0000 (13:28 +0900)
"git rebase" has learned to honor "--signoff" option when using
backends other than "am" (but not "--preserve-merges").

* pw/rebase-signoff:
rebase --keep-empty: always use interactive rebase
rebase -p: error out if --signoff is given
rebase: extend --signoff support

1  2 
Documentation/git-rebase.txt
git-rebase--am.sh
git-rebase--interactive.sh
git-rebase--merge.sh
git-rebase.sh
sequencer.c
index 3277ca143273e01f5f4973ed351c8a5cb4b8e0fa,86a887a9c574ab7ee483b9e16043be3141a32909..dd852068b1d06f8f40566da65579564144fef718
@@@ -12,7 -12,7 +12,7 @@@ SYNOPSI
        [<upstream> [<branch>]]
  'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
 -'git rebase' --continue | --skip | --abort | --quit | --edit-todo
 +'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch
  
  DESCRIPTION
  -----------
@@@ -244,22 -244,12 +244,22 @@@ leave out at most one of A and B, in wh
        Keep the commits that do not change anything from its
        parents in the result.
  
 +--allow-empty-message::
 +      By default, rebasing commits with an empty message will fail.
 +      This option overrides that behavior, allowing commits with empty
 +      messages to be rebased.
 +
  --skip::
        Restart the rebasing process by skipping the current patch.
  
  --edit-todo::
        Edit the todo list during an interactive rebase.
  
 +--show-current-patch::
 +      Show the current patch in an interactive rebase or when rebase
 +      is stopped because of conflicts. This is the equivalent of
 +      `git show REBASE_HEAD`.
 +
  -m::
  --merge::
        Use merging strategies to rebase.  When the recursive (default) merge
@@@ -364,9 -354,10 +364,10 @@@ default is `--no-fork-point`, otherwis
        Incompatible with the --interactive option.
  
  --signoff::
-       This flag is passed to 'git am' to sign off all the rebased
-       commits (see linkgit:git-am[1]). Incompatible with the
-       --interactive option.
+       Add a Signed-off-by: trailer to all the rebased commits. Note
+       that if `--interactive` is given then only commits marked to be
+       picked, edited or reworded will have the trailer added. Incompatible
+       with the `--preserve-merges` option.
  
  -i::
  --interactive::
diff --combined git-rebase--am.sh
index e5fd6101db3018aff5fca7047eb2958291b6b03d,91381d5872c6ce47ec49d0eb060e0e0c0ba033af..99b8c177875a7f26ae6c7f70be42c9d97231f7b9
@@@ -4,6 -4,15 +4,6 @@@
  # Copyright (c) 2010 Junio C Hamano.
  #
  
 -# 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
 -# to the function that dot-sourced us.
 -#
 -# However, older (9.x) versions of FreeBSD /bin/sh misbehave on such a
 -# construct and continue to run the statements that follow such a "return".
 -# As a work-around, we introduce an extra layer of a function
 -# here, and immediately call it after defining it.
  git_rebase__am () {
  
  case "$action" in
@@@ -18,9 -27,6 +18,9 @@@ skip
        move_to_original_branch
        return
        ;;
 +show-current-patch)
 +      exec git am --show-current-patch
 +      ;;
  esac
  
  if test -z "$rebase_root"
@@@ -32,60 -38,47 +32,47 @@@ els
  fi
  
  ret=0
- if test -n "$keep_empty"
- then
-       # we have to do this the hard way.  git format-patch completely squashes
-       # empty commits and even if it didn't the format doesn't really lend
-       # itself well to recording empty patches.  fortunately, cherry-pick
-       # makes this easy
-       git cherry-pick ${gpg_sign_opt:+"$gpg_sign_opt"} --allow-empty \
-               $allow_rerere_autoupdate --right-only "$revisions" \
-               $allow_empty_message \
-               ${restrict_revision+^$restrict_revision}
-       ret=$?
- else
-       rm -f "$GIT_DIR/rebased-patches"
+ rm -f "$GIT_DIR/rebased-patches"
  
      git format-patch -k --stdout --full-index --cherry-pick --right-only \
-               --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
-               --pretty=mboxrd \
-               $git_format_patch_opt \
-               "$revisions" ${restrict_revision+^$restrict_revision} \
-               >"$GIT_DIR/rebased-patches"
      ret=$?
+ git format-patch -k --stdout --full-index --cherry-pick --right-only \
+       --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
+       --pretty=mboxrd \
+       $git_format_patch_opt \
+       "$revisions" ${restrict_revision+^$restrict_revision} \
+       >"$GIT_DIR/rebased-patches"
+ ret=$?
  
      if test 0 != $ret
      then
-               rm -f "$GIT_DIR/rebased-patches"
-               case "$head_name" in
-               refs/heads/*)
-                       git checkout -q "$head_name"
-                       ;;
-               *)
-                       git checkout -q "$orig_head"
-                       ;;
-               esac
+ if test 0 != $ret
+ then
+       rm -f "$GIT_DIR/rebased-patches"
+       case "$head_name" in
+       refs/heads/*)
+               git checkout -q "$head_name"
+               ;;
+       *)
+               git checkout -q "$orig_head"
+               ;;
+       esac
  
-               cat >&2 <<-EOF
+       cat >&2 <<-EOF
  
-               git encountered an error while preparing the patches to replay
-               these revisions:
+       git encountered an error while preparing the patches to replay
+       these revisions:
  
-                   $revisions
+           $revisions
  
-               As a result, git cannot rebase them.
-               EOF
-               return $ret
      fi
+       As a result, git cannot rebase them.
+       EOF
+       return $ret
+ fi
  
      git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" \
-               --patch-format=mboxrd \
-               $allow_rerere_autoupdate \
-               ${gpg_sign_opt:+"$gpg_sign_opt"} <"$GIT_DIR/rebased-patches"
      ret=$?
+ git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" \
+       --patch-format=mboxrd \
+       $allow_rerere_autoupdate \
+       ${gpg_sign_opt:+"$gpg_sign_opt"} <"$GIT_DIR/rebased-patches"
+ ret=$?
  
-       rm -f "$GIT_DIR/rebased-patches"
- fi
+ rm -f "$GIT_DIR/rebased-patches"
  
  if test 0 != $ret
  then
@@@ -96,3 -89,5 +83,3 @@@ f
  move_to_original_branch
  
  }
 -# ... and then we call the whole thing.
 -git_rebase__am
index 50323fc27351666440e76ae50806ee480e998da3,2bcdb67982befaf04ab28b4707051dfe228d12c7..9947e6265fe7c0bc4531fd928bbf2373a0ae7552
@@@ -199,14 -199,12 +199,14 @@@ make_patch () 
  
  die_with_patch () {
        echo "$1" > "$state_dir"/stopped-sha
 +      git update-ref REBASE_HEAD "$1"
        make_patch "$1"
        die "$2"
  }
  
  exit_with_patch () {
        echo "$1" > "$state_dir"/stopped-sha
 +      git update-ref REBASE_HEAD "$1"
        make_patch $1
        git rev-parse --verify HEAD > "$amend"
        gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
@@@ -283,9 -281,9 +283,9 @@@ pick_one () 
  
        test -d "$rewritten" &&
                pick_one_preserving_merges "$@" && return
 -      output eval git cherry-pick $allow_rerere_autoupdate \
 +      output eval git cherry-pick $allow_rerere_autoupdate $allow_empty_message \
                        ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
-                       "$strategy_args" $empty_args $ff "$@"
+                       $signoff "$strategy_args" $empty_args $ff "$@"
  
        # If cherry-pick dies it leaves the to-be-picked commit unrecorded. Reschedule
        # previous task so this commit is not lost.
@@@ -307,14 -305,17 +307,14 @@@ pick_one_preserving_merges () 
        esac
        sha1=$(git rev-parse $sha1)
  
 -      if test -f "$state_dir"/current-commit
 +      if test -f "$state_dir"/current-commit && test "$fast_forward" = t
        then
 -              if test "$fast_forward" = t
 -              then
 -                      while read current_commit
 -                      do
 -                              git rev-parse HEAD > "$rewritten"/$current_commit
 -                      done <"$state_dir"/current-commit
 -                      rm "$state_dir"/current-commit ||
 -                              die "$(gettext "Cannot write current commit's replacement sha1")"
 -              fi
 +              while read current_commit
 +              do
 +                      git rev-parse HEAD > "$rewritten"/$current_commit
 +              done <"$state_dir"/current-commit
 +              rm "$state_dir"/current-commit ||
 +                      die "$(gettext "Cannot write current commit's replacement sha1")"
        fi
  
        echo $sha1 >> "$state_dir"/current-commit
                                        --sq-quote "$gpg_sign_opt")} \
                                $allow_rerere_autoupdate "$merge_args" \
                                "$strategy_args" \
 -                              -m $(git rev-parse --sq-quote "$msg_content") \
 +                              -m "$(git rev-parse --sq-quote "$msg_content")" \
                                "$new_parents"
                        then
                                printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
                        ;;
                *)
                        output eval git cherry-pick $allow_rerere_autoupdate \
 +                              $allow_empty_message \
                                ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
                                "$strategy_args" "$@" ||
                                die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")"
@@@ -524,10 -524,10 +524,10 @@@ do_pick () 
                # resolve before manually running git commit --amend then git
                # rebase --continue.
                git commit --allow-empty --allow-empty-message --amend \
-                          --no-post-rewrite -n -q -C $sha1 &&
+                          --no-post-rewrite -n -q -C $sha1 $signoff &&
                        pick_one -n $sha1 &&
                        git commit --allow-empty --allow-empty-message \
-                                  --amend --no-post-rewrite -n -q -C $sha1 \
+                                  --amend --no-post-rewrite -n -q -C $sha1 $signoff \
                                   ${gpg_sign_opt:+"$gpg_sign_opt"} ||
                                   die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
        else
@@@ -559,8 -559,7 +559,8 @@@ do_next () 
  
                mark_action_done
                do_pick $sha1 "$rest"
 -              git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || {
 +              git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} \
 +                      $allow_empty_message || {
                        warn "$(eval_gettext "\
  Could not amend commit after successfully picking \$sha1... \$rest
  This is most likely due to an empty commit message, or the pre-commit hook
@@@ -608,7 -607,7 +608,7 @@@ you are able to reword the commit.")
                        # This is an intermediate commit; its message will only be
                        # used in case of trouble.  So use the long version:
                        do_with_author output git commit --amend --no-verify -F "$squash_msg" \
 -                              ${gpg_sign_opt:+"$gpg_sign_opt"} ||
 +                              ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
                                die_failed_squash $sha1 "$rest"
                        ;;
                *)
                        if test -f "$fixup_msg"
                        then
                                do_with_author git commit --amend --no-verify -F "$fixup_msg" \
 -                                      ${gpg_sign_opt:+"$gpg_sign_opt"} ||
 +                                      ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
                                        die_failed_squash $sha1 "$rest"
                        else
                                cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
                                rm -f "$GIT_DIR"/MERGE_MSG
                                do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
 -                                      ${gpg_sign_opt:+"$gpg_sign_opt"} ||
 +                                      ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
                                        die_failed_squash $sha1 "$rest"
                        fi
                        rm -f "$squash_msg" "$fixup_msg"
@@@ -740,39 -739,36 +740,39 @@@ get_missing_commit_check_level () 
        printf '%s' "$check_level" | tr 'A-Z' 'a-z'
  }
  
 -# 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
 -# to the function that dot-sourced us.
 +# Initiate an action. If the cannot be any
 +# further action it  may exec a command
 +# or exit and not return.
  #
 -# However, older (9.x) versions of FreeBSD /bin/sh misbehave on such a
 -# construct and continue to run the statements that follow such a "return".
 -# As a work-around, we introduce an extra layer of a function
 -# here, and immediately call it after defining it.
 -git_rebase__interactive () {
 -
 -case "$action" in
 -continue)
 -      if test ! -d "$rewritten"
 -      then
 -              exec git rebase--helper ${force_rebase:+--no-ff} --continue
 -      fi
 -      # do we have anything to commit?
 -      if git diff-index --cached --quiet HEAD --
 -      then
 -              # Nothing to commit -- skip this commit
 -
 -              test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD ||
 -              rm "$GIT_DIR"/CHERRY_PICK_HEAD ||
 -              die "$(gettext "Could not remove CHERRY_PICK_HEAD")"
 -      else
 -              if ! test -f "$author_script"
 +# TODO: Consider a cleaner return model so it
 +# never exits and always return 0 if process
 +# is complete.
 +#
 +# Parameter 1 is the action to initiate.
 +#
 +# Returns 0 if the action was able to complete
 +# and if 1 if further processing is required.
 +initiate_action () {
 +      case "$1" in
 +      continue)
 +              if test ! -d "$rewritten"
                then
 -                      gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
 -                      die "$(eval_gettext "\
 +                      exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
 +                              --continue
 +              fi
 +              # do we have anything to commit?
 +              if git diff-index --cached --quiet HEAD --
 +              then
 +                      # Nothing to commit -- skip this commit
 +
 +                      test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD ||
 +                      rm "$GIT_DIR"/CHERRY_PICK_HEAD ||
 +                      die "$(gettext "Could not remove CHERRY_PICK_HEAD")"
 +              else
 +                      if ! test -f "$author_script"
 +                      then
 +                              gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
 +                              die "$(eval_gettext "\
  You have staged changes in your working tree.
  If these changes are meant to be
  squashed into the previous commit, run:
@@@ -787,199 -783,83 +787,199 @@@ In both cases, once you're done, contin
  
    git rebase --continue
  ")"
 -              fi
 -              . "$author_script" ||
 -                      die "$(gettext "Error trying to find the author identity to amend commit")"
 -              if test -f "$amend"
 -              then
 -                      current_head=$(git rev-parse --verify HEAD)
 -                      test "$current_head" = $(cat "$amend") ||
 -                      die "$(gettext "\
 +                      fi
 +                      . "$author_script" ||
 +                              die "$(gettext "Error trying to find the author identity to amend commit")"
 +                      if test -f "$amend"
 +                      then
 +                              current_head=$(git rev-parse --verify HEAD)
 +                              test "$current_head" = $(cat "$amend") ||
 +                              die "$(gettext "\
  You have uncommitted changes in your working tree. Please commit them
  first and then run 'git rebase --continue' again.")"
 -                      do_with_author git commit --amend --no-verify -F "$msg" -e \
 -                              ${gpg_sign_opt:+"$gpg_sign_opt"} ||
 -                              die "$(gettext "Could not commit staged changes.")"
 -              else
 -                      do_with_author git commit --no-verify -F "$msg" -e \
 -                              ${gpg_sign_opt:+"$gpg_sign_opt"} ||
 -                              die "$(gettext "Could not commit staged changes.")"
 +                              do_with_author git commit --amend --no-verify -F "$msg" -e \
 +                                      ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
 +                                      die "$(gettext "Could not commit staged changes.")"
 +                      else
 +                              do_with_author git commit --no-verify -F "$msg" -e \
 +                                      ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
 +                                      die "$(gettext "Could not commit staged changes.")"
 +                      fi
                fi
 -      fi
  
 -      if test -r "$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
 +              return 0
 +              ;;
 +      skip)
 +              git rerere clear
 +
 +              if test ! -d "$rewritten"
 +              then
 +                      exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
 +                              --continue
 +              fi
 +              do_rest
 +              return 0
 +              ;;
 +      edit-todo)
 +              git stripspace --strip-comments <"$todo" >"$todo".new
 +              mv -f "$todo".new "$todo"
 +              collapse_todo_ids
 +              append_todo_help
 +              gettext "
 +You are editing the todo file of an ongoing interactive rebase.
 +To continue rebase after editing, run:
 +    git rebase --continue
 +
 +" | git stripspace --comment-lines >>"$todo"
 +
 +              git_sequence_editor "$todo" ||
 +                      die "$(gettext "Could not execute editor")"
 +              expand_todo_ids
 +
 +              exit
 +              ;;
 +      show-current-patch)
 +              exec git show REBASE_HEAD --
 +              ;;
 +      *)
 +              return 1 # continue
 +              ;;
 +      esac
 +}
 +
 +setup_reflog_action () {
 +      comment_for_reflog start
 +
 +      if test ! -z "$switch_to"
        then
 -              record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
 +              GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
 +              output git checkout "$switch_to" -- ||
 +                      die "$(eval_gettext "Could not checkout \$switch_to")"
 +
 +              comment_for_reflog start
        fi
 +}
  
 -      require_clean_work_tree "rebase"
 -      do_rest
 -      return 0
 -      ;;
 -skip)
 -      git rerere clear
 +init_basic_state () {
 +      orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
 +      mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
 +      rm -f "$(git rev-parse --git-path REBASE_HEAD)"
 +
 +      : > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
 +      write_basic_state
 +}
  
 -      if test ! -d "$rewritten"
 +init_revisions_and_shortrevisions () {
 +      shorthead=$(git rev-parse --short $orig_head)
 +      shortonto=$(git rev-parse --short $onto)
 +      if test -z "$rebase_root"
 +              # this is now equivalent to ! -z "$upstream"
        then
 -              exec git rebase--helper ${force_rebase:+--no-ff} --continue
 +              shortupstream=$(git rev-parse --short $upstream)
 +              revisions=$upstream...$orig_head
 +              shortrevisions=$shortupstream..$shorthead
 +      else
 +              revisions=$onto...$orig_head
 +              shortrevisions=$shorthead
        fi
 -      do_rest
 -      return 0
 -      ;;
 -edit-todo)
 -      git stripspace --strip-comments <"$todo" >"$todo".new
 -      mv -f "$todo".new "$todo"
 -      collapse_todo_ids
 +}
 +
 +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"
 +
 +      todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
 +      todocount=${todocount##* }
 +
 +cat >>"$todo" <<EOF
 +
 +$comment_char $(eval_ngettext \
 +      "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
 +      "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
 +      "$todocount")
 +EOF
        append_todo_help
        gettext "
 -You are editing the todo file of an ongoing interactive rebase.
 -To continue rebase after editing, run:
 -    git rebase --continue
 +      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
 +              printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
 +      fi
 +
 +
 +      has_action "$todo" ||
 +              return 2
 +
 +      cp "$todo" "$todo".backup
 +      collapse_todo_ids
        git_sequence_editor "$todo" ||
 -              die "$(gettext "Could not execute editor")"
 +              die_abort "$(gettext "Could not execute editor")"
 +
 +      has_action "$todo" ||
 +              return 2
 +
 +      git rebase--helper --check-todo-list || {
 +              ret=$?
 +              checkout_onto
 +              exit $ret
 +      }
 +
        expand_todo_ids
  
 -      exit
 -      ;;
 -esac
 +      test -d "$rewritten" || test -n "$force_rebase" ||
 +      onto="$(git rebase--helper --skip-unnecessary-picks)" ||
 +      die "Could not skip unnecessary pick commands"
  
 -comment_for_reflog start
 +      checkout_onto
 +      if test -z "$rebase_root" && test ! -d "$rewritten"
 +      then
 +              require_clean_work_tree "rebase"
 +              exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
 +                      --continue
 +      fi
 +      do_rest
 +}
  
 -if test ! -z "$switch_to"
 -then
 -      GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
 -      output git checkout "$switch_to" -- ||
 -              die "$(eval_gettext "Could not checkout \$switch_to")"
 +git_rebase__interactive () {
 +      initiate_action "$action"
 +      ret=$?
 +      if test $ret = 0; then
 +              return 0
 +      fi
  
 -      comment_for_reflog start
 -fi
 +      setup_reflog_action
 +      init_basic_state
 +
 +      init_revisions_and_shortrevisions
 +
 +      git rebase--helper --make-script ${keep_empty:+--keep-empty} \
 +              $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
 +      die "$(gettext "Could not generate todo list")"
  
 -orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
 -mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
 +      complete_action
 +}
 +
 +git_rebase__interactive__preserve_merges () {
 +      initiate_action "$action"
 +      ret=$?
 +      if test $ret = 0; then
 +              return 0
 +      fi
 +
 +      setup_reflog_action
 +      init_basic_state
  
 -: > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
 -write_basic_state
 -if test t = "$preserve_merges"
 -then
        if test -z "$rebase_root"
        then
                mkdir "$rewritten" &&
                echo $onto > "$rewritten"/root ||
                        die "$(gettext "Could not init rewritten commits")"
        fi
 -      # No cherry-pick because our first pass is to determine
 -      # parents to rewrite and skipping dropped commits would
 -      # prematurely end our probe
 -      merges_option=
 -else
 -      merges_option="--no-merges --cherry-pick"
 -fi
 -
 -shorthead=$(git rev-parse --short $orig_head)
 -shortonto=$(git rev-parse --short $onto)
 -if test -z "$rebase_root"
 -      # this is now equivalent to ! -z "$upstream"
 -then
 -      shortupstream=$(git rev-parse --short $upstream)
 -      revisions=$upstream...$orig_head
 -      shortrevisions=$shortupstream..$shorthead
 -else
 -      revisions=$onto...$orig_head
 -      shortrevisions=$shorthead
 -fi
 -if test t != "$preserve_merges"
 -then
 -      git rebase--helper --make-script ${keep_empty:+--keep-empty} \
 -              $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
 -      die "$(gettext "Could not generate todo list")"
 -else
 +
 +      init_revisions_and_shortrevisions
 +
        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}" \
 +      git rev-list --format="%m%H ${format:-%s}" \
                --reverse --left-right --topo-order \
                $revisions ${restrict_revision+^$restrict_revision} | \
                sed -n "s/^>//p" |
        while read -r sha1 rest
        do
 -
                if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
                then
                        comment_out="$comment_char "
                        printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
                fi
        done
 -fi
  
 -# Watch for commits that been dropped by --cherry-pick
 -if test t = "$preserve_merges"
 -then
 +      # Watch for commits that been dropped by --cherry-pick
        mkdir "$dropped"
        # Save all non-cherry-picked changes
        git rev-list $revisions --left-right --cherry-pick | \
                        rm "$rewritten"/$rev
                fi
        done
 -fi
 -
 -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"
 -
 -todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
 -todocount=${todocount##* }
 -
 -cat >>"$todo" <<EOF
 -
 -$comment_char $(eval_ngettext \
 -      "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
 -      "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
 -      "$todocount")
 -EOF
 -append_todo_help
 -gettext "
 -However, if you remove everything, the rebase will be aborted.
 -
 -" | git stripspace --comment-lines >>"$todo"
 -
 -if test -z "$keep_empty"
 -then
 -      printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
 -fi
 -
 -
 -has_action "$todo" ||
 -      return 2
 -
 -cp "$todo" "$todo".backup
 -collapse_todo_ids
 -git_sequence_editor "$todo" ||
 -      die_abort "$(gettext "Could not execute editor")"
 -
 -has_action "$todo" ||
 -      return 2
 -
 -git rebase--helper --check-todo-list || {
 -      ret=$?
 -      checkout_onto
 -      exit $ret
 -}
 -
 -expand_todo_ids
 -
 -test -d "$rewritten" || test -n "$force_rebase" ||
 -onto="$(git rebase--helper --skip-unnecessary-picks)" ||
 -die "Could not skip unnecessary pick commands"
 -
 -checkout_onto
 -if test -z "$rebase_root" && test ! -d "$rewritten"
 -then
 -      require_clean_work_tree "rebase"
 -      exec git rebase--helper ${force_rebase:+--no-ff} --continue
 -fi
 -do_rest
  
 +      complete_action
  }
 -# ... and then we call the whole thing.
 -git_rebase__interactive
diff --combined git-rebase--merge.sh
index 685f48ca49bcdac63f5577ac20daa2993be5e4a2,9a60cef05251d25be9112f8c58c344c892b0ea9a..cf4c0422148935906ad939c5351652a1531e5f0d
@@@ -27,8 -27,7 +27,8 @@@ continue_merge () 
        cmt=$(cat "$state_dir/current")
        if ! git diff-index --quiet --ignore-submodules HEAD --
        then
-               if ! git commit ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message \
 -              if ! git commit ${gpg_sign_opt:+"$gpg_sign_opt"} $signoff --no-verify -C "$cmt"
++              if ! git commit ${gpg_sign_opt:+"$gpg_sign_opt"} $signoff $allow_empty_message \
 +                      --no-verify -C "$cmt"
                then
                        echo "Commit failed, please do not call \"git commit\""
                        echo "directly, but instead do one of the following: "
@@@ -58,7 -57,6 +58,7 @@@ call_merge () 
        echo "$msgnum" >"$state_dir/msgnum"
        cmt="$(cat "$state_dir/cmt.$msgnum")"
        echo "$cmt" > "$state_dir/current"
 +      git update-ref REBASE_HEAD "$cmt"
        hd=$(git rev-parse --verify HEAD)
        cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
        eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
@@@ -104,6 -102,15 +104,6 @@@ finish_rb_merge () 
        say All done.
  }
  
 -# 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
 -# to the function that dot-sourced us.
 -#
 -# However, older (9.x) versions of FreeBSD /bin/sh misbehave on such a
 -# construct and continue to run the statements that follow such a "return".
 -# As a work-around, we introduce an extra layer of a function
 -# here, and immediately call it after defining it.
  git_rebase__merge () {
  
  case "$action" in
@@@ -130,15 -137,11 +130,15 @@@ skip
        finish_rb_merge
        return
        ;;
 +show-current-patch)
 +      exec git show REBASE_HEAD --
 +      ;;
  esac
  
  mkdir -p "$state_dir"
  echo "$onto_name" > "$state_dir/onto_name"
  write_basic_state
 +rm -f "$(git rev-parse --git-path REBASE_HEAD)"
  
  msgnum=0
  for cmt in $(git rev-list --reverse --no-merges "$revisions")
@@@ -162,3 -165,5 +162,3 @@@ don
  finish_rb_merge
  
  }
 -# ... and then we call the whole thing.
 -git_rebase__merge
diff --combined git-rebase.sh
index 548c15e4a16160e15a7f32cee8ace37284dbb850,ee8c77ad99895a8d6af79c7e0c7853dd3b1d3b61..ded5de085a87505b244b7449b1977c3779650640
@@@ -24,7 -24,6 +24,7 @@@ m,merge!           use merging strategi
  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
  f,force-rebase!    force rebase even if branch is up to date
  X,strategy-option=! pass the argument through to the merge strategy
  stat!              display a diffstat of what changed upstream
@@@ -46,7 -45,6 +46,7 @@@ abort!             abort and check out 
  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
@@@ -92,7 -90,7 +92,8 @@@ action
  preserve_merges=
  autosquash=
  keep_empty=
 +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 ;;
@@@ -122,6 -120,10 +123,10 @@@ read_basic_state () 
                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
+       }
  }
  
  write_basic_state () {
        test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
                "$state_dir"/allow_rerere_autoupdate
        test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
+       test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
  }
  
  output () {
@@@ -185,7 -188,6 +191,7 @@@ You can run "git stash pop" or "git sta
  }
  
  finish_rebase () {
 +      rm -f "$(git rev-parse --git-path REBASE_HEAD)"
        apply_autostash &&
        { git gc --auto || true; } &&
        rm -rf "$state_dir"
@@@ -198,7 -200,6 +204,7 @@@ run_specific_rebase () 
                autosquash=
        fi
        . git-rebase--$type
 +      git_rebase__$type${preserve_merges:+__preserve_merges}
        ret=$?
        if test $ret -eq 0
        then
@@@ -251,7 -252,7 +257,7 @@@ d
        --verify)
                ok_to_skip_pre_rebase=
                ;;
 -      --continue|--skip|--abort|--quit|--edit-todo)
 +      --continue|--skip|--abort|--quit|--edit-todo|--show-current-patch)
                test $total_argc -eq 2 || usage
                action=${1##--}
                ;;
        --keep-empty)
                keep_empty=yes
                ;;
 +      --allow-empty-message)
 +              allow_empty_message=--allow-empty-message
 +              ;;
        --no-keep-empty)
                keep_empty=
                ;;
        --ignore-whitespace)
                git_am_opt="$git_am_opt $1"
                ;;
-       --committer-date-is-author-date|--ignore-date|--signoff|--no-signoff)
+       --signoff)
+               signoff=--signoff
+               ;;
+       --no-signoff)
+               signoff=
+               ;;
+       --committer-date-is-author-date|--ignore-date)
                git_am_opt="$git_am_opt $1"
                force_rebase=t
                ;;
@@@ -424,10 -428,6 +436,10 @@@ quit
  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
@@@ -452,6 -452,11 +464,11 @@@ the
        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
        type=interactive
@@@ -470,6 -475,14 +487,14 @@@ the
        git_format_patch_opt="$git_format_patch_opt --progress"
  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 -z "$rebase_root"
  then
        case "$#" in
diff --combined sequencer.c
index eedd45ef8327e97c167504806580fe43522deddf,4aeaef20e72acbc4905c38b1816e96fe3141c957..a7d31e0525cead13981172851383d2f5da4f70fc
@@@ -127,6 -127,7 +127,7 @@@ static GIT_PATH_FUNC(rebase_path_rewrit
  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")
  static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
+ static GIT_PATH_FUNC(rebase_path_signoff, "rebase-merge/signoff")
  static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name")
  static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
  static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
@@@ -282,7 -283,7 +283,7 @@@ struct commit_message 
  
  static const char *short_commit_name(struct commit *commit)
  {
 -      return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
 +      return find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);
  }
  
  static int get_message(struct commit *commit, struct commit_message *out)
@@@ -339,7 -340,7 +340,7 @@@ static void print_advice(int show_hint
  static int write_message(const void *buf, size_t len, const char *filename,
                         int append_eol)
  {
 -      static struct lock_file msg_file;
 +      struct lock_file msg_file = LOCK_INIT;
  
        int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0);
        if (msg_fd < 0)
                rollback_lock_file(&msg_file);
                return error_errno(_("could not write eol to '%s'"), filename);
        }
 -      if (commit_lock_file(&msg_file) < 0) {
 -              rollback_lock_file(&msg_file);
 -              return error(_("failed to finalize '%s'."), filename);
 -      }
 +      if (commit_lock_file(&msg_file) < 0)
 +              return error(_("failed to finalize '%s'"), filename);
  
        return 0;
  }
@@@ -483,7 -486,7 +484,7 @@@ static int do_recursive_merge(struct co
        struct tree *result, *next_tree, *base_tree, *head_tree;
        int clean;
        char **xopt;
 -      static struct lock_file index_lock;
 +      struct lock_file index_lock = LOCK_INIT;
  
        if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
                return -1;
                fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
        diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0);
 -      if (clean < 0)
 +      if (clean < 0) {
 +              rollback_lock_file(&index_lock);
                return clean;
 +      }
  
 -      if (active_cache_changed &&
 -          write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
 +      if (write_locked_index(&the_index, &index_lock,
 +                             COMMIT_LOCK | SKIP_IF_UNCHANGED))
                /*
                 * TRANSLATORS: %s will be "revert", "cherry-pick" or
                 * "rebase -i".
                 */
                return error(_("%s: Unable to write new index file"),
                        _(action_name(opts)));
 -      rollback_lock_file(&index_lock);
  
        if (!clean)
                append_conflicts_hint(msgbuf);
@@@ -1112,7 -1114,7 +1113,7 @@@ static int try_to_commit(struct strbuf 
                commit_list_insert(current_head, &parents);
        }
  
 -      if (write_cache_as_tree(tree.hash, 0, NULL)) {
 +      if (write_cache_as_tree(&tree, 0, NULL)) {
                res = error(_("git write-tree failed to write a tree"));
                goto out;
        }
                goto out;
        }
  
 -      if (commit_tree_extended(msg->buf, msg->len, tree.hash, parents,
 -                               oid->hash, author, opts->gpg_sign, extra)) {
 +      if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
 +                               oid, author, opts->gpg_sign, extra)) {
                res = error(_("failed to write commit object"));
                goto out;
        }
@@@ -1474,7 -1476,7 +1475,7 @@@ static int do_pick_commit(enum todo_com
                 * that represents the "current" state for merge-recursive
                 * to work on.
                 */
 -              if (write_cache_as_tree(head.hash, 0, NULL))
 +              if (write_cache_as_tree(&head, 0, NULL))
                        return error(_("your index file is unmerged."));
        } else {
                unborn = get_oid("HEAD", &head);
                }
        }
  
-       if (opts->signoff)
+       if (opts->signoff && !is_fixup(command))
                append_signoff(&msgbuf, 0, 0);
  
        if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
@@@ -1704,7 -1706,7 +1705,7 @@@ static int prepare_revs(struct replay_o
  
  static int read_and_refresh_cache(struct replay_opts *opts)
  {
 -      static struct lock_file index_lock;
 +      struct lock_file index_lock = LOCK_INIT;
        int index_fd = hold_locked_index(&index_lock, 0);
        if (read_index_preload(&the_index, NULL) < 0) {
                rollback_lock_file(&index_lock);
                        _(action_name(opts)));
        }
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
 -      if (the_index.cache_changed && index_fd >= 0) {
 -              if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
 +      if (index_fd >= 0) {
 +              if (write_locked_index(&the_index, &index_lock,
 +                                     COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
                        return error(_("git %s: failed to refresh the index"),
                                _(action_name(opts)));
                }
        }
 -      rollback_lock_file(&index_lock);
        return 0;
  }
  
@@@ -1868,31 -1870,22 +1869,31 @@@ static int count_commands(struct todo_l
        return count;
  }
  
 +static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)
 +{
 +      int fd;
 +      ssize_t len;
 +
 +      fd = open(path, O_RDONLY);
 +      if (fd < 0)
 +              return error_errno(_("could not open '%s'"), path);
 +      len = strbuf_read(sb, fd, 0);
 +      close(fd);
 +      if (len < 0)
 +              return error(_("could not read '%s'."), path);
 +      return len;
 +}
 +
  static int read_populate_todo(struct todo_list *todo_list,
                        struct replay_opts *opts)
  {
        struct stat st;
        const char *todo_file = get_todo_path(opts);
 -      int fd, res;
 +      int res;
  
        strbuf_reset(&todo_list->buf);
 -      fd = open(todo_file, O_RDONLY);
 -      if (fd < 0)
 -              return error_errno(_("could not open '%s'"), todo_file);
 -      if (strbuf_read(&todo_list->buf, fd, 0) < 0) {
 -              close(fd);
 -              return error(_("could not read '%s'."), todo_file);
 -      }
 -      close(fd);
 +      if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
 +              return -1;
  
        res = stat(todo_file, &st);
        if (res)
@@@ -2043,6 -2036,11 +2044,11 @@@ static int read_populate_opts(struct re
                if (file_exists(rebase_path_verbose()))
                        opts->verbose = 1;
  
+               if (file_exists(rebase_path_signoff())) {
+                       opts->allow_ff = 0;
+                       opts->signoff = 1;
+               }
                read_strategy_opts(opts, &buf);
                strbuf_release(&buf);
  
@@@ -2107,14 -2105,16 +2113,14 @@@ static int create_seq_dir(void
  
  static int save_head(const char *head)
  {
 -      static struct lock_file head_lock;
 +      struct lock_file head_lock = LOCK_INIT;
        struct strbuf buf = STRBUF_INIT;
        int fd;
        ssize_t written;
  
        fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);
 -      if (fd < 0) {
 -              rollback_lock_file(&head_lock);
 +      if (fd < 0)
                return error_errno(_("could not lock HEAD"));
 -      }
        strbuf_addf(&buf, "%s\n", head);
        written = write_in_full(fd, buf.buf, buf.len);
        strbuf_release(&buf);
                return error_errno(_("could not write to '%s'"),
                                   git_path_head_file());
        }
 -      if (commit_lock_file(&head_lock) < 0) {
 -              rollback_lock_file(&head_lock);
 -              return error(_("failed to finalize '%s'."), git_path_head_file());
 -      }
 +      if (commit_lock_file(&head_lock) < 0)
 +              return error(_("failed to finalize '%s'"), git_path_head_file());
        return 0;
  }
  
@@@ -2228,7 -2230,7 +2234,7 @@@ fail
  
  static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
  {
 -      static struct lock_file todo_lock;
 +      struct lock_file todo_lock = LOCK_INIT;
        const char *todo_path = get_todo_path(opts);
        int next = todo_list->current, offset, fd;
  
                        todo_list->buf.len - offset) < 0)
                return error_errno(_("could not write to '%s'"), todo_path);
        if (commit_lock_file(&todo_lock) < 0)
 -              return error(_("failed to finalize '%s'."), todo_path);
 +              return error(_("failed to finalize '%s'"), todo_path);
  
        if (is_rebase_i(opts)) {
                const char *done_path = rebase_path_done();
@@@ -2318,9 -2320,6 +2324,9 @@@ static int make_patch(struct commit *co
        p = short_commit_name(commit);
        if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
                return -1;
 +      if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid,
 +                     NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
 +              res |= error(_("could not update %s"), "REBASE_HEAD");
  
        strbuf_addf(&buf, "%s/patch", get_dir(opts));
        memset(&log_tree_opt, 0, sizeof(log_tree_opt));
@@@ -2572,7 -2571,6 +2578,7 @@@ static int pick_commits(struct todo_lis
                        unlink(rebase_path_author_script());
                        unlink(rebase_path_stopped_sha());
                        unlink(rebase_path_amend());
 +                      delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
@@@ -2876,10 -2874,9 +2882,10 @@@ int sequencer_pick_revisions(struct rep
  
                if (!get_oid(name, &oid)) {
                        if (!lookup_commit_reference_gently(&oid, 1)) {
 -                              enum object_type type = sha1_object_info(oid.hash, NULL);
 +                              enum object_type type = oid_object_info(&oid,
 +                                                                      NULL);
                                return error(_("%s: can't cherry-pick a %s"),
 -                                      name, typename(type));
 +                                      name, type_name(type));
                        }
                } else
                        return error(_("%s: bad revision"), name);
@@@ -3164,13 -3161,20 +3170,13 @@@ int check_todo_list(void
        struct strbuf todo_file = STRBUF_INIT;
        struct todo_list todo_list = TODO_LIST_INIT;
        struct strbuf missing = STRBUF_INIT;
 -      int advise_to_edit_todo = 0, res = 0, fd, i;
 +      int advise_to_edit_todo = 0, res = 0, i;
  
        strbuf_addstr(&todo_file, rebase_path_todo());
 -      fd = open(todo_file.buf, O_RDONLY);
 -      if (fd < 0) {
 -              res = error_errno(_("could not open '%s'"), todo_file.buf);
 -              goto leave_check;
 -      }
 -      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 -              close(fd);
 -              res = error(_("could not read '%s'."), todo_file.buf);
 +      if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
 +              res = -1;
                goto leave_check;
        }
 -      close(fd);
        advise_to_edit_todo = res =
                parse_insn_buffer(todo_list.buf.buf, &todo_list);
  
  
        todo_list_release(&todo_list);
        strbuf_addstr(&todo_file, ".backup");
 -      fd = open(todo_file.buf, O_RDONLY);
 -      if (fd < 0) {
 -              res = error_errno(_("could not open '%s'"), todo_file.buf);
 -              goto leave_check;
 -      }
 -      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 -              close(fd);
 -              res = error(_("could not read '%s'."), todo_file.buf);
 +      if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
 +              res = -1;
                goto leave_check;
        }
 -      close(fd);
        strbuf_release(&todo_file);
        res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
  
@@@ -3270,8 -3281,15 +3276,8 @@@ int skip_unnecessary_picks(void
        }
        strbuf_release(&buf);
  
 -      fd = open(todo_file, O_RDONLY);
 -      if (fd < 0) {
 -              return error_errno(_("could not open '%s'"), todo_file);
 -      }
 -      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 -              close(fd);
 -              return error(_("could not read '%s'."), todo_file);
 -      }
 -      close(fd);
 +      if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
 +              return -1;
        if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
                todo_list_release(&todo_list);
                return -1;
@@@ -3362,11 -3380,17 +3368,11 @@@ int rearrange_squash(void
        const char *todo_file = rebase_path_todo();
        struct todo_list todo_list = TODO_LIST_INIT;
        struct hashmap subject2item;
 -      int res = 0, rearranged = 0, *next, *tail, fd, i;
 +      int res = 0, rearranged = 0, *next, *tail, i;
        char **subjects;
  
 -      fd = open(todo_file, O_RDONLY);
 -      if (fd < 0)
 -              return error_errno(_("could not open '%s'"), todo_file);
 -      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 -              close(fd);
 -              return error(_("could not read '%s'."), todo_file);
 -      }
 -      close(fd);
 +      if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
 +              return -1;
        if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
                todo_list_release(&todo_list);
                return -1;