Merge branch 'js/rebase-i-final'
authorJunio C Hamano <gitster@pobox.com>
Tue, 3 Oct 2017 06:42:47 +0000 (15:42 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 3 Oct 2017 06:42:47 +0000 (15:42 +0900)
The final batch to "git rebase -i" updates to move more code from
the shell script to C.

* js/rebase-i-final:
rebase -i: rearrange fixup/squash lines using the rebase--helper
t3415: test fixup with wrapped oneline
rebase -i: skip unnecessary picks using the rebase--helper
rebase -i: check for missing commits in the rebase--helper
t3404: relax rebase.missingCommitsCheck tests
rebase -i: also expand/collapse the SHA-1s via the rebase--helper
rebase -i: do not invent onelines when expanding/collapsing SHA-1s
rebase -i: remove useless indentation
rebase -i: generate the script via rebase--helper
t3415: verify that an empty instructionFormat is handled as before

1  2 
Documentation/git-rebase.txt
git-rebase--interactive.sh
sequencer.c
index 6805a74aec20cacd7b9320136b8648ad5b40ef5b,a3c01dfdc8a6a4ec0c17cd0945c0e7df8642616e..3cedfb0fd22be1c060ab2e7b9a141a0d1deb03af
@@@ -334,7 -334,7 +334,7 @@@ which makes little sense
  
  -f::
  --force-rebase::
 -      Force a rebase even if the current branch is up-to-date and
 +      Force a rebase even if the current branch is up to date and
        the command without `--force` would return without doing anything.
  +
  You may find this (or --no-ff with an interactive rebase) helpful after
@@@ -430,13 -430,15 +430,15 @@@ without an explicit `--interactive`
  --autosquash::
  --no-autosquash::
        When the commit log message begins with "squash! ..." (or
-       "fixup! ..."), and there is a commit whose title begins with
-       the same ..., automatically modify the todo list of rebase -i
-       so that the commit marked for squashing comes right after the
-       commit to be modified, and change the action of the moved
-       commit from `pick` to `squash` (or `fixup`).  Ignores subsequent
-       "fixup! " or "squash! " after the first, in case you referred to an
-       earlier fixup/squash with `git commit --fixup/--squash`.
+       "fixup! ..."), and there is already a commit in the todo list that
+       matches the same `...`, automatically modify the todo list of rebase
+       -i so that the commit marked for squashing comes right after the
+       commit to be modified, and change the action of the moved commit
+       from `pick` to `squash` (or `fixup`).  A commit matches the `...` if
+       the commit subject matches, or if the `...` refers to the commit's
+       hash. As a fall-back, partial matches of the commit subject work,
+       too.  The recommended way to create fixup/squash commits is by using
+       the `--fixup`/`--squash` options of linkgit:git-commit[1].
  +
  This option is only valid when the `--interactive` option is used.
  +
index 29b7e8824b53abeaa68780b95d5954f67f734098,3b0340e7cc91f2bd049ee1d850f97b79cee23977..2563dc52daaf7acbbdba803360f94bfa5040c5b6
@@@ -155,13 -155,13 +155,13 @@@ reschedule_last_action () 
  append_todo_help () {
        gettext "
  Commands:
 p, pick = use commit
 r, reword = use commit, but edit the commit message
 e, edit = use commit, but stop for amending
 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
+ p, pick = use commit
+ r, reword = use commit, but edit the commit message
+ e, edit = use commit, but stop for amending
+ 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.
  " | git stripspace --comment-lines >>"$todo"
@@@ -281,7 -281,7 +281,7 @@@ pick_one () 
  
        test -d "$rewritten" &&
                pick_one_preserving_merges "$@" && return
 -      output eval git cherry-pick \
 +      output eval git cherry-pick $allow_rerere_autoupdate \
                        ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
                        "$strategy_args" $empty_args $ff "$@"
  
@@@ -393,8 -393,7 +393,8 @@@ pick_one_preserving_merges () 
                        merge_args="--no-log --no-ff"
                        if ! do_with_author output eval \
                        'git merge ${gpg_sign_opt:+"$gpg_sign_opt"} \
 -                              $merge_args $strategy_args -m "$msg_content" $new_parents'
 +                              $allow_rerere_autoupdate $merge_args \
 +                              $strategy_args -m "$msg_content" $new_parents'
                        then
                                printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
                                die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")"
                        echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
                        ;;
                *)
 -                      output eval git cherry-pick \
 +                      output eval git cherry-pick $allow_rerere_autoupdate \
                                ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
                                "$strategy_args" "$@" ||
                                die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")"
@@@ -714,154 -713,12 +714,12 @@@ do_rest () 
        done
  }
  
