commit: teach --gpg-sign option
[git] / builtin / fmt-merge-msg.c
1 #include "builtin.h"
2 #include "cache.h"
3 #include "commit.h"
4 #include "diff.h"
5 #include "revision.h"
6 #include "tag.h"
7 #include "string-list.h"
8 #include "gpg-interface.h"
9
10 static const char * const fmt_merge_msg_usage[] = {
11         "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
12         NULL
13 };
14
15 static int shortlog_len;
16
17 static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
18 {
19         if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
20                 int is_bool;
21                 shortlog_len = git_config_bool_or_int(key, value, &is_bool);
22                 if (!is_bool && shortlog_len < 0)
23                         return error("%s: negative length %s", key, value);
24                 if (is_bool && shortlog_len)
25                         shortlog_len = DEFAULT_MERGE_LOG_LEN;
26         }
27         return 0;
28 }
29
30 /* merge data per repository where the merged tips came from */
31 struct src_data {
32         struct string_list branch, tag, r_branch, generic;
33         int head_status;
34 };
35
36 static void init_src_data(struct src_data *data)
37 {
38         data->branch.strdup_strings = 1;
39         data->tag.strdup_strings = 1;
40         data->r_branch.strdup_strings = 1;
41         data->generic.strdup_strings = 1;
42 }
43
44 static struct string_list srcs = STRING_LIST_INIT_DUP;
45 static struct string_list origins = STRING_LIST_INIT_DUP;
46
47 static int handle_line(char *line)
48 {
49         int i, len = strlen(line);
50         unsigned char *sha1;
51         char *src, *origin;
52         struct src_data *src_data;
53         struct string_list_item *item;
54         int pulling_head = 0;
55
56         if (len < 43 || line[40] != '\t')
57                 return 1;
58
59         if (!prefixcmp(line + 41, "not-for-merge"))
60                 return 0;
61
62         if (line[41] != '\t')
63                 return 2;
64
65         line[40] = 0;
66         sha1 = xmalloc(20);
67         i = get_sha1(line, sha1);
68         line[40] = '\t';
69         if (i)
70                 return 3;
71
72         if (line[len - 1] == '\n')
73                 line[len - 1] = 0;
74         line += 42;
75
76         /*
77          * At this point, line points at the beginning of comment e.g.
78          * "branch 'frotz' of git://that/repository.git".
79          * Find the repository name and point it with src.
80          */
81         src = strstr(line, " of ");
82         if (src) {
83                 *src = 0;
84                 src += 4;
85                 pulling_head = 0;
86         } else {
87                 src = line;
88                 pulling_head = 1;
89         }
90
91         item = unsorted_string_list_lookup(&srcs, src);
92         if (!item) {
93                 item = string_list_append(&srcs, src);
94                 item->util = xcalloc(1, sizeof(struct src_data));
95                 init_src_data(item->util);
96         }
97         src_data = item->util;
98
99         if (pulling_head) {
100                 origin = src;
101                 src_data->head_status |= 1;
102         } else if (!prefixcmp(line, "branch ")) {
103                 origin = line + 7;
104                 string_list_append(&src_data->branch, origin);
105                 src_data->head_status |= 2;
106         } else if (!prefixcmp(line, "tag ")) {
107                 origin = line;
108                 string_list_append(&src_data->tag, origin + 4);
109                 src_data->head_status |= 2;
110         } else if (!prefixcmp(line, "remote-tracking branch ")) {
111                 origin = line + strlen("remote-tracking branch ");
112                 string_list_append(&src_data->r_branch, origin);
113                 src_data->head_status |= 2;
114         } else {
115                 origin = src;
116                 string_list_append(&src_data->generic, line);
117                 src_data->head_status |= 2;
118         }
119
120         if (!strcmp(".", src) || !strcmp(src, origin)) {
121                 int len = strlen(origin);
122                 if (origin[0] == '\'' && origin[len - 1] == '\'')
123                         origin = xmemdupz(origin + 1, len - 2);
124         } else {
125                 char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
126                 sprintf(new_origin, "%s of %s", origin, src);
127                 origin = new_origin;
128         }
129         string_list_append(&origins, origin)->util = sha1;
130         return 0;
131 }
132
133 static void print_joined(const char *singular, const char *plural,
134                 struct string_list *list, struct strbuf *out)
135 {
136         if (list->nr == 0)
137                 return;
138         if (list->nr == 1) {
139                 strbuf_addf(out, "%s%s", singular, list->items[0].string);
140         } else {
141                 int i;
142                 strbuf_addstr(out, plural);
143                 for (i = 0; i < list->nr - 1; i++)
144                         strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
145                                     list->items[i].string);
146                 strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
147         }
148 }
149
150 static void shortlog(const char *name, unsigned char *sha1,
151                 struct commit *head, struct rev_info *rev, int limit,
152                 struct strbuf *out)
153 {
154         int i, count = 0;
155         struct commit *commit;
156         struct object *branch;
157         struct string_list subjects = STRING_LIST_INIT_DUP;
158         int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
159         struct strbuf sb = STRBUF_INIT;
160
161         branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
162         if (!branch || branch->type != OBJ_COMMIT)
163                 return;
164
165         setup_revisions(0, NULL, rev, NULL);
166         rev->ignore_merges = 1;
167         add_pending_object(rev, branch, name);
168         add_pending_object(rev, &head->object, "^HEAD");
169         head->object.flags |= UNINTERESTING;
170         if (prepare_revision_walk(rev))
171                 die("revision walk setup failed");
172         while ((commit = get_revision(rev)) != NULL) {
173                 struct pretty_print_context ctx = {0};
174
175                 /* ignore merges */
176                 if (commit->parents && commit->parents->next)
177                         continue;
178
179                 count++;
180                 if (subjects.nr > limit)
181                         continue;
182
183                 format_commit_message(commit, "%s", &sb, &ctx);
184                 strbuf_ltrim(&sb);
185
186                 if (!sb.len)
187                         string_list_append(&subjects,
188                                            sha1_to_hex(commit->object.sha1));
189                 else
190                         string_list_append(&subjects, strbuf_detach(&sb, NULL));
191         }
192
193         if (count > limit)
194                 strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
195         else
196                 strbuf_addf(out, "\n* %s:\n", name);
197
198         for (i = 0; i < subjects.nr; i++)
199                 if (i >= limit)
200                         strbuf_addf(out, "  ...\n");
201                 else
202                         strbuf_addf(out, "  %s\n", subjects.items[i].string);
203
204         clear_commit_marks((struct commit *)branch, flags);
205         clear_commit_marks(head, flags);
206         free_commit_list(rev->commits);
207         rev->commits = NULL;
208         rev->pending.nr = 0;
209
210         string_list_clear(&subjects, 0);
211 }
212
213 static void fmt_merge_msg_title(struct strbuf *out,
214         const char *current_branch) {
215         int i = 0;
216         char *sep = "";
217
218         strbuf_addstr(out, "Merge ");
219         for (i = 0; i < srcs.nr; i++) {
220                 struct src_data *src_data = srcs.items[i].util;
221                 const char *subsep = "";
222
223                 strbuf_addstr(out, sep);
224                 sep = "; ";
225
226                 if (src_data->head_status == 1) {
227                         strbuf_addstr(out, srcs.items[i].string);
228                         continue;
229                 }
230                 if (src_data->head_status == 3) {
231                         subsep = ", ";
232                         strbuf_addstr(out, "HEAD");
233                 }
234                 if (src_data->branch.nr) {
235                         strbuf_addstr(out, subsep);
236                         subsep = ", ";
237                         print_joined("branch ", "branches ", &src_data->branch,
238                                         out);
239                 }
240                 if (src_data->r_branch.nr) {
241                         strbuf_addstr(out, subsep);
242                         subsep = ", ";
243                         print_joined("remote-tracking branch ", "remote-tracking branches ",
244                                         &src_data->r_branch, out);
245                 }
246                 if (src_data->tag.nr) {
247                         strbuf_addstr(out, subsep);
248                         subsep = ", ";
249                         print_joined("tag ", "tags ", &src_data->tag, out);
250                 }
251                 if (src_data->generic.nr) {
252                         strbuf_addstr(out, subsep);
253                         print_joined("commit ", "commits ", &src_data->generic,
254                                         out);
255                 }
256                 if (strcmp(".", srcs.items[i].string))
257                         strbuf_addf(out, " of %s", srcs.items[i].string);
258         }
259
260         if (!strcmp("master", current_branch))
261                 strbuf_addch(out, '\n');
262         else
263                 strbuf_addf(out, " into %s\n", current_branch);
264 }
265
266 static void fmt_tag_signature(struct strbuf *tagbuf,
267                               struct strbuf *sig,
268                               const char *buf,
269                               unsigned long len)
270 {
271         const char *tag_body = strstr(buf, "\n\n");
272         if (tag_body) {
273                 tag_body += 2;
274                 strbuf_add(tagbuf, tag_body, buf + len - tag_body);
275         }
276         strbuf_complete_line(tagbuf);
277         strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
278 }
279
280 static void fmt_merge_msg_sigs(struct strbuf *out)
281 {
282         int i, tag_number = 0, first_tag = 0;
283         struct strbuf tagbuf = STRBUF_INIT;
284
285         for (i = 0; i < origins.nr; i++) {
286                 unsigned char *sha1 = origins.items[i].util;
287                 enum object_type type;
288                 unsigned long size, len;
289                 char *buf = read_sha1_file(sha1, &type, &size);
290                 struct strbuf sig = STRBUF_INIT;
291
292                 if (!buf || type != OBJ_TAG)
293                         goto next;
294                 len = parse_signature(buf, size);
295
296                 if (size == len)
297                         ; /* merely annotated */
298                 else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) {
299                         if (!sig.len)
300                                 strbuf_addstr(&sig, "gpg verification failed.\n");
301                 }
302
303                 if (!tag_number++) {
304                         fmt_tag_signature(&tagbuf, &sig, buf, len);
305                         first_tag = i;
306                 } else {
307                         if (tag_number == 2) {
308                                 struct strbuf tagline = STRBUF_INIT;
309                                 strbuf_addf(&tagline, "\n# %s\n",
310                                             origins.items[first_tag].string);
311                                 strbuf_insert(&tagbuf, 0, tagline.buf,
312                                               tagline.len);
313                                 strbuf_release(&tagline);
314                         }
315                         strbuf_addf(&tagbuf, "\n# %s\n",
316                                     origins.items[i].string);
317                         fmt_tag_signature(&tagbuf, &sig, buf, len);
318                 }
319                 strbuf_release(&sig);
320         next:
321                 free(buf);
322         }
323         if (tagbuf.len) {
324                 strbuf_addch(out, '\n');
325                 strbuf_addbuf(out, &tagbuf);
326         }
327         strbuf_release(&tagbuf);
328 }
329
330 int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
331                   struct fmt_merge_msg_opts *opts)
332 {
333         int i = 0, pos = 0;
334         unsigned char head_sha1[20];
335         const char *current_branch;
336
337         /* get current branch */
338         current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
339         if (!current_branch)
340                 die("No current branch");
341         if (!prefixcmp(current_branch, "refs/heads/"))
342                 current_branch += 11;
343
344         /* get a line */
345         while (pos < in->len) {
346                 int len;
347                 char *newline, *p = in->buf + pos;
348
349                 newline = strchr(p, '\n');
350                 len = newline ? newline - p : strlen(p);
351                 pos += len + !!newline;
352                 i++;
353                 p[len] = 0;
354                 if (handle_line(p))
355                         die ("Error in line %d: %.*s", i, len, p);
356         }
357
358         if (opts->add_title && srcs.nr)
359                 fmt_merge_msg_title(out, current_branch);
360
361         if (origins.nr)
362                 fmt_merge_msg_sigs(out);
363
364         if (opts->shortlog_len) {
365                 struct commit *head;
366                 struct rev_info rev;
367
368                 head = lookup_commit_or_die(head_sha1, "HEAD");
369                 init_revisions(&rev, NULL);
370                 rev.commit_format = CMIT_FMT_ONELINE;
371                 rev.ignore_merges = 1;
372                 rev.limited = 1;
373
374                 if (suffixcmp(out->buf, "\n"))
375                         strbuf_addch(out, '\n');
376
377                 for (i = 0; i < origins.nr; i++)
378                         shortlog(origins.items[i].string, origins.items[i].util,
379                                  head, &rev, opts->shortlog_len, out);
380         }
381
382         strbuf_complete_line(out);
383         return 0;
384 }
385
386 int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
387 {
388         const char *inpath = NULL;
389         const char *message = NULL;
390         struct option options[] = {
391                 { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
392                   "populate log with at most <n> entries from shortlog",
393                   PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
394                 { OPTION_INTEGER, 0, "summary", &shortlog_len, "n",
395                   "alias for --log (deprecated)",
396                   PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL,
397                   DEFAULT_MERGE_LOG_LEN },
398                 OPT_STRING('m', "message", &message, "text",
399                         "use <text> as start of message"),
400                 OPT_FILENAME('F', "file", &inpath, "file to read from"),
401                 OPT_END()
402         };
403
404         FILE *in = stdin;
405         struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
406         int ret;
407         struct fmt_merge_msg_opts opts;
408
409         git_config(fmt_merge_msg_config, NULL);
410         argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
411                              0);
412         if (argc > 0)
413                 usage_with_options(fmt_merge_msg_usage, options);
414
415         if (shortlog_len < 0)
416                 die("Negative --log=%d", shortlog_len);
417
418         if (inpath && strcmp(inpath, "-")) {
419                 in = fopen(inpath, "r");
420                 if (!in)
421                         die_errno("cannot open '%s'", inpath);
422         }
423
424         if (strbuf_read(&input, fileno(in), 0) < 0)
425                 die_errno("could not read input file");
426
427         if (message)
428                 strbuf_addstr(&output, message);
429
430         memset(&opts, 0, sizeof(opts));
431         opts.add_title = !message;
432         opts.shortlog_len = shortlog_len;
433
434         ret = fmt_merge_msg(&input, &output, &opts);
435         if (ret)
436                 return ret;
437         write_in_full(STDOUT_FILENO, output.buf, output.len);
438         return 0;
439 }