push: require force for refs under refs/tags/
[gitweb.git] / commit.c
index 10f7b14e763feab4b86d3a8fb1f011cb918312c8..e8eb0aec5509a4fd09698bd15a37d1f4e8341f5e 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -3,70 +3,18 @@
 #include "commit.h"
 #include "pkt-line.h"
 #include "utf8.h"
-#include "interpolate.h"
 #include "diff.h"
 #include "revision.h"
+#include "notes.h"
+#include "gpg-interface.h"
+#include "mergesort.h"
 
-int save_commit_buffer = 1;
-
-struct sort_node
-{
-       /*
-        * the number of children of the associated commit
-        * that also occur in the list being sorted.
-        */
-       unsigned int indegree;
-
-       /*
-        * reference to original list item that we will re-use
-        * on output.
-        */
-       struct commit_list * list_item;
+static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
-};
+int save_commit_buffer = 1;
 
 const char *commit_type = "commit";
 
-static struct cmt_fmt_map {
-       const char *n;
-       size_t cmp_len;
-       enum cmit_fmt v;
-} cmt_fmts[] = {
-       { "raw",        1,      CMIT_FMT_RAW },
-       { "medium",     1,      CMIT_FMT_MEDIUM },
-       { "short",      1,      CMIT_FMT_SHORT },
-       { "email",      1,      CMIT_FMT_EMAIL },
-       { "full",       5,      CMIT_FMT_FULL },
-       { "fuller",     5,      CMIT_FMT_FULLER },
-       { "oneline",    1,      CMIT_FMT_ONELINE },
-       { "format:",    7,      CMIT_FMT_USERFORMAT},
-};
-
-static char *user_format;
-
-enum cmit_fmt get_commit_format(const char *arg)
-{
-       int i;
-
-       if (!arg || !*arg)
-               return CMIT_FMT_DEFAULT;
-       if (*arg == '=')
-               arg++;
-       if (!prefixcmp(arg, "format:")) {
-               if (user_format)
-                       free(user_format);
-               user_format = xstrdup(arg + 7);
-               return CMIT_FMT_USERFORMAT;
-       }
-       for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
-               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
-                   !strncmp(arg, cmt_fmts[i].n, strlen(arg)))
-                       return cmt_fmts[i].v;
-       }
-
-       die("invalid --pretty format: %s", arg);
-}
-
 static struct commit *check_commit(struct object *obj,
                                   const unsigned char *sha1,
                                   int quiet)
@@ -95,6 +43,18 @@ struct commit *lookup_commit_reference(const unsigned char *sha1)
        return lookup_commit_reference_gently(sha1, 0);
 }
 
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name)
+{
+       struct commit *c = lookup_commit_reference(sha1);
+       if (!c)
+               die(_("could not parse %s"), ref_name);
+       if (hashcmp(sha1, c->object.sha1)) {
+               warning(_("%s %s is not a commit!"),
+                       ref_name, sha1_to_hex(sha1));
+       }
+       return c;
+}
+
 struct commit *lookup_commit(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
@@ -105,22 +65,44 @@ struct commit *lookup_commit(const unsigned char *sha1)
        return check_commit(obj, sha1, 0);
 }
 
-static unsigned long parse_commit_date(const char *buf)
+struct commit *lookup_commit_reference_by_name(const char *name)
 {
-       unsigned long date;
+       unsigned char sha1[20];
+       struct commit *commit;
 
+       if (get_sha1_committish(name, sha1))
+               return NULL;
+       commit = lookup_commit_reference(sha1);
+       if (!commit || parse_commit(commit))
+               return NULL;
+       return commit;
+}
+
+static unsigned long parse_commit_date(const char *buf, const char *tail)
+{
+       const char *dateptr;
+
+       if (buf + 6 >= tail)
+               return 0;
        if (memcmp(buf, "author", 6))
                return 0;
-       while (*buf++ != '\n')
+       while (buf < tail && *buf++ != '\n')
                /* nada */;
+       if (buf + 9 >= tail)
+               return 0;
        if (memcmp(buf, "committer", 9))
                return 0;
-       while (*buf++ != '>')
+       while (buf < tail && *buf++ != '>')
+               /* nada */;
+       if (buf >= tail)
+               return 0;
+       dateptr = buf;
+       while (buf < tail && *buf++ != '\n')
                /* nada */;
-       date = strtoul(buf, NULL, 10);
-       if (date == ULONG_MAX)
-               date = 0;
-       return date;
+       if (buf >= tail)
+               return 0;
+       /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */
+       return strtoul(dateptr, NULL, 10);
 }
 
 static struct commit_graft **commit_graft;
@@ -180,16 +162,12 @@ struct commit_graft *read_graft_line(char *buf, int len)
        int i;
        struct commit_graft *graft = NULL;
 
-       if (buf[len-1] == '\n')
-               buf[--len] = 0;
+       while (len && isspace(buf[len-1]))
+               buf[--len] = '\0';
        if (buf[0] == '#' || buf[0] == '\0')
                return NULL;
-       if ((len + 1) % 41) {
-       bad_graft_data:
-               error("bad graft data: %s", buf);
-               free(graft);
-               return NULL;
-       }
+       if ((len + 1) % 41)
+               goto bad_graft_data;
        i = (len + 1) / 41 - 1;
        graft = xmalloc(sizeof(*graft) + 20 * i);
        graft->nr_parent = i;
@@ -202,9 +180,14 @@ struct commit_graft *read_graft_line(char *buf, int len)
                        goto bad_graft_data;
        }
        return graft;
+
+bad_graft_data:
+       error("bad graft data: %s", buf);
+       free(graft);
+       return NULL;
 }
 
-int read_graft_file(const char *graft_file)
+static int read_graft_file(const char *graft_file)
 {
        FILE *fp = fopen(graft_file, "r");
        char buf[1024];
@@ -237,7 +220,7 @@ static void prepare_commit_graft(void)
        commit_graft_prepared = 1;
 }
 
-static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
+struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
 {
        int pos;
        prepare_commit_graft();
@@ -247,24 +230,12 @@ static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
        return commit_graft[pos];
 }
 
-int write_shallow_commits(int fd, int use_pack_protocol)
+int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
 {
-       int i, count = 0;
-       for (i = 0; i < commit_graft_nr; i++)
-               if (commit_graft[i]->nr_parent < 0) {
-                       const char *hex =
-                               sha1_to_hex(commit_graft[i]->sha1);
-                       count++;
-                       if (use_pack_protocol)
-                               packet_write(fd, "shallow %s", hex);
-                       else {
-                               if (write_in_full(fd, hex,  40) != 40)
-                                       break;
-                               if (write_in_full(fd, "\n", 1) != 1)
-                                       break;
-                       }
-               }
-       return count;
+       int i, ret;
+       for (i = ret = 0; i < commit_graft_nr && !ret; i++)
+               ret = fn(commit_graft[i], cb_data);
+       return ret;
 }
 
 int unregister_shallow(const unsigned char *sha1)
@@ -273,34 +244,31 @@ int unregister_shallow(const unsigned char *sha1)
        if (pos < 0)
                return -1;
        if (pos + 1 < commit_graft_nr)
-               memcpy(commit_graft + pos, commit_graft + pos + 1,
+               memmove(commit_graft + pos, commit_graft + pos + 1,
                                sizeof(struct commit_graft *)
                                * (commit_graft_nr - pos - 1));
        commit_graft_nr--;
        return 0;
 }
 
-int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size)
 {
-       char *tail = buffer;
-       char *bufptr = buffer;
+       const char *tail = buffer;
+       const char *bufptr = buffer;
        unsigned char parent[20];
        struct commit_list **pptr;
        struct commit_graft *graft;
-       unsigned n_refs = 0;
 
        if (item->object.parsed)
                return 0;
        item->object.parsed = 1;
        tail += size;
-       if (tail <= bufptr + 5 || memcmp(bufptr, "tree ", 5))
+       if (tail <= bufptr + 46 || memcmp(bufptr, "tree ", 5) || bufptr[45] != '\n')
                return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
-       if (tail <= bufptr + 45 || get_sha1_hex(bufptr + 5, parent) < 0)
+       if (get_sha1_hex(bufptr + 5, parent) < 0)
                return error("bad tree pointer in commit %s",
                             sha1_to_hex(item->object.sha1));
        item->tree = lookup_tree(parent);
-       if (item->tree)
-               n_refs++;
        bufptr += 46; /* "tree " + "hex sha1" + "\n" */
        pptr = &item->parents;
 
@@ -313,13 +281,15 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
                    bufptr[47] != '\n')
                        return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
                bufptr += 48;
-               if (graft)
+               /*
+                * The clone is shallow if nr_parent < 0, and we must
+                * not traverse its real parents even when we unhide them.
+                */
+               if (graft && (graft->nr_parent < 0 || grafts_replace_parents))
                        continue;
                new_parent = lookup_commit(parent);