- # skip picking commits whose parents are unchanged
- skip_unnecessary_picks () {
-       fd=3
-       while read -r command rest
-       do
-               # fd=3 means we skip the command
-               case "$fd,$command" in
-               3,pick|3,p)
-                       # pick a commit whose parent is current $onto -> skip
-                       sha1=${rest%% *}
-                       case "$(git rev-parse --verify --quiet "$sha1"^)" in
-                       "$onto"*)
-                               onto=$sha1
-                               ;;
-                       *)
-                               fd=1
-                               ;;
-                       esac
-                       ;;
-               3,"$comment_char"*|3,)
-                       # copy comments
-                       ;;
-               *)
-                       fd=1
-                       ;;
-               esac
-               printf '%s\n' "$command${rest:+ }$rest" >&$fd
-       done <"$todo" >"$todo.new" 3>>"$done" &&
-       mv -f "$todo".new "$todo" &&
-       case "$(peek_next_command)" in
-       squash|s|fixup|f)
-               record_in_rewritten "$onto"
-               ;;
-       esac ||
-               die "$(gettext "Could not skip unnecessary pick commands")"
- }
- transform_todo_ids () {
-       while read -r command rest
-       do
-               case "$command" in
-               "$comment_char"* | exec)
-                       # Be careful for oddball commands like 'exec'
-                       # that do not have a SHA-1 at the beginning of $rest.
-                       ;;
-               *)
-                       sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[     ]*}) &&
-                       rest="$sha1 ${rest#*[    ]}"
-                       ;;
-               esac
-               printf '%s\n' "$command${rest:+ }$rest"
-       done <"$todo" >"$todo.new" &&
-       mv -f "$todo.new" "$todo"
- }
  expand_todo_ids() {
-       transform_todo_ids
+       git rebase--helper --expand-ids
  }
  
  collapse_todo_ids() {
-       transform_todo_ids --short
- }
- # Rearrange the todo list that has both "pick sha1 msg" and
- # "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%%!*}"
-                       rest=$message
-                       prefix=
-                       # skip all squash! or fixup! (but save for later)
-                       while :
-                       do
-                               case "$rest" in
-                               "squash! "*|"fixup! "*)
-                                       prefix="$prefix${rest%%!*},"
-                                       rest="${rest#*! }"
-                                       ;;
-                               *)
-                                       break
-                                       ;;
-                               esac
-                       done
-                       printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest"
-                       # if it's a single word, try to resolve to a full sha1 and
-                       # emit a second copy. This allows us to match on both message
-                       # and on sha1 prefix
-                       if test "${rest#* }" = "$rest"; then
-                               fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)"
-                               if test -n "$fullsha"; then
-                                       # prefix the action to uniquely identify this line as
-                                       # intended for full sha1 match
-                                       echo "$sha1 +$action $prefix $fullsha"
-                               fi
-                       fi
-               esac
-       done >"$1.sq" <"$1"
-       test -s "$1.sq" || return
-       used=
-       while read -r pick sha1 message
-       do
-               case " $used" in
-               *" $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 " $used" in
-                       *" $squash "*) continue ;;
-                       esac
-                       emit=0
-                       case "$action" in
-                       +*)
-                               action="${action#+}"
-                               # full sha1 prefix test
-                               case "$msg_content" in "$sha1"*) emit=1;; esac ;;
-                       *)
-                               # message prefix test
-                               case "$message" in "$msg_content"*) emit=1;; esac ;;
-                       esac
-                       if test $emit = 1; then
-                               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"
-       done >"$1.rearranged" <"$1"
-       cat "$1.rearranged" >"$1"
-       rm -f "$1.sq" "$1.rearranged"
+       git rebase--helper --shorten-ids
  }
  
  # Add commands after a pick or after a squash/fixup serie
@@@ -885,96 -742,6 +743,6 @@@ 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 number of the input
- # $3: the input filename
- 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
-               line="$(sed -n -e "${2}p" "$3")"
-               warn "$(eval_gettext "\
- Warning: the SHA-1 is missing or isn't a commit in the following line:
-  - \$line")"
-               warn
-       fi
-       return $badsha
- }
- # prints the bad commits and bad commands
- # from the todolist in stdin
- check_bad_cmd_and_sha () {
-       retval=0
-       lineno=0
-       while read -r command rest
-       do
-               lineno=$(( $lineno + 1 ))
-               case $command in
-               "$comment_char"*|''|noop|x|exec)
-                       # Doesn't expect a SHA-1
-                       ;;
-               "$cr")
-                       # Work around CR left by "read" (e.g. with Git for
-                       # Windows' Bash).
-                       ;;
-               pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
-                       if ! check_commit_sha "${rest%%[        ]*}" "$lineno" "$1"
-                       then
-                               retval=1
-                       fi
-                       ;;
-               *)
-                       line="$(sed -n -e "${lineno}p" "$1")"
-                       warn "$(eval_gettext "\
- Warning: the command isn't recognized in the following line:
-  - \$line")"
-                       warn
-                       retval=1
-                       ;;
-               esac
-       done <"$1"
-       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"
@@@ -989,74 -756,6 +757,6 @@@ get_missing_commit_check_level () 
        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 "$(gettext "\
