Merge branch 'jk/trailer-fixes' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 21 Nov 2018 13:57:41 +0000 (22:57 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 21 Nov 2018 13:57:42 +0000 (22:57 +0900)
"git interpret-trailers" and its underlying machinery had a buggy
code that attempted to ignore patch text after commit log message,
which triggered in various codepaths that will always get the log
message alone and never get such an input.

* jk/trailer-fixes:
append_signoff: use size_t for string offsets
sequencer: ignore "---" divider when parsing trailers
pretty, ref-filter: format %(trailers) with no_divider option
interpret-trailers: allow suppressing "---" divider
interpret-trailers: tighten check for "---" patch boundary
trailer: pass process_trailer_opts to trailer_info_get()
trailer: use size_t for iterating trailer list
trailer: use size_t for string offsets

1  2 
Documentation/git-interpret-trailers.txt
commit.c
commit.h
pretty.c
ref-filter.c
sequencer.c
sequencer.h
t/t6300-for-each-ref.sh
t/t7501-commit.sh
trailer.h
index b8fafb1e8bdd6c3e061b88b227e1ef264bbfbd07,ee131246812a93347bfd5c75795bb2d26f6b3b82..a5e8b36f62bcf5eeedfb5a04ac852c476d9f43f3
@@@ -56,8 -56,9 +56,9 @@@ least one Git-generated or user-configu
  least 25% trailers.
  The group must be preceded by one or more empty (or whitespace-only) lines.
  The group must either be at the end of the message or be the last
- non-whitespace lines before a line that starts with '---'. Such three
- minus signs start the patch part of the message.
+ non-whitespace lines before a line that starts with '---' (followed by a
+ space or the end of the line). Such three minus signs start the patch
+ part of the message. See also `--no-divider` below.
  
  When reading trailers, there can be whitespaces after the
  token, the separator and the value. There can also be whitespaces
@@@ -88,8 -89,7 +89,8 @@@ OPTION
        Specify where all new trailers will be added.  A setting
        provided with '--where' overrides all configuration variables
        and applies to all '--trailer' options until the next occurrence of
 -      '--where' or '--no-where'.
 +      '--where' or '--no-where'. Possible values are `after`, `before`,
 +      `end` or `start`.
  
  --if-exists <action>::
  --no-if-exists::
@@@ -97,8 -97,7 +98,8 @@@
        least one trailer with the same <token> in the message.  A setting
        provided with '--if-exists' overrides all configuration variables
        and applies to all '--trailer' options until the next occurrence of
 -      '--if-exists' or '--no-if-exists'.
 +      '--if-exists' or '--no-if-exists'. Possible actions are `addIfDifferent`,
 +      `addIfDifferentNeighbor`, `add`, `replace` and `doNothing`.
  
  --if-missing <action>::
  --no-if-missing::
        trailer with the same <token> in the message.  A setting
        provided with '--if-missing' overrides all configuration variables
        and applies to all '--trailer' options until the next occurrence of
 -      '--if-missing' or '--no-if-missing'.
 +      '--if-missing' or '--no-if-missing'. Possible actions are `doNothing`
 +      or `add`.
  
  --only-trailers::
        Output only the trailers, not any other parts of the input.
        A convenience alias for `--only-trailers --only-input
        --unfold`.
  
+ --no-divider::
+       Do not treat `---` as the end of the commit message. Use this
+       when you know your input contains just the commit message itself
+       (and not an email or the output of `git format-patch`).
  CONFIGURATION VARIABLES
  -----------------------
  
diff --combined commit.c
index 449c1f4920cff631f5cf2479772c350c9b8b7325,873ef0d5b1cf262f49f5f0332348e48f72c44061..db679c2adfd766a385f5406e6413ad13ba1e27af
+++ b/commit.c
@@@ -2,14 -2,11 +2,14 @@@
  #include "tag.h"
  #include "commit.h"
  #include "commit-graph.h"
 +#include "repository.h"
 +#include "object-store.h"
  #include "pkt-line.h"
  #include "utf8.h"
  #include "diff.h"
  #include "revision.h"
  #include "notes.h"
 +#include "alloc.h"
  #include "gpg-interface.h"
  #include "mergesort.h"
  #include "commit-slab.h"
@@@ -24,26 -21,24 +24,26 @@@ int save_commit_buffer = 1
  
  const char *commit_type = "commit";
  
 -struct commit *lookup_commit_reference_gently(const struct object_id *oid,
 -                                            int quiet)
 +struct commit *lookup_commit_reference_gently(struct repository *r,
 +              const struct object_id *oid, int quiet)
  {
 -      struct object *obj = deref_tag(parse_object(oid), NULL, 0);
 +      struct object *obj = deref_tag(r,
 +                                     parse_object(r, oid),
 +                                     NULL, 0);
  
        if (!obj)
                return NULL;
 -      return object_as_type(obj, OBJ_COMMIT, quiet);
 +      return object_as_type(r, obj, OBJ_COMMIT, quiet);
  }
  
 -struct commit *lookup_commit_reference(const struct object_id *oid)
 +struct commit *lookup_commit_reference(struct repository *r, const struct object_id *oid)
  {
 -      return lookup_commit_reference_gently(oid, 0);
 +      return lookup_commit_reference_gently(r, oid, 0);
  }
  
  struct commit *lookup_commit_or_die(const struct object_id *oid, const char *ref_name)
  {
 -      struct commit *c = lookup_commit_reference(oid);
 +      struct commit *c = lookup_commit_reference(the_repository, oid);
        if (!c)
                die(_("could not parse %s"), ref_name);
        if (oidcmp(oid, &c->object.oid)) {
        return c;
  }
  
 -struct commit *lookup_commit(const struct object_id *oid)
 +struct commit *lookup_commit(struct repository *r, const struct object_id *oid)
  {
 -      struct object *obj = lookup_object(oid->hash);
 +      struct object *obj = lookup_object(r, oid->hash);
        if (!obj)
 -              return create_object(oid->hash, alloc_commit_node());
 -      return object_as_type(obj, OBJ_COMMIT, 0);
 +              return create_object(r, oid->hash,
 +                                   alloc_commit_node(r));
 +      return object_as_type(r, obj, OBJ_COMMIT, 0);
  }
  
  struct commit *lookup_commit_reference_by_name(const char *name)
@@@ -69,7 -63,7 +69,7 @@@
  
        if (get_oid_committish(name, &oid))
                return NULL;
 -      commit = lookup_commit_reference(&oid);
 +      commit = lookup_commit_reference(the_repository, &oid);
        if (parse_commit(commit))
                return NULL;
        return commit;
@@@ -102,44 -96,41 +102,44 @@@ static timestamp_t parse_commit_date(co
        return parse_timestamp(dateptr, NULL, 10);
  }
  
 -static struct commit_graft **commit_graft;
 -static int commit_graft_alloc, commit_graft_nr;
 -
  static const unsigned char *commit_graft_sha1_access(size_t index, void *table)
  {
        struct commit_graft **commit_graft_table = table;
        return commit_graft_table[index]->oid.hash;
  }
  
 -static int commit_graft_pos(const unsigned char *sha1)
 +static int commit_graft_pos(struct repository *r, const unsigned char *sha1)
  {
 -      return sha1_pos(sha1, commit_graft, commit_graft_nr,
 +      return sha1_pos(sha1, r->parsed_objects->grafts,
 +                      r->parsed_objects->grafts_nr,
                        commit_graft_sha1_access);
  }
  
 -int register_commit_graft(struct commit_graft *graft, int ignore_dups)
 +int register_commit_graft(struct repository *r, struct commit_graft *graft,
 +                        int ignore_dups)
  {
 -      int pos = commit_graft_pos(graft->oid.hash);
 +      int pos = commit_graft_pos(r, graft->oid.hash);
  
        if (0 <= pos) {
                if (ignore_dups)
                        free(graft);
                else {
 -                      free(commit_graft[pos]);
 -                      commit_graft[pos] = graft;
 +                      free(r->parsed_objects->grafts[pos]);
 +                      r->parsed_objects->grafts[pos] = graft;
                }
                return 1;
        }
        pos = -pos - 1;
 -      ALLOC_GROW(commit_graft, commit_graft_nr + 1, commit_graft_alloc);
 -      commit_graft_nr++;
 -      if (pos < commit_graft_nr)
 -              MOVE_ARRAY(commit_graft + pos + 1, commit_graft + pos,
 -                         commit_graft_nr - pos - 1);
 -      commit_graft[pos] = graft;
 +      ALLOC_GROW(r->parsed_objects->grafts,
 +                 r->parsed_objects->grafts_nr + 1,
 +                 r->parsed_objects->grafts_alloc);
 +      r->parsed_objects->grafts_nr++;
 +      if (pos < r->parsed_objects->grafts_nr)
 +              memmove(r->parsed_objects->grafts + pos + 1,
 +                      r->parsed_objects->grafts + pos,
 +                      (r->parsed_objects->grafts_nr - pos - 1) *
 +                      sizeof(*r->parsed_objects->grafts));
 +      r->parsed_objects->grafts[pos] = graft;
        return 0;
  }
  
@@@ -181,7 -172,7 +181,7 @@@ bad_graft_data
        return NULL;
  }
  
 -static int read_graft_file(const char *graft_file)
 +static int read_graft_file(struct repository *r, const char *graft_file)
  {
        FILE *fp = fopen_or_warn(graft_file, "r");
        struct strbuf buf = STRBUF_INIT;
                struct commit_graft *graft = read_graft_line(&buf);
                if (!graft)
                        continue;
 -              if (register_commit_graft(graft, 1))
 +              if (register_commit_graft(r, graft, 1))
                        error("duplicate graft data: %s", buf.buf);
        }
        fclose(fp);
        return 0;
  }
  
 -static void prepare_commit_graft(void)
 +static void prepare_commit_graft(struct repository *r)
  {
 -      static int commit_graft_prepared;
        char *graft_file;
  
 -      if (commit_graft_prepared)
 +      if (r->parsed_objects->commit_graft_prepared)
                return;
        if (!startup_info->have_repository)
                return;
  
 -      graft_file = get_graft_file();
 -      read_graft_file(graft_file);
 +      graft_file = get_graft_file(r);
 +      read_graft_file(r, graft_file);
        /* make sure shallows are read */
 -      is_repository_shallow();
 -      commit_graft_prepared = 1;
 +      is_repository_shallow(r);
 +      r->parsed_objects->commit_graft_prepared = 1;
  }
  
 -struct commit_graft *lookup_commit_graft(const struct object_id *oid)
 +struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid)
  {
        int pos;
 -      prepare_commit_graft();
 -      pos = commit_graft_pos(oid->hash);
 +      prepare_commit_graft(r);
 +      pos = commit_graft_pos(r, oid->hash);
        if (pos < 0)
                return NULL;
 -      return commit_graft[pos];
 +      return r->parsed_objects->grafts[pos];
  }
  
  int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
  {
        int i, ret;
 -      for (i = ret = 0; i < commit_graft_nr && !ret; i++)
 -              ret = fn(commit_graft[i], cb_data);
 +      for (i = ret = 0; i < the_repository->parsed_objects->grafts_nr && !ret; i++)
 +              ret = fn(the_repository->parsed_objects->grafts[i], cb_data);
        return ret;
  }
  
  int unregister_shallow(const struct object_id *oid)
  {
 -      int pos = commit_graft_pos(oid->hash);
 +      int pos = commit_graft_pos(the_repository, oid->hash);
        if (pos < 0)
                return -1;
 -      if (pos + 1 < commit_graft_nr)
 -              MOVE_ARRAY(commit_graft + pos, commit_graft + pos + 1,
 -                         commit_graft_nr - pos - 1);
 -      commit_graft_nr--;
 +      if (pos + 1 < the_repository->parsed_objects->grafts_nr)
 +              MOVE_ARRAY(the_repository->parsed_objects->grafts + pos,
 +                         the_repository->parsed_objects->grafts + pos + 1,
 +                         the_repository->parsed_objects->grafts_nr - pos - 1);
 +      the_repository->parsed_objects->grafts_nr--;
        return 0;
  }
  
