ruby: add simpler option parser
[git] / shortlog.c
1 #include "cache.h"
2 #include "shortlog.h"
3 #include "commit.h"
4 #include "mailmap.h"
5 #include "utf8.h"
6
7 /*
8  * The util field of our string_list_items will contain one of two things:
9  *
10  *   - if --summary is not in use, it will point to a string list of the
11  *     oneline subjects assigned to this author
12  *
13  *   - if --summary is in use, we don't need that list; we only need to know
14  *     its size. So we abuse the pointer slot to store our integer counter.
15  *
16  *  This macro accesses the latter.
17  */
18 #define UTIL_TO_INT(x) ((intptr_t)(x)->util)
19
20 void shortlog_init(struct shortlog *log)
21 {
22         memset(log, 0, sizeof(*log));
23
24         read_mailmap(&log->mailmap, &log->common_repo_prefix);
25
26         log->list.strdup_strings = 1;
27         log->wrap = DEFAULT_WRAPLEN;
28         log->in1 = DEFAULT_INDENT1;
29         log->in2 = DEFAULT_INDENT2;
30 }
31
32 void shortlog_insert_one_record(struct shortlog *log,
33                 const char *author,
34                 const char *oneline)
35 {
36         struct string_list_item *item;
37         const char *mailbuf, *namebuf;
38         size_t namelen, maillen;
39         struct strbuf namemailbuf = STRBUF_INIT;
40         struct ident_split ident;
41
42         if (split_ident_line(&ident, author, strlen(author)))
43                 return;
44
45         namebuf = ident.name_begin;
46         mailbuf = ident.mail_begin;
47         namelen = ident.name_end - ident.name_begin;
48         maillen = ident.mail_end - ident.mail_begin;
49
50         map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
51         strbuf_add(&namemailbuf, namebuf, namelen);
52
53         if (log->email)
54                 strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf);
55
56         item = string_list_insert(&log->list, namemailbuf.buf);
57
58         if (log->summary)
59                 item->util = (void *)(UTIL_TO_INT(item) + 1);
60         else {
61                 const char *dot3 = log->common_repo_prefix;
62                 char *buffer, *p;
63                 struct strbuf subject = STRBUF_INIT;
64                 const char *eol;
65
66                 /* Skip any leading whitespace, including any blank lines. */
67                 while (*oneline && isspace(*oneline))
68                         oneline++;
69                 eol = strchr(oneline, '\n');
70                 if (!eol)
71                         eol = oneline + strlen(oneline);
72                 if (starts_with(oneline, "[PATCH")) {
73                         char *eob = strchr(oneline, ']');
74                         if (eob && (!eol || eob < eol))
75                                 oneline = eob + 1;
76                 }
77                 while (*oneline && isspace(*oneline) && *oneline != '\n')
78                         oneline++;
79                 format_subject(&subject, oneline, " ");
80                 buffer = strbuf_detach(&subject, NULL);
81
82                 if (dot3) {
83                         int dot3len = strlen(dot3);
84                         if (dot3len > 5) {
85                                 while ((p = strstr(buffer, dot3)) != NULL) {
86                                         int taillen = strlen(p) - dot3len;
87                                         memcpy(p, "/.../", 5);
88                                         memmove(p + 5, p + dot3len, taillen + 1);
89                                 }
90                         }
91                 }
92
93                 if (item->util == NULL)
94                         item->util = xcalloc(1, sizeof(struct string_list));
95                 string_list_append(item->util, buffer);
96         }
97 }
98
99 void shortlog_add_commit(struct shortlog *log, struct commit *commit)
100 {
101         const char *author = NULL, *buffer;
102         struct strbuf buf = STRBUF_INIT;
103         struct strbuf ufbuf = STRBUF_INIT;
104
105         pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
106         buffer = buf.buf;
107         while (*buffer && *buffer != '\n') {
108                 const char *eol = strchr(buffer, '\n');
109
110                 if (eol == NULL)
111                         eol = buffer + strlen(buffer);
112                 else
113                         eol++;
114
115                 if (starts_with(buffer, "author "))
116                         author = buffer + 7;
117                 buffer = eol;
118         }
119         if (!author) {
120                 warning(_("Missing author: %s"),
121                     oid_to_hex(&commit->object.oid));
122                 return;
123         }
124         if (log->user_format) {
125                 struct pretty_print_context ctx = {0};
126                 ctx.fmt = CMIT_FMT_USERFORMAT;
127                 ctx.abbrev = log->abbrev;
128                 ctx.subject = "";
129                 ctx.after_subject = "";
130                 ctx.date_mode.type = DATE_NORMAL;
131                 ctx.output_encoding = get_log_output_encoding();
132                 pretty_print_commit(&ctx, commit, &ufbuf);
133                 buffer = ufbuf.buf;
134         } else if (*buffer) {
135                 buffer++;
136         }
137         shortlog_insert_one_record(log, author, !*buffer ? "<none>" : buffer);
138         strbuf_release(&ufbuf);
139         strbuf_release(&buf);
140 }
141
142 static int compare_by_counter(const void *a1, const void *a2)
143 {
144         const struct string_list_item *i1 = a1, *i2 = a2;
145         return UTIL_TO_INT(i2) - UTIL_TO_INT(i1);
146 }
147
148 static int compare_by_list(const void *a1, const void *a2)
149 {
150         const struct string_list_item *i1 = a1, *i2 = a2;
151         const struct string_list *l1 = i1->util, *l2 = i2->util;
152
153         if (l1->nr < l2->nr)
154                 return 1;
155         else if (l1->nr == l2->nr)
156                 return 0;
157         else
158                 return -1;
159 }
160
161 static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
162                                      const struct shortlog *log)
163 {
164         strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
165         strbuf_addch(sb, '\n');
166 }
167
168 void shortlog_output(struct shortlog *log)
169 {
170         int i, j;
171         struct strbuf sb = STRBUF_INIT;
172
173         if (log->sort_by_number)
174                 qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
175                       log->summary ? compare_by_counter : compare_by_list);
176         for (i = 0; i < log->list.nr; i++) {
177                 const struct string_list_item *item = &log->list.items[i];
178                 if (log->summary) {
179                         printf("%6d\t%s\n", (int)UTIL_TO_INT(item), item->string);
180                 } else {
181                         struct string_list *onelines = item->util;
182                         printf("%s (%d):\n", item->string, onelines->nr);
183                         for (j = onelines->nr - 1; j >= 0; j--) {
184                                 const char *msg = onelines->items[j].string;
185
186                                 if (log->wrap_lines) {
187                                         strbuf_reset(&sb);
188                                         add_wrapped_shortlog_msg(&sb, msg, log);
189                                         fwrite(sb.buf, sb.len, 1, stdout);
190                                 }
191                                 else
192                                         printf("      %s\n", msg);
193                         }
194                         putchar('\n');
195                         onelines->strdup_strings = 1;
196                         string_list_clear(onelines, 0);
197                         free(onelines);
198                 }
199
200                 log->list.items[i].util = NULL;
201         }
202
203         strbuf_release(&sb);
204         log->list.strdup_strings = 1;
205         string_list_clear(&log->list, 1);
206         clear_mailmap(&log->mailmap);
207 }