Code

git-clean: correct printing relative path
[git.git] / builtin-shortlog.c
1 #include "builtin.h"
2 #include "cache.h"
3 #include "commit.h"
4 #include "diff.h"
5 #include "path-list.h"
6 #include "revision.h"
7 #include "utf8.h"
8 #include "mailmap.h"
9 #include "shortlog.h"
11 static const char shortlog_usage[] =
12 "git-shortlog [-n] [-s] [-e] [<commit-id>... ]";
14 static int compare_by_number(const void *a1, const void *a2)
15 {
16         const struct path_list_item *i1 = a1, *i2 = a2;
17         const struct path_list *l1 = i1->util, *l2 = i2->util;
19         if (l1->nr < l2->nr)
20                 return 1;
21         else if (l1->nr == l2->nr)
22                 return 0;
23         else
24                 return -1;
25 }
27 static void insert_one_record(struct shortlog *log,
28                               const char *author,
29                               const char *oneline)
30 {
31         const char *dot3 = log->common_repo_prefix;
32         char *buffer, *p;
33         struct path_list_item *item;
34         struct path_list *onelines;
35         char namebuf[1024];
36         size_t len;
37         const char *eol;
38         const char *boemail, *eoemail;
40         boemail = strchr(author, '<');
41         if (!boemail)
42                 return;
43         eoemail = strchr(boemail, '>');
44         if (!eoemail)
45                 return;
46         if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
47                 while (author < boemail && isspace(*author))
48                         author++;
49                 for (len = 0;
50                      len < sizeof(namebuf) - 1 && author + len < boemail;
51                      len++)
52                         namebuf[len] = author[len];
53                 while (0 < len && isspace(namebuf[len-1]))
54                         len--;
55                 namebuf[len] = '\0';
56         }
57         else
58                 len = strlen(namebuf);
60         if (log->email) {
61                 size_t room = sizeof(namebuf) - len - 1;
62                 int maillen = eoemail - boemail + 1;
63                 snprintf(namebuf + len, room, " %.*s", maillen, boemail);
64         }
66         buffer = xstrdup(namebuf);
67         item = path_list_insert(buffer, &log->list);
68         if (item->util == NULL)
69                 item->util = xcalloc(1, sizeof(struct path_list));
70         else
71                 free(buffer);
73         eol = strchr(oneline, '\n');
74         if (!eol)
75                 eol = oneline + strlen(oneline);
76         while (*oneline && isspace(*oneline) && *oneline != '\n')
77                 oneline++;
78         if (!prefixcmp(oneline, "[PATCH")) {
79                 char *eob = strchr(oneline, ']');
80                 if (eob && (!eol || eob < eol))
81                         oneline = eob + 1;
82         }
83         while (*oneline && isspace(*oneline) && *oneline != '\n')
84                 oneline++;
85         len = eol - oneline;
86         while (len && isspace(oneline[len-1]))
87                 len--;
88         buffer = xmemdupz(oneline, len);
90         if (dot3) {
91                 int dot3len = strlen(dot3);
92                 if (dot3len > 5) {
93                         while ((p = strstr(buffer, dot3)) != NULL) {
94                                 int taillen = strlen(p) - dot3len;
95                                 memcpy(p, "/.../", 5);
96                                 memmove(p + 5, p + dot3len, taillen + 1);
97                         }
98                 }
99         }
101         onelines = item->util;
102         if (onelines->nr >= onelines->alloc) {
103                 onelines->alloc = alloc_nr(onelines->nr);
104                 onelines->items = xrealloc(onelines->items,
105                                 onelines->alloc
106                                 * sizeof(struct path_list_item));
107         }
109         onelines->items[onelines->nr].util = NULL;
110         onelines->items[onelines->nr++].path = buffer;
113 static void read_from_stdin(struct shortlog *log)
115         char author[1024], oneline[1024];
117         while (fgets(author, sizeof(author), stdin) != NULL) {
118                 if (!(author[0] == 'A' || author[0] == 'a') ||
119                     prefixcmp(author + 1, "uthor: "))
120                         continue;
121                 while (fgets(oneline, sizeof(oneline), stdin) &&
122                        oneline[0] != '\n')
123                         ; /* discard headers */
124                 while (fgets(oneline, sizeof(oneline), stdin) &&
125                        oneline[0] == '\n')
126                         ; /* discard blanks */
127                 insert_one_record(log, author + 8, oneline);
128         }
131 void shortlog_add_commit(struct shortlog *log, struct commit *commit)
133         const char *author = NULL, *buffer;
135         buffer = commit->buffer;
136         while (*buffer && *buffer != '\n') {
137                 const char *eol = strchr(buffer, '\n');
139                 if (eol == NULL)
140                         eol = buffer + strlen(buffer);
141                 else
142                         eol++;
144                 if (!prefixcmp(buffer, "author "))
145                         author = buffer + 7;
146                 buffer = eol;
147         }
148         if (!author)
149                 die("Missing author: %s",
150                     sha1_to_hex(commit->object.sha1));
151         if (*buffer)
152                 buffer++;
153         insert_one_record(log, author, !*buffer ? "<none>" : buffer);
156 static void get_from_rev(struct rev_info *rev, struct shortlog *log)
158         struct commit *commit;
160         if (prepare_revision_walk(rev))
161                 die("revision walk setup failed");
162         while ((commit = get_revision(rev)) != NULL)
163                 shortlog_add_commit(log, commit);
166 static int parse_uint(char const **arg, int comma)
168         unsigned long ul;
169         int ret;
170         char *endp;
172         ul = strtoul(*arg, &endp, 10);
173         if (endp != *arg && *endp && *endp != comma)
174                 return -1;
175         ret = (int) ul;
176         if (ret != ul)
177                 return -1;
178         *arg = endp;
179         if (**arg)
180                 (*arg)++;
181         return ret;
184 static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
185 #define DEFAULT_WRAPLEN 76
186 #define DEFAULT_INDENT1 6
187 #define DEFAULT_INDENT2 9
189 static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap)
191         arg += 2; /* skip -w */
193         *wrap = parse_uint(&arg, ',');
194         if (*wrap < 0)
195                 die(wrap_arg_usage);
196         *in1 = parse_uint(&arg, ',');
197         if (*in1 < 0)
198                 die(wrap_arg_usage);
199         *in2 = parse_uint(&arg, '\0');
200         if (*in2 < 0)
201                 die(wrap_arg_usage);
203         if (!*wrap)
204                 *wrap = DEFAULT_WRAPLEN;
205         if (!*in1)
206                 *in1 = DEFAULT_INDENT1;
207         if (!*in2)
208                 *in2 = DEFAULT_INDENT2;
209         if (*wrap &&
210             ((*in1 && *wrap <= *in1) ||
211              (*in2 && *wrap <= *in2)))
212                 die(wrap_arg_usage);
215 void shortlog_init(struct shortlog *log)
217         memset(log, 0, sizeof(*log));
219         read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
221         log->list.strdup_paths = 1;
222         log->wrap = DEFAULT_WRAPLEN;
223         log->in1 = DEFAULT_INDENT1;
224         log->in2 = DEFAULT_INDENT2;
227 int cmd_shortlog(int argc, const char **argv, const char *prefix)
229         struct shortlog log;
230         struct rev_info rev;
232         shortlog_init(&log);
234         /* since -n is a shadowed rev argument, parse our args first */
235         while (argc > 1) {
236                 if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
237                         log.sort_by_number = 1;
238                 else if (!strcmp(argv[1], "-s") ||
239                                 !strcmp(argv[1], "--summary"))
240                         log.summary = 1;
241                 else if (!strcmp(argv[1], "-e") ||
242                          !strcmp(argv[1], "--email"))
243                         log.email = 1;
244                 else if (!prefixcmp(argv[1], "-w")) {
245                         log.wrap_lines = 1;
246                         parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap);
247                 }
248                 else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
249                         usage(shortlog_usage);
250                 else
251                         break;
252                 argv++;
253                 argc--;
254         }
255         init_revisions(&rev, prefix);
256         argc = setup_revisions(argc, argv, &rev, NULL);
257         if (argc > 1)
258                 die ("unrecognized argument: %s", argv[1]);
260         /* assume HEAD if from a tty */
261         if (!rev.pending.nr && isatty(0))
262                 add_head_to_pending(&rev);
263         if (rev.pending.nr == 0) {
264                 read_from_stdin(&log);
265         }
266         else
267                 get_from_rev(&rev, &log);
269         shortlog_output(&log);
270         return 0;
273 void shortlog_output(struct shortlog *log)
275         int i, j;
276         if (log->sort_by_number)
277                 qsort(log->list.items, log->list.nr, sizeof(struct path_list_item),
278                         compare_by_number);
279         for (i = 0; i < log->list.nr; i++) {
280                 struct path_list *onelines = log->list.items[i].util;
282                 if (log->summary) {
283                         printf("%6d\t%s\n", onelines->nr, log->list.items[i].path);
284                 } else {
285                         printf("%s (%d):\n", log->list.items[i].path, onelines->nr);
286                         for (j = onelines->nr - 1; j >= 0; j--) {
287                                 const char *msg = onelines->items[j].path;
289                                 if (log->wrap_lines) {
290                                         int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
291                                         if (col != log->wrap)
292                                                 putchar('\n');
293                                 }
294                                 else
295                                         printf("      %s\n", msg);
296                         }
297                         putchar('\n');
298                 }
300                 onelines->strdup_paths = 1;
301                 path_list_clear(onelines, 1);
302                 free(onelines);
303                 log->list.items[i].util = NULL;
304         }
306         log->list.strdup_paths = 1;
307         path_list_clear(&log->list, 1);
308         log->mailmap.strdup_paths = 1;
309         path_list_clear(&log->mailmap, 1);