-               if (new_parent) {
+               if (new_parent)
                        pptr = &commit_list_insert(new_parent, pptr)->next;
-                       n_refs++;
-               }
        }
        if (graft) {
                int i;
@@ -329,21 +299,9 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
                        if (!new_parent)
                                continue;
                        pptr = &commit_list_insert(new_parent, pptr)->next;
-                       n_refs++;
                }
        }
-       item->date = parse_commit_date(bufptr);
-
-       if (track_object_refs) {
-               unsigned i = 0;
-               struct commit_list *p;
-               struct object_refs *refs = alloc_object_refs(n_refs);
-               if (item->tree)
-                       refs->ref[i++] = &item->tree->object;
-               for (p = item->parents; p; p = p->next)
-                       refs->ref[i++] = &p->item->object;
-               set_object_refs(&item->object, refs);
-       }
+       item->date = parse_commit_date(bufptr, tail);
 
        return 0;
 }
@@ -355,6 +313,8 @@ int parse_commit(struct commit *item)
        unsigned long size;
        int ret;
 
+       if (!item)
+               return -1;
        if (item->object.parsed)
                return 0;
        buffer = read_sha1_file(item->object.sha1, &type, &size);
@@ -375,6 +335,25 @@ int parse_commit(struct commit *item)
        return ret;
 }
 
+int find_commit_subject(const char *commit_buffer, const char **subject)
+{
+       const char *eol;
+       const char *p = commit_buffer;
+
+       while (*p && (*p != '\n' || p[1] != '\n'))
+               p++;
+       if (*p) {
+               p += 2;
+               for (eol = p; *eol && *eol != '\n'; eol++)
+                       ; /* do nothing */
+       } else
+               eol = p;
+
+       *subject = p;
+
+       return eol - p;
+}
+
 struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
 {
        struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
@@ -384,6 +363,14 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
        return new_list;
 }
 
+unsigned commit_list_count(const struct commit_list *l)
+{
+       unsigned c = 0;
+       for (; l; l = l->next )
+               c++;
+       return c;
+}
+
 void free_commit_list(struct commit_list *list)
 {
        while (list) {
@@ -393,7 +380,7 @@ void free_commit_list(struct commit_list *list)
        }
 }
 
-struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
+struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list)
 {
        struct commit_list **pp = list;
        struct commit_list *p;
@@ -406,15 +393,31 @@ struct commit_list * insert_by_date(struct commit *item, struct commit_list **li
        return commit_list_insert(item, pp);
 }
 
+static int commit_list_compare_by_date(const void *a, const void *b)
+{
+       unsigned long a_date = ((const struct commit_list *)a)->item->date;
+       unsigned long b_date = ((const struct commit_list *)b)->item->date;
+       if (a_date < b_date)
+               return 1;
+       if (a_date > b_date)
+               return -1;
+       return 0;
+}
 
-void sort_by_date(struct commit_list **list)
+static void *commit_list_get_next(const void *a)
 {
-       struct commit_list *ret = NULL;
-       while (*list) {
-               insert_by_date((*list)->item, &ret);
-               *list = (*list)->next;
-       }
-       *list = ret;
+       return ((const struct commit_list *)a)->next;
+}
+
+static void commit_list_set_next(void *a, void *next)
+{
+       ((struct commit_list *)a)->next = next;
+}
+
+void commit_list_sort_by_date(struct commit_list **list)
+{
+       *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
+                               commit_list_compare_by_date);
 }
 
 struct commit *pop_most_recent_commit(struct commit_list **list,
@@ -429,17 +432,17 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
 
        while (parents) {
                struct commit *commit = parents->item;
-               parse_commit(commit);
-               if (!(commit->object.flags & mark)) {
+               if (!parse_commit(commit) && !(commit->object.flags & mark)) {
                        commit->object.flags |= mark;
-                       insert_by_date(commit, list);
+                       commit_list_insert_by_date(commit, list);
                }
                parents = parents->next;
        }
        return ret;
 }
 
-void clear_commit_marks(struct commit *commit, unsigned int mark)
+static void clear_commit_marks_1(struct commit_list **plist,
+                                struct commit *commit, unsigned int mark)
 {
        while (commit) {
                struct commit_list *parents;
@@ -454,1114 +457,903 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
                        return;
 
                while ((parents = parents->next))
-                       clear_commit_marks(parents->item, mark);
+                       commit_list_insert(parents->item, plist);
 
                commit = commit->parents->item;
        }
 }
 
-/*
- * Generic support for pretty-printing the header
- */
-static int get_one_line(const char *msg, unsigned long len)
+void clear_commit_marks(struct commit *commit, unsigned int mark)
 {
-       int ret = 0;
-
-       while (len--) {
-               char c = *msg++;
-               if (!c)
-                       break;
-               ret++;
-               if (c == '\n')
-                       break;
-       }
-       return ret;
+       struct commit_list *list = NULL;
+       commit_list_insert(commit, &list);
+       while (list)
+               clear_commit_marks_1(&list, pop_commit(&list), mark);
 }
 
-/* High bit set, or ISO-2022-INT */
-int non_ascii(int ch)
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
 {
-       ch = (ch & 0xff);
-       return ((ch & 0x80) || (ch == 0x1b));
+       struct object *object;
+       struct commit *commit;
+       unsigned int i;
+
+       for (i = 0; i < a->nr; i++) {
+               object = a->objects[i].item;
+               commit = lookup_commit_reference_gently(object->sha1, 1);
+               if (commit)
+                       clear_commit_marks(commit, mark);
+       }
 }
 
-static int is_rfc2047_special(char ch)
+struct commit *pop_commit(struct commit_list **stack)
 {
-       return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
-}
+       struct commit_list *top = *stack;
+       struct commit *item = top ? top->item : NULL;
 
-static int add_rfc2047(char *buf, const char *line, int len,
-                      const char *encoding)
-{
-       char *bp = buf;
-       int i, needquote;
-       char q_encoding[128];
-       const char *q_encoding_fmt = "=?%s?q?";
-
-       for (i = needquote = 0; !needquote && i < len; i++) {
-               int ch = line[i];
-               if (non_ascii(ch))
-                       needquote++;
-               if ((i + 1 < len) &&
-                   (ch == '=' && line[i+1] == '?'))
-                       needquote++;
-       }
-       if (!needquote)
-               return sprintf(buf, "%.*s", len, line);
-
-       i = snprintf(q_encoding, sizeof(q_encoding), q_encoding_fmt, encoding);
-       if (sizeof(q_encoding) < i)
-               die("Insanely long encoding name %s", encoding);
-       memcpy(bp, q_encoding, i);
-       bp += i;
-       for (i = 0; i < len; i++) {
-               unsigned ch = line[i] & 0xFF;
-               /*
-                * We encode ' ' using '=20' even though rfc2047
-                * allows using '_' for readability.  Unfortunately,
-                * many programs do not understand this and just
-                * leave the underscore in place.
-                */
-               if (is_rfc2047_special(ch) || ch == ' ') {
-                       sprintf(bp, "=%02X", ch);
-                       bp += 3;
-               }
-               else
-                       *bp++ = ch;
+       if (top) {
+               *stack = top->next;
+               free(top);
        }
-       memcpy(bp, "?=", 2);
-       bp += 2;
-       return bp - buf;
+       return item;
 }
 
-static unsigned long bound_rfc2047(unsigned long len, const char *encoding)
+/*
+ * Performs an in-place topological sort on the list supplied.
+ */
+void sort_in_topological_order(struct commit_list ** list, int lifo)
 {
-       /* upper bound of q encoded string of length 'len' */
-       unsigned long elen = strlen(encoding);
-
-       return len * 3 + elen + 100;
-}
+       struct commit_list *next, *orig = *list;
+       struct commit_list *work, **insert;
+       struct commit_list **pptr;
 
-static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
-                        const char *line, enum date_mode dmode,
-                        const char *encoding)
-{
-       char *date;
-       int namelen;
-       unsigned long time;
-       int tz, ret;
-       const char *filler = "    ";
+       if (!orig)
+               return;
+       *list = NULL;
 
-       if (fmt == CMIT_FMT_ONELINE)
-               return 0;
-       date = strchr(line, '>');
-       if (!date)
-               return 0;
-       namelen = ++date - line;
-       time = strtoul(date, &date, 10);
-       tz = strtol(date, NULL, 10);
-
-       if (fmt == CMIT_FMT_EMAIL) {
-               char *name_tail = strchr(line, '<');
-               int display_name_length;
-               if (!name_tail)
-                       return 0;
-               while (line < name_tail && isspace(name_tail[-1]))
-                       name_tail--;
-               display_name_length = name_tail - line;
-               filler = "";
-               strcpy(buf, "From: ");
-               ret = strlen(buf);
-               ret += add_rfc2047(buf + ret, line, display_name_length,
-                                  encoding);
-               memcpy(buf + ret, name_tail, namelen - display_name_length);
-               ret += namelen - display_name_length;
-               buf[ret++] = '\n';
+       /* Mark them and clear the indegree */
+       for (next = orig; next; next = next->next) {
+               struct commit *commit = next->item;
+               commit->indegree = 1;
        }
-       else {
-               ret = sprintf(buf, "%s: %.*s%.*s\n", what,
-                             (fmt == CMIT_FMT_FULLER) ? 4 : 0,
-                             filler, namelen, line);
+
+       /* update the indegree */
+       for (next = orig; next; next = next->next) {
+               struct commit_list * parents = next->item->parents;
+               while (parents) {
+                       struct commit *parent = parents->item;
+
+                       if (parent->indegree)
+                               parent->indegree++;
+                       parents = parents->next;
+               }
        }
-       switch (fmt) {
-       case CMIT_FMT_MEDIUM:
-               ret += sprintf(buf + ret, "Date:   %s\n",
-                              show_date(time, tz, dmode));
-               break;
-       case CMIT_FMT_EMAIL:
-               ret += sprintf(buf + ret, "Date: %s\n",
-                              show_date(time, tz, DATE_RFC2822));
-               break;
-       case CMIT_FMT_FULLER:
-               ret += sprintf(buf + ret, "%sDate: %s\n", what,
-                              show_date(time, tz, dmode));
-               break;
-       default:
-               /* notin' */
-               break;
+
+       /*
+        * find the tips
+        *
+        * tips are nodes not reachable from any other node in the list
+        *
+        * the tips serve as a starting set for the work queue.
+        */
+       work = NULL;
+       insert = &work;
+       for (next = orig; next; next = next->next) {
+               struct commit *commit = next->item;
+
+               if (commit->indegree == 1)
+                       insert = &commit_list_insert(commit, insert)->next;
        }
-       return ret;
-}
 
-static int is_empty_line(const char *line, int *len_p)
-{
-       int len = *len_p;
-       while (len && isspace(line[len-1]))
-               len--;
-       *len_p = len;
-       return !len;
-}
+       /* process the list in topological order */
+       if (!lifo)
+               commit_list_sort_by_date(&work);
 
-static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *commit, int abbrev)
-{
-       struct commit_list *parent = commit->parents;
-       int offset;
+       pptr = list;
+       *list = NULL;
+       while (work) {
+               struct commit *commit;
+               struct commit_list *parents, *work_item;
 
-       if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
-           !parent || !parent->next)
-               return 0;
+               work_item = work;
+               work = work_item->next;
+               work_item->next = NULL;
 
-       offset = sprintf(buf, "Merge:");
+               commit = work_item->item;
+               for (parents = commit->parents; parents ; parents = parents->next) {
+                       struct commit *parent = parents->item;
 
-       while (parent) {
-               struct commit *p = parent->item;
-               const char *hex = NULL;
-               const char *dots;
-               if (abbrev)
-                       hex = find_unique_abbrev(p->object.sha1, abbrev);
-               if (!hex)
-                       hex = sha1_to_hex(p->object.sha1);
-               dots = (abbrev && strlen(hex) != 40) ?  "..." : "";
-               parent = parent->next;
+                       if (!parent->indegree)
+                               continue;
 
-               offset += sprintf(buf + offset, " %s%s", hex, dots);
+                       /*
+                        * parents are only enqueued for emission
+                        * when all their children have been emitted thereby
+                        * guaranteeing topological order.
+                        */
+                       if (--parent->indegree == 1) {
+                               if (!lifo)
+                                       commit_list_insert_by_date(parent, &work);
+                               else
+                                       commit_list_insert(parent, &work);
+                       }
+               }
+               /*
+                * work_item is a commit all of whose children
+                * have already been emitted. we can emit it now.
+                */
+               commit->indegree = 0;
+               *pptr = work_item;
+               pptr = &work_item->next;
        }
-       buf[offset++] = '\n';
-       return offset;
 }
 
-static char *get_header(const struct commit *commit, const char *key)
-{
-       int key_len = strlen(key);
-       const char *line = commit->buffer;
+/* merge-base stuff */
 
-       for (;;) {
-               const char *eol = strchr(line, '\n'), *next;
+/* bits #0..15 in revision.h */
+#define PARENT1                (1u<<16)
+#define PARENT2                (1u<<17)
+#define STALE          (1u<<18)
+#define RESULT         (1u<<19)
 
-               if (line == eol)
-                       return NULL;
-               if (!eol) {
-                       eol = line + strlen(line);
-                       next = NULL;
-               } else
-                       next = eol + 1;
-               if (eol - line > key_len &&
-                   !strncmp(line, key, key_len) &&
-                   line[key_len] == ' ') {
-                       int len = eol - line - key_len;
-                       char *ret = xmalloc(len);
-                       memcpy(ret, line + key_len + 1, len - 1);
-                       ret[len - 1] = '\0';
-                       return ret;
-               }
-               line = next;
-       }
-}
+static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
 
-static char *replace_encoding_header(char *buf, const char *encoding)
+static struct commit *interesting(struct commit_list *list)
 {
-       char *encoding_header = strstr(buf, "\nencoding ");
-       char *header_end = strstr(buf, "\n\n");
-       char *end_of_encoding_header;
-       int encoding_header_pos;
-       int encoding_header_len;
-       int new_len;
-       int need_len;
-       int buflen = strlen(buf) + 1;
-
-       if (!header_end)
-               header_end = buf + buflen;
-       if (!encoding_header || encoding_header >= header_end)
-               return buf;
-       encoding_header++;
-       end_of_encoding_header = strchr(encoding_header, '\n');
-       if (!end_of_encoding_header)
-               return buf; /* should not happen but be defensive */
-       end_of_encoding_header++;
-
-       encoding_header_len = end_of_encoding_header - encoding_header;
-       encoding_header_pos = encoding_header - buf;
-
-       if (is_encoding_utf8(encoding)) {
-               /* we have re-coded to UTF-8; drop the header */
-               memmove(encoding_header, end_of_encoding_header,
-                       buflen - (encoding_header_pos + encoding_header_len));
-               return buf;
-       }
-       new_len = strlen(encoding);
-       need_len = new_len + strlen("encoding \n");
-       if (encoding_header_len < need_len) {
-               buf = xrealloc(buf, buflen + (need_len - encoding_header_len));
-               encoding_header = buf + encoding_header_pos;
-               end_of_encoding_header = encoding_header + encoding_header_len;
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & STALE)
+                       continue;
+               return commit;
        }
-       memmove(end_of_encoding_header + (need_len - encoding_header_len),
-               end_of_encoding_header,
-               buflen - (encoding_header_pos + encoding_header_len));
-       memcpy(encoding_header + 9, encoding, strlen(encoding));
-       encoding_header[9 + new_len] = '\n';
-       return buf;
+       return NULL;
 }
 
-static char *logmsg_reencode(const struct commit *commit,
-                            const char *output_encoding)
+/* 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 const char *utf8 = "utf-8";
-       const char *use_encoding;
-       char *encoding;
-       char *out;
-
-       if (!*output_encoding)
-               return NULL;
-       encoding = get_header(commit, "encoding");
-       use_encoding = encoding ? encoding : utf8;
-       if (!strcmp(use_encoding, output_encoding))
-               if (encoding) /* we'll strip encoding header later */
-                       out = xstrdup(commit->buffer);
-               else
-                       return NULL; /* nothing to do */
-       else
-               out = reencode_string(commit->buffer,
-                                     output_encoding, use_encoding);
-       if (out)
-               out = replace_encoding_header(out, output_encoding);
+       struct commit_list *list = NULL;
+       struct commit_list *result = NULL;
+       int i;
 
-       free(encoding);
-       return out;
-}
+       one->object.flags |= PARENT1;
+       commit_list_insert_by_date(one, &list);
+       if (!n)
+               return list;
+       for (i = 0; i < n; i++) {
+               twos[i]->object.flags |= PARENT2;
+               commit_list_insert_by_date(twos[i], &list);
+       }
 
-static void fill_person(struct interp *table, const char *msg, int len)
-{
-       int start, end, tz = 0;
-       unsigned long date;
-       char *ep;
-
-       /* parse name */
-       for (end = 0; end < len && msg[end] != '<'; end++)
-               ; /* do nothing */
-       start = end + 1;
-       while (end > 0 && isspace(msg[end - 1]))
-               end--;
-       table[0].value = xstrndup(msg, end);
-
-       if (start >= len)
-               return;
+       while (interesting(list)) {
+               struct commit *commit;
+               struct commit_list *parents;
+               struct commit_list *next;
+               int flags;
 
-       /* parse email */
-       for (end = start + 1; end < len && msg[end] != '>'; end++)
-               ; /* do nothing */
+               commit = list->item;
+               next = list->next;
+               free(list);
+               list = next;
 
-       if (end >= len)
-               return;
+               flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
+               if (flags == (PARENT1 | PARENT2)) {
+                       if (!(commit->object.flags & RESULT)) {
+                               commit->object.flags |= RESULT;
+                               commit_list_insert_by_date(commit, &result);
+                       }
+                       /* Mark parents of a found merge stale */
+                       flags |= STALE;
+               }
+               parents = commit->parents;
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parents = parents->next;
+                       if ((p->object.flags & flags) == flags)
+                               continue;
+                       if (parse_commit(p))
+                               return NULL;
+                       p->object.flags |= flags;
+                       commit_list_insert_by_date(p, &list);
+               }
+       }
 
-       table[1].value = xstrndup(msg + start, end - start);
+       free_commit_list(list);
+       return result;
+}
 
