git-send-email: skip RFC2047 quoting for ASCII subjects
[gitweb.git] / commit.c
index d39a9e96937d1d6f7e2932fe12729c25896b2977..02467676a3e70684065ed668b53e64c1cc561e1e 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -7,6 +7,9 @@
 #include "revision.h"
 #include "notes.h"
 #include "gpg-interface.h"
+#include "mergesort.h"
+
+static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
 int save_commit_buffer = 1;
 
@@ -67,7 +70,7 @@ struct commit *lookup_commit_reference_by_name(const char *name)
        unsigned char sha1[20];
        struct commit *commit;
 
-       if (get_sha1(name, sha1))
+       if (get_sha1_committish(name, sha1))
                return NULL;
        commit = lookup_commit_reference(sha1);
        if (!commit || parse_commit(commit))
@@ -390,15 +393,31 @@ struct commit_list * commit_list_insert_by_date(struct commit *item, struct comm
        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;
+}
+
+static void *commit_list_get_next(const void *a)
+{
+       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)
 {
-       struct commit_list *ret = NULL;
-       while (*list) {
-               commit_list_insert_by_date((*list)->item, &ret);
-               *list = (*list)->next;
-       }
-       *list = ret;
+       *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,
@@ -422,7 +441,8 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
        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;
@@ -437,12 +457,20 @@ 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;
        }
 }
 
+void clear_commit_marks(struct commit *commit, unsigned int mark)
+{
+       struct commit_list *list = NULL;
+       commit_list_insert(commit, &list);
+       while (list)
+               clear_commit_marks_1(&list, pop_commit(&list), mark);
+}
+
 void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
 {
        struct object *object;
@@ -692,6 +720,60 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)
        return ret;
 }
 
+static int remove_redundant(struct commit **array, int cnt)
+{
+       /*
+        * 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;
+
+       work = xcalloc(cnt, sizeof(*work));
+       redundant = xcalloc(cnt, 1);
+       filled_index = xmalloc(sizeof(*filled_index) * (cnt - 1));
+
+       for (i = 0; i < cnt; i++) {
+               struct commit_list *common;
+
+               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);
+       }
+
+       /* 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;
+}
+
 struct commit_list *get_merge_bases_many(struct commit *one,
                                         int n,
                                         struct commit **twos,
@@ -700,7 +782,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
        struct commit_list *list;
        struct commit **rslt;
        struct commit_list *result;
-       int cnt, i, j;
+       int cnt, i;
 
        result = merge_bases_many(one, n, twos);
        for (i = 0; i < n; i++) {
@@ -731,28 +813,11 @@ struct commit_list *get_merge_bases_many(struct commit *one,
        clear_commit_marks(one, all_flags);
        for (i = 0; i < n; i++)
                clear_commit_marks(twos[i], 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_many(rslt[i], 1, &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;
-                       }
-               }
-       }
 
-       /* Surviving ones in rslt[] are the independent results */
+       cnt = remove_redundant(rslt, cnt);
        result = NULL;
-       for (i = 0; i < cnt; i++) {
-               if (rslt[i])
-                       commit_list_insert_by_date(rslt[i], &result);
-       }
+       for (i = 0; i < cnt; i++)
+               commit_list_insert_by_date(rslt[i], &result);
        free(rslt);
        return result;
 }
