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