-       /* parse date */
-       for (start = end + 1; start < len && isspace(msg[start]); start++)
-               ; /* do nothing */
-       if (start >= len)
-               return;
-       date = strtoul(msg + start, &ep, 10);
-       if (msg + start == ep)
-               return;
+static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
+{
+       struct commit_list *list = NULL;
+       struct commit_list *result = NULL;
+       int i;
 
-       table[5].value = xstrndup(msg + start, ep - (msg + start));
+       for (i = 0; i < n; i++) {
+               if (one == twos[i])
+                       /*
+                        * We do not mark this even with RESULT so we do not
+                        * have to clean it up.
+                        */
+                       return commit_list_insert(one, &result);
+       }
 
-       /* parse tz */
-       for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
-               ; /* do nothing */
-       if (start + 1 < len) {
-               tz = strtoul(msg + start + 1, NULL, 10);
-               if (msg[start] == '-')
-                       tz = -tz;
+       if (parse_commit(one))
+               return NULL;
+       for (i = 0; i < n; i++) {
+               if (parse_commit(twos[i]))
+                       return NULL;
        }
 
-       interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
-       interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
-       interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
-       interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
+       list = paint_down_to_common(one, n, twos);
+
+       while (list) {
+               struct commit_list *next = list->next;
+               if (!(list->item->object.flags & STALE))
+                       commit_list_insert_by_date(list->item, &result);
+               free(list);
+               list = next;
+       }
+       return result;
 }
 
-static long format_commit_message(const struct commit *commit,
-               const char *msg, char **buf_p, unsigned long *space_p)
+struct commit_list *get_octopus_merge_bases(struct commit_list *in)
 {
-       struct interp table[] = {
-               { "%H" },       /* commit hash */
-               { "%h" },       /* abbreviated commit hash */
-               { "%T" },       /* tree hash */
-               { "%t" },       /* abbreviated tree hash */
-               { "%P" },       /* parent hashes */
-               { "%p" },       /* abbreviated parent hashes */
-               { "%an" },      /* author name */
-               { "%ae" },      /* author email */
-               { "%ad" },      /* author date */
-               { "%aD" },      /* author date, RFC2822 style */
-               { "%ar" },      /* author date, relative */
-               { "%at" },      /* author date, UNIX timestamp */
-               { "%ai" },      /* author date, ISO 8601 */
-               { "%cn" },      /* committer name */
-               { "%ce" },      /* committer email */
-               { "%cd" },      /* committer date */
-               { "%cD" },      /* committer date, RFC2822 style */
-               { "%cr" },      /* committer date, relative */
-               { "%ct" },      /* committer date, UNIX timestamp */
-               { "%ci" },      /* committer date, ISO 8601 */
-               { "%e" },       /* encoding */
-               { "%s" },       /* subject */
-               { "%b" },       /* body */
-               { "%Cred" },    /* red */
-               { "%Cgreen" },  /* green */
-               { "%Cblue" },   /* blue */
-               { "%Creset" },  /* reset color */
-               { "%n" },       /* newline */
-               { "%m" },       /* left/right/bottom */
-       };
-       enum interp_index {
-               IHASH = 0, IHASH_ABBREV,
-               ITREE, ITREE_ABBREV,
-               IPARENTS, IPARENTS_ABBREV,
-               IAUTHOR_NAME, IAUTHOR_EMAIL,
-               IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
-               IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
-               ICOMMITTER_NAME, ICOMMITTER_EMAIL,
-               ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
-               ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
-               ICOMMITTER_ISO8601,
-               IENCODING,
-               ISUBJECT,
-               IBODY,
-               IRED, IGREEN, IBLUE, IRESET_COLOR,
-               INEWLINE,
-               ILEFT_RIGHT,
-       };
-       struct commit_list *p;
-       char parents[1024];
-       int i;
-       enum { HEADER, SUBJECT, BODY } state;
-
-       if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
-               die("invalid interp table!");
-
-       /* these are independent of the commit */
-       interp_set_entry(table, IRED, "\033[31m");
-       interp_set_entry(table, IGREEN, "\033[32m");
-       interp_set_entry(table, IBLUE, "\033[34m");
-       interp_set_entry(table, IRESET_COLOR, "\033[m");
-       interp_set_entry(table, INEWLINE, "\n");
-
-       /* these depend on the commit */
-       if (!commit->object.parsed)
-               parse_object(commit->object.sha1);
-       interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
-       interp_set_entry(table, IHASH_ABBREV,
-                       find_unique_abbrev(commit->object.sha1,
-                               DEFAULT_ABBREV));
-       interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
-       interp_set_entry(table, ITREE_ABBREV,
-                       find_unique_abbrev(commit->tree->object.sha1,
-                               DEFAULT_ABBREV));
-       interp_set_entry(table, ILEFT_RIGHT,
-                        (commit->object.flags & BOUNDARY)
-                        ? "-"
-                        : (commit->object.flags & SYMMETRIC_LEFT)
-                        ? "<"
-                        : ">");
-
-       parents[1] = 0;
-       for (i = 0, p = commit->parents;
-                       p && i < sizeof(parents) - 1;
-                       p = p->next)
-               i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
-                       sha1_to_hex(p->item->object.sha1));
-       interp_set_entry(table, IPARENTS, parents + 1);
-
-       parents[1] = 0;
-       for (i = 0, p = commit->parents;
-                       p && i < sizeof(parents) - 1;
-                       p = p->next)
-               i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
-                       find_unique_abbrev(p->item->object.sha1,
-                               DEFAULT_ABBREV));
-       interp_set_entry(table, IPARENTS_ABBREV, parents + 1);
-
-       for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
-               int eol;
-               for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
-                       ; /* do nothing */
+       struct commit_list *i, *j, *k, *ret = NULL;
+       struct commit_list **pptr = &ret;
 
-               if (state == SUBJECT) {
-                       table[ISUBJECT].value = xstrndup(msg + i, eol - i);
-                       i = eol;
+       for (i = in; i; i = i->next) {
+               if (!ret)
+                       pptr = &commit_list_insert(i->item, pptr)->next;
+               else {
+                       struct commit_list *new = NULL, *end = NULL;
+
+                       for (j = ret; j; j = j->next) {
+                               struct commit_list *bases;
+                               bases = get_merge_bases(i->item, j->item, 1);
+                               if (!new)
+                                       new = bases;
+                               else
+                                       end->next = bases;
+                               for (k = bases; k; k = k->next)
+                                       end = k;
+                       }
+                       ret = new;
                }
-               if (i == eol) {
-                       state++;
-                       /* strip empty lines */
-                       while (msg[eol + 1] == '\n')
-                               eol++;
-               } else if (!prefixcmp(msg + i, "author "))
-                       fill_person(table + IAUTHOR_NAME,
-                                       msg + i + 7, eol - i - 7);
-               else if (!prefixcmp(msg + i, "committer "))
-                       fill_person(table + ICOMMITTER_NAME,
-                                       msg + i + 10, eol - i - 10);
-               else if (!prefixcmp(msg + i, "encoding "))
-                       table[IENCODING].value =
-                               xstrndup(msg + i + 9, eol - i - 9);
-               i = eol;
        }
-       if (msg[i])
-               table[IBODY].value = xstrdup(msg + i);
-       for (i = 0; i < ARRAY_SIZE(table); i++)
-               if (!table[i].value)
-                       interp_set_entry(table, i, "<unknown>");
-
-       do {
-               char *buf = *buf_p;
-               unsigned long space = *space_p;
-
-               space = interpolate(buf, space, user_format,
-                                   table, ARRAY_SIZE(table));
-               if (!space)
-                       break;
-               buf = xrealloc(buf, space);
-               *buf_p = buf;
-               *space_p = space;
-       } while (1);
-       interp_clear_table(table, ARRAY_SIZE(table));
-
-       return strlen(*buf_p);
+       return ret;
 }
 
