Merge branch 'jk/pretty-commit-header-incomplete-line'
[gitweb.git] / builtin / fmt-merge-msg.c
index c81a7fef2680620d521e118d60e8c59893d59234..d42015d8672d7a929758f69371eacbbc59c92888 100644 (file)
@@ -27,6 +27,8 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
                        merge_log_config = DEFAULT_MERGE_LOG_LEN;
        } else if (!strcmp(key, "merge.branchdesc")) {
                use_branch_desc = git_config_bool(key, value);
+       } else {
+               return git_default_config(key, value, cb);
        }
        return 0;
 }
@@ -53,7 +55,48 @@ static void init_src_data(struct src_data *data)
 static struct string_list srcs = STRING_LIST_INIT_DUP;
 static struct string_list origins = STRING_LIST_INIT_DUP;
 
-static int handle_line(char *line)
+struct merge_parents {
+       int alloc, nr;
+       struct merge_parent {
+               unsigned char given[20];
+               unsigned char commit[20];
+               unsigned char used;
+       } *item;
+};
+
+/*
+ * I know, I know, this is inefficient, but you won't be pulling and merging
+ * hundreds of heads at a time anyway.
+ */
+static struct merge_parent *find_merge_parent(struct merge_parents *table,
+                                             unsigned char *given,
+                                             unsigned char *commit)
+{
+       int i;
+       for (i = 0; i < table->nr; i++) {
+               if (given && hashcmp(table->item[i].given, given))
+                       continue;
+               if (commit && hashcmp(table->item[i].commit, commit))
+                       continue;
+               return &table->item[i];
+       }
+       return NULL;
+}
+
+static void add_merge_parent(struct merge_parents *table,
+                            unsigned char *given,
+                            unsigned char *commit)
+{
+       if (table->nr && find_merge_parent(table, given, commit))
+               return;
+       ALLOC_GROW(table->item, table->nr + 1, table->alloc);
+       hashcpy(table->item[table->nr].given, given);
+       hashcpy(table->item[table->nr].commit, commit);
+       table->item[table->nr].used = 0;
+       table->nr++;
+}
+
+static int handle_line(char *line, struct merge_parents *merge_parents)
 {
        int i, len = strlen(line);
        struct origin_data *origin_data;
@@ -61,6 +104,7 @@ static int handle_line(char *line)
        struct src_data *src_data;
        struct string_list_item *item;
        int pulling_head = 0;
+       unsigned char sha1[20];
 
        if (len < 43 || line[40] != '\t')
                return 1;
@@ -71,14 +115,15 @@ static int handle_line(char *line)
        if (line[41] != '\t')
                return 2;
 
-       line[40] = 0;
-       origin_data = xcalloc(1, sizeof(struct origin_data));
-       i = get_sha1(line, origin_data->sha1);
-       line[40] = '\t';
-       if (i) {
-               free(origin_data);
+       i = get_sha1_hex(line, sha1);
+       if (i)
                return 3;
-       }
+
+       if (!find_merge_parent(merge_parents, sha1, NULL))
+               return 0; /* subsumed by other parents */
+
+       origin_data = xcalloc(1, sizeof(struct origin_data));
+       hashcpy(origin_data->sha1, sha1);
 
        if (line[len - 1] == '\n')
                line[len - 1] = 0;
@@ -180,6 +225,101 @@ static void add_branch_desc(struct strbuf *out, const char *name)
        strbuf_release(&desc);
 }
 
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person(int which, struct string_list *people,
+                         struct commit *commit)
+{
+       char name_buf[MAX_GITNAME], *name, *name_end;
+       struct string_list_item *elem;
+       const char *field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+
+       name = strstr(commit->buffer, field);
+       if (!name)
+               return;
+       name += strlen(field);
+       name_end = strchrnul(name, '<');
+       if (*name_end)
+               name_end--;
+       while (isspace(*name_end) && name <= name_end)
+               name_end--;
+       if (name_end < name || name + MAX_GITNAME <= name_end)
+               return;
+       memcpy(name_buf, name, name_end - name + 1);
+       name_buf[name_end - name + 1] = '\0';
+
+       elem = string_list_lookup(people, name_buf);
+       if (!elem) {
+               elem = string_list_insert(people, name_buf);
+               elem->util = (void *)0;
+       }
+       elem->util = (void*)(util_as_integral(elem) + 1);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+       const struct string_list_item *a = a_, *b = b_;
+       return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+       if (people->nr == 1)
+               strbuf_addf(out, "%s", people->items[0].string);
+       else if (people->nr == 2)
+               strbuf_addf(out, "%s (%d) and %s (%d)",
+                           people->items[0].string,
+                           (int)util_as_integral(&people->items[0]),
+                           people->items[1].string,
+                           (int)util_as_integral(&people->items[1]));
+       else if (people->nr)
+               strbuf_addf(out, "%s (%d) and others",
+                           people->items[0].string,
+                           (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+                         struct string_list *them,
+                         int kind)
+{
+       const char *label;
+       const char *me;
+
+       if (kind == 'a') {
+               label = "\nBy ";
+               me = git_author_info(IDENT_NO_DATE);
+       } else {
+               label = "\nvia ";
+               me = git_committer_info(IDENT_NO_DATE);
+       }
+
+       if (!them->nr ||
+           (them->nr == 1 &&
+            me &&
+            (me = skip_prefix(me, them->items->string)) != NULL &&
+            skip_prefix(me, " <")))
+               return;
+       strbuf_addstr(out, label);
+       add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+                           struct string_list *authors,
+                           struct string_list *committers)
+{
+       if (authors->nr)
+               qsort(authors->items,
+                     authors->nr, sizeof(authors->items[0]),
+                     cmp_string_list_util_as_integral);
+       if (committers->nr)
+               qsort(committers->items,
+                     committers->nr, sizeof(committers->items[0]),
+                     cmp_string_list_util_as_integral);
+
+       credit_people(out, authors, 'a');
+       credit_people(out, committers, 'c');
+}
+
 static void shortlog(const char *name,
                     struct origin_data *origin_data,
                     struct commit *head,
@@ -190,6 +330,8 @@ static void shortlog(const char *name,
        struct commit *commit;
        struct object *branch;
        struct string_list subjects = STRING_LIST_INIT_DUP;
+       struct string_list authors = STRING_LIST_INIT_DUP;
+       struct string_list committers = STRING_LIST_INIT_DUP;
        int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
        struct strbuf sb = STRBUF_INIT;
        const unsigned char *sha1 = origin_data->sha1;
@@ -199,7 +341,6 @@ static void shortlog(const char *name,
                return;
 
        setup_revisions(0, NULL, rev, NULL);
-       rev->ignore_merges = 1;
        add_pending_object(rev, branch, name);
        add_pending_object(rev, &head->object, "^HEAD");
        head->object.flags |= UNINTERESTING;
@@ -208,10 +349,15 @@ static void shortlog(const char *name,
        while ((commit = get_revision(rev)) != NULL) {
                struct pretty_print_context ctx = {0};
 
-               /* ignore merges */
-               if (commit->parents && commit->parents->next)
+               if (commit->parents && commit->parents->next) {
+                       /* do not list a merge but count committer */
+                       record_person('c', &committers, commit);
                        continue;
-
+               }
+               if (!count)
+                       /* the 'tip' committer */
+                       record_person('c', &committers, commit);
+               record_person('a', &authors, commit);
                count++;
                if (subjects.nr > limit)
                        continue;
@@ -226,6 +372,7 @@ static void shortlog(const char *name,
                        string_list_append(&subjects, strbuf_detach(&sb, NULL));
        }
 
+       add_people_info(out, &authors, &committers);
        if (count > limit)
                strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
        else
@@ -246,6 +393,8 @@ static void shortlog(const char *name,
        rev->commits = NULL;
        rev->pending.nr = 0;
 
+       string_list_clear(&authors, 0);
+       string_list_clear(&committers, 0);
        string_list_clear(&subjects, 0);
 }
 
@@ -313,7 +462,10 @@ static void fmt_tag_signature(struct strbuf *tagbuf,
                strbuf_add(tagbuf, tag_body, buf + len - tag_body);
        }
        strbuf_complete_line(tagbuf);
-       strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+       if (sig->len) {
+               strbuf_addch(tagbuf, '\n');
+               strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+       }
 }
 
 static void fmt_merge_msg_sigs(struct strbuf *out)
@@ -366,6 +518,67 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
        strbuf_release(&tagbuf);
 }
 
+static void find_merge_parents(struct merge_parents *result,
+                              struct strbuf *in, unsigned char *head)
+{
+       struct commit_list *parents, *next;
+       struct commit *head_commit;
+       int pos = 0, i, j;
+
+       parents = NULL;
+       while (pos < in->len) {
+               int len;
+               char *p = in->buf + pos;
+               char *newline = strchr(p, '\n');
+               unsigned char sha1[20];
+               struct commit *parent;
+               struct object *obj;
+
+               len = newline ? newline - p : strlen(p);
+               pos += len + !!newline;
+
+               if (len < 43 ||
+                   get_sha1_hex(p, sha1) ||
+                   p[40] != '\t' ||
+                   p[41] != '\t')
+                       continue; /* skip not-for-merge */
+               /*
+                * Do not use get_merge_parent() here; we do not have
+                * "name" here and we do not want to contaminate its
+                * util field yet.
+                */
+               obj = parse_object(sha1);
+               parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
+               if (!parent)
+                       continue;
+               commit_list_insert(parent, &parents);
+               add_merge_parent(result, obj->sha1, parent->object.sha1);
+       }
+       head_commit = lookup_commit(head);
+       if (head_commit)
+               commit_list_insert(head_commit, &parents);
+       parents = reduce_heads(parents);
+
+       while (parents) {
+               for (i = 0; i < result->nr; i++)
+                       if (!hashcmp(result->item[i].commit,
+                                    parents->item->object.sha1))
+                               result->item[i].used = 1;
+               next = parents->next;
+               free(parents);
+               parents = next;
+       }
+
+       for (i = j = 0; i < result->nr; i++) {
+               if (result->item[i].used) {
+                       if (i != j)
+                               result->item[j] = result->item[i];
+                       j++;
+               }
+       }
+       result->nr = j;
+}
+
 int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
                  struct fmt_merge_msg_opts *opts)
 {
@@ -373,6 +586,9 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
        unsigned char head_sha1[20];
        const char *current_branch;
        void *current_branch_to_free;
+       struct merge_parents merge_parents;
+
+       memset(&merge_parents, 0, sizeof(merge_parents));
 
        /* get current branch */
        current_branch = current_branch_to_free =
@@ -382,6 +598,8 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
        if (!prefixcmp(current_branch, "refs/heads/"))
                current_branch += 11;
 
+       find_merge_parents(&merge_parents, in, head_sha1);
+
        /* get a line */
        while (pos < in->len) {
                int len;
@@ -392,7 +610,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
                pos += len + !!newline;
                i++;
                p[len] = 0;
-               if (handle_line(p))
+               if (handle_line(p, &merge_parents))
                        die ("Error in line %d: %.*s", i, len, p);
        }
 
@@ -412,8 +630,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
                rev.ignore_merges = 1;
                rev.limited = 1;
 
-               if (suffixcmp(out->buf, "\n"))
-                       strbuf_addch(out, '\n');
+               strbuf_complete_line(out);
 
                for (i = 0; i < origins.nr; i++)
                        shortlog(origins.items[i].string,
@@ -423,6 +640,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
 
        strbuf_complete_line(out);
        free(current_branch_to_free);
+       free(merge_parents.item);
        return 0;
 }