/* * We have an origin -- find the path that corresponds to it in its * parent and return an origin structure to represent it. */ static struct blame_origin *find_rename(struct commit *parent, struct blame_origin *origin) { struct blame_origin *porigin = NULL; struct diff_options diff_opts; int i; diff_setup(&diff_opts); diff_opts.flags.recursive = 1; diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_opts.single_follow = origin->path; diff_setup_done(&diff_opts); if (is_null_oid(&origin->commit->object.oid)) do_diff_cache(get_commit_tree_oid(parent), &diff_opts); else diff_tree_oid(get_commit_tree_oid(parent), get_commit_tree_oid(origin->commit), "", &diff_opts); diffcore_std(&diff_opts); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, origin->path)) { porigin = get_origin(parent, p->one->path); oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; } } diff_flush(&diff_opts); clear_pathspec(&diff_opts.pathspec); return porigin; }
static int commit_is_complete(struct commit *commit) { struct object_array study; struct object_array found; int is_incomplete = 0; int i; /* early return */ if (commit->object.flags & SEEN) return 1; if (commit->object.flags & INCOMPLETE) return 0; /* * Find all commits that are reachable and are not marked as * SEEN. Then make sure the trees and blobs contained are * complete. After that, mark these commits also as SEEN. * If some of the objects that are needed to complete this * commit are missing, mark this commit as INCOMPLETE. */ memset(&study, 0, sizeof(study)); memset(&found, 0, sizeof(found)); add_object_array(&commit->object, NULL, &study); add_object_array(&commit->object, NULL, &found); commit->object.flags |= STUDYING; while (study.nr) { struct commit *c; struct commit_list *parent; c = (struct commit *)object_array_pop(&study); if (!c->object.parsed && !parse_object(the_repository, &c->object.oid)) c->object.flags |= INCOMPLETE; if (c->object.flags & INCOMPLETE) { is_incomplete = 1; break; } else if (c->object.flags & SEEN) continue; for (parent = c->parents; parent; parent = parent->next) { struct commit *p = parent->item; if (p->object.flags & STUDYING) continue; p->object.flags |= STUDYING; add_object_array(&p->object, NULL, &study); add_object_array(&p->object, NULL, &found); } } if (!is_incomplete) { /* * make sure all commits in "found" array have all the * necessary objects. */ for (i = 0; i < found.nr; i++) { struct commit *c = (struct commit *)found.objects[i].item; if (!tree_is_complete(get_commit_tree_oid(c))) { is_incomplete = 1; c->object.flags |= INCOMPLETE; } } if (!is_incomplete) { /* mark all found commits as complete, iow SEEN */ for (i = 0; i < found.nr; i++) found.objects[i].item->flags |= SEEN; } } /* clear flags from the objects we traversed */ for (i = 0; i < found.nr; i++) found.objects[i].item->flags &= ~STUDYING; if (is_incomplete) commit->object.flags |= INCOMPLETE; else { /* * If we come here, we have (1) traversed the ancestry chain * from the "commit" until we reach SEEN commits (which are * known to be complete), and (2) made sure that the commits * encountered during the above traversal refer to trees that * are complete. Which means that we know *all* the commits * we have seen during this process are complete. */ for (i = 0; i < found.nr; i++) found.objects[i].item->flags |= SEEN; } /* free object arrays */ object_array_clear(&study); object_array_clear(&found); return !is_incomplete; }
/* * We have an origin -- check if the same path exists in the * parent and return an origin structure to represent it. */ static struct blame_origin *find_origin(struct commit *parent, struct blame_origin *origin) { struct blame_origin *porigin; struct diff_options diff_opts; const char *paths[2]; /* First check any existing origins */ for (porigin = parent->util; porigin; porigin = porigin->next) if (!strcmp(porigin->path, origin->path)) { /* * The same path between origin and its parent * without renaming -- the most common case. */ return blame_origin_incref (porigin); } /* See if the origin->path is different between parent * and origin first. Most of the time they are the * same and diff-tree is fairly efficient about this. */ diff_setup(&diff_opts); diff_opts.flags.recursive = 1; diff_opts.detect_rename = 0; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; paths[0] = origin->path; paths[1] = NULL; parse_pathspec(&diff_opts.pathspec, PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, PATHSPEC_LITERAL_PATH, "", paths); diff_setup_done(&diff_opts); if (is_null_oid(&origin->commit->object.oid)) do_diff_cache(get_commit_tree_oid(parent), &diff_opts); else diff_tree_oid(get_commit_tree_oid(parent), get_commit_tree_oid(origin->commit), "", &diff_opts); diffcore_std(&diff_opts); if (!diff_queued_diff.nr) { /* The path is the same as parent */ porigin = get_origin(parent, origin->path); oidcpy(&porigin->blob_oid, &origin->blob_oid); porigin->mode = origin->mode; } else { /* * Since origin->path is a pathspec, if the parent * commit had it as a directory, we will see a whole * bunch of deletion of files in the directory that we * do not care about. */ int i; struct diff_filepair *p = NULL; for (i = 0; i < diff_queued_diff.nr; i++) { const char *name; p = diff_queued_diff.queue[i]; name = p->one->path ? p->one->path : p->two->path; if (!strcmp(name, origin->path)) break; } if (!p) die("internal error in blame::find_origin"); switch (p->status) { default: die("internal error in blame::find_origin (%c)", p->status); case 'M': porigin = get_origin(parent, origin->path); oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; case 'A': case 'T': /* Did not exist in parent, or type changed */ break; } } diff_flush(&diff_opts); clear_pathspec(&diff_opts.pathspec); return porigin; }
/* * For lines target is suspected for, see if we can find code movement * across file boundary from the parent commit. porigin is the path * in the parent we already tried. */ static void find_copy_in_parent(struct blame_scoreboard *sb, struct blame_entry ***blamed, struct blame_entry **toosmall, struct blame_origin *target, struct commit *parent, struct blame_origin *porigin, int opt) { struct diff_options diff_opts; int i, j; struct blame_list *blame_list; int num_ents; struct blame_entry *unblamed = target->suspects; struct blame_entry *leftover = NULL; if (!unblamed) return; /* nothing remains for this target */ diff_setup(&diff_opts); diff_opts.flags.recursive = 1; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_setup_done(&diff_opts); /* Try "find copies harder" on new path if requested; * we do not want to use diffcore_rename() actually to * match things up; find_copies_harder is set only to * force diff_tree_oid() to feed all filepairs to diff_queue, * and this code needs to be after diff_setup_done(), which * usually makes find-copies-harder imply copy detection. */ if ((opt & PICKAXE_BLAME_COPY_HARDEST) || ((opt & PICKAXE_BLAME_COPY_HARDER) && (!porigin || strcmp(target->path, porigin->path)))) diff_opts.flags.find_copies_harder = 1; if (is_null_oid(&target->commit->object.oid)) do_diff_cache(get_commit_tree_oid(parent), &diff_opts); else diff_tree_oid(get_commit_tree_oid(parent), get_commit_tree_oid(target->commit), "", &diff_opts); if (!diff_opts.flags.find_copies_harder) diffcore_std(&diff_opts); do { struct blame_entry **unblamedtail = &unblamed; blame_list = setup_blame_list(unblamed, &num_ents); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; struct blame_origin *norigin; mmfile_t file_p; struct blame_entry potential[3]; if (!DIFF_FILE_VALID(p->one)) continue; /* does not exist in parent */ if (S_ISGITLINK(p->one->mode)) continue; /* ignore git links */ if (porigin && !strcmp(p->one->path, porigin->path)) /* find_move already dealt with this path */ continue; norigin = get_origin(parent, p->one->path); oidcpy(&norigin->blob_oid, &p->one->oid); norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p, &sb->num_read_blob); if (!file_p.ptr) continue; for (j = 0; j < num_ents; j++) { find_copy_in_blob(sb, blame_list[j].ent, norigin, potential, &file_p); copy_split_if_better(sb, blame_list[j].split, potential); decref_split(potential); } blame_origin_decref(norigin); } for (j = 0; j < num_ents; j++) { struct blame_entry *split = blame_list[j].split; if (split[1].suspect && sb->copy_score < blame_entry_score(sb, &split[1])) { split_blame(blamed, &unblamedtail, split, blame_list[j].ent); } else { blame_list[j].ent->next = leftover; leftover = blame_list[j].ent; } decref_split(split); } free(blame_list); *unblamedtail = NULL; toosmall = filter_small(sb, toosmall, &unblamed, sb->copy_score); } while (unblamed); target->suspects = reverse_blame(leftover, NULL); diff_flush(&diff_opts); clear_pathspec(&diff_opts.pathspec); }
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ const char *placeholder, void *context) { struct format_commit_context *c = context; const struct commit *commit = c->commit; const char *msg = c->message; struct commit_list *p; const char *arg; int ch; /* these are independent of the commit */ switch (placeholder[0]) { case 'C': if (starts_with(placeholder + 1, "(auto)")) { c->auto_color = want_color(c->pretty_ctx->color); if (c->auto_color && sb->len) strbuf_addstr(sb, GIT_COLOR_RESET); return 7; /* consumed 7 bytes, "C(auto)" */ } else { int ret = parse_color(sb, placeholder, c); if (ret) c->auto_color = 0; /* * Otherwise, we decided to treat %C<unknown> * as a literal string, and the previous * %C(auto) is still valid. */ return ret; } case 'n': /* newline */ strbuf_addch(sb, '\n'); return 1; case 'x': /* %x00 == NUL, %x0a == LF, etc. */ ch = hex2chr(placeholder + 1); if (ch < 0) return 0; strbuf_addch(sb, ch); return 3; case 'w': if (placeholder[1] == '(') { unsigned long width = 0, indent1 = 0, indent2 = 0; char *next; const char *start = placeholder + 2; const char *end = strchr(start, ')'); if (!end) return 0; if (end > start) { width = strtoul(start, &next, 10); if (*next == ',') { indent1 = strtoul(next + 1, &next, 10); if (*next == ',') { indent2 = strtoul(next + 1, &next, 10); } } if (*next != ')') return 0; } rewrap_message_tail(sb, c, width, indent1, indent2); return end - placeholder + 1; } else return 0; case '<': case '>': return parse_padding_placeholder(sb, placeholder, c); } /* these depend on the commit */ if (!commit->object.parsed) parse_object(&commit->object.oid); switch (placeholder[0]) { case 'H': /* commit hash */ strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT)); strbuf_addstr(sb, oid_to_hex(&commit->object.oid)); strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); return 1; case 'h': /* abbreviated commit hash */ strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT)); strbuf_add_unique_abbrev(sb, &commit->object.oid, c->pretty_ctx->abbrev); strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); return 1; case 'T': /* tree hash */ strbuf_addstr(sb, oid_to_hex(get_commit_tree_oid(commit))); return 1; case 't': /* abbreviated tree hash */ strbuf_add_unique_abbrev(sb, get_commit_tree_oid(commit), c->pretty_ctx->abbrev); return 1; case 'P': /* parent hashes */ for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_addstr(sb, oid_to_hex(&p->item->object.oid)); } return 1; case 'p': /* abbreviated parent hashes */ for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_add_unique_abbrev(sb, &p->item->object.oid, c->pretty_ctx->abbrev); } return 1; case 'm': /* left/right/bottom */ strbuf_addstr(sb, get_revision_mark(NULL, commit)); return 1; case 'd': load_ref_decorations(NULL, DECORATE_SHORT_REFS); format_decorations(sb, commit, c->auto_color); return 1; case 'D': load_ref_decorations(NULL, DECORATE_SHORT_REFS); format_decorations_extended(sb, commit, c->auto_color, "", ", ", ""); return 1; case 'g': /* reflog info */ switch(placeholder[1]) { case 'd': /* reflog selector */ case 'D': if (c->pretty_ctx->reflog_info) get_reflog_selector(sb, c->pretty_ctx->reflog_info, &c->pretty_ctx->date_mode, c->pretty_ctx->date_mode_explicit, (placeholder[1] == 'd')); return 2; case 's': /* reflog message */ if (c->pretty_ctx->reflog_info) get_reflog_message(sb, c->pretty_ctx->reflog_info); return 2; case 'n': case 'N': case 'e': case 'E': return format_reflog_person(sb, placeholder[1], c->pretty_ctx->reflog_info, &c->pretty_ctx->date_mode); } return 0; /* unknown %g placeholder */ case 'N': if (c->pretty_ctx->notes_message) { strbuf_addstr(sb, c->pretty_ctx->notes_message); return 1; } return 0; } if (placeholder[0] == 'G') { if (!c->signature_check.result) check_commit_signature(c->commit, &(c->signature_check)); switch (placeholder[1]) { case 'G': if (c->signature_check.gpg_output) strbuf_addstr(sb, c->signature_check.gpg_output); break; case '?': switch (c->signature_check.result) { case 'G': case 'B': case 'E': case 'U': case 'N': case 'X': case 'Y': case 'R': strbuf_addch(sb, c->signature_check.result); } break; case 'S': if (c->signature_check.signer) strbuf_addstr(sb, c->signature_check.signer); break; case 'K': if (c->signature_check.key) strbuf_addstr(sb, c->signature_check.key); break; default: return 0; } return 2; } /* For the rest we have to parse the commit header. */ if (!c->commit_header_parsed) parse_commit_header(c); switch (placeholder[0]) { case 'a': /* author ... */ return format_person_part(sb, placeholder[1], msg + c->author.off, c->author.len, &c->pretty_ctx->date_mode); case 'c': /* committer ... */ return format_person_part(sb, placeholder[1], msg + c->committer.off, c->committer.len, &c->pretty_ctx->date_mode); case 'e': /* encoding */ if (c->commit_encoding) strbuf_addstr(sb, c->commit_encoding); return 1; case 'B': /* raw body */ /* message_off is always left at the initial newline */ strbuf_addstr(sb, msg + c->message_off + 1); return 1; } /* Now we need to parse the commit message. */ if (!c->commit_message_parsed) parse_commit_message(c); switch (placeholder[0]) { case 's': /* subject */ format_subject(sb, msg + c->subject_off, " "); return 1; case 'f': /* sanitized subject */ format_sanitized_subject(sb, msg + c->subject_off); return 1; case 'b': /* body */ strbuf_addstr(sb, msg + c->body_off); return 1; } if (skip_prefix(placeholder, "(trailers", &arg)) { struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; if (*arg == ':') { arg++; for (;;) { if (match_placeholder_arg(arg, "only", &arg)) opts.only_trailers = 1; else if (match_placeholder_arg(arg, "unfold", &arg)) opts.unfold = 1; else break; } } if (*arg == ')') { format_trailers_from_commit(sb, msg + c->subject_off, &opts); return arg - placeholder + 1; } } return 0; /* unknown placeholder */ }
static void handle_commit(struct commit *commit, struct rev_info *rev, struct string_list *paths_of_changed_objects) { int saved_output_format = rev->diffopt.output_format; const char *commit_buffer; const char *author, *author_end, *committer, *committer_end; const char *encoding, *message; char *reencoded = NULL; struct commit_list *p; const char *refname; int i; rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; parse_commit_or_die(commit); commit_buffer = get_commit_buffer(commit, NULL); author = strstr(commit_buffer, "\nauthor "); if (!author) die ("Could not find author in commit %s", oid_to_hex(&commit->object.oid)); author++; author_end = strchrnul(author, '\n'); committer = strstr(author_end, "\ncommitter "); if (!committer) die ("Could not find committer in commit %s", oid_to_hex(&commit->object.oid)); committer++; committer_end = strchrnul(committer, '\n'); message = strstr(committer_end, "\n\n"); encoding = find_encoding(committer_end, message); if (message) message += 2; if (commit->parents && get_object_mark(&commit->parents->item->object) != 0 && !full_tree) { parse_commit_or_die(commit->parents->item); diff_tree_oid(get_commit_tree_oid(commit->parents->item), get_commit_tree_oid(commit), "", &rev->diffopt); } else diff_root_tree_oid(get_commit_tree_oid(commit), "", &rev->diffopt); /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) export_blob(&diff_queued_diff.queue[i]->two->oid); refname = *revision_sources_at(&revision_sources, commit); if (anonymize) { refname = anonymize_refname(refname); anonymize_ident_line(&committer, &committer_end); anonymize_ident_line(&author, &author_end); } mark_next_object(&commit->object); if (anonymize) reencoded = anonymize_commit_message(message); else if (!is_encoding_utf8(encoding)) reencoded = reencode_string(message, "UTF-8", encoding); if (!commit->parents) printf("reset %s\n", refname); printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s", refname, last_idnum, (int)(author_end - author), author, (int)(committer_end - committer), committer, (unsigned)(reencoded ? strlen(reencoded) : message ? strlen(message) : 0), reencoded ? reencoded : message ? message : ""); free(reencoded); unuse_commit_buffer(commit, commit_buffer); for (i = 0, p = commit->parents; p; p = p->next) { int mark = get_object_mark(&p->item->object); if (!mark) continue; if (i == 0) printf("from :%d\n", mark); else printf("merge :%d\n", mark); i++; } if (full_tree) printf("deleteall\n"); log_tree_diff_flush(rev); string_list_clear(paths_of_changed_objects, 0); rev->diffopt.output_format = saved_output_format; printf("\n"); show_progress(); }