@@@ -261,32 -252,18 +261,32 @@@ struct commit_buffer 
        unsigned long size;
  };
  define_commit_slab(buffer_slab, struct commit_buffer);
 -static struct buffer_slab buffer_slab = COMMIT_SLAB_INIT(1, buffer_slab);
  
 -void set_commit_buffer(struct commit *commit, void *buffer, unsigned long size)
 +struct buffer_slab *allocate_commit_buffer_slab(void)
 +{
 +      struct buffer_slab *bs = xmalloc(sizeof(*bs));
 +      init_buffer_slab(bs);
 +      return bs;
 +}
 +
 +void free_commit_buffer_slab(struct buffer_slab *bs)
 +{
 +      clear_buffer_slab(bs);
 +      free(bs);
 +}
 +
 +void set_commit_buffer(struct repository *r, struct commit *commit, void *buffer, unsigned long size)
  {
 -      struct commit_buffer *v = buffer_slab_at(&buffer_slab, commit);
 +      struct commit_buffer *v = buffer_slab_at(
 +              r->parsed_objects->buffer_slab, commit);
        v->buffer = buffer;
        v->size = size;
  }
  
 -const void *get_cached_commit_buffer(const struct commit *commit, unsigned long *sizep)
 +const void *get_cached_commit_buffer(struct repository *r, const struct commit *commit, unsigned long *sizep)
  {
 -      struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
 +      struct commit_buffer *v = buffer_slab_peek(
 +              r->parsed_objects->buffer_slab, commit);
        if (!v) {
                if (sizep)
                        *sizep = 0;
  
  const void *get_commit_buffer(const struct commit *commit, unsigned long *sizep)
  {
 -      const void *ret = get_cached_commit_buffer(commit, sizep);
 +      const void *ret = get_cached_commit_buffer(the_repository, commit, sizep);
        if (!ret) {
                enum object_type type;
                unsigned long size;
  
  void unuse_commit_buffer(const struct commit *commit, const void *buffer)
  {
 -      struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
 +      struct commit_buffer *v = buffer_slab_peek(
 +              the_repository->parsed_objects->buffer_slab, commit);
        if (!(v && v->buffer == buffer))
                free((void *)buffer);
  }
  
  void free_commit_buffer(struct commit *commit)
  {
 -      struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
 +      struct commit_buffer *v = buffer_slab_peek(
 +              the_repository->parsed_objects->buffer_slab, commit);
        if (v) {
                FREE_AND_NULL(v->buffer);
                v->size = 0;
@@@ -342,7 -317,7 +342,7 @@@ struct tree *get_commit_tree(const stru
        if (commit->graph_pos == COMMIT_NOT_FROM_GRAPH)
                BUG("commit has NULL tree, but was not loaded from commit-graph");
  
 -      return get_commit_tree_in_graph(commit);
 +      return get_commit_tree_in_graph(the_repository, commit);
  }
  
  struct object_id *get_commit_tree_oid(const struct commit *commit)
        return &get_commit_tree(commit)->object.oid;
  }
  
 +void release_commit_memory(struct commit *c)
 +{
 +      c->maybe_tree = NULL;
 +      c->index = 0;
 +      free_commit_buffer(c);
 +      free_commit_list(c->parents);
 +      /* TODO: what about commit->util? */
 +
 +      c->object.parsed = 0;
 +}
 +
  const void *detach_commit_buffer(struct commit *commit, unsigned long *sizep)
  {
 -      struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit);
 +      struct commit_buffer *v = buffer_slab_peek(
 +              the_repository->parsed_objects->buffer_slab, commit);
        void *ret;
  
        if (!v) {
        return ret;
  }
  
 -int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size)
 +int parse_commit_buffer(struct repository *r, struct commit *item, const void *buffer, unsigned long size, int check_graph)
  {
        const char *tail = buffer;
        const char *bufptr = buffer;
        struct object_id parent;
        struct commit_list **pptr;
        struct commit_graft *graft;
 -      const int tree_entry_len = GIT_SHA1_HEXSZ + 5;
 -      const int parent_entry_len = GIT_SHA1_HEXSZ + 7;
 +      const int tree_entry_len = the_hash_algo->hexsz + 5;
 +      const int parent_entry_len = the_hash_algo->hexsz + 7;
  
        if (item->object.parsed)
                return 0;
        if (get_oid_hex(bufptr + 5, &parent) < 0)
                return error("bad tree pointer in commit %s",
                             oid_to_hex(&item->object.oid));
 -      item->maybe_tree = lookup_tree(&parent);
 +      item->maybe_tree = lookup_tree(r, &parent);
        bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */
        pptr = &item->parents;
  
 -      graft = lookup_commit_graft(&item->object.oid);
 +      graft = lookup_commit_graft(r, &item->object.oid);
        while (bufptr + parent_entry_len < tail && !memcmp(bufptr, "parent ", 7)) {
                struct commit *new_parent;
  
                 */
                if (graft && (graft->nr_parent < 0 || grafts_replace_parents))
                        continue;
 -              new_parent = lookup_commit(&parent);
 +              new_parent = lookup_commit(r, &parent);
                if (new_parent)
                        pptr = &commit_list_insert(new_parent, pptr)->next;
        }
                int i;
                struct commit *new_parent;
                for (i = 0; i < graft->nr_parent; i++) {
 -                      new_parent = lookup_commit(&graft->parent[i]);
 +                      new_parent = lookup_commit(r,
 +                                                 &graft->parent[i]);
                        if (!new_parent)
                                continue;
                        pptr = &commit_list_insert(new_parent, pptr)->next;
        }
        item->date = parse_commit_date(bufptr, tail);
  
 +      if (check_graph)
 +              load_commit_graph_info(the_repository, item);
 +
        return 0;
  }
  
 -int parse_commit_gently(struct commit *item, int quiet_on_missing)
 +int parse_commit_internal(struct commit *item, int quiet_on_missing, int use_commit_graph)
  {
        enum object_type type;
        void *buffer;
                return -1;
        if (item->object.parsed)
                return 0;
 -      if (parse_commit_in_graph(item))
 +      if (use_commit_graph && parse_commit_in_graph(the_repository, item))
                return 0;
        buffer = read_object_file(&item->object.oid, &type, &size);
        if (!buffer)
                return error("Object %s not a commit",
                             oid_to_hex(&item->object.oid));
        }
 -      ret = parse_commit_buffer(item, buffer, size);
 +
 +      ret = parse_commit_buffer(the_repository, item, buffer, size, 0);
        if (save_commit_buffer && !ret) {
 -              set_commit_buffer(item, buffer, size);
 +              set_commit_buffer(the_repository, item, buffer, size);
                return 0;
        }
        free(buffer);
        return ret;
  }
  
 +int parse_commit_gently(struct commit *item, int quiet_on_missing)
 +{
 +      return parse_commit_internal(item, quiet_on_missing, 1);
 +}
 +
  void parse_commit_or_die(struct commit *item)
  {
        if (parse_commit(item))
@@@ -656,7 -609,7 +656,7 @@@ struct commit *pop_commit(struct commit
  define_commit_slab(indegree_slab, int);
  
  /* record author-date for each commit object */
 -define_commit_slab(author_date_slab, unsigned long);
 +define_commit_slab(author_date_slab, timestamp_t);
  
  static void record_author_date(struct author_date_slab *author_date,
                               struct commit *commit)
@@@ -700,24 -653,6 +700,24 @@@ static int compare_commits_by_author_da
        return 0;
  }
  
 +int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused)
 +{
 +      const struct commit *a = a_, *b = b_;
 +
 +      /* newer commits first */
 +      if (a->generation < b->generation)
 +              return 1;
 +      else if (a->generation > b->generation)
 +              return -1;
 +
 +      /* use date as a heuristic when generations are equal */
 +      if (a->date < b->date)
 +              return 1;
 +      else if (a->date > b->date)
 +              return -1;
 +      return 0;
 +}
 +
  int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)
  {
        const struct commit *a = a_, *b = b_;
@@@ -865,17 -800,11 +865,17 @@@ static int queue_has_nonstale(struct pr
  }
  
  /* all input commits in one and twos[] must have been parsed! */
 -static struct commit_list *paint_down_to_common(struct commit *one, int n, struct commit **twos)
 +static struct commit_list *paint_down_to_common(struct commit *one, int n,
 +                                              struct commit **twos,
 +                                              int min_generation)
  {
 -      struct prio_queue queue = { compare_commits_by_commit_date };
 +      struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
        struct commit_list *result = NULL;
        int i;
 +      uint32_t last_gen = GENERATION_NUMBER_INFINITY;
 +
 +      if (!min_generation)
 +              queue.compare = compare_commits_by_commit_date;
  
        one->object.flags |= PARENT1;
        if (!n) {
                struct commit_list *parents;
                int flags;
  
 +              if (min_generation && commit->generation > last_gen)
 +                      BUG("bad generation skip %8x > %8x at %s",
 +                          commit->generation, last_gen,
 +                          oid_to_hex(&commit->object.oid));
 +              last_gen = commit->generation;
 +
 +              if (commit->generation < min_generation)
 +                      break;
 +
                flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
                if (flags == (PARENT1 | PARENT2)) {
                        if (!(commit->object.flags & RESULT)) {
@@@ -951,7 -871,7 +951,7 @@@ static struct commit_list *merge_bases_
                        return NULL;
        }
  
 -      list = paint_down_to_common(one, n, twos);
 +      list = paint_down_to_common(one, n, twos, 0);
  
        while (list) {
                struct commit *commit = pop_commit(&list);
@@@ -1009,7 -929,6 +1009,7 @@@ static int remove_redundant(struct comm
                parse_commit(array[i]);
        for (i = 0; i < cnt; i++) {
                struct commit_list *common;
 +              uint32_t min_generation = array[i]->generation;
  
                if (redundant[i])
                        continue;
                                continue;
                        filled_index[filled] = j;
                        work[filled++] = array[j];
 +
 +                      if (array[j]->generation < min_generation)
 +                              min_generation = array[j]->generation;
                }
 -              common = paint_down_to_common(array[i], filled, work);
 +              common = paint_down_to_common(array[i], filled, work,
 +                                            min_generation);
                if (array[i]->object.flags & PARENT2)
                        redundant[i] = 1;
                for (j = 0; j < filled; j++)
@@@ -1133,21 -1048,14 +1133,21 @@@ int in_merge_bases_many(struct commit *
  {
        struct commit_list *bases;
        int ret = 0, i;
 +      uint32_t min_generation = GENERATION_NUMBER_INFINITY;
  
        if (parse_commit(commit))
                return ret;
 -      for (i = 0; i < nr_reference; i++)
 +      for (i = 0; i < nr_reference; i++) {
                if (parse_commit(reference[i]))
                        return ret;
 +              if (reference[i]->generation < min_generation)
 +                      min_generation = reference[i]->generation;
 +      }
  
 -      bases = paint_down_to_common(commit, nr_reference, reference);
 +      if (commit->generation > min_generation)
 +              return ret;
 +
 +      bases = paint_down_to_common(commit, nr_reference, reference, commit->generation);
        if (commit->object.flags & PARENT2)
                ret = 1;
        clear_commit_marks(commit, all_flags);
        return result;
  }
  
 +define_commit_slab(merge_desc_slab, struct merge_remote_desc *);
 +static struct merge_desc_slab merge_desc_slab = COMMIT_SLAB_INIT(1, merge_desc_slab);
 +
 +struct merge_remote_desc *merge_remote_util(struct commit *commit)
 +{
 +      return *merge_desc_slab_at(&merge_desc_slab, commit);
 +}
 +
  void set_merge_remote_desc(struct commit *commit,
                           const char *name, struct object *obj)
  {
        struct merge_remote_desc *desc;
        FLEX_ALLOC_STR(desc, name, name);
        desc->obj = obj;
 -      commit->util = desc;
 +      *merge_desc_slab_at(&merge_desc_slab, commit) = desc;
  }
  
  struct commit *get_merge_parent(const char *name)
        struct object_id oid;
        if (get_oid(name, &oid))
                return NULL;
 -      obj = parse_object(&oid);
 +      obj = parse_object(the_repository, &oid);
        commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
 -      if (commit && !commit->util)
 +      if (commit && !merge_remote_util(commit))
                set_merge_remote_desc(commit, name, obj);
        return commit;
  }
@@@ -1787,10 -1687,10 +1787,10 @@@ const char *find_commit_header(const ch
   * Returns the number of bytes from the tail to ignore, to be fed as
   * the second parameter to append_signoff().
   */
int ignore_non_trailer(const char *buf, size_t len)
size_t ignore_non_trailer(const char *buf, size_t len)
  {
-       int boc = 0;
-       int bol = 0;
+       size_t boc = 0;
+       size_t bol = 0;
        int in_old_conflicts_block = 0;
        size_t cutoff = wt_status_locate_end(buf, len);
  
diff --combined commit.h
index da0db36eba2bf16277dbb7aadcb3384e29191ecc,1f7b97cebd4c0fe5dfcf510ea53c97ea411856c9..43f93b973dffe3bf8e56aa175e66304c72b0e30a
+++ b/commit.h
  #include "pretty.h"
  
  #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF
 +#define GENERATION_NUMBER_INFINITY 0xFFFFFFFF
 +#define GENERATION_NUMBER_MAX 0x3FFFFFFF
 +#define GENERATION_NUMBER_ZERO 0
  
  struct commit_list {
        struct commit *item;
        struct commit_list *next;
  };
  
 +/*
 + * The size of this struct matters in full repo walk operations like
 + * 'git clone' or 'git gc'. Consider using commit-slab to attach data
 + * to a commit instead of adding new fields here.
 + */
  struct commit {
        struct object object;
 -      void *util;
        timestamp_t date;
        struct commit_list *parents;
  
@@@ -36,7 -29,6 +36,7 @@@
         */
        struct tree *maybe_tree;
        uint32_t graph_pos;
 +      uint32_t generation;
        unsigned int index;
  };
  
@@@ -63,11 -55,9 +63,11 @@@ enum decoration_type 
  void add_name_decoration(enum decoration_type type, const char *name, struct object *obj);
  const struct name_decoration *get_name_decoration(const struct object *obj);
  
 -struct commit *lookup_commit(const struct object_id *oid);
 -struct commit *lookup_commit_reference(const struct object_id *oid);
 -struct commit *lookup_commit_reference_gently(const struct object_id *oid,
 +struct commit *lookup_commit(struct repository *r, const struct object_id *oid);
 +struct commit *lookup_commit_reference(struct repository *r,
 +                                     const struct object_id *oid);
 +struct commit *lookup_commit_reference_gently(struct repository *r,
 +                                            const struct object_id *oid,
                                              int quiet);
  struct commit *lookup_commit_reference_by_name(const char *name);
  
@@@ -78,8 -68,7 +78,8 @@@
   */
  struct commit *lookup_commit_or_die(const struct object_id *oid, const char *ref_name);
  
 -int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
 +int parse_commit_buffer(struct repository *r, struct commit *item, const void *buffer, unsigned long size, int check_graph);
 +int parse_commit_internal(struct commit *item, int quiet_on_missing, int use_commit_graph);
  int parse_commit_gently(struct commit *item, int quiet_on_missing);
  static inline int parse_commit(struct commit *item)
  {
  }
  void parse_commit_or_die(struct commit *item);
  
 +struct buffer_slab;
 +struct buffer_slab *allocate_commit_buffer_slab(void);
 +void free_commit_buffer_slab(struct buffer_slab *bs);
 +
  /*
   * Associate an object buffer with the commit. The ownership of the
   * memory is handed over to the commit, and must be free()-able.
   */
 -void set_commit_buffer(struct commit *, void *buffer, unsigned long size);
 +void set_commit_buffer(struct repository *r, struct commit *, void *buffer, unsigned long size);
  
  /*
   * Get any cached object buffer associated with the commit. Returns NULL
   * if none. The resulting memory should not be freed.
   */
 -const void *get_cached_commit_buffer(const struct commit *, unsigned long *size);
 +const void *get_cached_commit_buffer(struct repository *, const struct commit *, unsigned long *size);
  
  /*
   * Get the commit's object contents, either from cache or by reading the object
@@@ -126,12 -111,6 +126,12 @@@ void free_commit_buffer(struct commit *
  struct tree *get_commit_tree(const struct commit *);
  struct object_id *get_commit_tree_oid(const struct commit *);
  
 +/*
 + * Release memory related to a commit, including the parent list and
 + * any cached object buffer.
 + */
 +void release_commit_memory(struct commit *c);
 +
  /*
   * Disassociate any cached object buffer from the commit, but do not free it.
   * The buffer (or NULL, if none) is returned.
@@@ -201,8 -180,8 +201,8 @@@ struct commit_graft 
  typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
  
  struct commit_graft *read_graft_line(struct strbuf *line);
 -int register_commit_graft(struct commit_graft *, int);
 -struct commit_graft *lookup_commit_graft(const struct object_id *oid);
 +int register_commit_graft(struct repository *r, struct commit_graft *, int);
 +struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
  
  extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2);
  extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos);
@@@ -216,15 -195,15 +216,15 @@@ extern struct commit_list *get_merge_ba
  
  struct oid_array;
  struct ref;
 -extern int register_shallow(const struct object_id *oid);
 +extern int register_shallow(struct repository *r, const struct object_id *oid);
  extern int unregister_shallow(const struct object_id *oid);
  extern int for_each_commit_graft(each_commit_graft_fn, void *);
 -extern int is_repository_shallow(void);
 +extern int is_repository_shallow(struct repository *r);
  extern struct commit_list *get_shallow_commits(struct object_array *heads,
                int depth, int shallow_flag, int not_shallow_flag);
  extern struct commit_list *get_shallow_commits_by_rev_list(
                int ac, const char **av, int shallow_flag, int not_shallow_flag);
 -extern void set_alternate_shallow_file(const char *path, int override);
 +extern void set_alternate_shallow_file(struct repository *r, const char *path, int override);
  extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol,
                                 const struct oid_array *extra);
  extern void setup_alternate_shallow(struct lock_file *shallow_lock,
@@@ -322,7 -301,7 +322,7 @@@ extern const char *find_commit_header(c
                                      size_t *out_len);
  
  /* Find the end of the log message, the right place for a new trailer. */
- extern int ignore_non_trailer(const char *buf, size_t len);
+ extern size_t ignore_non_trailer(const char *buf, size_t len);
  
  typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
                                 void *cb_data);
@@@ -333,7 -312,7 +333,7 @@@ struct merge_remote_desc 
        struct object *obj; /* the named object, could be a tag */
        char name[FLEX_ARRAY];
  };
 -#define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util))
 +extern struct merge_remote_desc *merge_remote_util(struct commit *);
  extern void set_merge_remote_desc(struct commit *commit,
                                  const char *name, struct object *obj);
  
@@@ -358,7 -337,6 +358,7 @@@ extern int remove_signature(struct strb
  extern int check_commit_signature(const struct commit *commit, struct signature_check *sigc);
  
  int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused);
 +int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused);
  
  LAST_ARG_MUST_BE_NULL
  extern int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...);
diff --combined pretty.c
index 98cf5228f9e30fe5622bdfb4c54d996cb3153808,c0af210e9defc3aec945ae4d96b4cd9c329221de..8ca29e92815608437ccb1fb9909e78d5b4989529
+++ b/pretty.c
@@@ -630,7 -630,7 +630,7 @@@ const char *logmsg_reencode(const struc
                 * the cached copy from get_commit_buffer, we need to duplicate it
                 * to avoid munging the cached copy.
                 */
 -              if (msg == get_cached_commit_buffer(commit, NULL))
 +              if (msg == get_cached_commit_buffer(the_repository, commit, NULL))
                        out = xstrdup(msg);
                else
                        out = (char *)msg;
@@@ -1146,7 -1146,7 +1146,7 @@@ static size_t format_commit_one(struct 
  
        /* these depend on the commit */
        if (!commit->object.parsed)
 -              parse_object(&commit->object.oid);
 +              parse_object(the_repository, &commit->object.oid);
  
        switch (placeholder[0]) {
        case 'H':               /* commit hash */
  
        if (skip_prefix(placeholder, "(trailers", &arg)) {
                struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
+               opts.no_divider = 1;
                if (*arg == ':') {
                        arg++;
                        for (;;) {
@@@ -1538,7 -1541,7 +1541,7 @@@ void format_commit_message(const struc
        }
  
        if (output_enc) {
 -              int outsz;
 +              size_t outsz;
                char *out = reencode_string_len(sb->buf, sb->len,
                                                output_enc, utf8, &outsz);
                if (out)
@@@ -1575,7 -1578,7 +1578,7 @@@ static void pp_header(struct pretty_pri
                }
  
                if (starts_with(line, "parent ")) {
 -                      if (linelen != 48)
 +                      if (linelen != the_hash_algo->hexsz + 8)
                                die("bad parent line in commit");
                        continue;
                }
                if (!parents_shown) {
                        unsigned num = commit_list_count(commit->parents);
                        /* with enough slop */
 -                      strbuf_grow(sb, num * 50 + 20);
 +                      strbuf_grow(sb, num * (GIT_MAX_HEXSZ + 10) + 20);
                        add_merge_info(pp, sb, commit);
                        parents_shown = 1;
                }
diff --combined ref-filter.c
index 0bccfceff2ae31200019838d9f2b67e13e32ef6f,92557d4c69b3f7a4092dfa93495525148f8adc93..3e8ee04d09b2271ea9b0a9a07de6ec932ad09387
@@@ -3,8 -3,6 +3,8 @@@
  #include "parse-options.h"
  #include "refs.h"
  #include "wildmatch.h"
 +#include "object-store.h"
 +#include "repository.h"
  #include "commit.h"
  #include "remote.h"
  #include "color.h"
@@@ -18,7 -16,6 +18,7 @@@
  #include "trailer.h"
  #include "wt-status.h"
  #include "commit-slab.h"
 +#include "commit-graph.h"
  
  static struct ref_msg {
        const char *gone;
@@@ -43,7 -40,6 +43,7 @@@ void setup_ref_filter_porcelain_msg(voi
  
  typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
  typedef enum { COMPARE_EQUAL, COMPARE_UNEQUAL, COMPARE_NONE } cmp_status;
 +typedef enum { SOURCE_NONE = 0, SOURCE_OBJ, SOURCE_OTHER } info_source;
  
  struct align {
        align_type position;
@@@ -63,17 -59,6 +63,17 @@@ struct refname_atom 
        int lstrip, rstrip;
  };
  
 +static struct expand_data {
 +      struct object_id oid;
 +      enum object_type type;
 +      unsigned long size;
 +      off_t disk_size;
 +      struct object_id delta_base_oid;
 +      void *content;
 +
 +      struct object_info info;
 +} oi, oi_deref;
 +
  /*
   * An atom is a valid field atom listed below, possibly prefixed with
   * a "*" to denote deref_tag().
@@@ -87,7 -72,6 +87,7 @@@
  static struct used_atom {
        const char *name;
        cmp_type type;
 +      info_source source;
        union {
                char color[COLOR_MAXLEN];
                struct align align;
@@@ -215,30 -199,6 +215,30 @@@ static int remote_ref_atom_parser(cons
        return 0;
  }
  
 +static int objecttype_atom_parser(const struct ref_format *format, struct used_atom *atom,
 +                                const char *arg, struct strbuf *err)
 +{
 +      if (arg)
 +              return strbuf_addf_ret(err, -1, _("%%(objecttype) does not take arguments"));
 +      if (*atom->name == '*')
 +              oi_deref.info.typep = &oi_deref.type;
 +      else
 +              oi.info.typep = &oi.type;
 +      return 0;
 +}
 +
 +static int objectsize_atom_parser(const struct ref_format *format, struct used_atom *atom,
 +                                const char *arg, struct strbuf *err)
 +{
 +      if (arg)
 +              return strbuf_addf_ret(err, -1, _("%%(objectsize) does not take arguments"));
 +      if (*atom->name == '*')
 +              oi_deref.info.sizep = &oi_deref.size;
 +      else
 +              oi.info.sizep = &oi.size;
 +      return 0;
 +}
 +
  static int body_atom_parser(const struct ref_format *format, struct used_atom *atom,
                            const char *arg, struct strbuf *err)
  {
@@@ -263,6 -223,8 +263,8 @@@ static int trailers_atom_parser(const s
        struct string_list params = STRING_LIST_INIT_DUP;
        int i;
  
+       atom->u.contents.trailer_opts.no_divider = 1;
        if (arg) {
                string_list_split(&params, arg, ',', -1);
                for (i = 0; i < params.nr; i++) {
@@@ -419,50 -381,49 +421,50 @@@ static int head_atom_parser(const struc
  
  static struct {
        const char *name;
 +      info_source source;
        cmp_type cmp_type;
        int (*parser)(const struct ref_format *format, struct used_atom *atom,
                      const char *arg, struct strbuf *err);
  } valid_atom[] = {
 -      { "refname" , FIELD_STR, refname_atom_parser },
 -      { "objecttype" },
 -      { "objectsize", FIELD_ULONG },
 -      { "objectname", FIELD_STR, objectname_atom_parser },
 -      { "tree" },
 -      { "parent" },
 -      { "numparent", FIELD_ULONG },
 -      { "object" },
 -      { "type" },
 -      { "tag" },
 -      { "author" },
 -      { "authorname" },
 -      { "authoremail" },
 -      { "authordate", FIELD_TIME },
 -      { "committer" },
 -      { "committername" },
 -      { "committeremail" },
 -      { "committerdate", FIELD_TIME },
 -      { "tagger" },
 -      { "taggername" },
 -      { "taggeremail" },
 -      { "taggerdate", FIELD_TIME },
 -      { "creator" },
 -      { "creatordate", FIELD_TIME },
 -      { "subject", FIELD_STR, subject_atom_parser },
 -      { "body", FIELD_STR, body_atom_parser },
 -      { "trailers", FIELD_STR, trailers_atom_parser },
 -      { "contents", FIELD_STR, contents_atom_parser },
 -      { "upstream", FIELD_STR, remote_ref_atom_parser },
 -      { "push", FIELD_STR, remote_ref_atom_parser },
 -      { "symref", FIELD_STR, refname_atom_parser },
 -      { "flag" },
 -      { "HEAD", FIELD_STR, head_atom_parser },
 -      { "color", FIELD_STR, color_atom_parser },
 -      { "align", FIELD_STR, align_atom_parser },
 -      { "end" },
 -      { "if", FIELD_STR, if_atom_parser },
 -      { "then" },
 -      { "else" },
 +      { "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
 +      { "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser },
 +      { "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser },
 +      { "objectname", SOURCE_OTHER, FIELD_STR, objectname_atom_parser },
 +      { "tree", SOURCE_OBJ },
 +      { "parent", SOURCE_OBJ },
 +      { "numparent", SOURCE_OBJ, FIELD_ULONG },
 +      { "object", SOURCE_OBJ },
 +      { "type", SOURCE_OBJ },
 +      { "tag", SOURCE_OBJ },
 +      { "author", SOURCE_OBJ },
 +      { "authorname", SOURCE_OBJ },
 +      { "authoremail", SOURCE_OBJ },
 +      { "authordate", SOURCE_OBJ, FIELD_TIME },
 +      { "committer", SOURCE_OBJ },
 +      { "committername", SOURCE_OBJ },
 +      { "committeremail", SOURCE_OBJ },
 +      { "committerdate", SOURCE_OBJ, FIELD_TIME },
 +      { "tagger", SOURCE_OBJ },
 +      { "taggername", SOURCE_OBJ },
 +      { "taggeremail", SOURCE_OBJ },
 +      { "taggerdate", SOURCE_OBJ, FIELD_TIME },
 +      { "creator", SOURCE_OBJ },
 +      { "creatordate", SOURCE_OBJ, FIELD_TIME },
 +      { "subject", SOURCE_OBJ, FIELD_STR, subject_atom_parser },
 +      { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
 +      { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
 +      { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser },
 +      { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
 +      { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
 +      { "symref", SOURCE_NONE, FIELD_STR, refname_atom_parser },
 +      { "flag", SOURCE_NONE },
 +      { "HEAD", SOURCE_NONE, FIELD_STR, head_atom_parser },
 +      { "color", SOURCE_NONE, FIELD_STR, color_atom_parser },
 +      { "align", SOURCE_NONE, FIELD_STR, align_atom_parser },
 +      { "end", SOURCE_NONE },
 +      { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
 +      { "then", SOURCE_NONE },
 +      { "else", SOURCE_NONE },
  };
  
  #define REF_FORMATTING_STATE_INIT  { 0, NULL }
@@@ -538,13 -499,6 +540,13 @@@ static int parse_ref_filter_atom(const 
        REALLOC_ARRAY(used_atom, used_atom_cnt);
        used_atom[at].name = xmemdupz(atom, ep - atom);
        used_atom[at].type = valid_atom[i].cmp_type;
 +      used_atom[at].source = valid_atom[i].source;
 +      if (used_atom[at].source == SOURCE_OBJ) {
 +              if (*atom == '*')
 +                      oi_deref.info.contentp = &oi_deref.content;
 +              else
 +                      oi.info.contentp = &oi.content;
 +      }
        if (arg) {
                arg = used_atom[at].name + (arg - atom) + 1;
                if (!*arg) {
@@@ -840,6 -794,24 +842,6 @@@ int verify_ref_format(struct ref_forma
        return 0;
  }
  
 -/*
 - * Given an object name, read the object data and size, and return a
 - * "struct object".  If the object data we are returning is also borrowed
 - * by the "struct object" representation, set *eaten as well---it is a
 - * signal from parse_object_buffer to us not to free the buffer.
 - */
 -static void *get_obj(const struct object_id *oid, struct object **obj, unsigned long *sz, int *eaten)
 -{
 -      enum object_type type;
 -      void *buf = read_object_file(oid, &type, sz);
 -
 -      if (buf)
 -              *obj = parse_object_buffer(oid, type, *sz, buf, eaten);
 -      else
 -              *obj = NULL;
 -      return buf;
 -}
 -
  static int grab_objectname(const char *name, const struct object_id *oid,
                           struct atom_value *v, struct used_atom *atom)
  {
  }
  
  /* See grab_values */
 -static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 +static void grab_common_values(struct atom_value *val, int deref, struct expand_data *oi)
  {
        int i;
  
                if (deref)
                        name++;
                if (!strcmp(name, "objecttype"))
 -                      v->s = type_name(obj->type);
 +                      v->s = type_name(oi->type);
                else if (!strcmp(name, "objectsize")) {
 -                      v->value = sz;
 -                      v->s = xstrfmt("%lu", sz);
 +                      v->value = oi->size;
 +                      v->s = xstrfmt("%lu", oi->size);
                }
                else if (deref)
 -                      grab_objectname(name, &obj->oid, v, &used_atom[i]);
 +                      grab_objectname(name, &oi->oid, v, &used_atom[i]);
        }
  }
  
@@@ -1237,6 -1209,7 +1239,6 @@@ static void fill_missing_values(struct 
   */
  static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
  {
 -      grab_common_values(val, deref, obj, buf, sz);
        switch (obj->type) {
        case OBJ_TAG:
                grab_tag_values(val, deref, obj, buf, sz);
@@@ -1460,36 -1433,24 +1462,36 @@@ static const char *get_refname(struct u
        return show_ref(&atom->u.refname, ref->refname);
  }
  
 -static int get_object(struct ref_array_item *ref, const struct object_id *oid,
 -                     int deref, struct object **obj, struct strbuf *err)
 +static int get_object(struct ref_array_item *ref, int deref, struct object **obj,
 +                    struct expand_data *oi, struct strbuf *err)
  {
 -      int eaten;
 -      int ret = 0;
 -      unsigned long size;
 -      void *buf = get_obj(oid, obj, &size, &eaten);
 -      if (!buf)
 -              ret = strbuf_addf_ret(err, -1, _("missing object %s for %s"),
 -                                    oid_to_hex(oid), ref->refname);
 -      else if (!*obj)
 -              ret = strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
 -                                    oid_to_hex(oid), ref->refname);
 -      else
 -              grab_values(ref->value, deref, *obj, buf, size);
 +      /* parse_object_buffer() will set eaten to 0 if free() will be needed */
 +      int eaten = 1;
 +      if (oi->info.contentp) {
 +              /* We need to know that to use parse_object_buffer properly */
 +              oi->info.sizep = &oi->size;
 +              oi->info.typep = &oi->type;
 +      }
 +      if (oid_object_info_extended(the_repository, &oi->oid, &oi->info,
 +                                   OBJECT_INFO_LOOKUP_REPLACE))
 +              return strbuf_addf_ret(err, -1, _("missing object %s for %s"),
 +                                     oid_to_hex(&oi->oid), ref->refname);
 +
 +      if (oi->info.contentp) {
 +              *obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);
 +              if (!obj) {
 +                      if (!eaten)
 +                              free(oi->content);
 +                      return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
 +                                             oid_to_hex(&oi->oid), ref->refname);
 +              }
 +              grab_values(ref->value, deref, *obj, oi->content, oi->size);
 +      }
 +
 +      grab_common_values(ref->value, deref, oi);
        if (!eaten)
 -              free(buf);
 -      return ret;
 +              free(oi->content);
 +      return 0;
  }
  
  /*
@@@ -1499,7 -1460,7 +1501,7 @@@ static int populate_value(struct ref_ar
  {
        struct object *obj;
        int i;
 -      const struct object_id *tagged;
 +      struct object_info empty = OBJECT_INFO_INIT;
  
        ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
  
                        refname = get_symref(atom, ref);
                else if (starts_with(name, "upstream")) {
                        const char *branch_name;
 +                      v->s = "";
                        /* only local branches may have an upstream */
                        if (!skip_prefix(ref->refname, "refs/heads/",
                                         &branch_name))
                        continue;
                } else if (atom->u.remote_ref.push) {
                        const char *branch_name;
 +                      v->s = "";
                        if (!skip_prefix(ref->refname, "refs/heads/",
                                         &branch_name))
                                continue;
                        continue;
                } else if (starts_with(name, "align")) {
                        v->handler = align_atom_handler;
 +                      v->s = "";
                        continue;
                } else if (!strcmp(name, "end")) {
                        v->handler = end_atom_handler;
 +                      v->s = "";
                        continue;
                } else if (starts_with(name, "if")) {
                        const char *s;
 -
 +                      v->s = "";
                        if (skip_prefix(name, "if:", &s))
                                v->s = xstrdup(s);
                        v->handler = if_atom_handler;
                        continue;
                } else if (!strcmp(name, "then")) {
                        v->handler = then_atom_handler;
 +                      v->s = "";
                        continue;
                } else if (!strcmp(name, "else")) {
                        v->handler = else_atom_handler;
 +                      v->s = "";
                        continue;
                } else
                        continue;
  
        for (i = 0; i < used_atom_cnt; i++) {
                struct atom_value *v = &ref->value[i];
 -              if (v->s == NULL)
 -                      break;
 +              if (v->s == NULL && used_atom[i].source == SOURCE_NONE)
 +                      return strbuf_addf_ret(err, -1, _("missing object %s for %s"),
 +                                             oid_to_hex(&ref->objectname), ref->refname);
        }
 -      if (used_atom_cnt <= i)
 +
 +      if (need_tagged)
 +              oi.info.contentp = &oi.content;
 +      if (!memcmp(&oi.info, &empty, sizeof(empty)) &&
 +          !memcmp(&oi_deref.info, &empty, sizeof(empty)))
                return 0;
  
 -      if (get_object(ref, &ref->objectname, 0, &obj, err))
 +
 +      oi.oid = ref->objectname;
 +      if (get_object(ref, 0, &obj, &oi, err))
                return -1;
  
        /*
         * If it is a tag object, see if we use a value that derefs
         * the object, and if we do grab the object it refers to.
         */
 -      tagged = &((struct tag *)obj)->tagged->oid;
 +      oi_deref.oid = ((struct tag *)obj)->tagged->oid;
  
        /*
         * NEEDSWORK: This derefs tag only once, which
         * is not consistent with what deref_tag() does
         * which peels the onion to the core.
         */
 -      return get_object(ref, tagged, 1, &obj, err);
 +      return get_object(ref, 1, &obj, &oi_deref, err);
  }
  
  /*
@@@ -1716,13 -1664,12 +1718,13 @@@ static int in_commit_list(const struct 
  }
  
  /*
 - * Test whether the candidate or one of its parents is contained in the list.
 + * Test whether the candidate is contained in the list.
   * Do not recurse to find out, though, but return -1 if inconclusive.
   */
  static enum contains_result contains_test(struct commit *candidate,
                                          const struct commit_list *want,
 -                                        struct contains_cache *cache)
 +                                        struct contains_cache *cache,
 +                                        uint32_t cutoff)
  {
        enum contains_result *cached = contains_cache_at(cache, candidate);
  
  
        /* Otherwise, we don't know; prepare to recurse */
        parse_commit_or_die(candidate);
 +
 +      if (candidate->generation < cutoff)
 +              return CONTAINS_NO;
 +
        return CONTAINS_UNKNOWN;
  }
  
@@@ -1757,18 -1700,8 +1759,18 @@@ static enum contains_result contains_ta
                                              struct contains_cache *cache)
  {
        struct contains_stack contains_stack = { 0, 0, NULL };
 -      enum contains_result result = contains_test(candidate, want, cache);
 +      enum contains_result result;
 +      uint32_t cutoff = GENERATION_NUMBER_INFINITY;
 +      const struct commit_list *p;
 +
 +      for (p = want; p; p = p->next) {
 +              struct commit *c = p->item;
 +              load_commit_graph_info(the_repository, c);
 +              if (c->generation < cutoff)
 +                      cutoff = c->generation;
 +      }
  
 +      result = contains_test(candidate, want, cache, cutoff);
        if (result != CONTAINS_UNKNOWN)
                return result;
  
                 * If we just popped the stack, parents->item has been marked,
                 * therefore contains_test will return a meaningful yes/no.
                 */
 -              else switch (contains_test(parents->item, want, cache)) {
 +              else switch (contains_test(parents->item, want, cache, cutoff)) {
                case CONTAINS_YES:
                        *contains_cache_at(cache, commit) = CONTAINS_YES;
                        contains_stack.nr--;
                }
        }
        free(contains_stack.contains_stack);
 -      return contains_test(candidate, want, cache);
 +      return contains_test(candidate, want, cache, cutoff);
  }
  
  static int commit_contains(struct ref_filter *filter, struct commit *commit,
@@@ -1866,7 -1799,7 +1868,7 @@@ static int match_name_as_path(const str
                     refname[plen] == '/' ||
                     p[plen-1] == '/'))
                        return 1;
 -              if (!wildmatch(p, refname, WM_PATHNAME))
 +              if (!wildmatch(p, refname, flags))
                        return 1;
        }
        return 0;
@@@ -1921,15 -1854,6 +1923,15 @@@ static int for_each_fullref_in_pattern(
                return for_each_fullref_in("", cb, cb_data, broken);
        }
  
 +      if (filter->ignore_case) {
 +              /*
 +               * we can't handle case-insensitive comparisons,
 +               * so just return everything and let the caller
 +               * sort it out.
 +               */
 +              return for_each_fullref_in("", cb, cb_data, broken);
 +      }
 +
        if (!filter->name_patterns[0]) {
                /* no patterns; we have to look at everything */
                return for_each_fullref_in("", cb, cb_data, broken);
@@@ -1975,7 -1899,7 +1977,7 @@@ static const struct object_id *match_po
  
        if (oid_array_lookup(points_at, oid) >= 0)
                return oid;
 -      obj = parse_object(oid);
 +      obj = parse_object(the_repository, oid);
        if (!obj)
                die(_("malformed object at '%s'"), refname);
        if (obj->type == OBJ_TAG)
@@@ -2085,8 -2009,7 +2087,8 @@@ static int ref_filter_handler(const cha
         * non-commits early. The actual filtering is done later.
         */
        if (filter->merge_commit || filter->with_commit || filter->no_commit || filter->verbose) {
 -              commit = lookup_commit_reference_gently(oid, 1);
 +              commit = lookup_commit_reference_gently(the_repository, oid,
 +                                                      1);
                if (!commit)
                        return 0;
                /* We perform the filtering for the '--contains' option... */
@@@ -2443,8 -2366,7 +2445,8 @@@ int parse_opt_merge_filter(const struc
        if (get_oid(arg, &oid))
                die(_("malformed object name %s"), arg);
  
 -      rf->merge_commit = lookup_commit_reference_gently(&oid, 0);
 +      rf->merge_commit = lookup_commit_reference_gently(the_repository,
 +                                                        &oid, 0);
        if (!rf->merge_commit)
                return opterror(opt, "must point to a commit", 0);
  
diff --combined sequencer.c
index dc2c58d464c14be033f4bcba2ab4332886ead327,f24d6c35978e8472faea5ef9af6612b340f20747..b9407f3375044428e9b500274a1f61bd8572f524
@@@ -2,7 -2,6 +2,7 @@@
  #include "config.h"
  #include "lockfile.h"
  #include "dir.h"
 +#include "object-store.h"
  #include "object.h"
  #include "commit.h"
  #include "sequencer.h"
@@@ -28,7 -27,6 +28,7 @@@
  #include "worktree.h"
  #include "oidmap.h"
  #include "oidset.h"
 +#include "commit-slab.h"
  #include "alias.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@@ -63,12 -61,12 +63,12 @@@ static GIT_PATH_FUNC(rebase_path_done, 
   * The file to keep track of how many commands were already processed (e.g.
   * for the prompt).
   */
 -static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum");
 +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum")
  /*
   * The file to keep track of how many commands are to be processed in total
   * (e.g. for the prompt).
   */
 -static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end");
 +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end")
  /*
   * The commit message that is planned to be used for any changes that
   * need to be committed following a user interaction.
@@@ -177,7 -175,6 +177,7 @@@ static int git_sequencer_config(const c
                        warning(_("invalid commit message cleanup mode '%s'"),
                                  s);
  
 +              free((char *)s);
                return status;
        }
  
@@@ -225,13 -222,16 +225,16 @@@ static const char *get_todo_path(const 
   * Returns 3 when sob exists within conforming footer as last entry
   */
  static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
-       int ignore_footer)
+       size_t ignore_footer)
  {
+       struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
        struct trailer_info info;
-       int i;
+       size_t i;
        int found_sob = 0, found_sob_last = 0;
  
-       trailer_info_get(&info, sb->buf);
+       opts.no_divider = 1;
+       trailer_info_get(&info, sb->buf, &opts);
  
        if (info.trailer_start == info.trailer_end)
                return 0;
@@@ -307,7 -307,7 +310,7 @@@ static const char *action_name(const st
        case REPLAY_INTERACTIVE_REBASE:
                return N_("rebase -i");
        }
 -      die(_("Unknown action: %d"), opts->action);
 +      die(_("unknown action: %d"), opts->action);
  }
  
  struct commit_message {
@@@ -358,7 -358,7 +361,7 @@@ static void print_advice(int show_hint
                 * (typically rebase --interactive) wants to take care
                 * of the commit itself so remove CHERRY_PICK_HEAD
                 */
 -              unlink(git_path_cherry_pick_head());
 +              unlink(git_path_cherry_pick_head(the_repository));
                return;
        }
  
@@@ -433,7 -433,7 +436,7 @@@ static int read_oneliner(struct strbuf 
  
  static struct tree *empty_tree(void)
  {
 -      return lookup_tree(the_hash_algo->empty_tree);
 +      return lookup_tree(the_repository, the_repository->hash_algo->empty_tree);
  }
  
  static int error_dirty_index(struct replay_opts *opts)
@@@ -594,7 -594,7 +597,7 @@@ static int is_index_unchanged(void
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
                return error(_("could not resolve HEAD commit"));
  
 -      head_commit = lookup_commit(&head_oid);
 +      head_commit = lookup_commit(the_repository, &head_oid);
  
        /*
         * If head_commit is NULL, check_commit, called from
@@@ -639,7 -639,7 +642,7 @@@ missing_author
                else if (*message != '\'')
                        strbuf_addch(&buf, *(message++));
                else
 -                      strbuf_addf(&buf, "'\\\\%c'", *(message++));
 +                      strbuf_addf(&buf, "'\\%c'", *(message++));
        strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='");
        while (*message && *message != '\n' && *message != '\r')
                if (skip_prefix(message, "> ", &message))
                else if (*message != '\'')
                        strbuf_addch(&buf, *(message++));
                else
 -                      strbuf_addf(&buf, "'\\\\%c'", *(message++));
 +                      strbuf_addf(&buf, "'\\%c'", *(message++));
        strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@");
        while (*message && *message != '\n' && *message != '\r')
                if (*message != '\'')
                        strbuf_addch(&buf, *(message++));
                else
 -                      strbuf_addf(&buf, "'\\\\%c'", *(message++));
 +                      strbuf_addf(&buf, "'\\%c'", *(message++));
 +      strbuf_addch(&buf, '\'');
        res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
        strbuf_release(&buf);
        return res;
  }
  
 +
 +/*
 + * write_author_script() used to fail to terminate the last line with a "'" and
 + * also escaped "'" incorrectly as "'\\\\''" rather than "'\\''". We check for
 + * the terminating "'" on the last line to see how "'" has been escaped in case
 + * git was upgraded while rebase was stopped.
 + */
 +static int quoting_is_broken(const char *s, size_t n)
 +{
 +      /* Skip any empty lines in case the file was hand edited */
 +      while (n > 0 && s[--n] == '\n')
 +              ; /* empty */
 +      if (n > 0 && s[n] != '\'')
 +              return 1;
 +
 +      return 0;
 +}
 +
  /*
   * Read a list of environment variable assignments (such as the author-script
   * file) into an environment block. Returns -1 on error, 0 otherwise.
  static int read_env_script(struct argv_array *env)
  {
        struct strbuf script = STRBUF_INIT;
 -      int i, count = 0;
 -      char *p, *p2;
 +      int i, count = 0, sq_bug;
 +      const char *p2;
 +      char *p;
  
        if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
                return -1;
 -
 +      /* write_author_script() used to quote incorrectly */
 +      sq_bug = quoting_is_broken(script.buf, script.len);
        for (p = script.buf; *p; p++)
 -              if (skip_prefix(p, "'\\\\''", (const char **)&p2))
 +              if (sq_bug && skip_prefix(p, "'\\\\''", &p2))
 +                      strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
 +              else if (skip_prefix(p, "'\\''", &p2))
                        strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
                else if (*p == '\'')
                        strbuf_splice(&script, p-- - script.buf, 1, "", 0);
@@@ -731,51 -708,43 +734,51 @@@ static const char *read_author_ident(st
        const char *keys[] = {
                "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
        };
 -      char *in, *out, *eol;
 -      int i = 0, len;
 +      struct strbuf out = STRBUF_INIT;
 +      char *in, *eol;
 +      const char *val[3];
 +      int i = 0;
  
        if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
                return NULL;
  
        /* dequote values and construct ident line in-place */
 -      for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
 +      for (in = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
                if (!skip_prefix(in, keys[i], (const char **)&in)) {
 -                      warning("could not parse '%s' (looking for '%s'",
 +                      warning(_("could not parse '%s' (looking for '%s')"),
                                rebase_path_author_script(), keys[i]);
                        return NULL;
                }
  
                eol = strchrnul(in, '\n');
                *eol = '\0';
 -              sq_dequote(in);
 -              len = strlen(in);
 -
 -              if (i > 0) /* separate values by spaces */
 -                      *(out++) = ' ';
 -              if (i == 1) /* email needs to be surrounded by <...> */
 -                      *(out++) = '<';
 -              memmove(out, in, len);
 -              out += len;
 -              if (i == 1) /* email needs to be surrounded by <...> */
 -                      *(out++) = '>';
 +              if (!sq_dequote(in)) {
 +                      warning(_("bad quoting on %s value in '%s'"),
 +                              keys[i], rebase_path_author_script());
 +                      return NULL;
 +              }
 +              val[i] = in;
                in = eol + 1;
        }
  
        if (i < 3) {
 -              warning("could not parse '%s' (looking for '%s')",
 +              warning(_("could not parse '%s' (looking for '%s')"),
                        rebase_path_author_script(), keys[i]);
                return NULL;
        }
  
 -      buf->len = out - buf->buf;
 +      /* validate date since fmt_ident() will die() on bad value */
 +      if (parse_date(val[2], &out)){
 +              warning(_("invalid date format '%s' in '%s'"),
 +                      val[2], rebase_path_author_script());
 +              strbuf_release(&out);
 +              return NULL;
 +      }
 +
 +      strbuf_reset(&out);
 +      strbuf_addstr(&out, fmt_ident(val[0], val[1], val[2], 0));
 +      strbuf_swap(buf, &out);
 +      strbuf_release(&out);
        return buf->buf;
  }
  
@@@ -820,18 -789,11 +823,18 @@@ static int run_git_commit(const char *d
  
        if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
                struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
 -              const char *author = is_rebase_i(opts) ?
 -                      read_author_ident(&script) : NULL;
 +              const char *author = NULL;
                struct object_id root_commit, *cache_tree_oid;
                int res = 0;
  
 +              if (is_rebase_i(opts)) {
 +                      author = read_author_ident(&script);
 +                      if (!author) {
 +                              strbuf_release(&script);
 +                              return -1;
 +                      }
 +              }
 +
                if (!defmsg)
                        BUG("root commit without message");
  
@@@ -1139,7 -1101,7 +1142,7 @@@ void print_commit_summary(const char *p
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
  
 -      commit = lookup_commit(oid);
 +      commit = lookup_commit(the_repository, oid);
        if (!commit)
                die(_("couldn't look up newly created commit"));
        if (parse_commit(commit))
@@@ -1214,7 -1176,7 +1217,7 @@@ static int parse_head(struct commit **h
        if (get_oid("HEAD", &oid)) {
                current_head = NULL;
        } else {
 -              current_head = lookup_commit_reference(&oid);
 +              current_head = lookup_commit_reference(the_repository, &oid);
                if (!current_head)
                        return error(_("could not parse HEAD"));
                if (oidcmp(&oid, &current_head->object.oid)) {
@@@ -1282,7 -1244,7 +1285,7 @@@ static int try_to_commit(struct strbuf 
                commit_list_insert(current_head, &parents);
        }
  
 -      if (write_cache_as_tree(&tree, 0, NULL)) {
 +      if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) {
                res = error(_("git write-tree failed to write a tree"));
                goto out;
        }
@@@ -1363,8 -1325,8 +1366,8 @@@ static int do_commit(const char *msg_fi
                                    &oid);
                strbuf_release(&sb);
                if (!res) {
 -                      unlink(git_path_cherry_pick_head());
 -                      unlink(git_path_merge_msg());
 +                      unlink(git_path_cherry_pick_head(the_repository));
 +                      unlink(git_path_merge_msg(the_repository));
                        if (!is_rebase_i(opts))
                                print_commit_summary(NULL, &oid,
                                                SUMMARY_SHOW_AUTHOR_DATE);
@@@ -1483,7 -1445,7 +1486,7 @@@ static const char *command_to_string(co
  {
        if (command < TODO_COMMENT)
                return todo_command_info[command].str;
 -      die("Unknown command: %d", command);
 +      die(_("unknown command: %d"), command);
  }
  
  static char command_to_char(const enum todo_command command)
@@@ -1549,7 -1511,7 +1552,7 @@@ static int update_squash_messages(enum 
  
                if (get_oid("HEAD", &head))
                        return error(_("need a HEAD to fixup"));
 -              if (!(head_commit = lookup_commit_reference(&head)))
 +              if (!(head_commit = lookup_commit_reference(the_repository, &head)))
                        return error(_("could not read HEAD"));
                if (!(head_message = get_commit_buffer(head_commit, NULL)))
                        return error(_("could not read HEAD's commit message"));
                unlink(rebase_path_fixup_msg());
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addf(&buf, _("This is the commit message #%d:"),
 -                          ++opts->current_fixup_count);
 +                          ++opts->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
                strbuf_addstr(&buf, body);
        } else if (command == TODO_FIXUP) {
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
 -                          ++opts->current_fixup_count);
 +                          ++opts->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
                strbuf_add_commented_lines(&buf, body, strlen(body));
        } else
@@@ -1652,7 -1614,7 +1655,7 @@@ static int do_pick_commit(enum todo_com
                struct replay_opts *opts, int final_fixup)
  {
        unsigned int flags = opts->edit ? EDIT_MSG : 0;
 -      const char *msg_file = opts->edit ? NULL : git_path_merge_msg();
 +      const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository);
        struct object_id head;
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
                 * that represents the "current" state for merge-recursive
                 * to work on.
                 */
 -              if (write_cache_as_tree(&head, 0, NULL))
 +              if (write_index_as_tree(&head, &the_index, get_index_file(), 0, NULL))
                        return error(_("your index file is unmerged."));
        } else {
                unborn = get_oid("HEAD", &head);
                        flags |= CLEANUP_MSG;
                        msg_file = rebase_path_fixup_msg();
                } else {
 -                      const char *dest = git_path_squash_msg();
 +                      const char *dest = git_path_squash_msg(the_repository);
                        unlink(dest);
                        if (copy_file(dest, rebase_path_squash_msg(), 0666))
                                return error(_("could not rename '%s' to '%s'"),
                                             rebase_path_squash_msg(), dest);
 -                      unlink(git_path_merge_msg());
 +                      unlink(git_path_merge_msg(the_repository));
                        msg_file = dest;
                        flags |= EDIT_MSG;
                }
                res = do_recursive_merge(base, next, base_label, next_label,
                                         &head, &msgbuf, opts);
                if (res < 0)
 -                      return res;
 +                      goto leave;
 +
                res |= write_message(msgbuf.buf, msgbuf.len,
 -                                   git_path_merge_msg(), 0);
 +                                   git_path_merge_msg(the_repository), 0);
        } else {
                struct commit_list *common = NULL;
                struct commit_list *remotes = NULL;
  
                res = write_message(msgbuf.buf, msgbuf.len,
 -                                  git_path_merge_msg(), 0);
 +                                  git_path_merge_msg(the_repository), 0);
  
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
@@@ -1902,6 -1863,8 +1905,6 @@@ static int prepare_revs(struct replay_o
        if (prepare_revision_walk(opts->revs))
                return error(_("revision walk setup failed"));
  
 -      if (!opts->revs->commits)
 -              return error(_("empty commit set passed"));
        return 0;
  }
  
@@@ -2045,7 -2008,7 +2048,7 @@@ static int parse_insn_line(struct todo_
        if (status < 0)
                return -1;
  
 -      item->commit = lookup_commit_reference(&commit_oid);
 +      item->commit = lookup_commit_reference(the_repository, &commit_oid);
        return !item->commit;
  }
  
@@@ -2243,7 -2206,6 +2246,7 @@@ static int populate_opts_cb(const char 
  static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
  {
        int i;
 +      char *strategy_opts_string;
  
        strbuf_reset(buf);
        if (!read_oneliner(buf, rebase_path_strategy(), 0))
        if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
                return;
  
 -      opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts);
 +      strategy_opts_string = buf->buf;
 +      if (*strategy_opts_string == ' ')
 +              strategy_opts_string++;
 +      opts->xopts_nr = split_cmdline(strategy_opts_string,
 +                                     (const char ***)&opts->xopts);
        for (i = 0; i < opts->xopts_nr; i++) {
                const char *arg = opts->xopts[i];
  
@@@ -2359,10 -2317,6 +2362,10 @@@ static int walk_revs_populate_todo(stru
                        short_commit_name(commit), subject_len, subject);
                unuse_commit_buffer(commit, commit_buffer);
        }
 +
 +      if (!todo_list->nr)
 +              return error(_("empty commit set passed"));
 +
        return 0;
  }
  
@@@ -2440,8 -2394,8 +2443,8 @@@ static int rollback_single_pick(void
  {
        struct object_id head_oid;
  
 -      if (!file_exists(git_path_cherry_pick_head()) &&
 -          !file_exists(git_path_revert_head()))
 +      if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
 +          !file_exists(git_path_revert_head(the_repository)))
                return error(_("no cherry-pick or revert in progress"));
        if (read_ref_full("HEAD", 0, &head_oid, NULL))
                return error(_("cannot resolve HEAD"));
@@@ -2639,39 -2593,23 +2642,39 @@@ static int error_with_patch(struct comm
        const char *subject, int subject_len,
        struct replay_opts *opts, int exit_code, int to_amend)
  {
 -      if (make_patch(commit, opts))
 -              return -1;
 +      if (commit) {
 +              if (make_patch(commit, opts))
 +                      return -1;
 +      } else if (copy_file(rebase_path_message(),
 +                           git_path_merge_msg(the_repository), 0666))
 +              return error(_("unable to copy '%s' to '%s'"),
 +                           git_path_merge_msg(the_repository), rebase_path_message());
  
        if (to_amend) {
                if (intend_to_amend())
                        return -1;
  
 -              fprintf(stderr, "You can amend the commit now, with\n"
 -                      "\n"
 -                      "  git commit --amend %s\n"
 -                      "\n"
 -                      "Once you are satisfied with your changes, run\n"
 -                      "\n"
 -                      "  git rebase --continue\n", gpg_sign_opt_quoted(opts));
 -      } else if (exit_code)
 -              fprintf(stderr, "Could not apply %s... %.*s\n",
 -                      short_commit_name(commit), subject_len, subject);
 +              fprintf(stderr,
 +                      _("You can amend the commit now, with\n"
 +                        "\n"
 +                        "  git commit --amend %s\n"
 +                        "\n"
 +                        "Once you are satisfied with your changes, run\n"
 +                        "\n"
 +                        "  git rebase --continue\n"),
 +                      gpg_sign_opt_quoted(opts));
 +      } else if (exit_code) {
 +              if (commit)
 +                      fprintf_ln(stderr, _("Could not apply %s... %.*s"),
 +                                 short_commit_name(commit), subject_len, subject);
 +              else
 +                      /*
 +                       * We don't have the hash of the parent so
 +                       * just print the line from the todo file.
 +                       */
 +                      fprintf_ln(stderr, _("Could not merge %.*s"),
 +                                 subject_len, subject);
 +      }
  
        return exit_code;
  }
@@@ -2682,11 -2620,10 +2685,11 @@@ static int error_failed_squash(struct c
        if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
                return error(_("could not copy '%s' to '%s'"),
                        rebase_path_squash_msg(), rebase_path_message());
 -      unlink(git_path_merge_msg());
 -      if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
 +      unlink(git_path_merge_msg(the_repository));
 +      if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666))
                return error(_("could not copy '%s' to '%s'"),
 -                           rebase_path_message(), git_path_merge_msg());
 +                           rebase_path_message(),
 +                           git_path_merge_msg(the_repository));
        return error_with_patch(commit, subject, subject_len, opts, 1, 0);
  }
  
@@@ -2699,8 -2636,6 +2702,8 @@@ static int do_exec(const char *command_
        fprintf(stderr, "Executing: %s\n", command_line);
        child_argv[0] = command_line;
        argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
 +      argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
 +                       absolute_path(get_git_work_tree()));
        status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
                                          child_env.argv);
  
@@@ -2784,7 -2719,7 +2787,7 @@@ static int do_label(const char *name, i
        struct object_id head_oid;
  
        if (len == 1 && *name == '#')
 -              return error("Illegal label name: '%.*s'", len, name);
 +              return error(_("illegal label name: '%.*s'"), len, name);
  
        strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
        strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
@@@ -2906,26 -2841,6 +2909,26 @@@ static int do_reset(const char *name, i
        return ret;
  }
  
 +static struct commit *lookup_label(const char *label, int len,
 +                                 struct strbuf *buf)
 +{
 +      struct commit *commit;
 +
 +      strbuf_reset(buf);
 +      strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
 +      commit = lookup_commit_reference_by_name(buf->buf);
 +      if (!commit) {
 +              /* fall back to non-rewritten ref or commit */
 +              strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
 +              commit = lookup_commit_reference_by_name(buf->buf);
 +      }
 +
 +      if (!commit)
 +              error(_("could not resolve '%s'"), buf->buf);
 +
 +      return commit;
 +}
 +
  static int do_merge(struct commit *commit, const char *arg, int arg_len,
                    int flags, struct replay_opts *opts)
  {
        struct strbuf ref_name = STRBUF_INIT;
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j, *reversed = NULL;
 +      struct commit_list *to_merge = NULL, **tail = &to_merge;
        struct merge_options o;
 -      int merge_arg_len, oneline_offset, can_fast_forward, ret;
 +      int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
        static struct lock_file lock;
        const char *p;
  
                goto leave_merge;
        }
  
 -      oneline_offset = arg_len;
 -      merge_arg_len = strcspn(arg, " \t\n");
 -      p = arg + merge_arg_len;
 -      p += strspn(p, " \t\n");
 -      if (*p == '#' && (!p[1] || isspace(p[1]))) {
 -              p += 1 + strspn(p + 1, " \t\n");
 -              oneline_offset = p - arg;
 -      } else if (p - arg < arg_len)
 -              BUG("octopus merges are not supported yet: '%s'", p);
 -
 -      strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
 -      merge_commit = lookup_commit_reference_by_name(ref_name.buf);
 -      if (!merge_commit) {
 -              /* fall back to non-rewritten ref or commit */
 -              strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
 -              merge_commit = lookup_commit_reference_by_name(ref_name.buf);
 +      /*
 +       * For octopus merges, the arg starts with the list of revisions to be
 +       * merged. The list is optionally followed by '#' and the oneline.
 +       */
 +      merge_arg_len = oneline_offset = arg_len;
 +      for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) {
 +              if (!*p)
 +                      break;
 +              if (*p == '#' && (!p[1] || isspace(p[1]))) {
 +                      p += 1 + strspn(p + 1, " \t\n");
 +                      oneline_offset = p - arg;
 +                      break;
 +              }
 +              k = strcspn(p, " \t\n");
 +              if (!k)
 +                      continue;
 +              merge_commit = lookup_label(p, k, &ref_name);
 +              if (!merge_commit) {
 +                      ret = error(_("unable to parse '%.*s'"), k, p);
 +                      goto leave_merge;
 +              }
 +              tail = &commit_list_insert(merge_commit, tail)->next;
 +              p += k;
 +              merge_arg_len = p - arg;
        }
  
 -      if (!merge_commit) {
 -              ret = error(_("could not resolve '%s'"), ref_name.buf);
 +      if (!to_merge) {
 +              ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
                goto leave_merge;
        }
  
                 * "[new root]", let's simply fast-forward to the merge head.
                 */
                rollback_lock_file(&lock);
 -              ret = fast_forward_to(&merge_commit->object.oid,
 -                                     &head_commit->object.oid, 0, opts);
 +              if (to_merge->next)
 +                      ret = error(_("octopus merge cannot be executed on "
 +                                    "top of a [new root]"));
 +              else
 +                      ret = fast_forward_to(&to_merge->item->object.oid,
 +                                            &head_commit->object.oid, 0,
 +                                            opts);
                goto leave_merge;
        }
  
                write_author_script(message);
                find_commit_subject(message, &body);
                len = strlen(body);
 -              ret = write_message(body, len, git_path_merge_msg(), 0);
 +              ret = write_message(body, len, git_path_merge_msg(the_repository), 0);
                unuse_commit_buffer(commit, message);
                if (ret) {
                        error_errno(_("could not write '%s'"),
 -                                  git_path_merge_msg());
 +                                  git_path_merge_msg(the_repository));
                        goto leave_merge;
                }
        } else {
                        p = arg + oneline_offset;
                        len = arg_len - oneline_offset;
                } else {
 -                      strbuf_addf(&buf, "Merge branch '%.*s'",
 +                      strbuf_addf(&buf, "Merge %s '%.*s'",
 +                                  to_merge->next ? "branches" : "branch",
                                    merge_arg_len, arg);
                        p = buf.buf;
                        len = buf.len;
                }
  
 -              ret = write_message(p, len, git_path_merge_msg(), 0);
 +              ret = write_message(p, len, git_path_merge_msg(the_repository), 0);
                strbuf_release(&buf);
                if (ret) {
                        error_errno(_("could not write '%s'"),
 -                                  git_path_merge_msg());
 +                                  git_path_merge_msg(the_repository));
                        goto leave_merge;
                }
        }
                        &head_commit->object.oid);
  
        /*
 -       * If the merge head is different from the original one, we cannot
 +       * If any merge head is different from the original one, we cannot
         * fast-forward.
         */
        if (can_fast_forward) {
 -              struct commit_list *second_parent = commit->parents->next;
 +              struct commit_list *p = commit->parents->next;
  
 -              if (second_parent && !second_parent->next &&
 -                  oidcmp(&merge_commit->object.oid,
 -                         &second_parent->item->object.oid))
 +              for (j = to_merge; j && p; j = j->next, p = p->next)
 +                      if (oidcmp(&j->item->object.oid,
 +                                 &p->item->object.oid)) {
 +                              can_fast_forward = 0;
 +                              break;
 +                      }
 +              /*
 +               * If the number of merge heads differs from the original merge
 +               * commit, we cannot fast-forward.
 +               */
 +              if (j || p)
                        can_fast_forward = 0;
        }
  
 -      if (can_fast_forward && commit->parents->next &&
 -          !commit->parents->next->next &&
 -          !oidcmp(&commit->parents->next->item->object.oid,
 -                  &merge_commit->object.oid)) {
 +      if (can_fast_forward) {
                rollback_lock_file(&lock);
                ret = fast_forward_to(&commit->object.oid,
                                      &head_commit->object.oid, 0, opts);
                goto leave_merge;
        }
  
 +      if (to_merge->next) {
 +              /* Octopus merge */
 +              struct child_process cmd = CHILD_PROCESS_INIT;
 +
 +              if (read_env_script(&cmd.env_array)) {
 +                      const char *gpg_opt = gpg_sign_opt_quoted(opts);
 +
 +                      ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
 +                      goto leave_merge;
 +              }
 +
 +              cmd.git_cmd = 1;
 +              argv_array_push(&cmd.args, "merge");
 +              argv_array_push(&cmd.args, "-s");
 +              argv_array_push(&cmd.args, "octopus");
 +              argv_array_push(&cmd.args, "--no-edit");
 +              argv_array_push(&cmd.args, "--no-ff");
 +              argv_array_push(&cmd.args, "--no-log");
 +              argv_array_push(&cmd.args, "--no-stat");
 +              argv_array_push(&cmd.args, "-F");
 +              argv_array_push(&cmd.args, git_path_merge_msg(the_repository));
 +              if (opts->gpg_sign)
 +                      argv_array_push(&cmd.args, opts->gpg_sign);
 +
 +              /* Add the tips to be merged */
 +              for (j = to_merge; j; j = j->next)
 +                      argv_array_push(&cmd.args,
 +                                      oid_to_hex(&j->item->object.oid));
 +
 +              strbuf_release(&ref_name);
 +              unlink(git_path_cherry_pick_head(the_repository));
 +              rollback_lock_file(&lock);
 +
 +              rollback_lock_file(&lock);
 +              ret = run_command(&cmd);
 +
 +              /* force re-reading of the cache */
 +              if (!ret && (discard_cache() < 0 || read_cache() < 0))
 +                      ret = error(_("could not read index"));
 +              goto leave_merge;
 +      }
 +
 +      merge_commit = to_merge->item;
        write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
 -                    git_path_merge_head(), 0);
 -      write_message("no-ff", 5, git_path_merge_mode(), 0);
 +                    git_path_merge_head(the_repository), 0);
 +      write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
  
        bases = get_merge_bases(head_commit, merge_commit);
        if (bases && !oidcmp(&merge_commit->object.oid,
                 * value (a negative one would indicate that the `merge`
                 * command needs to be rescheduled).
                 */
 -              ret = !!run_git_commit(git_path_merge_msg(), opts,
 +              ret = !!run_git_commit(git_path_merge_msg(the_repository), opts,
                                     run_commit_flags);
  
  leave_merge:
        strbuf_release(&ref_name);
        rollback_lock_file(&lock);
 +      free_commit_list(to_merge);
        return ret;
  }
  
@@@ -3366,27 -3217,10 +3369,27 @@@ static int pick_commits(struct todo_lis
                                        intend_to_amend();
                                return error_failed_squash(item->commit, opts,
                                        item->arg_len, item->arg);
 -                      } else if (res && is_rebase_i(opts) && item->commit)
 +                      } else if (res && is_rebase_i(opts) && item->commit) {
 +                              int to_amend = 0;
 +                              struct object_id oid;
 +
 +                              /*
 +                               * If we are rewording and have either
 +                               * fast-forwarded already, or are about to
 +                               * create a new root commit, we want to amend,
 +                               * otherwise we do not.
 +                               */
 +                              if (item->command == TODO_REWORD &&
 +                                  !get_oid("HEAD", &oid) &&
 +                                  (!oidcmp(&item->commit->object.oid, &oid) ||
 +                                   (opts->have_squash_onto &&
 +                                    !oidcmp(&opts->squash_onto, &oid))))
 +                                      to_amend = 1;
 +
                                return res | error_with_patch(item->commit,
 -                                      item->arg, item->arg_len, opts, res,
 -                                      item->command == TODO_REWORD);
 +                                              item->arg, item->arg_len, opts,
 +                                              res, to_amend);
 +                      }
                } else if (item->command == TODO_EXEC) {
                        char *end_of_arg = (char *)(item->arg + item->arg_len);
                        int saved = *end_of_arg;
@@@ -3567,8 -3401,8 +3570,8 @@@ static int continue_single_pick(void
  {
        const char *argv[] = { "commit", NULL };
  
 -      if (!file_exists(git_path_cherry_pick_head()) &&
 -          !file_exists(git_path_revert_head()))
 +      if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
 +          !file_exists(git_path_revert_head(the_repository)))
                return error(_("no cherry-pick or revert in progress"));
        return run_command_v_opt(argv, RUN_GIT_CMD);
  }
@@@ -3671,7 -3505,7 +3674,7 @@@ static int commit_staged_changes(struc
        }
  
        if (is_clean) {
 -              const char *cherry_pick_head = git_path_cherry_pick_head();
 +              const char *cherry_pick_head = git_path_cherry_pick_head(the_repository);
  
                if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
                        return error(_("could not remove CHERRY_PICK_HEAD"));
@@@ -3721,8 -3555,8 +3724,8 @@@ int sequencer_continue(struct replay_op
  
        if (!is_rebase_i(opts)) {
                /* Verify that the conflict has been resolved */
 -              if (file_exists(git_path_cherry_pick_head()) ||
 -                  file_exists(git_path_revert_head())) {
 +              if (file_exists(git_path_cherry_pick_head(the_repository)) ||
 +                  file_exists(git_path_revert_head(the_repository))) {
                        res = continue_single_pick();
                        if (res)
                                goto release_todo_list;
@@@ -3774,7 -3608,7 +3777,7 @@@ int sequencer_pick_revisions(struct rep
                        continue;
  
                if (!get_oid(name, &oid)) {
 -                      if (!lookup_commit_reference_gently(&oid, 1)) {
 +                      if (!lookup_commit_reference_gently(the_repository, &oid, 1)) {
                                enum object_type type = oid_object_info(the_repository,
                                                                        &oid,
                                                                        NULL);
                if (prepare_revision_walk(opts->revs))
                        return error(_("revision walk setup failed"));
                cmit = get_revision(opts->revs);
 -              if (!cmit || get_revision(opts->revs))
 -                      return error("BUG: expected exactly one commit from walk");
 +              if (!cmit)
 +                      return error(_("empty commit set passed"));
 +              if (get_revision(opts->revs))
 +                      BUG("unexpected extra commit from walk");
                return single_pick(cmit, opts);
        }
  
        return res;
  }
  
- void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
+ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
  {
        unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
        struct strbuf sob = STRBUF_INIT;
@@@ -4045,6 -3877,7 +4048,6 @@@ static int make_script_with_merges(stru
         */
        while ((commit = get_revision(revs))) {
                struct commit_list *to_merge;
 -              int is_octopus;
                const char *p1, *p2;
                struct object_id *oid;
                int is_empty;
                        continue;
                }
  
 -              is_octopus = to_merge && to_merge->next;
 -
 -              if (is_octopus)
 -                      BUG("Octopus merges not yet supported");
 -
                /* Create a label */
                strbuf_reset(&label);
                if (skip_prefix(oneline.buf, "Merge ", &p1) &&
                strbuf_addf(&buf, "%s -C %s",
                            cmd_merge, oid_to_hex(&commit->object.oid));
  
 -              /* label the tip of merged branch */
 -              oid = &to_merge->item->object.oid;
 -              strbuf_addch(&buf, ' ');
 +              /* label the tips of merged branches */
 +              for (; to_merge; to_merge = to_merge->next) {
 +                      oid = &to_merge->item->object.oid;
 +                      strbuf_addch(&buf, ' ');
 +
 +                      if (!oidset_contains(&interesting, oid)) {
 +                              strbuf_addstr(&buf, label_oid(oid, NULL,
 +                                                            &state));
 +                              continue;
 +                      }
  
 -              if (!oidset_contains(&interesting, oid))
 -                      strbuf_addstr(&buf, label_oid(oid, NULL, &state));
 -              else {
                        tips_tail = &commit_list_insert(to_merge->item,
                                                        tips_tail)->next;
  
                entry = oidmap_get(&state.commit2label, &commit->object.oid);
  
                if (entry)
 -                      fprintf(out, "\n# Branch %s\n", entry->string);
 +                      fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
                else
                        fprintf(out, "\n");
  
@@@ -4298,9 -4132,10 +4301,9 @@@ int sequencer_add_exec_commands(const c
  {
        const char *todo_file = rebase_path_todo();
        struct todo_list todo_list = TODO_LIST_INIT;
 -      struct todo_item *item;
        struct strbuf *buf = &todo_list.buf;
        size_t offset = 0, commands_len = strlen(commands);
 -      int i, first;
 +      int i, insert;
  
        if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
                return error(_("could not read '%s'."), todo_file);
                return error(_("unusable todo list: '%s'"), todo_file);
        }
  
 -      first = 1;
 -      /* insert <commands> before every pick except the first one */
 -      for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
 -              if (item->command == TODO_PICK && !first) {
 -                      strbuf_insert(buf, item->offset_in_buf + offset,
 -                                    commands, commands_len);
 +      /*
 +       * Insert <commands> after every pick. Here, fixup/squash chains
 +       * are considered part of the pick, so we insert the commands *after*
 +       * those chains if there are any.
 +       */
 +      insert = -1;
 +      for (i = 0; i < todo_list.nr; i++) {
 +              enum todo_command command = todo_list.items[i].command;
 +
 +              if (insert >= 0) {
 +                      /* skip fixup/squash chains */
 +                      if (command == TODO_COMMENT)
 +                              continue;
 +                      else if (is_fixup(command)) {
 +                              insert = i + 1;
 +                              continue;
 +                      }
 +                      strbuf_insert(buf,
 +                                    todo_list.items[insert].offset_in_buf +
 +                                    offset, commands, commands_len);
                        offset += commands_len;
 +                      insert = -1;
                }
 -              first = 0;
 +
 +              if (command == TODO_PICK || command == TODO_MERGE)
 +                      insert = i + 1;
        }
  
 -      /* append final <commands> */
 -      strbuf_add(buf, commands, commands_len);
 +      /* insert or append final <commands> */
 +      if (insert >= 0 && insert < todo_list.nr)
 +              strbuf_insert(buf, todo_list.items[insert].offset_in_buf +
 +                            offset, commands, commands_len);
 +      else if (insert >= 0 || !offset)
 +              strbuf_add(buf, commands, commands_len);
  
        i = write_message(buf->buf, buf->len, todo_file, 0);
        todo_list_release(&todo_list);
@@@ -4427,7 -4241,6 +4430,7 @@@ static enum check_level get_missing_com
        return CHECK_IGNORE;
  }
  
 +define_commit_slab(commit_seen, unsigned char);
  /*
   * Check if the user dropped some commits by mistake
   * Behaviour determined by rebase.missingCommitsCheck.
@@@ -4441,9 -4254,6 +4444,9 @@@ int check_todo_list(void
        struct todo_list todo_list = TODO_LIST_INIT;
        struct strbuf missing = STRBUF_INIT;
        int advise_to_edit_todo = 0, res = 0, i;
 +      struct commit_seen commit_seen;
 +
 +      init_commit_seen(&commit_seen);
  
        strbuf_addstr(&todo_file, rebase_path_todo());
        if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
        for (i = 0; i < todo_list.nr; i++) {
                struct commit *commit = todo_list.items[i].commit;
                if (commit)
 -                      commit->util = (void *)1;
 +                      *commit_seen_at(&commit_seen, commit) = 1;
        }
  
        todo_list_release(&todo_list);
        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) {
 +              if (commit && !*commit_seen_at(&commit_seen, commit)) {
                        strbuf_addf(&missing, " - %s %.*s\n",
                                    short_commit_name(commit),
                                    item->arg_len, item->arg);
 -                      commit->util = (void *)1;
 +                      *commit_seen_at(&commit_seen, commit) = 1;
                }
        }
  
                "The possible behaviours are: ignore, warn, error.\n\n"));
  
  leave_check:
 +      clear_commit_seen(&commit_seen);
        strbuf_release(&todo_file);
        todo_list_release(&todo_list);
  
@@@ -4627,8 -4436,6 +4630,8 @@@ static int subject2item_cmp(const void 
        return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
  }
  
 +define_commit_slab(commit_todo_item, struct todo_item *);
 +
  /*
   * 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
@@@ -4645,7 -4452,6 +4648,7 @@@ int rearrange_squash(void
        struct hashmap subject2item;
        int res = 0, rearranged = 0, *next, *tail, i;
        char **subjects;
 +      struct commit_todo_item commit_todo;
  
        if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
                return -1;
                return -1;
        }
  
 +      init_commit_todo_item(&commit_todo);
        /*
         * The hashmap maps onelines to the respective todo list index.
         *
  
                if (is_fixup(item->command)) {
                        todo_list_release(&todo_list);
 +                      clear_commit_todo_item(&commit_todo);
                        return error(_("the script was already rearranged."));
                }
  
 -              item->commit->util = item;
 +              *commit_todo_item_at(&commit_todo, item->commit) = item;
  
                parse_commit(item->commit);
                commit_buffer = get_commit_buffer(item->commit, NULL);
                        else if (!strchr(p, ' ') &&
                                 (commit2 =
                                  lookup_commit_reference_by_name(p)) &&
 -                               commit2->util)
 +                               *commit_todo_item_at(&commit_todo, commit2))
                                /* found by commit name */
 -                              i2 = (struct todo_item *)commit2->util
 +                              i2 = *commit_todo_item_at(&commit_todo, commit2)
                                        - todo_list.items;
                        else {
                                /* copy can be a prefix of the commit subject */
        hashmap_free(&subject2item, 1);
        todo_list_release(&todo_list);
  
 +      clear_commit_todo_item(&commit_todo);
        return res;
  }
diff --combined sequencer.h
index c751c9d6e4f78e7d9e2700dcc3fb3157961fb049,06ff5ff0651bf54770d34dc57f8434d31decfb5b..c986bc825161f1f4702a0cd435c6d9705e3be2df
@@@ -1,11 -1,6 +1,11 @@@
  #ifndef SEQUENCER_H
  #define SEQUENCER_H
  
 +#include "cache.h"
 +#include "strbuf.h"
 +
 +struct commit;
 +
  const char *git_path_commit_editmsg(void);
  const char *git_path_seq_dir(void);
  
@@@ -90,7 -85,14 +90,14 @@@ int rearrange_squash(void)
  
  extern const char sign_off_header[];
  
- void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
+ /*
+  * Append a signoff to the commit message in "msgbuf". The ignore_footer
+  * parameter specifies the number of bytes at the end of msgbuf that should
+  * not be considered at all. I.e., they are not checked for existing trailers,
+  * and the new signoff will be spliced into the buffer before those bytes.
+  */
+ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
  void append_conflicts_hint(struct strbuf *msgbuf);
  int message_is_empty(const struct strbuf *sb,
                     enum commit_msg_cleanup_mode cleanup_mode);
diff --combined t/t6300-for-each-ref.sh
index 024f8c06f7c58a424204d6fca69021ace2de3cbf,3d26ba0c2aaf5b2222a81d8b5d66086673f8ae2e..97bfbee6e8d69e46bd1ef1c94dae32d64be977b2
@@@ -715,6 -715,29 +715,29 @@@ test_expect_success 'basic atom: head c
        test_cmp expect actual.clean
  '
  
+ test_expect_success 'trailer parsing not fooled by --- line' '
+       git commit --allow-empty -F - <<-\EOF &&
+       this is the subject
+       This is the body. The message has a "---" line which would confuse a
+       message+patch parser. But here we know we have only a commit message,
+       so we get it right.
+       trailer: wrong
+       ---
+       This is more body.
+       trailer: right
+       EOF
+       {
+               echo "trailer: right" &&
+               echo
+       } >expect &&
+       git for-each-ref --format="%(trailers)" refs/heads/master >actual &&
+       test_cmp expect actual
+ '
  test_expect_success 'Add symbolic ref for the following tests' '
        git symbolic-ref refs/heads/sym refs/heads/master
  '
@@@ -795,14 -818,4 +818,14 @@@ test_expect_success ':remotename and :r
        )
  '
  
 +test_expect_success 'for-each-ref --ignore-case ignores case' '
 +      git for-each-ref --format="%(refname)" refs/heads/MASTER >actual &&
 +      test_must_be_empty actual &&
 +
 +      echo refs/heads/master >expect &&
 +      git for-each-ref --format="%(refname)" --ignore-case \
 +              refs/heads/MASTER >actual &&
 +      test_cmp expect actual
 +'
 +
  test_done
diff --combined t/t7501-commit.sh
index 4cae92804d11f75f24bdd6f6517a88c834e7cfc7,025d65b51770b06c2ce05a540bb862783ce261af..1a6773ee6889939a0046664bd8a7cdcc4f21fe01
@@@ -47,7 -47,7 +47,7 @@@ test_expect_success 'paths and -a do no
  test_expect_success PERL 'can use paths with --interactive' '
        echo bong-o-bong >file &&
        # 2: update, 1:st path, that is all, 7: quit
 -      ( echo 2; echo 1; echo; echo 7 ) |
 +      test_write_lines 2 1 "" 7 |
        git commit -m foo --interactive file &&
        git reset --hard HEAD^
  '
@@@ -293,7 -293,7 +293,7 @@@ test_expect_success PERL 'interactive a
  test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
        echo zoo >file &&
        test_must_fail git diff --exit-code >diff1 &&
 -      (echo u ; echo "*" ; echo q) |
 +      test_write_lines u "*" q |
        (
                EDITOR=: &&
                export EDITOR &&
@@@ -411,8 -411,8 +411,8 @@@ test_expect_success 'sign off (1)' 
        git commit -s -m "thank you" &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
        (
 -              echo thank you
 -              echo
 +              echo thank you &&
 +              echo &&
                git var GIT_COMMITTER_IDENT |
                sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
        ) >expected &&
@@@ -430,9 -430,9 +430,9 @@@ test_expect_success 'sign off (2)' 
  $existing" &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
        (
 -              echo thank you
 -              echo
 -              echo $existing
 +              echo thank you &&
 +              echo &&
 +              echo $existing &&
                git var GIT_COMMITTER_IDENT |
                sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
        ) >expected &&
@@@ -450,9 -450,9 +450,9 @@@ test_expect_success 'signoff gap' 
  $alt" &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
        (
 -              echo welcome
 -              echo
 -              echo $alt
 +              echo welcome &&
 +              echo &&
 +              echo $alt &&
                git var GIT_COMMITTER_IDENT |
                sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
        ) >expected &&
@@@ -470,11 -470,11 +470,11 @@@ We have no
  $alt" &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
        (
 -              echo welcome
 -              echo
 -              echo We have now
 -              echo $alt
 -              echo
 +              echo welcome &&
 +              echo &&
 +              echo We have now &&
 +              echo $alt &&
 +              echo &&
                git var GIT_COMMITTER_IDENT |
                sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
        ) >expected &&
@@@ -491,11 -491,11 +491,11 @@@ non-trailer lin
  Myfooter: x" &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
        (
 -              echo subject
 -              echo
 -              echo non-trailer line
 -              echo Myfooter: x
 -              echo
 +              echo subject &&
 +              echo &&
 +              echo non-trailer line &&
 +              echo Myfooter: x &&
 +              echo &&
                echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
        ) >expected &&
        test_cmp expected actual &&
@@@ -508,15 -508,31 +508,31 @@@ non-trailer lin
  Myfooter: x" &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
        (
 -              echo subject
 -              echo
 -              echo non-trailer line
 -              echo Myfooter: x
 +              echo subject &&
 +              echo &&
 +              echo non-trailer line &&
 +              echo Myfooter: x &&
                echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
        ) >expected &&
        test_cmp expected actual
  '
  
+ test_expect_success 'signoff not confused by ---' '
+       cat >expected <<-EOF &&
+               subject
+               body
+               ---
+               these dashes confuse the parser!
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       # should be a noop, since we already signed
+       git commit --allow-empty --signoff -F expected &&
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expected actual
+ '
  test_expect_success 'multiple -m' '
  
        >negative &&
        git commit -m "one" -m "two" -m "three" &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
        (
 -              echo one
 -              echo
 -              echo two
 -              echo
 +              echo one &&
 +              echo &&
 +              echo two &&
 +              echo &&
                echo three
        ) >expected &&
        test_cmp expected actual
@@@ -582,11 -598,13 +598,11 @@@ test_expect_success 'same tree (merge a
  
        git merge -s ours side -m "empty ok" &&
        git diff HEAD^ HEAD >actual &&
 -      : >expected &&
 -      test_cmp expected actual &&
 +      test_must_be_empty actual &&
  
        git commit --amend -m "empty really ok" &&
        git diff HEAD^ HEAD >actual &&
 -      : >expected &&
 -      test_cmp expected actual
 +      test_must_be_empty actual
  
  '
  
@@@ -675,7 -693,7 +691,7 @@@ test_expect_success '--dry-run with con
        git checkout -b branch-2 HEAD^1 &&
        echo "commit-2-state" >test-file &&
        git commit -m "commit 2" -i test-file &&
 -      ! $(git merge --no-commit commit-1) &&
 +      test_must_fail git merge --no-commit commit-1 &&
        echo "commit-2-state" >test-file &&
        git add test-file &&
        git commit --dry-run &&
diff --combined trailer.h
index 9c10026c358326ce0c0098ca52341ce8160c5bbe,f47b16e2c45970d84d58659113e1c55b8a3db020..b997739649a37e8791e8448c2e7daefe8023bb71
+++ b/trailer.h
@@@ -3,8 -3,6 +3,8 @@@
  
  #include "list.h"
  
 +struct strbuf;
 +
  enum trailer_where {
        WHERE_DEFAULT,
        WHERE_END,
@@@ -71,6 -69,7 +71,7 @@@ struct process_trailer_options 
        int only_trailers;
        int only_input;
        int unfold;
+       int no_divider;
  };
  
  #define PROCESS_TRAILER_OPTIONS_INIT {0}
@@@ -79,7 -78,8 +80,8 @@@ void process_trailers(const char *file
                      const struct process_trailer_options *opts,
                      struct list_head *new_trailer_head);
  
- void trailer_info_get(struct trailer_info *info, const char *str);
+ void trailer_info_get(struct trailer_info *info, const char *str,
+                     const struct process_trailer_options *opts);
  
  void trailer_info_release(struct trailer_info *info);