fork
Configure Feed
Select the types of activity you want to include in your feed.
Git fork
fork
Configure Feed
Select the types of activity you want to include in your feed.
1#define USE_THE_REPOSITORY_VARIABLE
2#define DISABLE_SIGN_COMPARE_WARNINGS
3
4#include "git-compat-util.h"
5#include "config.h"
6#include "environment.h"
7#include "refs.h"
8#include "object-name.h"
9#include "odb.h"
10#include "diff.h"
11#include "diff-merges.h"
12#include "hex.h"
13#include "revision.h"
14#include "tag.h"
15#include "string-list.h"
16#include "branch.h"
17#include "fmt-merge-msg.h"
18#include "commit-reach.h"
19#include "gpg-interface.h"
20#include "wildmatch.h"
21
22static int use_branch_desc;
23static int suppress_dest_pattern_seen;
24static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;
25
26int fmt_merge_msg_config(const char *key, const char *value,
27 const struct config_context *ctx, void *cb)
28{
29 int *merge_log_config = cb;
30
31 if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
32 int is_bool;
33 *merge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);
34 if (!is_bool && *merge_log_config < 0)
35 return error("%s: negative length %s", key, value);
36 if (is_bool && *merge_log_config)
37 *merge_log_config = DEFAULT_MERGE_LOG_LEN;
38 } else if (!strcmp(key, "merge.branchdesc")) {
39 use_branch_desc = git_config_bool(key, value);
40 } else if (!strcmp(key, "merge.suppressdest")) {
41 if (!value)
42 return config_error_nonbool(key);
43 if (!*value)
44 string_list_clear(&suppress_dest_patterns, 0);
45 else
46 string_list_append(&suppress_dest_patterns, value);
47 suppress_dest_pattern_seen = 1;
48 } else {
49 return git_default_config(key, value, ctx, cb);
50 }
51 return 0;
52}
53
54/* merge data per repository where the merged tips came from */
55struct src_data {
56 struct string_list branch, tag, r_branch, generic;
57 int head_status;
58};
59
60struct origin_data {
61 struct object_id oid;
62 unsigned is_local_branch:1;
63};
64
65static void init_src_data(struct src_data *data)
66{
67 data->branch.strdup_strings = 1;
68 data->tag.strdup_strings = 1;
69 data->r_branch.strdup_strings = 1;
70 data->generic.strdup_strings = 1;
71}
72
73static struct string_list srcs = STRING_LIST_INIT_DUP;
74static struct string_list origins = STRING_LIST_INIT_DUP;
75
76struct merge_parents {
77 int alloc, nr;
78 struct merge_parent {
79 struct object_id given;
80 struct object_id commit;
81 unsigned char used;
82 } *item;
83};
84
85/*
86 * I know, I know, this is inefficient, but you won't be pulling and merging
87 * hundreds of heads at a time anyway.
88 */
89static struct merge_parent *find_merge_parent(struct merge_parents *table,
90 struct object_id *given,
91 struct object_id *commit)
92{
93 int i;
94 for (i = 0; i < table->nr; i++) {
95 if (given && !oideq(&table->item[i].given, given))
96 continue;
97 if (commit && !oideq(&table->item[i].commit, commit))
98 continue;
99 return &table->item[i];
100 }
101 return NULL;
102}
103
104static void add_merge_parent(struct merge_parents *table,
105 struct object_id *given,
106 struct object_id *commit)
107{
108 if (table->nr && find_merge_parent(table, given, commit))
109 return;
110 ALLOC_GROW(table->item, table->nr + 1, table->alloc);
111 oidcpy(&table->item[table->nr].given, given);
112 oidcpy(&table->item[table->nr].commit, commit);
113 table->item[table->nr].used = 0;
114 table->nr++;
115}
116
117static int handle_line(char *line, struct merge_parents *merge_parents)
118{
119 int i, len = strlen(line);
120 struct origin_data *origin_data;
121 char *src;
122 const char *origin, *tag_name;
123 char *to_free = NULL;
124 struct src_data *src_data;
125 struct string_list_item *item;
126 int pulling_head = 0;
127 struct object_id oid;
128 const unsigned hexsz = the_hash_algo->hexsz;
129
130 if (len < hexsz + 3 || line[hexsz] != '\t')
131 return 1;
132
133 if (starts_with(line + hexsz + 1, "not-for-merge"))
134 return 0;
135
136 if (line[hexsz + 1] != '\t')
137 return 2;
138
139 i = get_oid_hex(line, &oid);
140 if (i)
141 return 3;
142
143 if (!find_merge_parent(merge_parents, &oid, NULL))
144 return 0; /* subsumed by other parents */
145
146 CALLOC_ARRAY(origin_data, 1);
147 oidcpy(&origin_data->oid, &oid);
148
149 if (line[len - 1] == '\n')
150 line[len - 1] = 0;
151 line += hexsz + 2;
152
153 /*
154 * At this point, line points at the beginning of comment e.g.
155 * "branch 'frotz' of git://that/repository.git".
156 * Find the repository name and point it with src.
157 */
158 src = strstr(line, " of ");
159 if (src) {
160 *src = 0;
161 src += 4;
162 pulling_head = 0;
163 } else {
164 src = line;
165 pulling_head = 1;
166 }
167
168 item = unsorted_string_list_lookup(&srcs, src);
169 if (!item) {
170 item = string_list_append(&srcs, src);
171 item->util = xcalloc(1, sizeof(struct src_data));
172 init_src_data(item->util);
173 }
174 src_data = item->util;
175
176 if (pulling_head) {
177 origin = src;
178 src_data->head_status |= 1;
179 } else if (skip_prefix(line, "branch ", &origin)) {
180 origin_data->is_local_branch = 1;
181 string_list_append(&src_data->branch, origin);
182 src_data->head_status |= 2;
183 } else if (skip_prefix(line, "tag ", &tag_name)) {
184 origin = line;
185 string_list_append(&src_data->tag, tag_name);
186 src_data->head_status |= 2;
187 } else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
188 string_list_append(&src_data->r_branch, origin);
189 src_data->head_status |= 2;
190 } else {
191 origin = src;
192 string_list_append(&src_data->generic, line);
193 src_data->head_status |= 2;
194 }
195
196 if (!strcmp(".", src) || !strcmp(src, origin)) {
197 int len = strlen(origin);
198 if (origin[0] == '\'' && origin[len - 1] == '\'')
199 origin = to_free = xmemdupz(origin + 1, len - 2);
200 } else
201 origin = to_free = xstrfmt("%s of %s", origin, src);
202 if (strcmp(".", src))
203 origin_data->is_local_branch = 0;
204 string_list_append(&origins, origin)->util = origin_data;
205 free(to_free);
206 return 0;
207}
208
209static void print_joined(const char *singular, const char *plural,
210 struct string_list *list, struct strbuf *out)
211{
212 if (list->nr == 0)
213 return;
214 if (list->nr == 1) {
215 strbuf_addf(out, "%s%s", singular, list->items[0].string);
216 } else {
217 int i;
218 strbuf_addstr(out, plural);
219 for (i = 0; i < list->nr - 1; i++)
220 strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
221 list->items[i].string);
222 strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
223 }
224}
225
226static void add_branch_desc(struct strbuf *out, const char *name)
227{
228 struct strbuf desc = STRBUF_INIT;
229
230 if (!read_branch_desc(&desc, name)) {
231 const char *bp = desc.buf;
232 while (*bp) {
233 const char *ep = strchrnul(bp, '\n');
234 if (*ep)
235 ep++;
236 strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
237 bp = ep;
238 }
239 strbuf_complete_line(out);
240 }
241 strbuf_release(&desc);
242}
243
244#define util_as_integral(elem) ((intptr_t)((elem)->util))
245
246static void record_person_from_buf(int which, struct string_list *people,
247 const char *buffer)
248{
249 char *name_buf, *name, *name_end;
250 struct string_list_item *elem;
251 const char *field;
252
253 field = (which == 'a') ? "\nauthor " : "\ncommitter ";
254 name = strstr(buffer, field);
255 if (!name)
256 return;
257 name += strlen(field);
258 name_end = strchrnul(name, '<');
259 if (*name_end)
260 name_end--;
261 while (isspace(*name_end) && name <= name_end)
262 name_end--;
263 if (name_end < name)
264 return;
265 name_buf = xmemdupz(name, name_end - name + 1);
266
267 elem = string_list_lookup(people, name_buf);
268 if (!elem) {
269 elem = string_list_insert(people, name_buf);
270 elem->util = (void *)0;
271 }
272 elem->util = (void*)(util_as_integral(elem) + 1);
273 free(name_buf);
274}
275
276
277static void record_person(int which, struct string_list *people,
278 struct commit *commit)
279{
280 const char *buffer = repo_get_commit_buffer(the_repository, commit,
281 NULL);
282 record_person_from_buf(which, people, buffer);
283 repo_unuse_commit_buffer(the_repository, commit, buffer);
284}
285
286static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
287{
288 const struct string_list_item *a = a_, *b = b_;
289 return util_as_integral(b) - util_as_integral(a);
290}
291
292static void add_people_count(struct strbuf *out, struct string_list *people)
293{
294 if (people->nr == 1)
295 strbuf_addstr(out, people->items[0].string);
296 else if (people->nr == 2)
297 strbuf_addf(out, "%s (%d) and %s (%d)",
298 people->items[0].string,
299 (int)util_as_integral(&people->items[0]),
300 people->items[1].string,
301 (int)util_as_integral(&people->items[1]));
302 else if (people->nr)
303 strbuf_addf(out, "%s (%d) and others",
304 people->items[0].string,
305 (int)util_as_integral(&people->items[0]));
306}
307
308static void credit_people(struct strbuf *out,
309 struct string_list *them,
310 int kind)
311{
312 const char *label;
313 const char *me;
314
315 if (kind == 'a') {
316 label = "By";
317 me = git_author_info(IDENT_NO_DATE);
318 } else {
319 label = "Via";
320 me = git_committer_info(IDENT_NO_DATE);
321 }
322
323 if (!them->nr ||
324 (them->nr == 1 &&
325 me &&
326 skip_prefix(me, them->items->string, &me) &&
327 starts_with(me, " <")))
328 return;
329 strbuf_addf(out, "\n%s %s ", comment_line_str, label);
330 add_people_count(out, them);
331}
332
333static void add_people_info(struct strbuf *out,
334 struct string_list *authors,
335 struct string_list *committers)
336{
337 QSORT(authors->items, authors->nr,
338 cmp_string_list_util_as_integral);
339 QSORT(committers->items, committers->nr,
340 cmp_string_list_util_as_integral);
341
342 credit_people(out, authors, 'a');
343 credit_people(out, committers, 'c');
344}
345
346static void shortlog(const char *name,
347 struct origin_data *origin_data,
348 struct commit *head,
349 struct rev_info *rev,
350 struct fmt_merge_msg_opts *opts,
351 struct strbuf *out)
352{
353 int i, count = 0;
354 struct commit *commit;
355 struct object *branch;
356 struct string_list subjects = STRING_LIST_INIT_DUP;
357 struct string_list authors = STRING_LIST_INIT_DUP;
358 struct string_list committers = STRING_LIST_INIT_DUP;
359 int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
360 struct strbuf sb = STRBUF_INIT;
361 const struct object_id *oid = &origin_data->oid;
362 int limit = opts->shortlog_len;
363
364 branch = deref_tag(the_repository, parse_object(the_repository, oid),
365 oid_to_hex(oid),
366 the_hash_algo->hexsz);
367 if (!branch || branch->type != OBJ_COMMIT)
368 return;
369
370 setup_revisions(0, NULL, rev, NULL);
371 add_pending_object(rev, branch, name);
372 add_pending_object(rev, &head->object, "^HEAD");
373 head->object.flags |= UNINTERESTING;
374 if (prepare_revision_walk(rev))
375 die("revision walk setup failed");
376 while ((commit = get_revision(rev)) != NULL) {
377 struct pretty_print_context ctx = {0};
378
379 if (commit->parents && commit->parents->next) {
380 /* do not list a merge but count committer */
381 if (opts->credit_people)
382 record_person('c', &committers, commit);
383 continue;
384 }
385 if (!count && opts->credit_people)
386 /* the 'tip' committer */
387 record_person('c', &committers, commit);
388 if (opts->credit_people)
389 record_person('a', &authors, commit);
390 count++;
391 if (subjects.nr > limit)
392 continue;
393
394 repo_format_commit_message(the_repository, commit, "%s", &sb,
395 &ctx);
396 strbuf_ltrim(&sb);
397
398 if (!sb.len)
399 string_list_append(&subjects,
400 oid_to_hex(&commit->object.oid));
401 else
402 string_list_append_nodup(&subjects,
403 strbuf_detach(&sb, NULL));
404 }
405
406 if (opts->credit_people)
407 add_people_info(out, &authors, &committers);
408 if (count > limit)
409 strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
410 else
411 strbuf_addf(out, "\n* %s:\n", name);
412
413 if (origin_data->is_local_branch && use_branch_desc)
414 add_branch_desc(out, name);
415
416 for (i = 0; i < subjects.nr; i++)
417 if (i >= limit)
418 strbuf_addstr(out, " ...\n");
419 else
420 strbuf_addf(out, " %s\n", subjects.items[i].string);
421
422 clear_commit_marks((struct commit *)branch, flags);
423 clear_commit_marks(head, flags);
424 free_commit_list(rev->commits);
425 rev->commits = NULL;
426 rev->pending.nr = 0;
427
428 string_list_clear(&authors, 0);
429 string_list_clear(&committers, 0);
430 string_list_clear(&subjects, 0);
431}
432
433/*
434 * See if dest_branch matches with any glob pattern on the
435 * suppress_dest_patterns list.
436 *
437 * We may want to also allow negative matches e.g. ":!glob" like we do
438 * for pathspec, but for now, let's keep it simple and stupid.
439 */
440static int dest_suppressed(const char *dest_branch)
441{
442 struct string_list_item *item;
443
444 for_each_string_list_item(item, &suppress_dest_patterns) {
445 if (!wildmatch(item->string, dest_branch, WM_PATHNAME))
446 return 1;
447 }
448 return 0;
449}
450
451static void fmt_merge_msg_title(struct strbuf *out,
452 const char *current_branch)
453{
454 int i = 0;
455 const char *sep = "";
456
457 strbuf_addstr(out, "Merge ");
458 for (i = 0; i < srcs.nr; i++) {
459 struct src_data *src_data = srcs.items[i].util;
460 const char *subsep = "";
461
462 strbuf_addstr(out, sep);
463 sep = "; ";
464
465 if (src_data->head_status == 1) {
466 strbuf_addstr(out, srcs.items[i].string);
467 continue;
468 }
469 if (src_data->head_status == 3) {
470 subsep = ", ";
471 strbuf_addstr(out, "HEAD");
472 }
473 if (src_data->branch.nr) {
474 strbuf_addstr(out, subsep);
475 subsep = ", ";
476 print_joined("branch ", "branches ", &src_data->branch,
477 out);
478 }
479 if (src_data->r_branch.nr) {
480 strbuf_addstr(out, subsep);
481 subsep = ", ";
482 print_joined("remote-tracking branch ", "remote-tracking branches ",
483 &src_data->r_branch, out);
484 }
485 if (src_data->tag.nr) {
486 strbuf_addstr(out, subsep);
487 subsep = ", ";
488 print_joined("tag ", "tags ", &src_data->tag, out);
489 }
490 if (src_data->generic.nr) {
491 strbuf_addstr(out, subsep);
492 print_joined("commit ", "commits ", &src_data->generic,
493 out);
494 }
495 if (strcmp(".", srcs.items[i].string))
496 strbuf_addf(out, " of %s", srcs.items[i].string);
497 }
498
499 if (!dest_suppressed(current_branch))
500 strbuf_addf(out, " into %s", current_branch);
501 strbuf_addch(out, '\n');
502}
503
504static void fmt_tag_signature(struct strbuf *tagbuf,
505 struct strbuf *sig,
506 const char *buf,
507 unsigned long len)
508{
509 const char *tag_body = strstr(buf, "\n\n");
510 if (tag_body) {
511 tag_body += 2;
512 strbuf_add(tagbuf, tag_body, buf + len - tag_body);
513 }
514 strbuf_complete_line(tagbuf);
515 if (sig->len) {
516 strbuf_addch(tagbuf, '\n');
517 strbuf_add_commented_lines(tagbuf, sig->buf, sig->len,
518 comment_line_str);
519 }
520}
521
522static void fmt_merge_msg_sigs(struct strbuf *out)
523{
524 int i, tag_number = 0, first_tag = 0;
525 struct strbuf tagbuf = STRBUF_INIT;
526
527 for (i = 0; i < origins.nr; i++) {
528 struct object_id *oid = origins.items[i].util;
529 enum object_type type;
530 unsigned long size;
531 char *buf = odb_read_object(the_repository->objects, oid,
532 &type, &size);
533 char *origbuf = buf;
534 unsigned long len = size;
535 struct signature_check sigc = { NULL };
536 struct strbuf payload = STRBUF_INIT, sig = STRBUF_INIT;
537
538 if (!buf || type != OBJ_TAG)
539 goto next;
540
541 if (!parse_signature(buf, size, &payload, &sig))
542 ;/* merely annotated */
543 else {
544 buf = payload.buf;
545 len = payload.len;
546 sigc.payload_type = SIGNATURE_PAYLOAD_TAG;
547 sigc.payload = strbuf_detach(&payload, &sigc.payload_len);
548 if (check_signature(&sigc, sig.buf, sig.len) &&
549 !sigc.output)
550 strbuf_addstr(&sig, "gpg verification failed.\n");
551 else
552 strbuf_addstr(&sig, sigc.output);
553 }
554
555 if (!tag_number++) {
556 fmt_tag_signature(&tagbuf, &sig, buf, len);
557 first_tag = i;
558 } else {
559 if (tag_number == 2) {
560 struct strbuf tagline = STRBUF_INIT;
561 strbuf_addch(&tagline, '\n');
562 strbuf_add_commented_lines(&tagline,
563 origins.items[first_tag].string,
564 strlen(origins.items[first_tag].string),
565 comment_line_str);
566 strbuf_insert(&tagbuf, 0, tagline.buf,
567 tagline.len);
568 strbuf_release(&tagline);
569 }
570 strbuf_addch(&tagbuf, '\n');
571 strbuf_add_commented_lines(&tagbuf,
572 origins.items[i].string,
573 strlen(origins.items[i].string),
574 comment_line_str);
575 fmt_tag_signature(&tagbuf, &sig, buf, len);
576 }
577 strbuf_release(&payload);
578 strbuf_release(&sig);
579 signature_check_clear(&sigc);
580 next:
581 free(origbuf);
582 }
583 if (tagbuf.len) {
584 strbuf_addch(out, '\n');
585 strbuf_addbuf(out, &tagbuf);
586 }
587 strbuf_release(&tagbuf);
588}
589
590static void find_merge_parents(struct merge_parents *result,
591 struct strbuf *in, struct object_id *head)
592{
593 struct commit_list *parents;
594 struct commit *head_commit;
595 int pos = 0, i, j;
596
597 parents = NULL;
598 while (pos < in->len) {
599 int len;
600 char *p = in->buf + pos;
601 char *newline = strchr(p, '\n');
602 const char *q;
603 struct object_id oid;
604 struct commit *parent;
605 struct object *obj;
606
607 len = newline ? newline - p : strlen(p);
608 pos += len + !!newline;
609
610 if (parse_oid_hex(p, &oid, &q) ||
611 q[0] != '\t' ||
612 q[1] != '\t')
613 continue; /* skip not-for-merge */
614 /*
615 * Do not use get_merge_parent() here; we do not have
616 * "name" here and we do not want to contaminate its
617 * util field yet.
618 */
619 obj = parse_object(the_repository, &oid);
620 parent = (struct commit *)repo_peel_to_type(the_repository,
621 NULL, 0, obj,
622 OBJ_COMMIT);
623 if (!parent)
624 continue;
625 commit_list_insert(parent, &parents);
626 add_merge_parent(result, &obj->oid, &parent->object.oid);
627 }
628 head_commit = lookup_commit(the_repository, head);
629 if (head_commit)
630 commit_list_insert(head_commit, &parents);
631 reduce_heads_replace(&parents);
632
633 while (parents) {
634 struct commit *cmit = pop_commit(&parents);
635 for (i = 0; i < result->nr; i++)
636 if (oideq(&result->item[i].commit, &cmit->object.oid))
637 result->item[i].used = 1;
638 }
639
640 for (i = j = 0; i < result->nr; i++) {
641 if (result->item[i].used) {
642 if (i != j)
643 result->item[j] = result->item[i];
644 j++;
645 }
646 }
647 result->nr = j;
648}
649
650
651int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
652 struct fmt_merge_msg_opts *opts)
653{
654 int i = 0, pos = 0;
655 struct object_id head_oid;
656 const char *current_branch;
657 void *current_branch_to_free;
658 struct merge_parents merge_parents;
659
660 if (!suppress_dest_pattern_seen) {
661 string_list_append(&suppress_dest_patterns, "main");
662 string_list_append(&suppress_dest_patterns, "master");
663 }
664
665 memset(&merge_parents, 0, sizeof(merge_parents));
666
667 /* learn the commit that we merge into and the current branch name */
668 current_branch = current_branch_to_free =
669 refs_resolve_refdup(get_main_ref_store(the_repository),
670 "HEAD", RESOLVE_REF_READING, &head_oid,
671 NULL);
672 if (!current_branch)
673 die("No current branch");
674
675 if (opts->into_name)
676 current_branch = opts->into_name;
677 else if (starts_with(current_branch, "refs/heads/"))
678 current_branch += 11;
679
680 find_merge_parents(&merge_parents, in, &head_oid);
681
682 /* get a line */
683 while (pos < in->len) {
684 int len;
685 char *newline, *p = in->buf + pos;
686
687 newline = strchr(p, '\n');
688 len = newline ? newline - p : strlen(p);
689 pos += len + !!newline;
690 i++;
691 p[len] = 0;
692 if (handle_line(p, &merge_parents))
693 die("error in line %d: %.*s", i, len, p);
694 }
695
696 if (opts->add_title && srcs.nr)
697 fmt_merge_msg_title(out, current_branch);
698
699 if (origins.nr)
700 fmt_merge_msg_sigs(out);
701
702 if (opts->shortlog_len) {
703 struct commit *head;
704 struct rev_info rev;
705
706 head = lookup_commit_or_die(&head_oid, "HEAD");
707 repo_init_revisions(the_repository, &rev, NULL);
708 rev.commit_format = CMIT_FMT_ONELINE;
709 diff_merges_suppress(&rev);
710 rev.limited = 1;
711
712 strbuf_complete_line(out);
713
714 for (i = 0; i < origins.nr; i++)
715 shortlog(origins.items[i].string,
716 origins.items[i].util,
717 head, &rev, opts, out);
718 release_revisions(&rev);
719 }
720
721 strbuf_complete_line(out);
722 free(current_branch_to_free);
723 free(merge_parents.item);
724 return 0;
725}