- Warning: some commits may have been dropped accidentally.
- 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 "$(gettext "\
- 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.")"
-                       warn
-               fi
-               ;;
-       ignore)
-               ;;
-       *)
-               warn "$(eval_gettext "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 "$(gettext "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.")"
-               die "$(gettext "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
@@@ -1211,26 -910,27 +911,27 @@@ els
        revisions=$onto...$orig_head
        shortrevisions=$shorthead
  fi
- 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
- do
-       if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
-       then
-               comment_out="$comment_char "
-       else
-               comment_out=
-       fi
+ if test t != "$preserve_merges"
+ then
+       git rebase--helper --make-script ${keep_empty:+--keep-empty} \
+               $revisions ${restrict_revision+^$restrict_revision} >"$todo"
+ else
+       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
+       do
+               if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
+               then
+                       comment_out="$comment_char "
+               else
+                       comment_out=
+               fi
  
-       if test t != "$preserve_merges"
-       then
-               printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
-       else
                if test -z "$rebase_root"
                then
                        preserve=t
                        touch "$rewritten"/$sha1
                        printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
                fi
-       fi
- done
+       done
+ fi
  
  # Watch for commits that been dropped by --cherry-pick
  if test t = "$preserve_merges"
@@@ -1280,7 -980,7 +981,7 @@@ the
  fi
  
  test -s "$todo" || echo noop >> "$todo"
- test -n "$autosquash" && rearrange_squash "$todo"
+ test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
  test -n "$cmd" && add_exec_commands "$todo"
  
  todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
@@@ -1316,11 -1016,17 +1017,17 @@@ git_sequence_editor "$todo" |
  has_action "$todo" ||
        return 2
  
- check_todo_list
+ git rebase--helper --check-todo-list || {
+       ret=$?
+       checkout_onto
+       exit $ret
+ }
  
  expand_todo_ids
  
- test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
+ 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"
diff --combined sequencer.c
index 60636ce54b615e19a22dc2396211d56540b4e920,c54596f9699838f0a0e0f45f9eb48eca3eb7502c..b8c1e876fa7b57986e6ae93873e23f87c45b9e49
@@@ -20,6 -20,7 +20,7 @@@
  #include "trailer.h"
  #include "log-tree.h"
  #include "wt-status.h"
