]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ah/sequencer-rewrite-todo-fix'
authorJunio C Hamano <gitster@pobox.com>
Wed, 2 Aug 2023 16:37:23 +0000 (09:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 2 Aug 2023 16:37:24 +0000 (09:37 -0700)
When the user edits "rebase -i" todo file so that it starts with a
"fixup", which would make it invalid, the command truncated the
rest of the file before giving an error and returning the control
back to the user.  Stop truncating to make it easier to correct
such a malformed todo file.

* ah/sequencer-rewrite-todo-fix:
  sequencer: finish parsing the todo list despite an invalid first line

1  2 
sequencer.c

diff --combined sequencer.c
index cc9821ece2c752b30ba07133cefc1c08c6d1e6d6,8e1a3746f4e0ab47081d2f7196461573e9c096cf..adc9cfb4df39f2af7ec00e1c256fcc345f8ade3f
@@@ -1,18 -1,9 +1,18 @@@
 -#include "cache.h"
 +#include "git-compat-util.h"
 +#include "abspath.h"
 +#include "advice.h"
  #include "config.h"
 +#include "copy.h"
 +#include "environment.h"
 +#include "gettext.h"
 +#include "hex.h"
  #include "lockfile.h"
  #include "dir.h"
 -#include "object-store.h"
 +#include "object-file.h"
 +#include "object-name.h"
 +#include "object-store-ll.h"
  #include "object.h"
 +#include "pager.h"
  #include "commit.h"
  #include "sequencer.h"
  #include "tag.h"
  #include "utf8.h"
  #include "cache-tree.h"
  #include "diff.h"
 +#include "path.h"
  #include "revision.h"
  #include "rerere.h"
 +#include "merge.h"
  #include "merge-ort.h"
  #include "merge-ort-wrappers.h"
  #include "refs.h"
 +#include "sparse-index.h"
  #include "strvec.h"
  #include "quote.h"
  #include "trailer.h"
@@@ -220,8 -208,7 +220,8 @@@ static struct update_ref_record *init_u
        return rec;
  }
  
 -static int git_sequencer_config(const char *k, const char *v, void *cb)
 +static int git_sequencer_config(const char *k, const char *v,
 +                              const struct config_context *ctx, void *cb)
  {
        struct replay_opts *opts = cb;
        int status;
        if (opts->action == REPLAY_REVERT && !strcmp(k, "revert.reference"))
                opts->commit_use_reference = git_config_bool(k, v);
  
 -      status = git_gpg_config(k, v, NULL);
 -      if (status)
 -              return status;
 -
 -      return git_diff_basic_config(k, v, NULL);
 +      return git_diff_basic_config(k, v, ctx, NULL);
  }
  
  void sequencer_init_config(struct replay_opts *opts)
@@@ -366,7 -357,9 +366,7 @@@ void replay_opts_release(struct replay_
        free(opts->reflog_action);
        free(opts->default_strategy);
        free(opts->strategy);
 -      for (size_t i = 0; i < opts->xopts_nr; i++)
 -              free(opts->xopts[i]);
 -      free(opts->xopts);
 +      strvec_clear (&opts->xopts);
        strbuf_release(&opts->current_fixups);
        if (opts->revs)
                release_revisions(opts->revs);
@@@ -426,8 -419,7 +426,8 @@@ struct commit_message 
  
  static const char *short_commit_name(struct commit *commit)
  {
 -      return find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);
 +      return repo_find_unique_abbrev(the_repository, &commit->object.oid,
 +                                     DEFAULT_ABBREV);
  }
  
  static int get_message(struct commit *commit, struct commit_message *out)
        const char *abbrev, *subject;
        int subject_len;
  
 -      out->message = logmsg_reencode(commit, NULL, get_commit_output_encoding());
 +      out->message = repo_logmsg_reencode(the_repository, commit, NULL,
 +                                          get_commit_output_encoding());
        abbrev = short_commit_name(commit);
  
        subject_len = find_commit_subject(out->message, &subject);