-static void pp_header(enum cmit_fmt fmt,
-                     int abbrev,
-                     enum date_mode dmode,
-                     const char *encoding,
-                     const struct commit *commit,
-                     const char **msg_p,
-                     unsigned long *len_p,
-                     unsigned long *ofs_p,
-                     char **buf_p,
-                     unsigned long *space_p)
+static int remove_redundant(struct commit **array, int cnt)
 {
-       int parents_shown = 0;
-
-       for (;;) {
-               const char *line = *msg_p;
-               char *dst;
-               int linelen = get_one_line(*msg_p, *len_p);
-               unsigned long len;
+       /*
+        * Some commit in the array may be an ancestor of
+        * another commit.  Move such commit to the end of
+        * the array, and return the number of commits that
+        * are independent from each other.
+        */
+       struct commit **work;
+       unsigned char *redundant;
+       int *filled_index;
+       int i, j, filled;
 
-               if (!linelen)
-                       return;
-               *msg_p += linelen;
-               *len_p -= linelen;
+       work = xcalloc(cnt, sizeof(*work));
+       redundant = xcalloc(cnt, 1);
+       filled_index = xmalloc(sizeof(*filled_index) * (cnt - 1));
 
-               if (linelen == 1)
-                       /* End of header */
-                       return;
-
-               ALLOC_GROW(*buf_p, linelen + *ofs_p + 20, *space_p);
-               dst = *buf_p + *ofs_p;
+       for (i = 0; i < cnt; i++)
+               parse_commit(array[i]);
+       for (i = 0; i < cnt; i++) {
+               struct commit_list *common;
 
-               if (fmt == CMIT_FMT_RAW) {
-                       memcpy(dst, line, linelen);
-                       *ofs_p += linelen;
+               if (redundant[i])
                        continue;
+               for (j = filled = 0; j < cnt; j++) {
+                       if (i == j || redundant[j])
+                               continue;
+                       filled_index[filled] = j;
+                       work[filled++] = array[j];
                }
+               common = paint_down_to_common(array[i], filled, work);
+               if (array[i]->object.flags & PARENT2)
+                       redundant[i] = 1;
+               for (j = 0; j < filled; j++)
+                       if (work[j]->object.flags & PARENT1)
+                               redundant[filled_index[j]] = 1;
+               clear_commit_marks(array[i], all_flags);
+               for (j = 0; j < filled; j++)
+                       clear_commit_marks(work[j], all_flags);
+               free_commit_list(common);
+       }
 
-               if (!memcmp(line, "parent ", 7)) {
-                       if (linelen != 48)
-                               die("bad parent line in commit");
-                       continue;
-               }
+       /* Now collect the result */
+       memcpy(work, array, sizeof(*array) * cnt);
+       for (i = filled = 0; i < cnt; i++)
+               if (!redundant[i])
+                       array[filled++] = work[i];
+       for (j = filled, i = 0; i < cnt; i++)
+               if (redundant[i])
+                       array[j++] = work[i];
+       free(work);
+       free(redundant);
+       free(filled_index);
+       return filled;
+}
 
-               if (!parents_shown) {
-                       struct commit_list *parent;
-                       int num;
-                       for (parent = commit->parents, num = 0;
-                            parent;
-                            parent = parent->next, num++)
-                               ;
-                       /* with enough slop */
-                       num = *ofs_p + num * 50 + 20;
-                       ALLOC_GROW(*buf_p, num, *space_p);
-                       dst = *buf_p + *ofs_p;
-                       *ofs_p += add_merge_info(fmt, dst, commit, abbrev);
-                       parents_shown = 1;
-               }
+struct commit_list *get_merge_bases_many(struct commit *one,
+                                        int n,
+                                        struct commit **twos,
+                                        int cleanup)
+{
+       struct commit_list *list;
+       struct commit **rslt;
+       struct commit_list *result;
+       int cnt, i;
 
-               /*
-                * MEDIUM == DEFAULT shows only author with dates.
-                * FULL shows both authors but not dates.
-                * FULLER shows both authors and dates.
-                */
-               if (!memcmp(line, "author ", 7)) {
-                       len = linelen;
-                       if (fmt == CMIT_FMT_EMAIL)
-                               len = bound_rfc2047(linelen, encoding);
-                       ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
-                       dst = *buf_p + *ofs_p;
-                       *ofs_p += add_user_info("Author", fmt, dst,
-                                               line + 7, dmode, encoding);
+       result = merge_bases_many(one, n, twos);
+       for (i = 0; i < n; i++) {
+               if (one == twos[i])
+                       return result;
+       }
+       if (!result || !result->next) {
+               if (cleanup) {
+                       clear_commit_marks(one, all_flags);
+                       for (i = 0; i < n; i++)
+                               clear_commit_marks(twos[i], all_flags);
                }
+               return result;
+       }
 
-               if (!memcmp(line, "committer ", 10) &&
-                   (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
-                       len = linelen;
-                       if (fmt == CMIT_FMT_EMAIL)
-                               len = bound_rfc2047(linelen, encoding);
-                       ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
-                       dst = *buf_p + *ofs_p;
-                       *ofs_p += add_user_info("Commit", fmt, dst,
-                                               line + 10, dmode, encoding);
-               }
+       /* There are more than one */
+       cnt = 0;
+       list = result;
+       while (list) {
+               list = list->next;
+               cnt++;
        }
-}
+       rslt = xcalloc(cnt, sizeof(*rslt));
+       for (list = result, i = 0; list; list = list->next)
+               rslt[i++] = list->item;
+       free_commit_list(result);
 
-static void pp_title_line(enum cmit_fmt fmt,
-                         const char **msg_p,
-                         unsigned long *len_p,
-                         unsigned long *ofs_p,
-                         char **buf_p,
-                         unsigned long *space_p,
-                         int indent,
-                         const char *subject,
-                         const char *after_subject,
-                         const char *encoding,
-                         int plain_non_ascii)
-{
-       char *title;
-       unsigned long title_alloc, title_len;
-       unsigned long len;
+       clear_commit_marks(one, all_flags);
+       for (i = 0; i < n; i++)
+               clear_commit_marks(twos[i], all_flags);
 
-       title_len = 0;
-       title_alloc = 80;
-       title = xmalloc(title_alloc);
-       for (;;) {
-               const char *line = *msg_p;
-               int linelen = get_one_line(line, *len_p);
-               *msg_p += linelen;
-               *len_p -= linelen;
+       cnt = remove_redundant(rslt, cnt);
+       result = NULL;
+       for (i = 0; i < cnt; i++)
+               commit_list_insert_by_date(rslt[i], &result);
+       free(rslt);
+       return result;
+}
 
-               if (!linelen || is_empty_line(line, &linelen))
-                       break;
+struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
+                                   int cleanup)
+{
+       return get_merge_bases_many(one, 1, &two, cleanup);
+}
 
-               if (title_alloc <= title_len + linelen + 2) {
-                       title_alloc = title_len + linelen + 80;
-                       title = xrealloc(title, title_alloc);
-               }
-               len = 0;
-               if (title_len) {
-                       if (fmt == CMIT_FMT_EMAIL) {
-                               len++;
-                               title[title_len++] = '\n';
-                       }
-                       len++;
-                       title[title_len++] = ' ';
-               }
-               memcpy(title + title_len, line, linelen);
-               title_len += linelen;
-       }
+/*
+ * Is "commit" a decendant of one of the elements on the "with_commit" list?
+ */
+int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
+{
+       if (!with_commit)
+               return 1;
+       while (with_commit) {
+               struct commit *other;
 
-       /* Enough slop for the MIME header and rfc2047 */
-       len = bound_rfc2047(title_len, encoding)+ 1000;
-       if (subject)
-               len += strlen(subject);
-       if (after_subject)
-               len += strlen(after_subject);
-       if (encoding)
-               len += strlen(encoding);
-       ALLOC_GROW(*buf_p, title_len + *ofs_p + len, *space_p);
-
-       if (subject) {
-               len = strlen(subject);
-               memcpy(*buf_p + *ofs_p, subject, len);
-               *ofs_p += len;
-               *ofs_p += add_rfc2047(*buf_p + *ofs_p,
-                                     title, title_len, encoding);
-       } else {
-               memcpy(*buf_p + *ofs_p, title, title_len);
-               *ofs_p += title_len;
-       }
-       (*buf_p)[(*ofs_p)++] = '\n';
-       if (plain_non_ascii) {
-               const char *header_fmt =
-                       "MIME-Version: 1.0\n"
-                       "Content-Type: text/plain; charset=%s\n"
-                       "Content-Transfer-Encoding: 8bit\n";
-               *ofs_p += snprintf(*buf_p + *ofs_p,
-                                  *space_p - *ofs_p,
-                                  header_fmt, encoding);
-       }
-       if (after_subject) {
-               len = strlen(after_subject);
-               memcpy(*buf_p + *ofs_p, after_subject, len);
-               *ofs_p += len;
-       }
-       free(title);
-       if (fmt == CMIT_FMT_EMAIL) {
-               ALLOC_GROW(*buf_p, *ofs_p + 20, *space_p);
-               (*buf_p)[(*ofs_p)++] = '\n';
+               other = with_commit->item;
+               with_commit = with_commit->next;
+               if (in_merge_bases(other, commit))
+                       return 1;
        }
+       return 0;
 }
 
-static void pp_remainder(enum cmit_fmt fmt,
-                        const char **msg_p,
-                        unsigned long *len_p,
-                        unsigned long *ofs_p,
-                        char **buf_p,
-                        unsigned long *space_p,
-                        int indent)
+/*
+ * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
+ */
+int in_merge_bases(struct commit *commit, struct commit *reference)
 {
-       int first = 1;
-       for (;;) {
-               const char *line = *msg_p;
-               int linelen = get_one_line(line, *len_p);
-               *msg_p += linelen;
-               *len_p -= linelen;
+       struct commit_list *bases;
+       int ret = 0;
 
-               if (!linelen)
-                       break;
+       if (parse_commit(commit) || parse_commit(reference))
+               return ret;
 
-               if (is_empty_line(line, &linelen)) {
-                       if (first)
-                               continue;
-                       if (fmt == CMIT_FMT_SHORT)
-                               break;
-               }
-               first = 0;
+       bases = paint_down_to_common(commit, 1, &reference);
+       if (commit->object.flags & PARENT2)
+               ret = 1;
+       clear_commit_marks(commit, all_flags);
+       clear_commit_marks(reference, all_flags);
+       free_commit_list(bases);
+       return ret;
+}
+
+struct commit_list *reduce_heads(struct commit_list *heads)
+{
+       struct commit_list *p;
+       struct commit_list *result = NULL, **tail = &result;
+       struct commit **array;
+       int num_head, i;
+
+       if (!heads)
+               return NULL;
 
-               ALLOC_GROW(*buf_p, *ofs_p + linelen + indent + 20, *space_p);
-               if (indent) {
-                       memset(*buf_p + *ofs_p, ' ', indent);
-                       *ofs_p += indent;
+       /* Uniquify */
+       for (p = heads; p; p = p->next)
+               p->item->object.flags &= ~STALE;
+       for (p = heads, num_head = 0; p; p = p->next) {
+               if (p->item->object.flags & STALE)
+                       continue;
+               p->item->object.flags |= STALE;
+               num_head++;
+       }
+       array = xcalloc(sizeof(*array), num_head);
+       for (p = heads, i = 0; p; p = p->next) {
+               if (p->item->object.flags & STALE) {
+                       array[i++] = p->item;
+                       p->item->object.flags &= ~STALE;
                }
-               memcpy(*buf_p + *ofs_p, line, linelen);
-               *ofs_p += linelen;
-               (*buf_p)[(*ofs_p)++] = '\n';
        }
+       num_head = remove_redundant(array, num_head);
+       for (i = 0; i < num_head; i++)
+               tail = &commit_list_insert(array[i], tail)->next;
+       return result;
 }
 
-unsigned long pretty_print_commit(enum cmit_fmt fmt,
-                                 const struct commit *commit,
-                                 unsigned long len,
-                                 char **buf_p, unsigned long *space_p,
-                                 int abbrev, const char *subject,
-                                 const char *after_subject,
-                                 enum date_mode dmode,
-                                 int plain_non_ascii)
+static const char gpg_sig_header[] = "gpgsig";
+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
 {
-       unsigned long offset = 0;
-       unsigned long beginning_of_body;
-       int indent = 4;
-       const char *msg = commit->buffer;
-       char *reencoded;
-       const char *encoding;
-       char *buf;
+       struct strbuf sig = STRBUF_INIT;
+       int inspos, copypos;
 
-       if (fmt == CMIT_FMT_USERFORMAT)
-               return format_commit_message(commit, msg, buf_p, space_p);
-
-       encoding = (git_log_output_encoding
-                   ? git_log_output_encoding
-                   : git_commit_encoding);
-       if (!encoding)
-               encoding = "utf-8";
-       reencoded = logmsg_reencode(commit, encoding);
-       if (reencoded) {
-               msg = reencoded;
-               len = strlen(reencoded);
+       /* find the end of the header */
+       inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+
+       if (!keyid || !*keyid)
+               keyid = get_signing_key();
+       if (sign_buffer(buf, &sig, keyid)) {
+               strbuf_release(&sig);
+               return -1;
        }
 
-       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
-               indent = 0;
+       for (copypos = 0; sig.buf[copypos]; ) {
+               const char *bol = sig.buf + copypos;
+               const char *eol = strchrnul(bol, '\n');
+               int len = (eol - bol) + !!*eol;
 
-       /* After-subject is used to pass in Content-Type: multipart
-        * MIME header; in that case we do not have to do the
-        * plaintext content type even if the commit message has
-        * non 7-bit ASCII character.  Otherwise, check if we need
-        * to say this is not a 7-bit ASCII.
-        */
-       if (fmt == CMIT_FMT_EMAIL && !after_subject) {
-               int i, ch, in_body;
-
-               for (in_body = i = 0; (ch = msg[i]) && i < len; i++) {
-                       if (!in_body) {
-                               /* author could be non 7-bit ASCII but
-                                * the log may be so; skip over the
-                                * header part first.
-                                */
-                               if (ch == '\n' &&
-                                   i + 1 < len && msg[i+1] == '\n')
-                                       in_body = 1;
-                       }
-                       else if (non_ascii(ch)) {
-                               plain_non_ascii = 1;
-                               break;
-                       }
+               if (!copypos) {
+                       strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+                       inspos += gpg_sig_header_len;
                }
+               strbuf_insert(buf, inspos++, " ", 1);
+               strbuf_insert(buf, inspos, bol, len);
+               inspos += len;
+               copypos += len;
        }
+       strbuf_release(&sig);
+       return 0;
+}
 
-       pp_header(fmt, abbrev, dmode, encoding,
-                 commit, &msg, &len,
-                 &offset, buf_p, space_p);
-       if (fmt != CMIT_FMT_ONELINE && !subject) {
-               ALLOC_GROW(*buf_p, offset + 20, *space_p);
-               (*buf_p)[offset++] = '\n';
-       }
-
-       /* Skip excess blank lines at the beginning of body, if any... */
-       for (;;) {
-               int linelen = get_one_line(msg, len);
-               int ll = linelen;
-               if (!linelen)
-                       break;
-               if (!is_empty_line(msg, &ll))
-                       break;
-               msg += linelen;
-               len -= linelen;
+int parse_signed_commit(const unsigned char *sha1,
+                       struct strbuf *payload, struct strbuf *signature)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buffer = read_sha1_file(sha1, &type, &size);
+       int in_signature, saw_signature = -1;
+       char *line, *tail;
+
+       if (!buffer || type != OBJ_COMMIT)
+               goto cleanup;
+
+       line = buffer;
+       tail = buffer + size;
+       in_signature = 0;
+       saw_signature = 0;
+       while (line < tail) {
+               const char *sig = NULL;
+               char *next = memchr(line, '\n', tail - line);
+
+               next = next ? next + 1 : tail;
+               if (in_signature && line[0] == ' ')
+                       sig = line + 1;
+               else if (!prefixcmp(line, gpg_sig_header) &&
+                        line[gpg_sig_header_len] == ' ')
+                       sig = line + gpg_sig_header_len + 1;
+               if (sig) {
+                       strbuf_add(signature, sig, next - sig);
+                       saw_signature = 1;
+                       in_signature = 1;
+               } else {
+                       if (*line == '\n')
+                               /* dump the whole remainder of the buffer */
+                               next = tail;
+                       strbuf_add(payload, line, next - line);
+                       in_signature = 0;
+               }
+               line = next;
        }
+ cleanup:
+       free(buffer);
+       return saw_signature;
+}
 
-       /* These formats treat the title line specially. */
-       if (fmt == CMIT_FMT_ONELINE
-           || fmt == CMIT_FMT_EMAIL)
-               pp_title_line(fmt, &msg, &len, &offset,
-                             buf_p, space_p, indent,
-                             subject, after_subject, encoding,
-                             plain_non_ascii);
-
-       beginning_of_body = offset;
-       if (fmt != CMIT_FMT_ONELINE)
-               pp_remainder(fmt, &msg, &len, &offset,
-                            buf_p, space_p, indent);
-
-       while (offset && isspace((*buf_p)[offset-1]))
-               offset--;
-
-       ALLOC_GROW(*buf_p, offset + 20, *space_p);
-       buf = *buf_p;
-
-       /* Make sure there is an EOLN for the non-oneline case */
-       if (fmt != CMIT_FMT_ONELINE)
-               buf[offset++] = '\n';
+static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
+{
+       struct merge_remote_desc *desc;
+       struct commit_extra_header *mergetag;
+       char *buf;
+       unsigned long size, len;
+       enum object_type type;
 
+       desc = merge_remote_util(parent);
+       if (!desc || !desc->obj)
+               return;
+       buf = read_sha1_file(desc->obj->sha1, &type, &size);
+       if (!buf || type != OBJ_TAG)
+               goto free_return;
+       len = parse_signature(buf, size);
+       if (size == len)
+               goto free_return;
        /*
-        * The caller may append additional body text in e-mail
-        * format.  Make sure we did not strip the blank line
-        * between the header and the body.
+        * We could verify this signature and either omit the tag when
+        * it does not validate, but the integrator may not have the
+        * public key of the signer of the tag he is merging, while a
+        * later auditor may have it while auditing, so let's not run
+        * verify-signed-buffer here for now...
+        *
+        * if (verify_signed_buffer(buf, len, buf + len, size - len, ...))
+        *      warn("warning: signed tag unverified.");
         */
-       if (fmt == CMIT_FMT_EMAIL && offset <= beginning_of_body)
-               buf[offset++] = '\n';
-       buf[offset] = '\0';
-       free(reencoded);
-       return offset;
+       mergetag = xcalloc(1, sizeof(*mergetag));
+       mergetag->key = xstrdup("mergetag");
+       mergetag->value = buf;
+       mergetag->len = size;
+
+       **tail = mergetag;
+       *tail = &mergetag->next;
+       return;
+
+free_return:
+       free(buf);
 }
 
-struct commit *pop_commit(struct commit_list **stack)
+void append_merge_tag_headers(struct commit_list *parents,
+                             struct commit_extra_header ***tail)
 {
-       struct commit_list *top = *stack;
-       struct commit *item = top ? top->item : NULL;
-
-       if (top) {
-               *stack = top->next;
-               free(top);
+       while (parents) {
+               struct commit *parent = parents->item;
+               handle_signed_tag(parent, tail);
+               parents = parents->next;
        }
-       return item;
 }
 
-void topo_sort_default_setter(struct commit *c, void *data)
+static void add_extra_header(struct strbuf *buffer,
+                            struct commit_extra_header *extra)
 {
-       c->util = data;
+       strbuf_addstr(buffer, extra->key);
+       if (extra->len)
+               strbuf_add_lines(buffer, " ", extra->value, extra->len);
+       else
+               strbuf_addch(buffer, '\n');
 }
 
-void *topo_sort_default_getter(struct commit *c)
+struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
+                                                     const char **exclude)
 {
-       return c->util;
+       struct commit_extra_header *extra = NULL;
+       unsigned long size;
+       enum object_type type;
+       char *buffer = read_sha1_file(commit->object.sha1, &type, &size);
+       if (buffer && type == OBJ_COMMIT)
+               extra = read_commit_extra_header_lines(buffer, size, exclude);
+       free(buffer);
+       return extra;
 }
 
-/*
- * Performs an in-place topological sort on the list supplied.
- */
-void sort_in_topological_order(struct commit_list ** list, int lifo)
+static inline int standard_header_field(const char *field, size_t len)
 {
-       sort_in_topological_order_fn(list, lifo, topo_sort_default_setter,
-                                    topo_sort_default_getter);
+       return ((len == 4 && !memcmp(field, "tree ", 5)) ||
+               (len == 6 && !memcmp(field, "parent ", 7)) ||
+               (len == 6 && !memcmp(field, "author ", 7)) ||
+               (len == 9 && !memcmp(field, "committer ", 10)) ||
+               (len == 8 && !memcmp(field, "encoding ", 9)));
 }
 
-void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
-                                 topo_sort_set_fn_t setter,
-                                 topo_sort_get_fn_t getter)
+static int excluded_header_field(const char *field, size_t len, const char **exclude)
 {
-       struct commit_list * next = *list;
-       struct commit_list * work = NULL, **insert;
-       struct commit_list ** pptr = list;
-       struct sort_node * nodes;
-       struct sort_node * next_nodes;
-       int count = 0;
-
-       /* determine the size of the list */
-       while (next) {
-               next = next->next;
-               count++;
-       }
+       if (!exclude)
+               return 0;
 
-       if (!count)
-               return;
-       /* allocate an array to help sort the list */
-       nodes = xcalloc(count, sizeof(*nodes));
-       /* link the list to the array */
-       next_nodes = nodes;
-       next=*list;
-       while (next) {
-               next_nodes->list_item = next;
-               setter(next->item, next_nodes);
-               next_nodes++;
-               next = next->next;
+       while (*exclude) {
+               size_t xlen = strlen(*exclude);
+               if (len == xlen &&
+                   !memcmp(field, *exclude, xlen) && field[xlen] == ' ')
+                       return 1;
+               exclude++;
        }
-       /* update the indegree */
-       next=*list;
-       while (next) {
-               struct commit_list * parents = next->item->parents;
-               while (parents) {
-                       struct commit * parent=parents->item;
-                       struct sort_node * pn = (struct sort_node *) getter(parent);
+       return 0;
+}
 
-                       if (pn)
-                               pn->indegree++;
-                       parents=parents->next;
+static struct commit_extra_header *read_commit_extra_header_lines(
+       const char *buffer, size_t size,
+       const char **exclude)
+{
+       struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
+       const char *line, *next, *eof, *eob;
+       struct strbuf buf = STRBUF_INIT;
+
+       for (line = buffer, eob = line + size;
+            line < eob && *line != '\n';
+            line = next) {
+               next = memchr(line, '\n', eob - line);
+               next = next ? next + 1 : eob;
+               if (*line == ' ') {
+                       /* continuation */
+                       if (it)
+                               strbuf_add(&buf, line + 1, next - (line + 1));
+                       continue;
                }
-               next=next->next;
-       }
-       /*
-         * find the tips
-         *
-         * tips are nodes not reachable from any other node in the list
-         *
-         * the tips serve as a starting set for the work queue.
-         */
-       next=*list;
-       insert = &work;
-       while (next) {
-               struct sort_node * node = (struct sort_node *) getter(next->item);
+               if (it)
+                       it->value = strbuf_detach(&buf, &it->len);
+               strbuf_reset(&buf);
+               it = NULL;
 
-               if (node->indegree == 0) {
-                       insert = &commit_list_insert(next->item, insert)->next;
-               }
-               next=next->next;
-       }
+               eof = strchr(line, ' ');
+               if (next <= eof)
+                       eof = next;
 
-       /* process the list in topological order */
-       if (!lifo)
-               sort_by_date(&work);
-       while (work) {
-               struct commit * work_item = pop_commit(&work);
-               struct sort_node * work_node = (struct sort_node *) getter(work_item);
-               struct commit_list * parents = work_item->parents;
+               if (standard_header_field(line, eof - line) ||
+                   excluded_header_field(line, eof - line, exclude))
+                       continue;
 
-               while (parents) {
-                       struct commit * parent=parents->item;
-                       struct sort_node * pn = (struct sort_node *) getter(parent);
-
-                       if (pn) {
-                               /*
-                                * parents are only enqueued for emission
-                                 * when all their children have been emitted thereby
-                                 * guaranteeing topological order.
-                                 */
-                               pn->indegree--;
-                               if (!pn->indegree) {
-                                       if (!lifo)
-                                               insert_by_date(parent, &work);
-                                       else
-                                               commit_list_insert(parent, &work);
-                               }
-                       }
-                       parents=parents->next;
-               }
-               /*
-                 * work_item is a commit all of whose children
-                 * have already been emitted. we can emit it now.
-                 */
-               *pptr = work_node->list_item;
-               pptr = &(*pptr)->next;
-               *pptr = NULL;
-               setter(work_item, NULL);
+               it = xcalloc(1, sizeof(*it));
+               it->key = xmemdupz(line, eof-line);
+               *tail = it;
+               tail = &it->next;
+               if (eof + 1 < next)
+                       strbuf_add(&buf, eof + 1, next - (eof + 1));
        }
-       free(nodes);
+       if (it)
+               it->value = strbuf_detach(&buf, &it->len);
+       return extra;
 }
 
-/* merge-base stuff */
+void free_commit_extra_headers(struct commit_extra_header *extra)
+{
+       while (extra) {
+               struct commit_extra_header *next = extra->next;
+               free(extra->key);
+               free(extra->value);
+               free(extra);
+               extra = next;
+       }
+}
 
-/* bits #0..15 in revision.h */
-#define PARENT1                (1u<<16)
-#define PARENT2                (1u<<17)
-#define STALE          (1u<<18)
-#define RESULT         (1u<<19)
+int commit_tree(const struct strbuf *msg, unsigned char *tree,
+               struct commit_list *parents, unsigned char *ret,
+               const char *author, const char *sign_commit)
+{
+       struct commit_extra_header *extra = NULL, **tail = &extra;
+       int result;
 
-static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
+       append_merge_tag_headers(parents, &tail);
+       result = commit_tree_extended(msg, tree, parents, ret,
+                                     author, sign_commit, extra);
+       free_commit_extra_headers(extra);
+       return result;
+}
 
-static struct commit *interesting(struct commit_list *list)
+static int find_invalid_utf8(const char *buf, int len)
 {
-       while (list) {
-               struct commit *commit = list->item;
-               list = list->next;
-               if (commit->object.flags & STALE)
+       int offset = 0;
+
+       while (len) {
+               unsigned char c = *buf++;
+               int bytes, bad_offset;
+
+               len--;
+               offset++;
+
+               /* Simple US-ASCII? No worries. */
+               if (c < 0x80)
                        continue;
-               return commit;
-       }
-       return NULL;
-}
 
-static struct commit_list *merge_bases(struct commit *one, struct commit *two)
-{
-       struct commit_list *list = NULL;
-       struct commit_list *result = NULL;
+               bad_offset = offset-1;
 
-       if (one == two)
-               /* We do not mark this even with RESULT so we do not
-                * have to clean it up.
+               /*
+                * Count how many more high bits set: that's how
+                * many more bytes this sequence should have.
                 */
-               return commit_list_insert(one, &result);
+               bytes = 0;
+               while (c & 0x40) {
+                       c <<= 1;
+                       bytes++;
+               }
 
-       parse_commit(one);
-       parse_commit(two);
+               /* Must be between 1 and 5 more bytes */
+               if (bytes < 1 || bytes > 5)
+                       return bad_offset;
 
-       one->object.flags |= PARENT1;
-       two->object.flags |= PARENT2;
-       insert_by_date(one, &list);
-       insert_by_date(two, &list);
+               /* Do we *have* that many bytes? */
+               if (len < bytes)
+                       return bad_offset;
 
-       while (interesting(list)) {
-               struct commit *commit;
-               struct commit_list *parents;
-               struct commit_list *n;
-               int flags;
+               offset += bytes;
+               len -= bytes;
 
-               commit = list->item;
-               n = list->next;
-               free(list);
-               list = n;
+               /* And verify that they are good continuation bytes */
+               do {
+                       if ((*buf++ & 0xc0) != 0x80)
+                               return bad_offset;
+               } while (--bytes);
 
-               flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
-               if (flags == (PARENT1 | PARENT2)) {
-                       if (!(commit->object.flags & RESULT)) {
-                               commit->object.flags |= RESULT;
-                               insert_by_date(commit, &result);
-                       }
-                       /* Mark parents of a found merge stale */
-                       flags |= STALE;
-               }
-               parents = commit->parents;
-               while (parents) {
-                       struct commit *p = parents->item;
-                       parents = parents->next;
-                       if ((p->object.flags & flags) == flags)
-                               continue;
-                       parse_commit(p);
-                       p->object.flags |= flags;
-                       insert_by_date(p, &list);
-               }
+               /* We could/should check the value and length here too */
        }
+       return -1;
+}
 
-       /* Clean up the result to remove stale ones */
-       free_commit_list(list);
-       list = result; result = NULL;
-       while (list) {
-               struct commit_list *n = list->next;
-               if (!(list->item->object.flags & STALE))
-                       insert_by_date(list->item, &result);
-               free(list);
-               list = n;
+/*
+ * This verifies that the buffer is in proper utf8 format.
+ *
+ * If it isn't, it assumes any non-utf8 characters are Latin1,
+ * and does the conversion.
+ *
+ * Fixme: we should probably also disallow overlong forms and
+ * invalid characters. But we don't do that currently.
+ */
+static int verify_utf8(struct strbuf *buf)
+{
+       int ok = 1;
+       long pos = 0;
+
+       for (;;) {
+               int bad;
+               unsigned char c;
+               unsigned char replace[2];
+
+               bad = find_invalid_utf8(buf->buf + pos, buf->len - pos);
+               if (bad < 0)
+                       return ok;
+               pos += bad;
+               ok = 0;
+               c = buf->buf[pos];
+               strbuf_remove(buf, pos, 1);
+
+               /* We know 'c' must be in the range 128-255 */
+               replace[0] = 0xc0 + (c >> 6);
+               replace[1] = 0x80 + (c & 0x3f);
+               strbuf_insert(buf, pos, replace, 2);
+               pos += 2;
        }
-       return result;
 }
 
-struct commit_list *get_merge_bases(struct commit *one,
-                                   struct commit *two,
-                                    int cleanup)
+static const char commit_utf8_warn[] =
+"Warning: commit message did not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
+int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
+                        struct commit_list *parents, unsigned char *ret,
+                        const char *author, const char *sign_commit,
+                        struct commit_extra_header *extra)
 {
-       struct commit_list *list;
-       struct commit **rslt;
-       struct commit_list *result;
-       int cnt, i, j;
+       int result;
+       int encoding_is_utf8;
+       struct strbuf buffer;
 
-       result = merge_bases(one, two);
-       if (one == two)
-               return result;
-       if (!result || !result->next) {
-               if (cleanup) {
-                       clear_commit_marks(one, all_flags);
-                       clear_commit_marks(two, all_flags);
-               }
-               return result;
-       }
+       assert_sha1_type(tree, OBJ_TREE);
 
-       /* There are more than one */
-       cnt = 0;
-       list = result;
-       while (list) {
-               list = list->next;
-               cnt++;
-       }
-       rslt = xcalloc(cnt, sizeof(*rslt));
-       for (list = result, i = 0; list; list = list->next)
-               rslt[i++] = list->item;
-       free_commit_list(result);
+       if (memchr(msg->buf, '\0', msg->len))
+               return error("a NUL byte in commit log message not allowed.");
 
-       clear_commit_marks(one, all_flags);
-       clear_commit_marks(two, all_flags);
-       for (i = 0; i < cnt - 1; i++) {
-               for (j = i+1; j < cnt; j++) {
-                       if (!rslt[i] || !rslt[j])
-                               continue;
-                       result = merge_bases(rslt[i], rslt[j]);
-                       clear_commit_marks(rslt[i], all_flags);
-                       clear_commit_marks(rslt[j], all_flags);
-                       for (list = result; list; list = list->next) {
-                               if (rslt[i] == list->item)
-                                       rslt[i] = NULL;
-                               if (rslt[j] == list->item)
-                                       rslt[j] = NULL;
-                       }
-               }
+       /* Not having i18n.commitencoding is the same as having utf-8 */
+       encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+       strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
+       strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
+
+       /*
+        * NOTE! This ordering means that the same exact tree merged with a
+        * different order of parents will be a _different_ changeset even
+        * if everything else stays the same.
+        */
+       while (parents) {
+               struct commit_list *next = parents->next;
+               struct commit *parent = parents->item;
+
+               strbuf_addf(&buffer, "parent %s\n",
+                           sha1_to_hex(parent->object.sha1));
+               free(parents);
+               parents = next;
        }
 
-       /* Surviving ones in rslt[] are the independent results */
-       result = NULL;
-       for (i = 0; i < cnt; i++) {
-               if (rslt[i])
-                       insert_by_date(rslt[i], &result);
+       /* Person/date information */
+       if (!author)
+               author = git_author_info(IDENT_STRICT);
+       strbuf_addf(&buffer, "author %s\n", author);
+       strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_STRICT));
+       if (!encoding_is_utf8)
+               strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
+
+       while (extra) {
+               add_extra_header(&buffer, extra);
+               extra = extra->next;
        }
-       free(rslt);
+       strbuf_addch(&buffer, '\n');
+
+       /* And add the comment */
+       strbuf_addbuf(&buffer, msg);
+
+       /* And check the encoding */
+       if (encoding_is_utf8 && !verify_utf8(&buffer))
+               fprintf(stderr, commit_utf8_warn);
+
+       if (sign_commit && do_sign_commit(&buffer, sign_commit))
+               return -1;
+
+       result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+       strbuf_release(&buffer);
        return result;
 }
 
-int in_merge_bases(struct commit *commit, struct commit **reference, int num)
+struct commit *get_merge_parent(const char *name)
 {
-       struct commit_list *bases, *b;
-       int ret = 0;
-
-       if (num == 1)
-               bases = get_merge_bases(commit, *reference, 1);
-       else
-               die("not yet");
-       for (b = bases; b; b = b->next) {
-               if (!hashcmp(commit->object.sha1, b->item->object.sha1)) {
-                       ret = 1;
-                       break;
-               }
+       struct object *obj;
+       struct commit *commit;
+       unsigned char sha1[20];
+       if (get_sha1(name, sha1))
+               return NULL;
+       obj = parse_object(sha1);
+       commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
+       if (commit && !commit->util) {
+               struct merge_remote_desc *desc;
+               desc = xmalloc(sizeof(*desc));
+               desc->obj = obj;
+               desc->name = strdup(name);
+               commit->util = desc;
        }
+       return commit;
+}
 
-       free_commit_list(bases);
-       return ret;
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ *     struct commit_list *list;
+ *     struct commit_list **next = &list;
+ *
+ *     next = commit_list_append(c1, next);
+ *     next = commit_list_append(c2, next);
+ *     assert(commit_list_count(list) == 2);
+ *     return list;
+ */
+struct commit_list **commit_list_append(struct commit *commit,
+                                       struct commit_list **next)
+{
+       struct commit_list *new = xmalloc(sizeof(struct commit_list));
+       new->item = commit;
+       *next = new;
+       new->next = NULL;
+       return &new->next;
+}
+
+void print_commit_list(struct commit_list *list,
+                      const char *format_cur,
+                      const char *format_last)
+{
+       for ( ; list; list = list->next) {
+               const char *format = list->next ? format_cur : format_last;
+               printf(format, sha1_to_hex(list->item->object.sha1));
+       }
 }