commit-tree: teach -m/-F options to read logs from elsewhere
[git/git.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 }