Git fork
at reftables-rust 725 lines 19 kB view raw
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}