request-pull: rewrite to C
[git] / builtin / shortlog.c
1 #include "builtin.h"
2 #include "cache.h"
3 #include "commit.h"
4 #include "string-list.h"
5 #include "revision.h"
6 #include "shortlog.h"
7 #include "parse-options.h"
8
9 static char const * const shortlog_usage[] = {
10         N_("git shortlog [<options>] [<revision range>] [[--] [<path>...]]"),
11         NULL
12 };
13
14 static void read_from_stdin(struct shortlog *log)
15 {
16         char author[1024], oneline[1024];
17
18         while (fgets(author, sizeof(author), stdin) != NULL) {
19                 if (!(author[0] == 'A' || author[0] == 'a') ||
20                     prefixcmp(author + 1, "uthor: "))
21                         continue;
22                 while (fgets(oneline, sizeof(oneline), stdin) &&
23                        oneline[0] != '\n')
24                         ; /* discard headers */
25                 while (fgets(oneline, sizeof(oneline), stdin) &&
26                        oneline[0] == '\n')
27                         ; /* discard blanks */
28                 shortlog_insert_one_record(log, author + 8, oneline);
29         }
30 }
31
32 void shortlog_add_commit(struct shortlog *log, struct commit *commit)
33 {
34         const char *author = NULL, *buffer;
35         struct strbuf buf = STRBUF_INIT;
36         struct strbuf ufbuf = STRBUF_INIT;
37
38         pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
39         buffer = buf.buf;
40         while (*buffer && *buffer != '\n') {
41                 const char *eol = strchr(buffer, '\n');
42
43                 if (eol == NULL)
44                         eol = buffer + strlen(buffer);
45                 else
46                         eol++;
47
48                 if (!prefixcmp(buffer, "author "))
49                         author = buffer + 7;
50                 buffer = eol;
51         }
52         if (!author) {
53                 warning(_("Missing author: %s"),
54                     sha1_to_hex(commit->object.sha1));
55                 return;
56         }
57         if (log->user_format) {
58                 struct pretty_print_context ctx = {0};
59                 ctx.fmt = CMIT_FMT_USERFORMAT;
60                 ctx.abbrev = log->abbrev;
61                 ctx.subject = "";
62                 ctx.after_subject = "";
63                 ctx.date_mode = DATE_NORMAL;
64                 ctx.output_encoding = get_log_output_encoding();
65                 pretty_print_commit(&ctx, commit, &ufbuf);
66                 buffer = ufbuf.buf;
67         } else if (*buffer) {
68                 buffer++;
69         }
70         insert_one_record(log, author, !*buffer ? "<none>" : buffer);
71         strbuf_release(&ufbuf);
72         strbuf_release(&buf);
73 }
74
75 static void get_from_rev(struct rev_info *rev, struct shortlog *log)
76 {
77         struct commit *commit;
78
79         if (prepare_revision_walk(rev))
80                 die(_("revision walk setup failed"));
81         while ((commit = get_revision(rev)) != NULL)
82                 shortlog_add_commit(log, commit);
83 }
84
85 static int parse_uint(char const **arg, int comma, int defval)
86 {
87         unsigned long ul;
88         int ret;
89         char *endp;
90
91         ul = strtoul(*arg, &endp, 10);
92         if (*endp && *endp != comma)
93                 return -1;
94         if (ul > INT_MAX)
95                 return -1;
96         ret = *arg == endp ? defval : (int)ul;
97         *arg = *endp ? endp + 1 : endp;
98         return ret;
99 }
100
101 static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
102
103 static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
104 {
105         struct shortlog *log = opt->value;
106
107         log->wrap_lines = !unset;
108         if (unset)
109                 return 0;
110         if (!arg) {
111                 log->wrap = DEFAULT_WRAPLEN;
112                 log->in1 = DEFAULT_INDENT1;
113                 log->in2 = DEFAULT_INDENT2;
114                 return 0;
115         }
116
117         log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
118         log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
119         log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
120         if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
121                 return error(wrap_arg_usage);
122         if (log->wrap &&
123             ((log->in1 && log->wrap <= log->in1) ||
124              (log->in2 && log->wrap <= log->in2)))
125                 return error(wrap_arg_usage);
126         return 0;
127 }
128
129 int cmd_shortlog(int argc, const char **argv, const char *prefix)
130 {
131         static struct shortlog log;
132         static struct rev_info rev;
133         int nongit = !startup_info->have_repository;
134
135         static const struct option options[] = {
136                 OPT_BOOL('n', "numbered", &log.sort_by_number,
137                          N_("sort output according to the number of commits per author")),
138                 OPT_BOOL('s', "summary", &log.summary,
139                          N_("Suppress commit descriptions, only provides commit count")),
140                 OPT_BOOL('e', "email", &log.email,
141                          N_("Show the email address of each author")),
142                 { OPTION_CALLBACK, 'w', NULL, &log, N_("w[,i1[,i2]]"),
143                         N_("Linewrap output"), PARSE_OPT_OPTARG, &parse_wrap_args },
144                 OPT_END(),
145         };
146
147         struct parse_opt_ctx_t ctx;
148
149         git_config(git_default_config, NULL);
150         shortlog_init(&log);
151         init_revisions(&rev, prefix);
152         parse_options_start(&ctx, argc, argv, prefix, options,
153                             PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
154
155         for (;;) {
156                 switch (parse_options_step(&ctx, options, shortlog_usage)) {
157                 case PARSE_OPT_HELP:
158                         exit(129);
159                 case PARSE_OPT_DONE:
160                         goto parse_done;
161                 }
162                 parse_revision_opt(&rev, &ctx, options, shortlog_usage);
163         }
164 parse_done:
165         argc = parse_options_end(&ctx);
166
167         if (setup_revisions(argc, argv, &rev, NULL) != 1) {
168                 error(_("unrecognized argument: %s"), argv[1]);
169                 usage_with_options(shortlog_usage, options);
170         }
171
172         log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
173         log.abbrev = rev.abbrev;
174
175         /* assume HEAD if from a tty */
176         if (!nongit && !rev.pending.nr && isatty(0))
177                 add_head_to_pending(&rev);
178         if (rev.pending.nr == 0) {
179                 if (isatty(0))
180                         fprintf(stderr, _("(reading log message from standard input)\n"));
181                 read_from_stdin(&log);
182         }
183         else
184                 get_from_rev(&rev, &log);
185
186         shortlog_output(&log);
187         return 0;
188 }