@@@ -453,7 -444,7 +453,7 @@@ static void free_message(struct commit 
        free(msg->parent_label);
        free(msg->label);
        free(msg->subject);
 -      unuse_commit_buffer(commit, msg->message);
 +      repo_unuse_commit_buffer(the_repository, commit, msg->message);
  }
  
  static void print_advice(struct repository *r, int show_hint,
@@@ -570,7 -561,7 +570,7 @@@ static void update_abort_safety_file(vo
        if (!file_exists(git_path_seq_dir()))
                return;
  
 -      if (!get_oid("HEAD", &head))
 +      if (!repo_get_oid(the_repository, "HEAD", &head))
                write_file(git_path_abort_safety_file(), "%s", oid_to_hex(&head));
        else
                write_file(git_path_abort_safety_file(), "%s", "");
@@@ -662,12 -653,11 +662,12 @@@ void append_conflicts_hint(struct index
        }
  
        strbuf_addch(msgbuf, '\n');
 -      strbuf_commented_addf(msgbuf, "Conflicts:\n");
 +      strbuf_commented_addf(msgbuf, comment_line_char, "Conflicts:\n");
        for (i = 0; i < istate->cache_nr;) {
                const struct cache_entry *ce = istate->cache[i++];
                if (ce_stage(ce)) {
 -                      strbuf_commented_addf(msgbuf, "\t%s\n", ce->name);
 +                      strbuf_commented_addf(msgbuf, comment_line_char,
 +                                            "\t%s\n", ce->name);
                        while (i < istate->cache_nr &&
                               !strcmp(ce->name, istate->cache[i]->name))
                                i++;
@@@ -702,11 -692,11 +702,11 @@@ static int do_recursive_merge(struct re
        o.show_rename_progress = 1;
  
        head_tree = parse_tree_indirect(head);
 -      next_tree = next ? get_commit_tree(next) : empty_tree(r);
 -      base_tree = base ? get_commit_tree(base) : empty_tree(r);
 +      next_tree = next ? repo_get_commit_tree(r, next) : empty_tree(r);
 +      base_tree = base ? repo_get_commit_tree(r, base) : empty_tree(r);
  
 -      for (i = 0; i < opts->xopts_nr; i++)
 -              parse_merge_opt(&o, opts->xopts[i]);
 +      for (i = 0; i < opts->xopts.nr; i++)
 +              parse_merge_opt(&o, opts->xopts.v[i]);
  
        if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
                memset(&result, 0, sizeof(result));
@@@ -776,12 -766,12 +776,12 @@@ static int is_index_unchanged(struct re
        /*
         * If head_commit is NULL, check_commit, called from
         * lookup_commit, would have indicated that head_commit is not
 -       * a commit object already.  parse_commit() will return failure
 +       * a commit object already.  repo_parse_commit() will return failure
         * without further complaints in such a case.  Otherwise, if
 -       * the commit is invalid, parse_commit() will complain.  So
 +       * the commit is invalid, repo_parse_commit() will complain.  So
         * there is nothing for us to say here.  Just return failure.
         */
 -      if (parse_commit(head_commit))
 +      if (repo_parse_commit(r, head_commit))
                return -1;
  
        if (!(cache_tree_oid = get_cache_tree_oid(istate)))
@@@ -1146,8 -1136,7 +1146,8 @@@ void cleanup_message(struct strbuf *msg
            cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
                strbuf_setlen(msgbuf, wt_status_locate_end(msgbuf->buf, msgbuf->len));
        if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
 -              strbuf_stripspace(msgbuf, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
 +              strbuf_stripspace(msgbuf,
 +                cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
  }
  
  /*
@@@ -1178,8 -1167,7 +1178,8 @@@ int template_untouched(const struct str
        if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
                return 0;
  
 -      strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
 +      strbuf_stripspace(&tmpl,
 +        cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
        if (!skip_prefix(sb->buf, tmpl.buf, &start))
                start = sb->buf;
        strbuf_release(&tmpl);
@@@ -1348,15 -1336,13 +1348,15 @@@ void print_commit_summary(struct reposi
        commit = lookup_commit(r, oid);
        if (!commit)
                die(_("couldn't look up newly created commit"));
 -      if (parse_commit(commit))
 +      if (repo_parse_commit(r, commit))
                die(_("could not parse newly created commit"));
  
        strbuf_addstr(&format, "format:%h] %s");
  
 -      format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
 -      format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
 +      repo_format_commit_message(r, commit, "%an <%ae>", &author_ident,
 +                                 &pctx);
 +      repo_format_commit_message(r, commit, "%cn <%ce>", &committer_ident,
 +                                 &pctx);
        if (strbuf_cmp(&author_ident, &committer_ident)) {
                strbuf_addstr(&format, "\n Author: ");
                strbuf_addbuf_percentquote(&format, &author_ident);
        if (flags & SUMMARY_SHOW_AUTHOR_DATE) {
                struct strbuf date = STRBUF_INIT;
  
 -              format_commit_message(commit, "%ad", &date, &pctx);
 +              repo_format_commit_message(r, commit, "%ad", &date, &pctx);
                strbuf_addstr(&format, "\n Date: ");
                strbuf_addbuf_percentquote(&format, &date);
                strbuf_release(&date);
        rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
        diff_setup_done(&rev.diffopt);
  
 -      refs = get_main_ref_store(the_repository);
 +      refs = get_main_ref_store(r);
        head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
        if (!head)
                die(_("unable to resolve HEAD after creating commit"));
@@@ -1420,7 -1406,7 +1420,7 @@@ static int parse_head(struct repositor
        struct commit *current_head;
        struct object_id oid;
  
 -      if (get_oid("HEAD", &oid)) {
 +      if (repo_get_oid(r, "HEAD", &oid)) {
                current_head = NULL;
        } else {
                current_head = lookup_commit_reference(r, &oid);
                        warning(_("HEAD %s is not a commit!"),
                                oid_to_hex(&oid));
                }
 -              if (parse_commit(current_head))
 +              if (repo_parse_commit(r, current_head))
                        return error(_("could not parse HEAD commit"));
        }
        *head = current_head;
@@@ -1473,8 -1459,8 +1473,8 @@@ static int try_to_commit(struct reposit
        if (flags & AMEND_MSG) {
                const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
                const char *out_enc = get_commit_output_encoding();
 -              const char *message = logmsg_reencode(current_head, NULL,
 -                                                    out_enc);
 +              const char *message = repo_logmsg_reencode(r, current_head,
 +                                                         NULL, out_enc);
  
                if (!msg) {
                        const char *orig_message = NULL;
                        hook_commit = "HEAD";
                }
                author = amend_author = get_author(message);
 -              unuse_commit_buffer(current_head, message);
 +              repo_unuse_commit_buffer(r, current_head,
 +                                       message);
                if (!author) {
                        res = error(_("unable to parse commit author"));
                        goto out;
                cleanup = opts->default_msg_cleanup;
  
        if (cleanup != COMMIT_MSG_CLEANUP_NONE)
 -              strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
 +              strbuf_stripspace(msg,
 +                cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
        if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
@@@ -1684,12 -1668,12 +1684,12 @@@ static int is_original_commit_empty(str
  {
        const struct object_id *ptree_oid;
  
 -      if (parse_commit(commit))
 +      if (repo_parse_commit(the_repository, commit))
                return error(_("could not parse commit %s"),
                             oid_to_hex(&commit->object.oid));
        if (commit->parents) {
                struct commit *parent = commit->parents->item;
 -              if (parse_commit(parent))
 +              if (repo_parse_commit(the_repository, parent))
                        return error(_("could not parse parent commit %s"),
                                oid_to_hex(&parent->object.oid));
                ptree_oid = get_commit_tree_oid(parent);
@@@ -1846,7 -1830,7 +1846,7 @@@ static void add_commented_lines(struct 
                s += count;
                len -= count;
        }
 -      strbuf_add_commented_lines(buf, s, len);
 +      strbuf_add_commented_lines(buf, s, len, comment_line_char);
  }
  
  /* Does the current fixup chain contain a squash command? */
@@@ -1945,7 -1929,7 +1945,7 @@@ static int append_squash_message(struc
        strbuf_addf(buf, _(nth_commit_msg_fmt),
                    ++opts->current_fixup_count + 1);
        strbuf_addstr(buf, "\n\n");
 -      strbuf_add_commented_lines(buf, body, commented_len);
 +      strbuf_add_commented_lines(buf, body, commented_len, comment_line_char);
        /* buf->buf may be reallocated so store an offset into the buffer */
        fixup_off = buf->len;
        strbuf_addstr(buf, body + commented_len);
@@@ -2013,18 -1997,17 +2013,18 @@@ static int update_squash_messages(struc
                struct commit *head_commit;
                const char *head_message, *body;
  
 -              if (get_oid("HEAD", &head))
 +              if (repo_get_oid(r, "HEAD", &head))
                        return error(_("need a HEAD to fixup"));
                if (!(head_commit = lookup_commit_reference(r, &head)))
                        return error(_("could not read HEAD"));
 -              if (!(head_message = logmsg_reencode(head_commit, NULL, encoding)))
 +              if (!(head_message = repo_logmsg_reencode(r, head_commit, NULL,
 +                                                        encoding)))
                        return error(_("could not read HEAD's commit message"));
  
                find_commit_subject(head_message, &body);
                if (command == TODO_FIXUP && !flag && write_message(body, strlen(body),
                                                        rebase_path_fixup_msg(), 0) < 0) {
 -                      unuse_commit_buffer(head_commit, head_message);
 +                      repo_unuse_commit_buffer(r, head_commit, head_message);
                        return error(_("cannot write '%s'"), rebase_path_fixup_msg());
                }
                strbuf_addf(&buf, "%c ", comment_line_char);
                              _(first_commit_msg_str));
                strbuf_addstr(&buf, "\n\n");
                if (is_fixup_flag(command, flag))
 -                      strbuf_add_commented_lines(&buf, body, strlen(body));
 +                      strbuf_add_commented_lines(&buf, body, strlen(body),
 +                                                 comment_line_char);
                else
                        strbuf_addstr(&buf, body);
  
 -              unuse_commit_buffer(head_commit, head_message);
 +              repo_unuse_commit_buffer(r, head_commit, head_message);
        }
  
 -      if (!(message = logmsg_reencode(commit, NULL, encoding)))
 +      if (!(message = repo_logmsg_reencode(r, commit, NULL, encoding)))
                return error(_("could not read commit message of %s"),
                             oid_to_hex(&commit->object.oid));
        find_commit_subject(message, &body);
                strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
                            ++opts->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
 -              strbuf_add_commented_lines(&buf, body, strlen(body));
 +              strbuf_add_commented_lines(&buf, body, strlen(body),
 +                                         comment_line_char);
        } else
                return error(_("unknown command: %d"), command);
 -      unuse_commit_buffer(commit, message);
 +      repo_unuse_commit_buffer(r, commit, message);
  
        if (!res)
                res = write_message(buf.buf, buf.len, rebase_path_squash_msg(),
@@@ -2086,7 -2067,7 +2086,7 @@@ static void flush_rewritten_pending(voi
        FILE *out;
  
        if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), (GIT_MAX_HEXSZ + 1) * 2) > 0 &&
 -          !get_oid("HEAD", &newoid) &&
 +          !repo_get_oid(the_repository, "HEAD", &newoid) &&
            (out = fopen_or_warn(rebase_path_rewritten_list(), "a"))) {
                char *bol = buf.buf, *eol;
  
@@@ -2138,8 -2119,7 +2138,8 @@@ static void refer_to_commit(struct repl
                        .abbrev = DEFAULT_ABBREV,
                        .date_mode.type = DATE_SHORT,
                };
 -              format_commit_message(commit, "%h (%s, %ad)", msgbuf, &ctx);
 +              repo_format_commit_message(the_repository, commit,
 +                                         "%h (%s, %ad)", msgbuf, &ctx);
        } else {
                strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid));
        }
@@@ -2172,7 -2152,7 +2172,7 @@@ static int do_pick_commit(struct reposi
                if (write_index_as_tree(&head, r->index, r->index_file, 0, NULL))
                        return error(_("your index file is unmerged."));
        } else {
 -              unborn = get_oid("HEAD", &head);
 +              unborn = repo_get_oid(r, "HEAD", &head);
                /* Do we want to generate a root commit? */
                if (is_pick_or_similar(command) && opts->have_squash_onto &&
                    oideq(&head, &opts->squash_onto)) {
                msg_file = NULL;
                goto fast_forward_edit;
        }
 -      if (parent && parse_commit(parent) < 0)
 +      if (parent && repo_parse_commit(r, parent) < 0)
                /* TRANSLATORS: The first %s will be a "todo" command like
                   "revert" or "pick", the second %s a SHA1. */
                return error(_("%s: cannot parse parent commit %s"),
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
                res |= try_merge_command(r, opts->strategy,
 -                                       opts->xopts_nr, (const char **)opts->xopts,
 +                                       opts->xopts.nr, opts->xopts.v,
                                        common, oid_to_hex(&head), remotes);
                free_commit_list(common);
                free_commit_list(remotes);
@@@ -2485,6 -2465,7 +2485,6 @@@ void todo_list_release(struct todo_lis
  static struct todo_item *append_new_todo(struct todo_list *todo_list)
  {
        ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);
 -      todo_list->total_nr++;
        return todo_list->items + todo_list->nr++;
  }
  
@@@ -2625,7 -2606,7 +2625,7 @@@ static int parse_insn_line(struct repos
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
 -      status = get_oid(bol, &commit_oid);
 +      status = repo_get_oid(r, bol, &commit_oid);
        if (status < 0)
                error(_("could not parse '%s'"), bol); /* return later */
        *end_of_object_name = saved;
@@@ -2675,7 -2656,7 +2675,7 @@@ int todo_list_parse_insn_buffer(struct 
        char *p = buf, *next_p;
        int i, res = 0, fixup_okay = file_exists(rebase_path_done());
  
 -      todo_list->current = todo_list->nr = 0;
 +      todo_list->current = todo_list->nr = todo_list->total_nr = 0;
  
        for (i = 1; *p; i++, p = next_p) {
                char *eol = strchrnul(p, '\n');
                        item->commit = NULL;
                }
  
 +              if (item->command != TODO_COMMENT)
 +                      todo_list->total_nr++;
 +
                if (fixup_okay)
                        ; /* do nothing */
                else if (is_fixup(item->command))
-                       return error(_("cannot '%s' without a previous commit"),
+                       res = error(_("cannot '%s' without a previous commit"),
                                command_to_string(item->command));
                else if (!is_noop(item->command))
                        fixup_okay = 1;
@@@ -2889,9 -2867,7 +2889,9 @@@ static int git_config_string_dup(char *
        return 0;
  }
  
 -static int populate_opts_cb(const char *key, const char *value, void *data)
 +static int populate_opts_cb(const char *key, const char *value,
 +                          const struct config_context *ctx,
 +                          void *data)
  {
        struct replay_opts *opts = data;
        int error_flag = 1;
        if (!value)
                error_flag = 0;
        else if (!strcmp(key, "options.no-commit"))
 -              opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
 +              opts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.edit"))
 -              opts->edit = git_config_bool_or_int(key, value, &error_flag);
 +              opts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-empty"))
                opts->allow_empty =
 -                      git_config_bool_or_int(key, value, &error_flag);
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-empty-message"))
                opts->allow_empty_message =
 -                      git_config_bool_or_int(key, value, &error_flag);
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.keep-redundant-commits"))
                opts->keep_redundant_commits =
 -                      git_config_bool_or_int(key, value, &error_flag);
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.signoff"))
 -              opts->signoff = git_config_bool_or_int(key, value, &error_flag);
 +              opts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.record-origin"))
 -              opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
 +              opts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-ff"))
 -              opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
 +              opts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.mainline"))
 -              opts->mainline = git_config_int(key, value);
 +              opts->mainline = git_config_int(key, value, ctx->kvi);
        else if (!strcmp(key, "options.strategy"))
                git_config_string_dup(&opts->strategy, key, value);
        else if (!strcmp(key, "options.gpg-sign"))
                git_config_string_dup(&opts->gpg_sign, key, value);
        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);
 +              strvec_push(&opts->xopts, value);
        } else if (!strcmp(key, "options.allow-rerere-auto"))
                opts->allow_rerere_auto =
 -                      git_config_bool_or_int(key, value, &error_flag) ?
 +                      git_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?
                                RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
        else if (!strcmp(key, "options.default-msg-cleanup")) {
                opts->explicit_cleanup = 1;
        return 0;
  }
  
 -void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
 +static void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
  {
        int i;
 +      int count;
 +      const char **argv;
        char *strategy_opts_string = raw_opts;
  
        if (*strategy_opts_string == ' ')
                strategy_opts_string++;
  
 -      opts->xopts_nr = split_cmdline(strategy_opts_string,
 -                                     (const char ***)&opts->xopts);
 -      for (i = 0; i < opts->xopts_nr; i++) {
 -              const char *arg = opts->xopts[i];
 +      count = split_cmdline(strategy_opts_string, &argv);
 +      if (count < 0)
 +              BUG("could not split '%s': %s", strategy_opts_string,
 +                          split_cmdline_strerror(count));
 +      for (i = 0; i < count; i++) {
 +              const char *arg = argv[i];
  
                skip_prefix(arg, "--", &arg);
 -              opts->xopts[i] = xstrdup(arg);
 +              strvec_push(&opts->xopts, arg);
        }
 +      free(argv);
  }
  
  static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
        strbuf_reset(buf);
        if (!read_oneliner(buf, rebase_path_strategy(), 0))
                return;
 -      free(opts->strategy);
        opts->strategy = strbuf_detach(buf, NULL);
        if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
                return;
@@@ -3049,7 -3022,7 +3049,7 @@@ static int read_populate_opts(struct re
                }
  
                if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
 -                      if (get_oid_committish(buf.buf, &opts->squash_onto) < 0) {
 +                      if (repo_get_oid_committish(the_repository, buf.buf, &opts->squash_onto) < 0) {
                                ret = error(_("unusable squash-onto"));
                                goto done_rebase_i;
                        }
@@@ -3077,13 -3050,12 +3077,13 @@@ done_rebase_i
  
  static void write_strategy_opts(struct replay_opts *opts)
  {
 -      int i;
        struct strbuf buf = STRBUF_INIT;
  
 -      for (i = 0; i < opts->xopts_nr; ++i)
 -              strbuf_addf(&buf, " --%s", opts->xopts[i]);
 -
 +      /*
 +       * Quote strategy options so that they can be read correctly
 +       * by split_cmdline().
 +       */
 +      quote_cmdline(&buf, opts->xopts.v);
        write_file(rebase_path_strategy_opts(), "%s\n", buf.buf);
        strbuf_release(&buf);
  }
@@@ -3106,7 -3078,7 +3106,7 @@@ int write_basic_state(struct replay_opt
                write_file(rebase_path_verbose(), "%s", "");
        if (opts->strategy)
                write_file(rebase_path_strategy(), "%s\n", opts->strategy);
 -      if (opts->xopts_nr > 0)
 +      if (opts->xopts.nr > 0)
                write_strategy_opts(opts);
  
        if (opts->allow_rerere_auto == RERERE_AUTOUPDATE)
@@@ -3150,9 -3122,7 +3150,9 @@@ static int walk_revs_populate_todo(stru
  
        while ((commit = get_revision(opts->revs))) {
                struct todo_item *item = append_new_todo(todo_list);
 -              const char *commit_buffer = logmsg_reencode(commit, NULL, encoding);
 +              const char *commit_buffer = repo_logmsg_reencode(the_repository,
 +                                                               commit, NULL,
 +                                                               encoding);
                const char *subject;
                int subject_len;
  
                subject_len = find_commit_subject(commit_buffer, &subject);
                strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string,
                        short_commit_name(commit), subject_len, subject);
 -              unuse_commit_buffer(commit, commit_buffer);
 +              repo_unuse_commit_buffer(the_repository, commit,
 +                                       commit_buffer);
        }
  
        if (!todo_list->nr)
@@@ -3215,7 -3184,25 +3215,7 @@@ static int create_seq_dir(struct reposi
  
  static int save_head(const char *head)
  {
 -      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)
 -              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);
 -      if (written < 0) {
 -              error_errno(_("could not write to '%s'"), git_path_head_file());
 -              rollback_lock_file(&head_lock);
 -              return -1;
 -      }
 -      if (commit_lock_file(&head_lock) < 0)
 -              return error(_("failed to finalize '%s'"), git_path_head_file());
 -      return 0;
 +      return write_message(head, strlen(head), git_path_head_file(), 1);
  }
  
  static int rollback_is_safe(void)
        else
                die_errno(_("could not read '%s'"), git_path_abort_safety_file());
  
 -      if (get_oid("HEAD", &actual_head))
 +      if (repo_get_oid(the_repository, "HEAD", &actual_head))
                oidclr(&actual_head);
  
        return oideq(&actual_head, &expected_head);
@@@ -3475,10 -3462,13 +3475,10 @@@ static int save_opts(struct replay_opt
        if (opts->gpg_sign)
                res |= git_config_set_in_file_gently(opts_file,
                                        "options.gpg-sign", opts->gpg_sign);
 -      if (opts->xopts) {
 -              int i;
 -              for (i = 0; i < opts->xopts_nr; i++)
 -                      res |= git_config_set_multivar_in_file_gently(opts_file,
 -                                      "options.strategy-option",
 -                                      opts->xopts[i], "^$", 0);
 -      }
 +      for (size_t i = 0; i < opts->xopts.nr; i++)
 +              res |= git_config_set_multivar_in_file_gently(opts_file,
 +                              "options.strategy-option",
 +                              opts->xopts.v[i], "^$", 0);
        if (opts->allow_rerere_auto)
                res |= git_config_set_in_file_gently(opts_file,
                                "options.allow-rerere-auto",
@@@ -3528,13 -3518,10 +3528,13 @@@ static int make_patch(struct repositor
        strbuf_addf(&buf, "%s/message", get_dir(opts));
        if (!file_exists(buf.buf)) {
                const char *encoding = get_commit_output_encoding();
 -              const char *commit_buffer = logmsg_reencode(commit, NULL, encoding);
 +              const char *commit_buffer = repo_logmsg_reencode(r,
 +                                                               commit, NULL,
 +                                                               encoding);
                find_commit_subject(commit_buffer, &subject);
                res |= write_message(subject, strlen(subject), buf.buf, 1);
 -              unuse_commit_buffer(commit, commit_buffer);
 +              repo_unuse_commit_buffer(r, commit,
 +                                       commit_buffer);
        }
        strbuf_release(&buf);
        release_revisions(&log_tree_opt);
@@@ -3547,7 -3534,7 +3547,7 @@@ static int intend_to_amend(void
        struct object_id head;
        char *p;
  
 -      if (get_oid("HEAD", &head))
 +      if (repo_get_oid(the_repository, "HEAD", &head))
                return error(_("cannot read HEAD"));
  
        p = oid_to_hex(&head);
@@@ -3638,14 -3625,14 +3638,14 @@@ static int do_exec(struct repository *r
                          "  git rebase --continue\n"
                          "\n"),
                        command_line,
 -                      dirty ? N_("and made changes to the index and/or the "
 -                              "working tree\n") : "");
 +                      dirty ? _("and made changes to the index and/or the "
 +                              "working tree.\n") : "");
                if (status == 127)
                        /* command not found */
                        status = 1;
        } else if (dirty) {
                warning(_("execution succeeded: %s\nbut "
 -                        "left changes to the index and/or the working tree\n"
 +                        "left changes to the index and/or the working tree.\n"
                          "Commit or stash your changes, and then run\n"
                          "\n"
                          "  git rebase --continue\n"
@@@ -3686,6 -3673,7 +3686,6 @@@ static int safe_append(const char *file
        }
        if (commit_lock_file(&lock) < 0) {
                strbuf_release(&buf);
 -              rollback_lock_file(&lock);
                return error(_("failed to finalize '%s'"), filename);
        }
  
@@@ -3712,7 -3700,7 +3712,7 @@@ static int do_label(struct repository *
        if (!transaction) {
                error("%s", err.buf);
                ret = -1;
 -      } else if (get_oid("HEAD", &head_oid)) {
 +      } else if (repo_get_oid(r, "HEAD", &head_oid)) {
                error(_("could not read HEAD"));
                ret = -1;
        } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
@@@ -3890,7 -3878,7 +3890,7 @@@ static int do_merge(struct repository *
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j;
        struct commit_list *to_merge = NULL, **tail = &to_merge;
 -      const char *strategy = !opts->xopts_nr &&
 +      const char *strategy = !opts->xopts.nr &&
                (!opts->strategy ||
                 !strcmp(opts->strategy, "recursive") ||
                 !strcmp(opts->strategy, "ort")) ?
  
        if (commit) {
                const char *encoding = get_commit_output_encoding();
 -              const char *message = logmsg_reencode(commit, NULL, encoding);
 +              const char *message = repo_logmsg_reencode(r, commit, NULL,
 +                                                         encoding);
                const char *body;
                int len;
  
                find_commit_subject(message, &body);
                len = strlen(body);
                ret = write_message(body, len, git_path_merge_msg(r), 0);
 -              unuse_commit_buffer(commit, message);
 +              repo_unuse_commit_buffer(r, commit, message);
                if (ret) {
                        error_errno(_("could not write '%s'"),
                                    git_path_merge_msg(r));
                        strvec_push(&cmd.args, "octopus");
                else {
                        strvec_push(&cmd.args, strategy);
 -                      for (k = 0; k < opts->xopts_nr; k++)
 +                      for (k = 0; k < opts->xopts.nr; k++)
                                strvec_pushf(&cmd.args,
 -                                           "-X%s", opts->xopts[k]);
 +                                           "-X%s", opts->xopts.v[k]);
                }
                if (!(flags & TODO_EDIT_MERGE_MSG))
                        strvec_push(&cmd.args, "--no-edit");
        }
  
        merge_commit = to_merge->item;
 -      bases = get_merge_bases(head_commit, merge_commit);
 +      bases = repo_get_merge_bases(r, head_commit, merge_commit);
        if (bases && oideq(&merge_commit->object.oid,
                           &bases->item->object.oid)) {
                ret = 0;
@@@ -4282,7 -4269,7 +4282,7 @@@ void todo_list_filter_update_refs(struc
                if (!is_null_oid(&rec->after))
                        continue;
  
 -              for (j = 0; !found && j < todo_list->total_nr; j++) {
 +              for (j = 0; !found && j < todo_list->nr; j++) {
                        struct todo_item *item = &todo_list->items[j];
                        const char *arg = todo_list->buf.buf + item->arg_offset;
  
         * For each todo_item, check if its ref is in the update_refs list.
         * If not, then add it as an un-updated ref.
         */
 -      for (i = 0; i < todo_list->total_nr; i++) {
 +      for (i = 0; i < todo_list->nr; i++) {
                struct todo_item *item = &todo_list->items[i];
                const char *arg = todo_list->buf.buf + item->arg_offset;
                int j, found = 0;
@@@ -4469,7 -4456,7 +4469,7 @@@ void create_autostash(struct repositor
                if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
                        die(_("Cannot autostash"));
                strbuf_trim_trailing_newline(&buf);
 -              if (get_oid(buf.buf, &oid))
 +              if (repo_get_oid(r, buf.buf, &oid))
                        die(_("Unexpected stash response: '%s'"),
                            buf.buf);
                strbuf_reset(&buf);
@@@ -4594,9 -4581,9 +4594,9 @@@ static int stopped_at_head(struct repos
        struct commit *commit;
        struct commit_message message;
  
 -      if (get_oid("HEAD", &head) ||
 +      if (repo_get_oid(r, "HEAD", &head) ||
            !(commit = lookup_commit(r, &head)) ||
 -          parse_commit(commit) || get_message(commit, &message))
 +          repo_parse_commit(r, commit) || get_message(commit, &message))
                fprintf(stderr, _("Stopped at HEAD\n"));
        else {
                fprintf(stderr, _("Stopped at %s\n"), message.label);
@@@ -4744,7 -4731,7 +4744,7 @@@ static int pick_commits(struct reposito
                                 * otherwise we do not.
                                 */
                                if (item->command == TODO_REWORD &&
 -                                  !get_oid("HEAD", &oid) &&
 +                                  !repo_get_oid(r, "HEAD", &oid) &&
                                    (oideq(&item->commit->object.oid, &oid) ||
                                     (opts->have_squash_onto &&
                                      oideq(&opts->squash_onto, &oid))))
                        struct object_id head, orig;
                        int res;
  
 -                      if (get_oid("HEAD", &head)) {
 +                      if (repo_get_oid(r, "HEAD", &head)) {
                                res = error(_("cannot read HEAD"));
  cleanup_head_ref:
                                strbuf_release(&head_ref);
                        log_tree_opt.disable_stdin = 1;
  
                        if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
 -                          !get_oid(buf.buf, &orig) &&
 -                          !get_oid("HEAD", &head)) {
 +                          !repo_get_oid(r, buf.buf, &orig) &&
 +                          !repo_get_oid(r, "HEAD", &head)) {
                                diff_tree_oid(&orig, &head, "",
                                              &log_tree_opt.diffopt);
                                log_tree_diff_flush(&log_tree_opt);
@@@ -4973,7 -4960,7 +4973,7 @@@ static int commit_staged_changes(struc
                struct strbuf rev = STRBUF_INIT;
                struct object_id head, to_amend;
  
 -              if (get_oid("HEAD", &head))
 +              if (repo_get_oid(r, "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());
                                const char *encoding = get_commit_output_encoding();
  
                                if (parse_head(r, &commit) ||
 -                                  !(p = logmsg_reencode(commit, NULL, encoding)) ||
 +                                  !(p = repo_logmsg_reencode(r, commit, NULL, encoding)) ||
                                    write_message(p, strlen(p), path, 0)) {
 -                                      unuse_commit_buffer(commit, p);
 +                                      repo_unuse_commit_buffer(r, commit, p);
                                        return error(_("could not write file: "
                                                       "'%s'"), path);
                                }
 -                              unuse_commit_buffer(commit, p);
 +                              repo_unuse_commit_buffer(r,
 +                                                       commit, p);
                        }
                }
  
@@@ -5199,7 -5185,7 +5199,7 @@@ int sequencer_pick_revisions(struct rep
                if (!strlen(name))
                        continue;
  
 -              if (!get_oid(name, &oid)) {
 +              if (!repo_get_oid(r, name, &oid)) {
                        if (!lookup_commit_reference_gently(r, &oid, 1)) {
                                enum object_type type = oid_object_info(r,
                                                                        &oid,
        if (walk_revs_populate_todo(&todo_list, opts) ||
                        create_seq_dir(r) < 0)
                return -1;
 -      if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT))
 +      if (repo_get_oid(r, "HEAD", &oid) && (opts->action == REPLAY_REVERT))
                return error(_("can't revert as initial commit"));
        if (save_head(oid_to_hex(&oid)))
                return -1;
@@@ -5358,7 -5344,7 +5358,7 @@@ static const char *label_oid(struct obj
         * For "uninteresting" commits, i.e. commits that are not to be
         * rebased, and which can therefore not be labeled, we use a unique
         * abbreviation of the commit name. This is slightly more complicated
 -       * than calling find_unique_abbrev() because we also need to make
 +       * than calling repo_find_unique_abbrev() because we also need to make
         * sure that the abbreviation does not conflict with any other
         * label.
         *
                strbuf_grow(&state->buf, GIT_MAX_HEXSZ);
                label = p = state->buf.buf;
  
 -              find_unique_abbrev_r(p, oid, default_abbrev);
 +              repo_find_unique_abbrev_r(the_repository, p, oid,
 +                                        default_abbrev);
  
                /*
                 * We may need to extend the abbreviated hash so that there is
@@@ -5937,7 -5922,7 +5937,7 @@@ static int skip_unnecessary_picks(struc
                        continue;
                if (item->command != TODO_PICK)
                        break;
 -              if (parse_commit(item->commit)) {
 +              if (repo_parse_commit(r, item->commit)) {
                        return error(_("could not parse commit '%s'"),
                                oid_to_hex(&item->commit->object.oid));
                }
@@@ -6108,8 -6093,7 +6108,8 @@@ int complete_action(struct repository *
        struct object_id oid = onto->object.oid;
        int res;
  
 -      find_unique_abbrev_r(shortonto, &oid, DEFAULT_ABBREV);
 +      repo_find_unique_abbrev_r(r, shortonto, &oid,
 +                                DEFAULT_ABBREV);
  
        if (buf->len == 0) {
                struct todo_item *item = append_new_todo(todo_list);
        todo_list_to_strbuf(r, &new_todo, &buf2, -1, 0);
        strbuf_swap(&new_todo.buf, &buf2);
        strbuf_release(&buf2);
 -      new_todo.total_nr -= new_todo.nr;
 +      /* Nothing is done yet, and we're reparsing, so let's reset the count */
 +      new_todo.total_nr = 0;
        if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) < 0)
                BUG("invalid todo list after expanding IDs:\n%s",
                    new_todo.buf.buf);
@@@ -6271,15 -6254,12 +6271,15 @@@ int todo_list_rearrange_squash(struct t
                        return error(_("the script was already rearranged."));
                }
  
 -              parse_commit(item->commit);
 -              commit_buffer = logmsg_reencode(item->commit, NULL, "UTF-8");
 +              repo_parse_commit(the_repository, item->commit);
 +              commit_buffer = repo_logmsg_reencode(the_repository,
 +                                                   item->commit, NULL,
 +                                                   "UTF-8");
                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);
 +              repo_unuse_commit_buffer(the_repository, item->commit,
 +                                       commit_buffer);
                if (skip_fixupish(subject, &p)) {
                        struct commit *commit2;
  
@@@ -6389,8 -6369,8 +6389,8 @@@ int sequencer_determine_whence(struct r
                if (file_exists(git_path_seq_dir()))
                        *whence = FROM_CHERRY_PICK_MULTI;
                if (file_exists(rebase_path()) &&
 -                  !get_oid("REBASE_HEAD", &rebase_head) &&
 -                  !get_oid("CHERRY_PICK_HEAD", &cherry_pick_head) &&
 +                  !repo_get_oid(r, "REBASE_HEAD", &rebase_head) &&
 +                  !repo_get_oid(r, "CHERRY_PICK_HEAD", &cherry_pick_head) &&
                    oideq(&rebase_head, &cherry_pick_head))
                        *whence = FROM_REBASE_PICK;
                else