@@ -805,51 +870,31 @@ struct commit_list *reduce_heads(struct commit_list *heads)
 {
        struct commit_list *p;
        struct commit_list *result = NULL, **tail = &result;
-       struct commit **other;
-       size_t num_head, num_other;
+       struct commit **array;
+       int num_head, i;
 
        if (!heads)
                return NULL;
 
-       /* Avoid unnecessary reallocations */
-       for (p = heads, num_head = 0; p; p = p->next)
-               num_head++;
-       other = xcalloc(sizeof(*other), num_head);
-
-       /* For each commit, see if it can be reached by others */
-       for (p = heads; p; p = p->next) {
-               struct commit_list *q, *base;
-
-               /* Do we already have this in the result? */
-               for (q = result; q; q = q->next)
-                       if (p->item == q->item)
-                               break;
-               if (q)
+       /* 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;
-
-               num_other = 0;
-               for (q = heads; q; q = q->next) {
-                       if (p->item == q->item)
-                               continue;
-                       other[num_other++] = q->item;
+               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;
                }
-               if (num_other)
-                       base = get_merge_bases_many(p->item, num_other, other, 1);
-               else
-                       base = NULL;
-               /*
-                * If p->item does not have anything common with other
-                * commits, there won't be any merge base.  If it is
-                * reachable from some of the others, p->item will be
-                * the merge base.  If its history is connected with
-                * others, but p->item is not reachable by others, we
-                * will get something other than p->item back.
-                */
-               if (!base || (base->item != p->item))
-                       tail = &(commit_list_insert(p->item, tail)->next);
-               free_commit_list(base);
        }
-       free(other);
+       num_head = remove_redundant(array, num_head);
+       for (i = 0; i < num_head; i++)
+               tail = &commit_list_insert(array[i], tail)->next;
        return result;
 }
 
@@ -1030,8 +1075,9 @@ static int excluded_header_field(const char *field, size_t len, const char **exc
        return 0;
 }
 
-struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size,
-                                                          const char **exclude)
+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;
@@ -1098,8 +1144,92 @@ int commit_tree(const struct strbuf *msg, unsigned char *tree,
        return result;
 }
 
+static int find_invalid_utf8(const char *buf, int len)
+{
+       int offset = 0;
+
+       while (len) {
+               unsigned char c = *buf++;
+               int bytes, bad_offset;
+
+               len--;
+               offset++;
+
+               /* Simple US-ASCII? No worries. */
+               if (c < 0x80)
+                       continue;
+
+               bad_offset = offset-1;
+
+               /*
+                * Count how many more high bits set: that's how
+                * many more bytes this sequence should have.
+                */
+               bytes = 0;
+               while (c & 0x40) {
+                       c <<= 1;
+                       bytes++;
+               }
+
+               /* Must be between 1 and 5 more bytes */
+               if (bytes < 1 || bytes > 5)
+                       return bad_offset;
+
+               /* Do we *have* that many bytes? */
+               if (len < bytes)
+                       return bad_offset;
+
+               offset += bytes;
+               len -= bytes;
+
+               /* And verify that they are good continuation bytes */
+               do {
+                       if ((*buf++ & 0xc0) != 0x80)
+                               return bad_offset;
+               } while (--bytes);
+
+               /* We could/should check the value and length here too */
+       }
+       return -1;
+}
+
+/*
+ * 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;
+       }
+}
+
 static const char commit_utf8_warn[] =
-"Warning: commit message does not conform to UTF-8.\n"
+"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";
 
@@ -1140,9 +1270,9 @@ int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
 
        /* Person/date information */
        if (!author)
-               author = git_author_info(IDENT_ERROR_ON_NO_NAME);
+               author = git_author_info(IDENT_STRICT);
        strbuf_addf(&buffer, "author %s\n", author);
-       strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
+       strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_STRICT));
        if (!encoding_is_utf8)
                strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
 
@@ -1156,7 +1286,7 @@ int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
        strbuf_addbuf(&buffer, msg);
 
        /* And check the encoding */
-       if (encoding_is_utf8 && !is_utf8(buffer.buf))
+       if (encoding_is_utf8 && !verify_utf8(&buffer))
                fprintf(stderr, commit_utf8_warn);
 
        if (sign_commit && do_sign_commit(&buffer, sign_commit))
@@ -1185,3 +1315,30 @@ struct commit *get_merge_parent(const char *name)
        }
        return commit;
 }
+
+/*
+ * 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;
+}