+ #include "hashmap.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
@@@ -127,7 -128,6 +128,7 @@@ static GIT_PATH_FUNC(rebase_path_onto, 
  static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
  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 inline int is_rebase_i(const struct replay_opts *opts)
  {
@@@ -692,7 -692,7 +693,7 @@@ static int run_git_commit(const char *d
  
  static int is_original_commit_empty(struct commit *commit)
  {
 -      const unsigned char *ptree_sha1;
 +      const struct object_id *ptree_oid;
  
        if (parse_commit(commit))
                return error(_("could not parse commit %s\n"),
                if (parse_commit(parent))
                        return error(_("could not parse parent commit %s\n"),
                                oid_to_hex(&parent->object.oid));
 -              ptree_sha1 = parent->tree->object.oid.hash;
 +              ptree_oid = &parent->tree->object.oid;
        } else {
 -              ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
 +              ptree_oid = &empty_tree_oid; /* commit is root */
        }
  
 -      return !hashcmp(ptree_sha1, commit->tree->object.oid.hash);
 +      return !oidcmp(ptree_oid, &commit->tree->object.oid);
  }
  
  /*
@@@ -897,18 -897,18 +898,18 @@@ static int update_squash_messages(enum 
  
  static void flush_rewritten_pending(void) {
        struct strbuf buf = STRBUF_INIT;
 -      unsigned char newsha1[20];
 +      struct object_id newoid;
        FILE *out;
  
 -      if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 &&
 -          !get_sha1("HEAD", newsha1) &&
 +      if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), (GIT_MAX_HEXSZ + 1) * 2) > 0 &&
 +          !get_oid("HEAD", &newoid) &&
            (out = fopen_or_warn(rebase_path_rewritten_list(), "a"))) {
                char *bol = buf.buf, *eol;
  
                while (*bol) {
                        eol = strchrnul(bol, '\n');
                        fprintf(out, "%.*s %s\n", (int)(eol - bol),
 -                                      bol, sha1_to_hex(newsha1));
 +                                      bol, oid_to_hex(&newoid));
                        if (!*eol)
                                break;
                        bol = eol + 1;
@@@ -1439,11 -1439,7 +1440,11 @@@ static int populate_opts_cb(const char 
        else if (!strcmp(key, "options.strategy-option")) {
                ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
                opts->xopts[opts->xopts_nr++] = xstrdup(value);
 -      } else
 +      } else if (!strcmp(key, "options.allow-rerere-auto"))
 +              opts->allow_rerere_auto =
 +                      git_config_bool_or_int(key, value, &error_flag) ?
 +                              RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
 +      else
                return error(_("invalid key: %s"), key);
  
        if (!error_flag)
@@@ -1484,15 -1480,6 +1485,15 @@@ static int read_populate_opts(struct re
                                free(opts->gpg_sign);
                                opts->gpg_sign = xstrdup(buf.buf + 2);
                        }
 +                      strbuf_reset(&buf);
 +              }
 +
 +              if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) {
 +                      if (!strcmp(buf.buf, "--rerere-autoupdate"))
 +                              opts->allow_rerere_auto = RERERE_AUTOUPDATE;
 +                      else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
 +                              opts->allow_rerere_auto = RERERE_NOAUTOUPDATE;
 +                      strbuf_reset(&buf);
                }
  
                if (file_exists(rebase_path_verbose()))
@@@ -1565,7 -1552,6 +1566,7 @@@ static int save_head(const char *head
        static struct lock_file head_lock;
        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) {
                return error_errno(_("could not lock HEAD"));
        }
        strbuf_addf(&buf, "%s\n", head);
 -      if (write_in_full(fd, buf.buf, buf.len) < 0) {
 +      written = write_in_full(fd, buf.buf, buf.len);
 +      strbuf_release(&buf);
 +      if (written < 0) {
                rollback_lock_file(&head_lock);
                return error_errno(_("could not write to '%s'"),
                                   git_path_head_file());
@@@ -1611,37 -1595,36 +1612,37 @@@ static int rollback_is_safe(void
        return !oidcmp(&actual_head, &expected_head);
  }
  
 -static int reset_for_rollback(const unsigned char *sha1)
 +static int reset_for_rollback(const struct object_id *oid)
  {
        const char *argv[4];    /* reset --merge <arg> + NULL */
  
        argv[0] = "reset";
        argv[1] = "--merge";
 -      argv[2] = sha1_to_hex(sha1);
 +      argv[2] = oid_to_hex(oid);
        argv[3] = NULL;
        return run_command_v_opt(argv, RUN_GIT_CMD);
  }
  
  static int rollback_single_pick(void)
  {
 -      unsigned char head_sha1[20];
 +      struct object_id head_oid;
  
        if (!file_exists(git_path_cherry_pick_head()) &&
            !file_exists(git_path_revert_head()))
                return error(_("no cherry-pick or revert in progress"));
 -      if (read_ref_full("HEAD", 0, head_sha1, NULL))
 +      if (read_ref_full("HEAD", 0, head_oid.hash, NULL))
                return error(_("cannot resolve HEAD"));
 -      if (is_null_sha1(head_sha1))
 +      if (is_null_oid(&head_oid))
                return error(_("cannot abort from a branch yet to be born"));
 -      return reset_for_rollback(head_sha1);
 +      return reset_for_rollback(&head_oid);
  }
  
  int sequencer_rollback(struct replay_opts *opts)
  {
        FILE *f;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct strbuf buf = STRBUF_INIT;
 +      const char *p;
  
        f = fopen(git_path_head_file(), "r");
        if (!f && errno == ENOENT) {
                goto fail;
        }
        fclose(f);
 -      if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
 +      if (parse_oid_hex(buf.buf, &oid, &p) || *p != '\0') {
                error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
                        git_path_head_file());
                goto fail;
        }
 -      if (is_null_sha1(sha1)) {
 +      if (is_null_oid(&oid)) {
                error(_("cannot abort from a branch yet to be born"));
                goto fail;
        }
                warning(_("You seem to have moved HEAD. "
                          "Not rewinding, check your HEAD!"));
        } else
 -      if (reset_for_rollback(sha1))
 +      if (reset_for_rollback(&oid))
                goto fail;
        strbuf_release(&buf);
        return sequencer_remove_state(opts);
@@@ -1760,10 -1743,6 +1761,10 @@@ static int save_opts(struct replay_opt
                                                        "options.strategy-option",
                                                        opts->xopts[i], "^$", 0);
        }
 +      if (opts->allow_rerere_auto)
 +              res |= git_config_set_in_file_gently(opts_file, "options.allow-rerere-auto",
 +                                                   opts->allow_rerere_auto == RERERE_AUTOUPDATE ?
 +                                                   "true" : "false");
        return res;
  }
  
@@@ -1810,13 -1789,13 +1811,13 @@@ static int make_patch(struct commit *co
  
  static int intend_to_amend(void)
  {
 -      unsigned char head[20];
 +      struct object_id head;
        char *p;
  
 -      if (get_sha1("HEAD", head))
 +      if (get_oid("HEAD", &head))
                return error(_("cannot read HEAD"));
  
 -      p = sha1_to_hex(head);
 +      p = oid_to_hex(&head);
        return write_message(p, strlen(p), rebase_path_amend(), 1);
  }
  
@@@ -2101,10 -2080,10 +2102,10 @@@ static int pick_commits(struct todo_lis
                if (read_oneliner(&head_ref, rebase_path_head_name(), 0) &&
                                starts_with(head_ref.buf, "refs/")) {
                        const char *msg;
 -                      unsigned char head[20], orig[20];
 +                      struct object_id head, orig;
                        int res;
  
 -                      if (get_sha1("HEAD", head)) {
 +                      if (get_oid("HEAD", &head)) {
                                res = error(_("cannot read HEAD"));
  cleanup_head_ref:
                                strbuf_release(&head_ref);
                                return res;
                        }
                        if (!read_oneliner(&buf, rebase_path_orig_head(), 0) ||
 -                                      get_sha1_hex(buf.buf, orig)) {
 +                                      get_oid_hex(buf.buf, &orig)) {
                                res = error(_("could not read orig-head"));
                                goto cleanup_head_ref;
                        }
                        }
                        msg = reflog_message(opts, "finish", "%s onto %s",
                                head_ref.buf, buf.buf);
 -                      if (update_ref(msg, head_ref.buf, head, orig,
 +                      if (update_ref(msg, head_ref.buf, head.hash, orig.hash,
                                        REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
                                res = error(_("could not update %s"),
                                        head_ref.buf);
                        log_tree_opt.disable_stdin = 1;
  
                        if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
 -                          !get_sha1(buf.buf, orig.hash) &&
 -                          !get_sha1("HEAD", head.hash)) {
 +                          !get_oid(buf.buf, &orig) &&
 +                          !get_oid("HEAD", &head)) {
                                diff_tree_oid(&orig, &head, "",
                                              &log_tree_opt.diffopt);
                                log_tree_diff_flush(&log_tree_opt);
@@@ -2227,16 -2206,16 +2228,16 @@@ static int commit_staged_changes(struc
  
        if (file_exists(rebase_path_amend())) {
                struct strbuf rev = STRBUF_INIT;
 -              unsigned char head[20], to_amend[20];
 +              struct object_id head, to_amend;
  
 -              if (get_sha1("HEAD", head))
 +              if (get_oid("HEAD", &head))
                        return error(_("cannot amend non-existing commit"));
                if (!read_oneliner(&rev, rebase_path_amend(), 0))
                        return error(_("invalid file: '%s'"), rebase_path_amend());
 -              if (get_sha1_hex(rev.buf, to_amend))
 +              if (get_oid_hex(rev.buf, &to_amend))
                        return error(_("invalid contents: '%s'"),
                                rebase_path_amend());
 -              if (hashcmp(head, to_amend))
 +              if (oidcmp(&head, &to_amend))
                        return error(_("\nYou have uncommitted changes in your "
                                       "working tree. Please, commit them\n"
                                       "first and then run 'git rebase "
@@@ -2288,7 -2267,7 +2289,7 @@@ int sequencer_continue(struct replay_op
                struct object_id oid;
  
                if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
 -                  !get_sha1_committish(buf.buf, oid.hash))
 +                  !get_oid_committish(buf.buf, &oid))
                        record_in_rewritten(&oid, peek_command(&todo_list, 0));
                strbuf_release(&buf);
        }
@@@ -2435,3 -2414,533 +2436,533 @@@ void append_signoff(struct strbuf *msgb
  
        strbuf_release(&sob);
  }
+ int sequencer_make_script(int keep_empty, FILE *out,
+               int argc, const char **argv)
+ {
+       char *format = NULL;
+       struct pretty_print_context pp = {0};
+       struct strbuf buf = STRBUF_INIT;
+       struct rev_info revs;
+       struct commit *commit;
+       init_revisions(&revs, NULL);
+       revs.verbose_header = 1;
+       revs.max_parents = 1;
+       revs.cherry_pick = 1;
+       revs.limited = 1;
+       revs.reverse = 1;
+       revs.right_only = 1;
+       revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
+       revs.topo_order = 1;
+       revs.pretty_given = 1;
+       git_config_get_string("rebase.instructionFormat", &format);
+       if (!format || !*format) {
+               free(format);
+               format = xstrdup("%s");
+       }
+       get_commit_format(format, &revs);
+       free(format);
+       pp.fmt = revs.commit_format;
+       pp.output_encoding = get_log_output_encoding();
+       if (setup_revisions(argc, argv, &revs, NULL) > 1)
+               return error(_("make_script: unhandled options"));
+       if (prepare_revision_walk(&revs) < 0)
+               return error(_("make_script: error preparing revisions"));
+       while ((commit = get_revision(&revs))) {
+               strbuf_reset(&buf);
+               if (!keep_empty && is_original_commit_empty(commit))
+                       strbuf_addf(&buf, "%c ", comment_line_char);
+               strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
+               pretty_print_commit(&pp, commit, &buf);
+               strbuf_addch(&buf, '\n');
+               fputs(buf.buf, out);
+       }
+       strbuf_release(&buf);
+       return 0;
+ }
+ int transform_todo_ids(int shorten_ids)
+ {
+       const char *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       int fd, res, i;
+       FILE *out;
+       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);
+       res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
+       if (res) {
+               todo_list_release(&todo_list);
+               return error(_("unusable todo list: '%s'"), todo_file);
+       }
+       out = fopen(todo_file, "w");
+       if (!out) {
+               todo_list_release(&todo_list);
+               return error(_("unable to open '%s' for writing"), todo_file);
+       }
+       for (i = 0; i < todo_list.nr; i++) {
+               struct todo_item *item = todo_list.items + i;
+               int bol = item->offset_in_buf;
+               const char *p = todo_list.buf.buf + bol;
+               int eol = i + 1 < todo_list.nr ?
+                       todo_list.items[i + 1].offset_in_buf :
+                       todo_list.buf.len;
+               if (item->command >= TODO_EXEC && item->command != TODO_DROP)
+                       fwrite(p, eol - bol, 1, out);
+               else {
+                       const char *id = shorten_ids ?
+                               short_commit_name(item->commit) :
+                               oid_to_hex(&item->commit->object.oid);
+                       int len;
+                       p += strspn(p, " \t"); /* left-trim command */
+                       len = strcspn(p, " \t"); /* length of command */
+                       fprintf(out, "%.*s %s %.*s\n",
+                               len, p, id, item->arg_len, item->arg);
+               }
+       }
+       fclose(out);
+       todo_list_release(&todo_list);
+       return 0;
+ }
+ enum check_level {
+       CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
+ };
+ static enum 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;
+       if (!strcasecmp("warn", value))
+               return CHECK_WARN;
+       if (!strcasecmp("error", value))
+               return CHECK_ERROR;
+       warning(_("unrecognized setting %s for option"
+                 "rebase.missingCommitsCheck. Ignoring."), value);
+       return CHECK_IGNORE;
+ }
+ /*
+  * 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.
+  */
+ int check_todo_list(void)
+ {
+       enum 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;
+       int advise_to_edit_todo = 0, res = 0, fd, 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);
+               goto leave_check;
+       }
+       close(fd);
+       advise_to_edit_todo = res =
+               parse_insn_buffer(todo_list.buf.buf, &todo_list);
+       if (res || check_level == CHECK_IGNORE)
+               goto leave_check;
+       /* Mark the commits in git-rebase-todo as seen */
+       for (i = 0; i < todo_list.nr; i++) {
+               struct commit *commit = todo_list.items[i].commit;
+               if (commit)
+                       commit->util = (void *)1;
+       }
+       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);
+               goto leave_check;
+       }
+       close(fd);
+       strbuf_release(&todo_file);
+       res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
+       /* Find commits in git-rebase-todo.backup yet unseen */
+       for (i = todo_list.nr - 1; i >= 0; i--) {
+               struct todo_item *item = todo_list.items + i;
+               struct commit *commit = item->commit;
+               if (commit && !commit->util) {
+                       strbuf_addf(&missing, " - %s %.*s\n",
+                                   short_commit_name(commit),
+                                   item->arg_len, item->arg);
+                       commit->util = (void *)1;
+               }
+       }
+       /* Warn about missing commits */
+       if (!missing.len)
+               goto leave_check;
+       if (check_level == CHECK_ERROR)
+               advise_to_edit_todo = res = 1;
+       fprintf(stderr,
+               _("Warning: some commits may have been dropped accidentally.\n"
+               "Dropped commits (newer to older):\n"));
+       /* Make the list user-friendly and display */
+       fputs(missing.buf, stderr);
+       strbuf_release(&missing);
+       fprintf(stderr, _("To avoid this message, use \"drop\" to "
+               "explicitly remove a commit.\n\n"
+               "Use 'git config rebase.missingCommitsCheck' to change "
+               "the level of warnings.\n"
+               "The possible behaviours are: ignore, warn, error.\n\n"));
+ leave_check:
+       strbuf_release(&todo_file);
+       todo_list_release(&todo_list);
+       if (advise_to_edit_todo)
+               fprintf(stderr,
+                       _("You can fix this with 'git rebase --edit-todo' "
+                         "and then run 'git rebase --continue'.\n"
+                         "Or you can abort the rebase with 'git rebase"
+                         " --abort'.\n"));
+       return res;
+ }
+ /* skip picking commits whose parents are unchanged */
+ int skip_unnecessary_picks(void)
+ {
+       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;
+       int fd, i;
+       if (!read_oneliner(&buf, rebase_path_onto(), 0))
+               return error(_("could not read 'onto'"));
+       if (get_oid(buf.buf, &onto_oid)) {
+               strbuf_release(&buf);
+               return error(_("need a HEAD to fixup"));
+       }
+       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 (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+               todo_list_release(&todo_list);
+               return -1;
+       }
+       for (i = 0; i < todo_list.nr; i++) {
+               struct todo_item *item = todo_list.items + i;
+               if (item->command >= TODO_NOOP)
+                       continue;
+               if (item->command != TODO_PICK)
+                       break;
+               if (parse_commit(item->commit)) {
+                       todo_list_release(&todo_list);
+                       return error(_("could not parse commit '%s'"),
+                               oid_to_hex(&item->commit->object.oid));
+               }
+               if (!item->commit->parents)
+                       break; /* root commit */
+               if (item->commit->parents->next)
+                       break; /* merge commit */
+               parent_oid = &item->commit->parents->item->object.oid;
+               if (hashcmp(parent_oid->hash, oid->hash))
+                       break;
+               oid = &item->commit->object.oid;
+       }
+       if (i > 0) {
+               int offset = i < todo_list.nr ?
+                       todo_list.items[i].offset_in_buf : todo_list.buf.len;
+               const char *done_path = rebase_path_done();
+               fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
+               if (fd < 0) {
+                       error_errno(_("could not open '%s' for writing"),
+                                   done_path);
+                       todo_list_release(&todo_list);
+                       return -1;
+               }
+               if (write_in_full(fd, todo_list.buf.buf, offset) < 0) {
+                       error_errno(_("could not write to '%s'"), done_path);
+                       todo_list_release(&todo_list);
+                       close(fd);
+                       return -1;
+               }
+               close(fd);
+               fd = open(rebase_path_todo(), O_WRONLY, 0666);
+               if (fd < 0) {
+                       error_errno(_("could not open '%s' for writing"),
+                                   rebase_path_todo());
+                       todo_list_release(&todo_list);
+                       return -1;
+               }
+               if (write_in_full(fd, todo_list.buf.buf + offset,
+                               todo_list.buf.len - offset) < 0) {
+                       error_errno(_("could not write to '%s'"),
+                                   rebase_path_todo());
+                       close(fd);
+                       todo_list_release(&todo_list);
+                       return -1;
+               }
+               if (ftruncate(fd, todo_list.buf.len - offset) < 0) {
+                       error_errno(_("could not truncate '%s'"),
+                                   rebase_path_todo());
+                       todo_list_release(&todo_list);
+                       close(fd);
+                       return -1;
+               }
+               close(fd);
+               todo_list.current = i;
+               if (is_fixup(peek_command(&todo_list, 0)))
+                       record_in_rewritten(oid, peek_command(&todo_list, 0));
+       }
+       todo_list_release(&todo_list);
+       printf("%s\n", oid_to_hex(oid));
+       return 0;
+ }
+ struct subject2item_entry {
+       struct hashmap_entry entry;
+       int i;
+       char subject[FLEX_ARRAY];
+ };
+ static int subject2item_cmp(const void *fndata,
+                           const struct subject2item_entry *a,
+                           const struct subject2item_entry *b, const void *key)
+ {
+       return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
+ }
+ /*
+  * Rearrange the todo list that has both "pick commit-id msg" and "pick
+  * commit-id fixup!/squash! msg" in it so that the latter is put 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 have to be retrieved from the commit (as the oneline in the
+  * script cannot be trusted) in order to normalize the autosquash arrangement.
+  */
+ 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;
+       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 (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+               todo_list_release(&todo_list);
+               return -1;
+       }
+       /*
+        * The hashmap maps onelines to the respective todo list index.
+        *
+        * If any items need to be rearranged, the next[i] value will indicate
+        * which item was moved directly after the i'th.
+        *
+        * In that case, last[i] will indicate the index of the latest item to
+        * be moved to appear after the i'th.
+        */
+       hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp,
+                    NULL, todo_list.nr);
+       ALLOC_ARRAY(next, todo_list.nr);
+       ALLOC_ARRAY(tail, todo_list.nr);
+       ALLOC_ARRAY(subjects, todo_list.nr);
+       for (i = 0; i < todo_list.nr; i++) {
+               struct strbuf buf = STRBUF_INIT;
+               struct todo_item *item = todo_list.items + i;
+               const char *commit_buffer, *subject, *p;
+               size_t subject_len;
+               int i2 = -1;
+               struct subject2item_entry *entry;
+               next[i] = tail[i] = -1;
+               if (item->command >= TODO_EXEC) {
+                       subjects[i] = NULL;
+                       continue;
+               }
+               if (is_fixup(item->command)) {
+                       todo_list_release(&todo_list);
+                       return error(_("the script was already rearranged."));
+               }
+               item->commit->util = item;
+               parse_commit(item->commit);
+               commit_buffer = get_commit_buffer(item->commit, NULL);
+               find_commit_subject(commit_buffer, &subject);
+               format_subject(&buf, subject, " ");
+               subject = subjects[i] = strbuf_detach(&buf, &subject_len);
+               unuse_commit_buffer(item->commit, commit_buffer);
+               if ((skip_prefix(subject, "fixup! ", &p) ||
+                    skip_prefix(subject, "squash! ", &p))) {
+                       struct commit *commit2;
+                       for (;;) {
+                               while (isspace(*p))
+                                       p++;
+                               if (!skip_prefix(p, "fixup! ", &p) &&
+                                   !skip_prefix(p, "squash! ", &p))
+                                       break;
+                       }
+                       if ((entry = hashmap_get_from_hash(&subject2item,
+                                                          strhash(p), p)))
+                               /* found by title */
+                               i2 = entry->i;
+                       else if (!strchr(p, ' ') &&
+                                (commit2 =
+                                 lookup_commit_reference_by_name(p)) &&
+                                commit2->util)
+                               /* found by commit name */
+                               i2 = (struct todo_item *)commit2->util
+                                       - todo_list.items;
+                       else {
+                               /* copy can be a prefix of the commit subject */
+                               for (i2 = 0; i2 < i; i2++)
+                                       if (subjects[i2] &&
+                                           starts_with(subjects[i2], p))
+                                               break;
+                               if (i2 == i)
+                                       i2 = -1;
+                       }
+               }
+               if (i2 >= 0) {
+                       rearranged = 1;
+                       todo_list.items[i].command =
+                               starts_with(subject, "fixup!") ?
+                               TODO_FIXUP : TODO_SQUASH;
+                       if (next[i2] < 0)
+                               next[i2] = i;
+                       else
+                               next[tail[i2]] = i;
+                       tail[i2] = i;
+               } else if (!hashmap_get_from_hash(&subject2item,
+                                               strhash(subject), subject)) {
+                       FLEX_ALLOC_MEM(entry, subject, subject, subject_len);
+                       entry->i = i;
+                       hashmap_entry_init(entry, strhash(entry->subject));
+                       hashmap_put(&subject2item, entry);
+               }
+       }
+       if (rearranged) {
+               struct strbuf buf = STRBUF_INIT;
+               for (i = 0; i < todo_list.nr; i++) {
+                       enum todo_command command = todo_list.items[i].command;
+                       int cur = i;
+                       /*
+                        * Initially, all commands are 'pick's. If it is a
+                        * fixup or a squash now, we have rearranged it.
+                        */
+                       if (is_fixup(command))
+                               continue;
+                       while (cur >= 0) {
+                               int offset = todo_list.items[cur].offset_in_buf;
+                               int end_offset = cur + 1 < todo_list.nr ?
+                                       todo_list.items[cur + 1].offset_in_buf :
+                                       todo_list.buf.len;
+                               char *bol = todo_list.buf.buf + offset;
+                               char *eol = todo_list.buf.buf + end_offset;
+                               /* replace 'pick', by 'fixup' or 'squash' */
+                               command = todo_list.items[cur].command;
+                               if (is_fixup(command)) {
+                                       strbuf_addstr(&buf,
+                                               todo_command_info[command].str);
+                                       bol += strcspn(bol, " \t");
+                               }
+                               strbuf_add(&buf, bol, eol - bol);
+                               cur = next[cur];
+                       }
+               }
+               fd = open(todo_file, O_WRONLY);
+               if (fd < 0)
+                       res = error_errno(_("could not open '%s'"), todo_file);
+               else if (write(fd, buf.buf, buf.len) < 0)
+                       res = error_errno(_("could not read '%s'."), todo_file);
+               else if (ftruncate(fd, buf.len) < 0)
+                       res = error_errno(_("could not finish '%s'"),
+                                          todo_file);
+               close(fd);
+               strbuf_release(&buf);
+       }
+       free(next);
+       free(tail);
+       for (i = 0; i < todo_list.nr; i++)
+               free(subjects[i]);
+       free(subjects);
+       hashmap_free(&subject2item, 1);
+       todo_list_release(&todo_list);
+       return res;
+ }