-/*
- * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
- */
-
-#include <assert.h>
-#include <time.h>
-#include <sys/time.h>
-#include <math.h>
-
#include "cache.h"
#include "refs.h"
-#include "tag.h"
-#include "commit.h"
-#include "tree.h"
-#include "blob.h"
+#include "cache-tree.h"
+#include "mergesort.h"
#include "diff.h"
#include "diffcore.h"
-#include "revision.h"
-#include "xdiff-interface.h"
-#include "quote.h"
-
-#ifndef DEBUG
-#define DEBUG 0
-#endif
-
-static const char blame_usage[] =
-"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] file [commit]\n"
-" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
-" -l, --long Show long commit SHA1 (Default: off)\n"
-" -t, --time Show raw timestamp (Default: off)\n"
-" -f, --show-name Show original filename (Default: auto)\n"
-" -n, --show-number Show original linenumber (Default: off)\n"
-" -p, --porcelain Show in a format designed for machine consumption\n"
-" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"
-" -h, --help This message";
-
-static struct commit **blame_lines;
-static int num_blame_lines;
-static char *blame_contents;
-static int blame_len;
-
-struct util_info {
- int *line_map;
- unsigned char sha1[20]; /* blob sha, not commit! */
- char *buf;
- unsigned long size;
- int num_lines;
- const char *pathname;
- unsigned meta_given:1;
-
- void *topo_data;
-};
+#include "tag.h"
+#include "blame.h"
-struct chunk {
- int off1, len1; /* --- */
- int off2, len2; /* +++ */
-};
+void blame_origin_decref(struct blame_origin *o)
+{
+ if (o && --o->refcnt <= 0) {
+ struct blame_origin *p, *l = NULL;
+ if (o->previous)
+ blame_origin_decref(o->previous);
+ free(o->file.ptr);
+ /* Should be present exactly once in commit chain */
+ for (p = o->commit->util; p; l = p, p = p->next) {
+ if (p == o) {
+ if (l)
+ l->next = p->next;
+ else
+ o->commit->util = p->next;
+ free(o);
+ return;
+ }
+ }
+ die("internal error in blame_origin_decref");
+ }
+}
-struct patch {
- struct chunk *chunks;
- int num;
-};
+/*
+ * Given a commit and a path in it, create a new origin structure.
+ * The callers that add blame to the scoreboard should use
+ * get_origin() to obtain shared, refcounted copy instead of calling
+ * this function directly.
+ */
+static struct blame_origin *make_origin(struct commit *commit, const char *path)
+{
+ struct blame_origin *o;
+ FLEX_ALLOC_STR(o, path, path);
+ o->commit = commit;
+ o->refcnt = 1;
+ o->next = commit->util;
+ commit->util = o;
+ return o;
+}
-static void get_blob(struct commit *commit);
+/*
+ * Locate an existing origin or create a new one.
+ * This moves the origin to front position in the commit util list.
+ */
+static struct blame_origin *get_origin(struct commit *commit, const char *path)
+{
+ struct blame_origin *o, *l;
+
+ for (o = commit->util, l = NULL; o; l = o, o = o->next) {
+ if (!strcmp(o->path, path)) {
+ /* bump to front */
+ if (l) {
+ l->next = o->next;
+ o->next = commit->util;
+ commit->util = o;
+ }
+ return blame_origin_incref(o);
+ }
+ }
+ return make_origin(commit, path);
+}
-/* Only used for statistics */
-static int num_get_patch;
-static int num_commits;
-static int patch_time;
-static int num_read_blob;
-struct blame_diff_state {
- struct xdiff_emit_state xm;
- struct patch *ret;
-};
-static void process_u0_diff(void *state_, char *line, unsigned long len)
+static void verify_working_tree_path(struct commit *work_tree, const char *path)
{
- struct blame_diff_state *state = state_;
- struct chunk *chunk;
+ struct commit_list *parents;
+ int pos;
- if (len < 4 || line[0] != '@' || line[1] != '@')
- return;
+ for (parents = work_tree->parents; parents; parents = parents->next) {
+ const struct object_id *commit_oid = &parents->item->object.oid;
+ struct object_id blob_oid;
+ unsigned mode;
- if (DEBUG)
- printf("chunk line: %.*s", (int)len, line);
- state->ret->num++;
- state->ret->chunks = xrealloc(state->ret->chunks,
- sizeof(struct chunk) * state->ret->num);
- chunk = &state->ret->chunks[state->ret->num - 1];
+ if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) &&
+ sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB)
+ return;
+ }
- assert(!strncmp(line, "@@ -", 4));
+ pos = cache_name_pos(path, strlen(path));
+ if (pos >= 0)
+ ; /* path is in the index */
+ else if (-1 - pos < active_nr &&
+ !strcmp(active_cache[-1 - pos]->name, path))
+ ; /* path is in the index, unmerged */
+ else
+ die("no such path '%s' in HEAD", path);
+}
- if (parse_hunk_header(line, len,
- &chunk->off1, &chunk->len1,
- &chunk->off2, &chunk->len2)) {
- state->ret->num--;
- return;
- }
+static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid)
+{
+ struct commit *parent;
- if (chunk->len1 == 0)
- chunk->off1++;
- if (chunk->len2 == 0)
- chunk->off2++;
+ parent = lookup_commit_reference(oid);
+ if (!parent)
+ die("no such commit %s", oid_to_hex(oid));
+ return &commit_list_insert(parent, tail)->next;
+}
- if (chunk->off1 > 0)
- chunk->off1--;
- if (chunk->off2 > 0)
- chunk->off2--;
+static void append_merge_parents(struct commit_list **tail)
+{
+ int merge_head;
+ struct strbuf line = STRBUF_INIT;
- assert(chunk->off1 >= 0);
- assert(chunk->off2 >= 0);
+ merge_head = open(git_path_merge_head(), O_RDONLY);
+ if (merge_head < 0) {
+ if (errno == ENOENT)
+ return;
+ die("cannot open '%s' for reading", git_path_merge_head());
+ }
+
+ while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
+ struct object_id oid;
+ if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid))
+ die("unknown line in '%s': %s", git_path_merge_head(), line.buf);
+ tail = append_parent(tail, &oid);
+ }
+ close(merge_head);
+ strbuf_release(&line);
}
-static struct patch *get_patch(struct commit *commit, struct commit *other)
+/*
+ * This isn't as simple as passing sb->buf and sb->len, because we
+ * want to transfer ownership of the buffer to the commit (so we
+ * must use detach).
+ */
+static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb)
{
- struct blame_diff_state state;
- xpparam_t xpp;
- xdemitconf_t xecfg;
- mmfile_t file_c, file_o;
- xdemitcb_t ecb;
- struct util_info *info_c = (struct util_info *)commit->util;
- struct util_info *info_o = (struct util_info *)other->util;
- struct timeval tv_start, tv_end;
-
- get_blob(commit);
- file_c.ptr = info_c->buf;
- file_c.size = info_c->size;
-
- get_blob(other);
- file_o.ptr = info_o->buf;
- file_o.size = info_o->size;
-
- gettimeofday(&tv_start, NULL);
-
- xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = 0;
- xecfg.flags = 0;
- ecb.outf = xdiff_outf;
- ecb.priv = &state;
- memset(&state, 0, sizeof(state));
- state.xm.consume = process_u0_diff;
- state.ret = xmalloc(sizeof(struct patch));
- state.ret->chunks = NULL;
- state.ret->num = 0;
-
- xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb);
-
- gettimeofday(&tv_end, NULL);
- patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
- tv_end.tv_usec - tv_start.tv_usec;
-
- num_get_patch++;
- return state.ret;
+ size_t len;
+ void *buf = strbuf_detach(sb, &len);
+ set_commit_buffer(c, buf, len);
}
-static void free_patch(struct patch *p)
+/*
+ * Prepare a dummy commit that represents the work tree (or staged) item.
+ * Note that annotating work tree item never works in the reverse.
+ */
+static struct commit *fake_working_tree_commit(struct diff_options *opt,
+ const char *path,
+ const char *contents_from)
{
- free(p->chunks);
- free(p);
+ struct commit *commit;
+ struct blame_origin *origin;
+ struct commit_list **parent_tail, *parent;
+ struct object_id head_oid;
+ struct strbuf buf = STRBUF_INIT;
+ const char *ident;
+ time_t now;
+ int size, len;
+ struct cache_entry *ce;
+ unsigned mode;
+ struct strbuf msg = STRBUF_INIT;
+
+ read_cache();
+ time(&now);
+ commit = alloc_commit_node();
+ commit->object.parsed = 1;
+ commit->date = now;
+ parent_tail = &commit->parents;
+
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
+ die("no such ref: HEAD");
+
+ parent_tail = append_parent(parent_tail, &head_oid);
+ append_merge_parents(parent_tail);
+ verify_working_tree_path(commit, path);
+
+ origin = make_origin(commit, path);
+
+ ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+ strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
+ for (parent = commit->parents; parent; parent = parent->next)
+ strbuf_addf(&msg, "parent %s\n",
+ oid_to_hex(&parent->item->object.oid));
+ strbuf_addf(&msg,
+ "author %s\n"
+ "committer %s\n\n"
+ "Version of %s from %s\n",
+ ident, ident, path,
+ (!contents_from ? path :
+ (!strcmp(contents_from, "-") ? "standard input" : contents_from)));
+ set_commit_buffer_from_strbuf(commit, &msg);
+
+ if (!contents_from || strcmp("-", contents_from)) {
+ struct stat st;
+ const char *read_from;
+ char *buf_ptr;
+ unsigned long buf_len;
+
+ if (contents_from) {
+ if (stat(contents_from, &st) < 0)
+ die_errno("Cannot stat '%s'", contents_from);
+ read_from = contents_from;
+ }
+ else {
+ if (lstat(path, &st) < 0)
+ die_errno("Cannot lstat '%s'", path);
+ read_from = path;
+ }
+ mode = canon_mode(st.st_mode);
+
+ switch (st.st_mode & S_IFMT) {
+ case S_IFREG:
+ if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+ textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len))
+ strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
+ else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
+ die_errno("cannot open or read '%s'", read_from);
+ break;
+ case S_IFLNK:
+ if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
+ die_errno("cannot readlink '%s'", read_from);
+ break;
+ default:
+ die("unsupported file type %s", read_from);
+ }
+ }
+ else {
+ /* Reading from stdin */
+ mode = 0;
+ if (strbuf_read(&buf, 0, 0) < 0)
+ die_errno("failed to read from stdin");
+ }
+ convert_to_git(&the_index, path, buf.buf, buf.len, &buf, 0);
+ origin->file.ptr = buf.buf;
+ origin->file.size = buf.len;
+ pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash);
+
+ /*
+ * Read the current index, replace the path entry with
+ * origin->blob_sha1 without mucking with its mode or type
+ * bits; we are not going to write this index out -- we just
+ * want to run "diff-index --cached".
+ */
+ discard_cache();
+ read_cache();
+
+ len = strlen(path);
+ if (!mode) {
+ int pos = cache_name_pos(path, len);
+ if (0 <= pos)
+ mode = active_cache[pos]->ce_mode;
+ else
+ /* Let's not bother reading from HEAD tree */
+ mode = S_IFREG | 0644;
+ }
+ size = cache_entry_size(len);
+ ce = xcalloc(1, size);
+ oidcpy(&ce->oid, &origin->blob_oid);
+ memcpy(ce->name, path, len);
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = len;
+ ce->ce_mode = create_ce_mode(mode);
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+
+ cache_tree_invalidate_path(&the_index, path);
+
+ return commit;
}
-static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
- int baselen, const char *pathname,
- unsigned mode, int stage);
-static unsigned char blob_sha1[20];
-static const char *blame_file;
-static int get_blob_sha1(struct tree *t, const char *pathname,
- unsigned char *sha1)
-{
- const char *pathspec[2];
- blame_file = pathname;
- pathspec[0] = pathname;
- pathspec[1] = NULL;
- hashclr(blob_sha1);
- read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
- if (is_null_sha1(blob_sha1))
- return -1;
+static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b,
+ xdl_emit_hunk_consume_func_t hunk_func, void *cb_data, int xdl_opts)
+{
+ xpparam_t xpp = {0};
+ xdemitconf_t xecfg = {0};
+ xdemitcb_t ecb = {NULL};
+
+ xpp.flags = xdl_opts;
+ xecfg.hunk_func = hunk_func;
+ ecb.priv = cb_data;
+ return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
+}
- hashcpy(sha1, blob_sha1);
- return 0;
+/*
+ * Given an origin, prepare mmfile_t structure to be used by the
+ * diff machinery
+ */
+static void fill_origin_blob(struct diff_options *opt,
+ struct blame_origin *o, mmfile_t *file, int *num_read_blob)
+{
+ if (!o->file.ptr) {
+ enum object_type type;
+ unsigned long file_size;
+
+ (*num_read_blob)++;
+ if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+ textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
+ ;
+ else
+ file->ptr = read_sha1_file(o->blob_oid.hash, &type,
+ &file_size);
+ file->size = file_size;
+
+ if (!file->ptr)
+ die("Cannot read blob %s for path %s",
+ oid_to_hex(&o->blob_oid),
+ o->path);
+ o->file = *file;
+ }
+ else
+ *file = o->file;
}
-static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
- int baselen, const char *pathname,
- unsigned mode, int stage)
+static void drop_origin_blob(struct blame_origin *o)
{
- if (S_ISDIR(mode))
- return READ_TREE_RECURSIVE;
+ if (o->file.ptr) {
+ FREE_AND_NULL(o->file.ptr);
+ }
+}
- if (strncmp(blame_file, base, baselen) ||
- strcmp(blame_file + baselen, pathname))
- return -1;
+/*
+ * Any merge of blames happens on lists of blames that arrived via
+ * different parents in a single suspect. In this case, we want to
+ * sort according to the suspect line numbers as opposed to the final
+ * image line numbers. The function body is somewhat longish because
+ * it avoids unnecessary writes.
+ */
- hashcpy(blob_sha1, sha1);
- return -1;
+static struct blame_entry *blame_merge(struct blame_entry *list1,
+ struct blame_entry *list2)
+{
+ struct blame_entry *p1 = list1, *p2 = list2,
+ **tail = &list1;
+
+ if (!p1)
+ return p2;
+ if (!p2)
+ return p1;
+
+ if (p1->s_lno <= p2->s_lno) {
+ do {
+ tail = &p1->next;
+ if ((p1 = *tail) == NULL) {
+ *tail = p2;
+ return list1;
+ }
+ } while (p1->s_lno <= p2->s_lno);
+ }
+ for (;;) {
+ *tail = p2;
+ do {
+ tail = &p2->next;
+ if ((p2 = *tail) == NULL) {
+ *tail = p1;
+ return list1;
+ }
+ } while (p1->s_lno > p2->s_lno);
+ *tail = p1;
+ do {
+ tail = &p1->next;
+ if ((p1 = *tail) == NULL) {
+ *tail = p2;
+ return list1;
+ }
+ } while (p1->s_lno <= p2->s_lno);
+ }
}
-static void get_blob(struct commit *commit)
+static void *get_next_blame(const void *p)
{
- struct util_info *info = commit->util;
- char type[20];
+ return ((struct blame_entry *)p)->next;
+}
- if (info->buf)
- return;
+static void set_next_blame(void *p1, void *p2)
+{
+ ((struct blame_entry *)p1)->next = p2;
+}
- info->buf = read_sha1_file(info->sha1, type, &info->size);
- num_read_blob++;
+/*
+ * Final image line numbers are all different, so we don't need a
+ * three-way comparison here.
+ */
- assert(!strcmp(type, blob_type));
+static int compare_blame_final(const void *p1, const void *p2)
+{
+ return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno
+ ? 1 : -1;
}
-/* For debugging only */
-static void print_patch(struct patch *p)
+static int compare_blame_suspect(const void *p1, const void *p2)
{
- int i;
- printf("Num chunks: %d\n", p->num);
- for (i = 0; i < p->num; i++) {
- printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
- p->chunks[i].off2, p->chunks[i].len2);
- }
+ const struct blame_entry *s1 = p1, *s2 = p2;
+ /*
+ * to allow for collating suspects, we sort according to the
+ * respective pointer value as the primary sorting criterion.
+ * The actual relation is pretty unimportant as long as it
+ * establishes a total order. Comparing as integers gives us
+ * that.
+ */
+ if (s1->suspect != s2->suspect)
+ return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1;
+ if (s1->s_lno == s2->s_lno)
+ return 0;
+ return s1->s_lno > s2->s_lno ? 1 : -1;
}
-#if DEBUG
-/* For debugging only */
-static void print_map(struct commit *cmit, struct commit *other)
+void blame_sort_final(struct blame_scoreboard *sb)
{
- struct util_info *util = cmit->util;
- struct util_info *util2 = other->util;
-
- int i;
- int max =
- util->num_lines >
- util2->num_lines ? util->num_lines : util2->num_lines;
- int num;
-
- if (print_map == NULL)
- ; /* to avoid "unused function" warning */
+ sb->ent = llist_mergesort(sb->ent, get_next_blame, set_next_blame,
+ compare_blame_final);
+}
- for (i = 0; i < max; i++) {
- printf("i: %d ", i);
- num = -1;
+static int compare_commits_by_reverse_commit_date(const void *a,
+ const void *b,
+ void *c)
+{
+ return -compare_commits_by_commit_date(a, b, c);
+}
- if (i < util->num_lines) {
- num = util->line_map[i];
- printf("%d\t", num);
+/*
+ * For debugging -- origin is refcounted, and this asserts that
+ * we do not underflow.
+ */
+static void sanity_check_refcnt(struct blame_scoreboard *sb)
+{
+ int baa = 0;
+ struct blame_entry *ent;
+
+ for (ent = sb->ent; ent; ent = ent->next) {
+ /* Nobody should have zero or negative refcnt */
+ if (ent->suspect->refcnt <= 0) {
+ fprintf(stderr, "%s in %s has negative refcnt %d\n",
+ ent->suspect->path,
+ oid_to_hex(&ent->suspect->commit->object.oid),
+ ent->suspect->refcnt);
+ baa = 1;
}
- else
- printf("\t");
+ }
+ if (baa)
+ sb->on_sanity_fail(sb, baa);
+}
- if (i < util2->num_lines) {
- int num2 = util2->line_map[i];
- printf("%d\t", num2);
- if (num != -1 && num2 != num)
- printf("---");
+/*
+ * If two blame entries that are next to each other came from
+ * contiguous lines in the same origin (i.e. <commit, path> pair),
+ * merge them together.
+ */
+void blame_coalesce(struct blame_scoreboard *sb)
+{
+ struct blame_entry *ent, *next;
+
+ for (ent = sb->ent; ent && (next = ent->next); ent = next) {
+ if (ent->suspect == next->suspect &&
+ ent->s_lno + ent->num_lines == next->s_lno) {
+ ent->num_lines += next->num_lines;
+ ent->next = next->next;
+ blame_origin_decref(next->suspect);
+ free(next);
+ ent->score = 0;
+ next = ent; /* again */
}
- else
- printf("\t");
-
- printf("\n");
}
+
+ if (sb->debug) /* sanity */
+ sanity_check_refcnt(sb);
}
-#endif
-/* p is a patch from commit to other. */
-static void fill_line_map(struct commit *commit, struct commit *other,
- struct patch *p)
+/*
+ * Merge the given sorted list of blames into a preexisting origin.
+ * If there were no previous blames to that commit, it is entered into
+ * the commit priority queue of the score board.
+ */
+
+static void queue_blames(struct blame_scoreboard *sb, struct blame_origin *porigin,
+ struct blame_entry *sorted)
{
- struct util_info *util = commit->util;
- struct util_info *util2 = other->util;
- int *map = util->line_map;
- int *map2 = util2->line_map;
- int cur_chunk = 0;
- int i1, i2;
-
- if (DEBUG) {
- if (p->num)
- print_patch(p);
- printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
- util2->num_lines);
+ if (porigin->suspects)
+ porigin->suspects = blame_merge(porigin->suspects, sorted);
+ else {
+ struct blame_origin *o;
+ for (o = porigin->commit->util; o; o = o->next) {
+ if (o->suspects) {
+ porigin->suspects = sorted;
+ return;
+ }
+ }
+ porigin->suspects = sorted;
+ prio_queue_put(&sb->commits, porigin->commit);
}
+}
- for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
- struct chunk *chunk = NULL;
- if (cur_chunk < p->num)
- chunk = &p->chunks[cur_chunk];
+/*
+ * Fill the blob_sha1 field of an origin if it hasn't, so that later
+ * call to fill_origin_blob() can use it to locate the data. blob_sha1
+ * for an origin is also used to pass the blame for the entire file to
+ * the parent to detect the case where a child's blob is identical to
+ * that of its parent's.
+ *
+ * This also fills origin->mode for corresponding tree path.
+ */
+static int fill_blob_sha1_and_mode(struct blame_origin *origin)
+{
+ if (!is_null_oid(&origin->blob_oid))
+ return 0;
+ if (get_tree_entry(origin->commit->object.oid.hash,
+ origin->path,
+ origin->blob_oid.hash, &origin->mode))
+ goto error_out;
+ if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB)
+ goto error_out;
+ return 0;
+ error_out:
+ oidclr(&origin->blob_oid);
+ origin->mode = S_IFINVALID;
+ return -1;
+}
+
+/*
+ * 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];
- if (chunk && chunk->off1 == i1) {
- if (DEBUG && i2 != chunk->off2)
- printf("i2: %d off2: %d\n", i2, chunk->off2);
+ /* 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);
+ }
- assert(i2 == chunk->off2);
+ /* 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_OPT_SET(&diff_opts, RECURSIVE);
+ diff_opts.detect_rename = 0;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ paths[0] = origin->path;
+ paths[1] = NULL;
- i1--;
- i2--;
- if (chunk->len1 > 0)
- i1 += chunk->len1;
+ parse_pathspec(&diff_opts.pathspec,
+ PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
+ PATHSPEC_LITERAL_PATH, "", paths);
+ diff_setup_done(&diff_opts);
- if (chunk->len2 > 0)
- i2 += chunk->len2;
+ if (is_null_oid(&origin->commit->object.oid))
+ do_diff_cache(&parent->tree->object.oid, &diff_opts);
+ else
+ diff_tree_oid(&parent->tree->object.oid,
+ &origin->commit->tree->object.oid,
+ "", &diff_opts);
+ diffcore_std(&diff_opts);
- cur_chunk++;
- }
- else {
- if (i2 >= util2->num_lines)
+ 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;
+}
- if (map[i1] != map2[i2] && map[i1] != -1) {
- if (DEBUG)
- printf("map: i1: %d %d %p i2: %d %d %p\n",
- i1, map[i1],
- (void *) (i1 != -1 ? blame_lines[map[i1]] : NULL),
- i2, map2[i2],
- (void *) (i2 != -1 ? blame_lines[map2[i2]] : NULL));
- if (map2[i2] != -1 &&
- blame_lines[map[i1]] &&
- !blame_lines[map2[i2]])
- map[i1] = map2[i2];
- }
+/*
+ * 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;
- if (map[i1] == -1 && map2[i2] != -1)
- map[i1] = map2[i2];
- }
+ diff_setup(&diff_opts);
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
+ 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(&parent->tree->object.oid, &diff_opts);
+ else
+ diff_tree_oid(&parent->tree->object.oid,
+ &origin->commit->tree->object.oid,
+ "", &diff_opts);
+ diffcore_std(&diff_opts);
- if (DEBUG > 1)
- printf("l1: %d l2: %d i1: %d i2: %d\n",
- map[i1], map2[i2], i1, i2);
+ 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 map_line(struct commit *commit, int line)
+/*
+ * Append a new blame entry to a given output queue.
+ */
+static void add_blame_entry(struct blame_entry ***queue,
+ const struct blame_entry *src)
{
- struct util_info *info = commit->util;
- assert(line >= 0 && line < info->num_lines);
- return info->line_map[line];
+ struct blame_entry *e = xmalloc(sizeof(*e));
+ memcpy(e, src, sizeof(*e));
+ blame_origin_incref(e->suspect);
+
+ e->next = **queue;
+ **queue = e;
+ *queue = &e->next;
}
-static struct util_info *get_util(struct commit *commit)
+/*
+ * src typically is on-stack; we want to copy the information in it to
+ * a malloced blame_entry that gets added to the given queue. The
+ * origin of dst loses a refcnt.
+ */
+static void dup_entry(struct blame_entry ***queue,
+ struct blame_entry *dst, struct blame_entry *src)
{
- struct util_info *util = commit->util;
+ blame_origin_incref(src->suspect);
+ blame_origin_decref(dst->suspect);
+ memcpy(dst, src, sizeof(*src));
+ dst->next = **queue;
+ **queue = dst;
+ *queue = &dst->next;
+}
- if (util)
- return util;
+const char *blame_nth_line(struct blame_scoreboard *sb, long lno)
+{
+ return sb->final_buf + sb->lineno[lno];
+}
- util = xcalloc(1, sizeof(struct util_info));
- util->num_lines = -1;
- commit->util = util;
- return util;
+/*
+ * It is known that lines between tlno to same came from parent, and e
+ * has an overlap with that range. it also is known that parent's
+ * line plno corresponds to e's line tlno.
+ *
+ * <---- e ----->
+ * <------>
+ * <------------>
+ * <------------>
+ * <------------------>
+ *
+ * Split e into potentially three parts; before this chunk, the chunk
+ * to be blamed for the parent, and after that portion.
+ */
+static void split_overlap(struct blame_entry *split,
+ struct blame_entry *e,
+ int tlno, int plno, int same,
+ struct blame_origin *parent)
+{
+ int chunk_end_lno;
+ memset(split, 0, sizeof(struct blame_entry [3]));
+
+ if (e->s_lno < tlno) {
+ /* there is a pre-chunk part not blamed on parent */
+ split[0].suspect = blame_origin_incref(e->suspect);
+ split[0].lno = e->lno;
+ split[0].s_lno = e->s_lno;
+ split[0].num_lines = tlno - e->s_lno;
+ split[1].lno = e->lno + tlno - e->s_lno;
+ split[1].s_lno = plno;
+ }
+ else {
+ split[1].lno = e->lno;
+ split[1].s_lno = plno + (e->s_lno - tlno);
+ }
+
+ if (same < e->s_lno + e->num_lines) {
+ /* there is a post-chunk part not blamed on parent */
+ split[2].suspect = blame_origin_incref(e->suspect);
+ split[2].lno = e->lno + (same - e->s_lno);
+ split[2].s_lno = e->s_lno + (same - e->s_lno);
+ split[2].num_lines = e->s_lno + e->num_lines - same;
+ chunk_end_lno = split[2].lno;
+ }
+ else
+ chunk_end_lno = e->lno + e->num_lines;
+ split[1].num_lines = chunk_end_lno - split[1].lno;
+
+ /*
+ * if it turns out there is nothing to blame the parent for,
+ * forget about the splitting. !split[1].suspect signals this.
+ */
+ if (split[1].num_lines < 1)
+ return;
+ split[1].suspect = blame_origin_incref(parent);
}
-static int fill_util_info(struct commit *commit)
+/*
+ * split_overlap() divided an existing blame e into up to three parts
+ * in split. Any assigned blame is moved to queue to
+ * reflect the split.
+ */
+static void split_blame(struct blame_entry ***blamed,
+ struct blame_entry ***unblamed,
+ struct blame_entry *split,
+ struct blame_entry *e)
{
- struct util_info *util = commit->util;
+ if (split[0].suspect && split[2].suspect) {
+ /* The first part (reuse storage for the existing entry e) */
+ dup_entry(unblamed, e, &split[0]);
- assert(util);
- assert(util->pathname);
+ /* The last part -- me */
+ add_blame_entry(unblamed, &split[2]);
- return !!get_blob_sha1(commit->tree, util->pathname, util->sha1);
+ /* ... and the middle part -- parent */
+ add_blame_entry(blamed, &split[1]);
+ }
+ else if (!split[0].suspect && !split[2].suspect)
+ /*
+ * The parent covers the entire area; reuse storage for
+ * e and replace it with the parent.
+ */
+ dup_entry(blamed, e, &split[1]);
+ else if (split[0].suspect) {
+ /* me and then parent */
+ dup_entry(unblamed, e, &split[0]);
+ add_blame_entry(blamed, &split[1]);
+ }
+ else {
+ /* parent and then me */
+ dup_entry(blamed, e, &split[1]);
+ add_blame_entry(unblamed, &split[2]);
+ }
}
-static void alloc_line_map(struct commit *commit)
+/*
+ * After splitting the blame, the origins used by the
+ * on-stack blame_entry should lose one refcnt each.
+ */
+static void decref_split(struct blame_entry *split)
{
- struct util_info *util = commit->util;
int i;
- if (util->line_map)
- return;
+ for (i = 0; i < 3; i++)
+ blame_origin_decref(split[i].suspect);
+}
+
+/*
+ * reverse_blame reverses the list given in head, appending tail.
+ * That allows us to build lists in reverse order, then reverse them
+ * afterwards. This can be faster than building the list in proper
+ * order right away. The reason is that building in proper order
+ * requires writing a link in the _previous_ element, while building
+ * in reverse order just requires placing the list head into the
+ * _current_ element.
+ */
- get_blob(commit);
+static struct blame_entry *reverse_blame(struct blame_entry *head,
+ struct blame_entry *tail)
+{
+ while (head) {
+ struct blame_entry *next = head->next;
+ head->next = tail;
+ tail = head;
+ head = next;
+ }
+ return tail;
+}
- util->num_lines = 0;
- for (i = 0; i < util->size; i++) {
- if (util->buf[i] == '\n')
- util->num_lines++;
+/*
+ * Process one hunk from the patch between the current suspect for
+ * blame_entry e and its parent. This first blames any unfinished
+ * entries before the chunk (which is where target and parent start
+ * differing) on the parent, and then splits blame entries at the
+ * start and at the end of the difference region. Since use of -M and
+ * -C options may lead to overlapping/duplicate source line number
+ * ranges, all we can rely on from sorting/merging is the order of the
+ * first suspect line number.
+ */
+static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
+ int tlno, int offset, int same,
+ struct blame_origin *parent)
+{
+ struct blame_entry *e = **srcq;
+ struct blame_entry *samep = NULL, *diffp = NULL;
+
+ while (e && e->s_lno < tlno) {
+ struct blame_entry *next = e->next;
+ /*
+ * current record starts before differing portion. If
+ * it reaches into it, we need to split it up and
+ * examine the second part separately.
+ */
+ if (e->s_lno + e->num_lines > tlno) {
+ /* Move second half to a new record */
+ int len = tlno - e->s_lno;
+ struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
+ n->suspect = e->suspect;
+ n->lno = e->lno + len;
+ n->s_lno = e->s_lno + len;
+ n->num_lines = e->num_lines - len;
+ e->num_lines = len;
+ e->score = 0;
+ /* Push new record to diffp */
+ n->next = diffp;
+ diffp = n;
+ } else
+ blame_origin_decref(e->suspect);
+ /* Pass blame for everything before the differing
+ * chunk to the parent */
+ e->suspect = blame_origin_incref(parent);
+ e->s_lno += offset;
+ e->next = samep;
+ samep = e;
+ e = next;
+ }
+ /*
+ * As we don't know how much of a common stretch after this
+ * diff will occur, the currently blamed parts are all that we
+ * can assign to the parent for now.
+ */
+
+ if (samep) {
+ **dstq = reverse_blame(samep, **dstq);
+ *dstq = &samep->next;
}
- if (util->buf[util->size - 1] != '\n')
- util->num_lines++;
+ /*
+ * Prepend the split off portions: everything after e starts
+ * after the blameable portion.
+ */
+ e = reverse_blame(diffp, e);
+
+ /*
+ * Now retain records on the target while parts are different
+ * from the parent.
+ */
+ samep = NULL;
+ diffp = NULL;
+ while (e && e->s_lno < same) {
+ struct blame_entry *next = e->next;
+
+ /*
+ * If current record extends into sameness, need to split.
+ */
+ if (e->s_lno + e->num_lines > same) {
+ /*
+ * Move second half to a new record to be
+ * processed by later chunks
+ */
+ int len = same - e->s_lno;
+ struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
+ n->suspect = blame_origin_incref(e->suspect);
+ n->lno = e->lno + len;
+ n->s_lno = e->s_lno + len;
+ n->num_lines = e->num_lines - len;
+ e->num_lines = len;
+ e->score = 0;
+ /* Push new record to samep */
+ n->next = samep;
+ samep = n;
+ }
+ e->next = diffp;
+ diffp = e;
+ e = next;
+ }
+ **srcq = reverse_blame(diffp, reverse_blame(samep, e));
+ /* Move across elements that are in the unblamable portion */
+ if (diffp)
+ *srcq = &diffp->next;
+}
+
+struct blame_chunk_cb_data {
+ struct blame_origin *parent;
+ long offset;
+ struct blame_entry **dstq;
+ struct blame_entry **srcq;
+};
+
+/* diff chunks are from parent to target */
+static int blame_chunk_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
+{
+ struct blame_chunk_cb_data *d = data;
+ if (start_a - start_b != d->offset)
+ die("internal error in blame::blame_chunk_cb");
+ blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b,
+ start_b + count_b, d->parent);
+ d->offset = start_a + count_a - (start_b + count_b);
+ return 0;
+}
- util->line_map = xmalloc(sizeof(int) * util->num_lines);
+/*
+ * We are looking at the origin 'target' and aiming to pass blame
+ * for the lines it is suspected to its parent. Run diff to find
+ * which lines came from parent and pass blame for them.
+ */
+static void pass_blame_to_parent(struct blame_scoreboard *sb,
+ struct blame_origin *target,
+ struct blame_origin *parent)
+{
+ mmfile_t file_p, file_o;
+ struct blame_chunk_cb_data d;
+ struct blame_entry *newdest = NULL;
+
+ if (!target->suspects)
+ return; /* nothing remains for this target */
+
+ d.parent = parent;
+ d.offset = 0;
+ d.dstq = &newdest; d.srcq = &target->suspects;
+
+ fill_origin_blob(&sb->revs->diffopt, parent, &file_p, &sb->num_read_blob);
+ fill_origin_blob(&sb->revs->diffopt, target, &file_o, &sb->num_read_blob);
+ sb->num_get_patch++;
+
+ if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, sb->xdl_opts))
+ die("unable to generate diff (%s -> %s)",
+ oid_to_hex(&parent->commit->object.oid),
+ oid_to_hex(&target->commit->object.oid));
+ /* The rest are the same as the parent */
+ blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
+ *d.dstq = NULL;
+ queue_blames(sb, parent, newdest);
+
+ return;
+}
- for (i = 0; i < util->num_lines; i++)
- util->line_map[i] = -1;
+/*
+ * The lines in blame_entry after splitting blames many times can become
+ * very small and trivial, and at some point it becomes pointless to
+ * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any
+ * ordinary C program, and it is not worth to say it was copied from
+ * totally unrelated file in the parent.
+ *
+ * Compute how trivial the lines in the blame_entry are.
+ */
+unsigned blame_entry_score(struct blame_scoreboard *sb, struct blame_entry *e)
+{
+ unsigned score;
+ const char *cp, *ep;
+
+ if (e->score)
+ return e->score;
+
+ score = 1;
+ cp = blame_nth_line(sb, e->lno);
+ ep = blame_nth_line(sb, e->lno + e->num_lines);
+ while (cp < ep) {
+ unsigned ch = *((unsigned char *)cp);
+ if (isalnum(ch))
+ score++;
+ cp++;
+ }
+ e->score = score;
+ return score;
}
-static void init_first_commit(struct commit *commit, const char *filename)
+/*
+ * best_so_far[] and this[] are both a split of an existing blame_entry
+ * that passes blame to the parent. Maintain best_so_far the best split
+ * so far, by comparing this and best_so_far and copying this into
+ * bst_so_far as needed.
+ */
+static void copy_split_if_better(struct blame_scoreboard *sb,
+ struct blame_entry *best_so_far,
+ struct blame_entry *this)
{
- struct util_info *util = commit->util;
int i;
- util->pathname = filename;
- if (fill_util_info(commit))
- die("fill_util_info failed");
+ if (!this[1].suspect)
+ return;
+ if (best_so_far[1].suspect) {
+ if (blame_entry_score(sb, &this[1]) < blame_entry_score(sb, &best_so_far[1]))
+ return;
+ }
- alloc_line_map(commit);
+ for (i = 0; i < 3; i++)
+ blame_origin_incref(this[i].suspect);
+ decref_split(best_so_far);
+ memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
+}
- util = commit->util;
+/*
+ * We are looking at a part of the final image represented by
+ * ent (tlno and same are offset by ent->s_lno).
+ * tlno is where we are looking at in the final image.
+ * up to (but not including) same match preimage.
+ * plno is where we are looking at in the preimage.
+ *
+ * <-------------- final image ---------------------->
+ * <------ent------>
+ * ^tlno ^same
+ * <---------preimage----->
+ * ^plno
+ *
+ * All line numbers are 0-based.
+ */
+static void handle_split(struct blame_scoreboard *sb,
+ struct blame_entry *ent,
+ int tlno, int plno, int same,
+ struct blame_origin *parent,
+ struct blame_entry *split)
+{
+ if (ent->num_lines <= tlno)
+ return;
+ if (tlno < same) {
+ struct blame_entry this[3];
+ tlno += ent->s_lno;
+ same += ent->s_lno;
+ split_overlap(this, ent, tlno, plno, same, parent);
+ copy_split_if_better(sb, split, this);
+ decref_split(this);
+ }
+}
+
+struct handle_split_cb_data {
+ struct blame_scoreboard *sb;
+ struct blame_entry *ent;
+ struct blame_origin *parent;
+ struct blame_entry *split;
+ long plno;
+ long tlno;
+};
- for (i = 0; i < util->num_lines; i++)
- util->line_map[i] = i;
+static int handle_split_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
+{
+ struct handle_split_cb_data *d = data;
+ handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent,
+ d->split);
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+ return 0;
}
-static void process_commits(struct rev_info *rev, const char *path,
- struct commit **initial)
+/*
+ * Find the lines from parent that are the same as ent so that
+ * we can pass blames to it. file_p has the blob contents for
+ * the parent.
+ */
+static void find_copy_in_blob(struct blame_scoreboard *sb,
+ struct blame_entry *ent,
+ struct blame_origin *parent,
+ struct blame_entry *split,
+ mmfile_t *file_p)
{
- int i;
- struct util_info *util;
- int lines_left;
- int *blame_p;
- int *new_lines;
- int new_lines_len;
-
- struct commit *commit = get_revision(rev);
- assert(commit);
- init_first_commit(commit, path);
-
- util = commit->util;
- num_blame_lines = util->num_lines;
- blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
- blame_contents = util->buf;
- blame_len = util->size;
-
- for (i = 0; i < num_blame_lines; i++)
- blame_lines[i] = NULL;
-
- lines_left = num_blame_lines;
- blame_p = xmalloc(sizeof(int) * num_blame_lines);
- new_lines = xmalloc(sizeof(int) * num_blame_lines);
- do {
- struct commit_list *parents;
- int num_parents;
- struct util_info *util;
+ const char *cp;
+ mmfile_t file_o;
+ struct handle_split_cb_data d;
+
+ memset(&d, 0, sizeof(d));
+ d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
+ /*
+ * Prepare mmfile that contains only the lines in ent.
+ */
+ cp = blame_nth_line(sb, ent->lno);
+ file_o.ptr = (char *) cp;
+ file_o.size = blame_nth_line(sb, ent->lno + ent->num_lines) - cp;
+
+ /*
+ * file_o is a part of final image we are annotating.
+ * file_p partially may match that image.
+ */
+ memset(split, 0, sizeof(struct blame_entry [3]));
+ if (diff_hunks(file_p, &file_o, handle_split_cb, &d, sb->xdl_opts))
+ die("unable to generate diff (%s)",
+ oid_to_hex(&parent->commit->object.oid));
+ /* remainder, if any, all match the preimage */
+ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
+}
- if (DEBUG)
- printf("\nProcessing commit: %d %s\n", num_commits,
- sha1_to_hex(commit->object.sha1));
+/* Move all blame entries from list *source that have a score smaller
+ * than score_min to the front of list *small.
+ * Returns a pointer to the link pointing to the old head of the small list.
+ */
- if (lines_left == 0)
- return;
+static struct blame_entry **filter_small(struct blame_scoreboard *sb,
+ struct blame_entry **small,
+ struct blame_entry **source,
+ unsigned score_min)
+{
+ struct blame_entry *p = *source;
+ struct blame_entry *oldsmall = *small;
+ while (p) {
+ if (blame_entry_score(sb, p) <= score_min) {
+ *small = p;
+ small = &p->next;
+ p = *small;
+ } else {
+ *source = p;
+ source = &p->next;
+ p = *source;
+ }
+ }
+ *small = oldsmall;
+ *source = NULL;
+ return small;
+}
- num_commits++;
- memset(blame_p, 0, sizeof(int) * num_blame_lines);
- new_lines_len = 0;
- num_parents = 0;
- for (parents = commit->parents;
- parents != NULL; parents = parents->next)
- num_parents++;
+/*
+ * See if lines currently target is suspected for can be attributed to
+ * parent.
+ */
+static void find_move_in_parent(struct blame_scoreboard *sb,
+ struct blame_entry ***blamed,
+ struct blame_entry **toosmall,
+ struct blame_origin *target,
+ struct blame_origin *parent)
+{
+ struct blame_entry *e, split[3];
+ struct blame_entry *unblamed = target->suspects;
+ struct blame_entry *leftover = NULL;
+ mmfile_t file_p;
- if (num_parents == 0)
- *initial = commit;
+ if (!unblamed)
+ return; /* nothing remains for this target */
- if (fill_util_info(commit))
- continue;
+ fill_origin_blob(&sb->revs->diffopt, parent, &file_p, &sb->num_read_blob);
+ if (!file_p.ptr)
+ return;
- alloc_line_map(commit);
- util = commit->util;
+ /* At each iteration, unblamed has a NULL-terminated list of
+ * entries that have not yet been tested for blame. leftover
+ * contains the reversed list of entries that have been tested
+ * without being assignable to the parent.
+ */
+ do {
+ struct blame_entry **unblamedtail = &unblamed;
+ struct blame_entry *next;
+ for (e = unblamed; e; e = next) {
+ next = e->next;
+ find_copy_in_blob(sb, e, parent, split, &file_p);
+ if (split[1].suspect &&
+ sb->move_score < blame_entry_score(sb, &split[1])) {
+ split_blame(blamed, &unblamedtail, split, e);
+ } else {
+ e->next = leftover;
+ leftover = e;
+ }
+ decref_split(split);
+ }
+ *unblamedtail = NULL;
+ toosmall = filter_small(sb, toosmall, &unblamed, sb->move_score);
+ } while (unblamed);
+ target->suspects = reverse_blame(leftover, NULL);
+}
- for (parents = commit->parents;
- parents != NULL; parents = parents->next) {
- struct commit *parent = parents->item;
- struct patch *patch;
+struct blame_list {
+ struct blame_entry *ent;
+ struct blame_entry split[3];
+};
- if (parse_commit(parent) < 0)
- die("parse_commit error");
+/*
+ * Count the number of entries the target is suspected for,
+ * and prepare a list of entry and the best split.
+ */
+static struct blame_list *setup_blame_list(struct blame_entry *unblamed,
+ int *num_ents_p)
+{
+ struct blame_entry *e;
+ int num_ents, i;
+ struct blame_list *blame_list = NULL;
+
+ for (e = unblamed, num_ents = 0; e; e = e->next)
+ num_ents++;
+ if (num_ents) {
+ blame_list = xcalloc(num_ents, sizeof(struct blame_list));
+ for (e = unblamed, i = 0; e; e = e->next)
+ blame_list[i++].ent = e;
+ }
+ *num_ents_p = num_ents;
+ return blame_list;
+}
- if (DEBUG)
- printf("parent: %s\n",
- sha1_to_hex(parent->object.sha1));
+/*
+ * 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 */
- if (fill_util_info(parent)) {
- num_parents--;
+ diff_setup(&diff_opts);
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
+ 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_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
+
+ if (is_null_oid(&target->commit->object.oid))
+ do_diff_cache(&parent->tree->object.oid, &diff_opts);
+ else
+ diff_tree_oid(&parent->tree->object.oid,
+ &target->commit->tree->object.oid,
+ "", &diff_opts);
+
+ if (!DIFF_OPT_TST(&diff_opts, 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 this[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;
- }
- patch = get_patch(parent, commit);
- alloc_line_map(parent);
- fill_line_map(parent, commit, patch);
-
- for (i = 0; i < patch->num; i++) {
- int l;
- for (l = 0; l < patch->chunks[i].len2; l++) {
- int mapped_line =
- map_line(commit, patch->chunks[i].off2 + l);
- if (mapped_line != -1) {
- blame_p[mapped_line]++;
- if (blame_p[mapped_line] == num_parents)
- new_lines[new_lines_len++] = mapped_line;
- }
- }
+ for (j = 0; j < num_ents; j++) {
+ find_copy_in_blob(sb, blame_list[j].ent,
+ norigin, this, &file_p);
+ copy_split_if_better(sb, blame_list[j].split,
+ this);
+ decref_split(this);
}
- free_patch(patch);
+ blame_origin_decref(norigin);
}
- if (DEBUG)
- printf("parents: %d\n", num_parents);
-
- for (i = 0; i < new_lines_len; i++) {
- int mapped_line = new_lines[i];
- if (blame_lines[mapped_line] == NULL) {
- blame_lines[mapped_line] = commit;
- lines_left--;
- if (DEBUG)
- printf("blame: mapped: %d i: %d\n",
- mapped_line, i);
+ 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);
}
- } while ((commit = get_revision(rev)) != NULL);
+ 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 int compare_tree_path(struct rev_info *revs,
- struct commit *c1, struct commit *c2)
+/*
+ * The blobs of origin and porigin exactly match, so everything
+ * origin is suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(struct blame_scoreboard *sb,
+ struct blame_origin *origin, struct blame_origin *porigin)
{
- int ret;
- const char *paths[2];
- struct util_info *util = c2->util;
- paths[0] = util->pathname;
- paths[1] = NULL;
+ struct blame_entry *e, *suspects;
- diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
- &revs->pruning);
- ret = rev_compare_tree(revs, c1->tree, c2->tree);
- diff_tree_release_paths(&revs->pruning);
- return ret;
+ if (!porigin->file.ptr && origin->file.ptr) {
+ /* Steal its file */
+ porigin->file = origin->file;
+ origin->file.ptr = NULL;
+ }
+ suspects = origin->suspects;
+ origin->suspects = NULL;
+ for (e = suspects; e; e = e->next) {
+ blame_origin_incref(porigin);
+ blame_origin_decref(e->suspect);
+ e->suspect = porigin;
+ }
+ queue_blames(sb, porigin, suspects);
}
-static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1,
- const char *path)
+/*
+ * We pass blame from the current commit to its parents. We keep saying
+ * "parent" (and "porigin"), but what we mean is to find scapegoat to
+ * exonerate ourselves.
+ */
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit,
+ int reverse)
{
- int ret;
- const char *paths[2];
- paths[0] = path;
- paths[1] = NULL;
-
- diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
- &revs->pruning);
- ret = rev_same_tree_as_empty(revs, t1);
- diff_tree_release_paths(&revs->pruning);
- return ret;
+ if (!reverse) {
+ if (revs->first_parent_only &&
+ commit->parents &&
+ commit->parents->next) {
+ free_commit_list(commit->parents->next);
+ commit->parents->next = NULL;
+ }
+ return commit->parents;
+ }
+ return lookup_decoration(&revs->children, &commit->object);
}
-static const char *find_rename(struct commit *commit, struct commit *parent)
+static int num_scapegoats(struct rev_info *revs, struct commit *commit, int reverse)
{
- struct util_info *cutil = commit->util;
- struct diff_options diff_opts;
- const char *paths[1];
- int i;
+ struct commit_list *l = first_scapegoat(revs, commit, reverse);
+ return commit_list_count(l);
+}
- if (DEBUG) {
- printf("find_rename commit: %s ",
- sha1_to_hex(commit->object.sha1));
- puts(sha1_to_hex(parent->object.sha1));
+/* Distribute collected unsorted blames to the respected sorted lists
+ * in the various origins.
+ */
+static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *blamed)
+{
+ blamed = llist_mergesort(blamed, get_next_blame, set_next_blame,
+ compare_blame_suspect);
+ while (blamed)
+ {
+ struct blame_origin *porigin = blamed->suspect;
+ struct blame_entry *suspects = NULL;
+ do {
+ struct blame_entry *next = blamed->next;
+ blamed->next = suspects;
+ suspects = blamed;
+ blamed = next;
+ } while (blamed && blamed->suspect == porigin);
+ suspects = reverse_blame(suspects, NULL);
+ queue_blames(sb, porigin, suspects);
}
+}
- diff_setup(&diff_opts);
- diff_opts.recursive = 1;
- diff_opts.detect_rename = DIFF_DETECT_RENAME;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff_setup_done failed");
+#define MAXSG 16
- diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,
- "", &diff_opts);
- diffcore_std(&diff_opts);
+static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin, int opt)
+{
+ struct rev_info *revs = sb->revs;
+ int i, pass, num_sg;
+ struct commit *commit = origin->commit;
+ struct commit_list *sg;
+ struct blame_origin *sg_buf[MAXSG];
+ struct blame_origin *porigin, **sg_origin = sg_buf;
+ struct blame_entry *toosmall = NULL;
+ struct blame_entry *blames, **blametail = &blames;
+
+ num_sg = num_scapegoats(revs, commit, sb->reverse);
+ if (!num_sg)
+ goto finish;
+ else if (num_sg < ARRAY_SIZE(sg_buf))
+ memset(sg_buf, 0, sizeof(sg_buf));
+ else
+ sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
+
+ /*
+ * The first pass looks for unrenamed path to optimize for
+ * common cases, then we look for renames in the second pass.
+ */
+ for (pass = 0; pass < 2 - sb->no_whole_file_rename; pass++) {
+ struct blame_origin *(*find)(struct commit *, struct blame_origin *);
+ find = pass ? find_rename : find_origin;
+
+ for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct commit *p = sg->item;
+ int j, same;
+
+ if (sg_origin[i])
+ continue;
+ if (parse_commit(p))
+ continue;
+ porigin = find(p, origin);
+ if (!porigin)
+ continue;
+ if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) {
+ pass_whole_blame(sb, origin, porigin);
+ blame_origin_decref(porigin);
+ goto finish;
+ }
+ for (j = same = 0; j < i; j++)
+ if (sg_origin[j] &&
+ !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) {
+ same = 1;
+ break;
+ }
+ if (!same)
+ sg_origin[i] = porigin;
+ else
+ blame_origin_decref(porigin);
+ }
+ }
- for (i = 0; i < diff_queued_diff.nr; i++) {
- struct diff_filepair *p = diff_queued_diff.queue[i];
+ sb->num_commits++;
+ for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct blame_origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ if (!origin->previous) {
+ blame_origin_incref(porigin);
+ origin->previous = porigin;
+ }
+ pass_blame_to_parent(sb, origin, porigin);
+ if (!origin->suspects)
+ goto finish;
+ }
+
+ /*
+ * Optionally find moves in parents' files.
+ */
+ if (opt & PICKAXE_BLAME_MOVE) {
+ filter_small(sb, &toosmall, &origin->suspects, sb->move_score);
+ if (origin->suspects) {
+ for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct blame_origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ find_move_in_parent(sb, &blametail, &toosmall, origin, porigin);
+ if (!origin->suspects)
+ break;
+ }
+ }
+ }
- if (p->status == 'R' &&
- !strcmp(p->one->path, cutil->pathname)) {
- if (DEBUG)
- printf("rename %s -> %s\n",
- p->one->path, p->two->path);
- return p->two->path;
+ /*
+ * Optionally find copies from parents' files.
+ */
+ if (opt & PICKAXE_BLAME_COPY) {
+ if (sb->copy_score > sb->move_score)
+ filter_small(sb, &toosmall, &origin->suspects, sb->copy_score);
+ else if (sb->copy_score < sb->move_score) {
+ origin->suspects = blame_merge(origin->suspects, toosmall);
+ toosmall = NULL;
+ filter_small(sb, &toosmall, &origin->suspects, sb->copy_score);
+ }
+ if (!origin->suspects)
+ goto finish;
+
+ for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct blame_origin *porigin = sg_origin[i];
+ find_copy_in_parent(sb, &blametail, &toosmall,
+ origin, sg->item, porigin, opt);
+ if (!origin->suspects)
+ goto finish;
}
}
- return 0;
+finish:
+ *blametail = NULL;
+ distribute_blame(sb, blames);
+ /*
+ * prepend toosmall to origin->suspects
+ *
+ * There is no point in sorting: this ends up on a big
+ * unsorted list in the caller anyway.
+ */
+ if (toosmall) {
+ struct blame_entry **tail = &toosmall;
+ while (*tail)
+ tail = &(*tail)->next;
+ *tail = origin->suspects;
+ origin->suspects = toosmall;
+ }
+ for (i = 0; i < num_sg; i++) {
+ if (sg_origin[i]) {
+ drop_origin_blob(sg_origin[i]);
+ blame_origin_decref(sg_origin[i]);
+ }
+ }
+ drop_origin_blob(origin);
+ if (sg_buf != sg_origin)
+ free(sg_origin);
}
-static void simplify_commit(struct rev_info *revs, struct commit *commit)
+/*
+ * The main loop -- while we have blobs with lines whose true origin
+ * is still unknown, pick one blob, and allow its lines to pass blames
+ * to its parents. */
+void assign_blame(struct blame_scoreboard *sb, int opt)
{
- struct commit_list **pp, *parent;
+ struct rev_info *revs = sb->revs;
+ struct commit *commit = prio_queue_get(&sb->commits);
- if (!commit->tree)
- return;
+ while (commit) {
+ struct blame_entry *ent;
+ struct blame_origin *suspect = commit->util;
- if (!commit->parents) {
- struct util_info *util = commit->util;
- if (!same_tree_as_empty_path(revs, commit->tree,
- util->pathname))
- commit->object.flags |= TREECHANGE;
- return;
- }
-
- pp = &commit->parents;
- while ((parent = *pp) != NULL) {
- struct commit *p = parent->item;
+ /* find one suspect to break down */
+ while (suspect && !suspect->suspects)
+ suspect = suspect->next;
- if (p->object.flags & UNINTERESTING) {
- pp = &parent->next;
+ if (!suspect) {
+ commit = prio_queue_get(&sb->commits);
continue;
}
- parse_commit(p);
- switch (compare_tree_path(revs, p, commit)) {
- case REV_TREE_SAME:
- parent->next = NULL;
- commit->parents = parent;
- get_util(p)->pathname = get_util(commit)->pathname;
- return;
-
- case REV_TREE_NEW:
- {
- struct util_info *util = commit->util;
- if (revs->remove_empty_trees &&
- same_tree_as_empty_path(revs, p->tree,
- util->pathname)) {
- const char *new_name = find_rename(commit, p);
- if (new_name) {
- struct util_info *putil = get_util(p);
- if (!putil->pathname)
- putil->pathname = xstrdup(new_name);
- }
- else {
- *pp = parent->next;
+ assert(commit == suspect->commit);
+
+ /*
+ * We will use this suspect later in the loop,
+ * so hold onto it in the meantime.
+ */
+ blame_origin_incref(suspect);
+ parse_commit(commit);
+ if (sb->reverse ||
+ (!(commit->object.flags & UNINTERESTING) &&
+ !(revs->max_age != -1 && commit->date < revs->max_age)))
+ pass_blame(sb, suspect, opt);
+ else {
+ commit->object.flags |= UNINTERESTING;
+ if (commit->object.parsed)
+ mark_parents_uninteresting(commit);
+ }
+ /* treat root commit as boundary */
+ if (!commit->parents && !sb->show_root)
+ commit->object.flags |= UNINTERESTING;
+
+ /* Take responsibility for the remaining entries */
+ ent = suspect->suspects;
+ if (ent) {
+ suspect->guilty = 1;
+ for (;;) {
+ struct blame_entry *next = ent->next;
+ if (sb->found_guilty_entry)
+ sb->found_guilty_entry(ent, sb->found_guilty_entry_data);
+ if (next) {
+ ent = next;
continue;
}
+ ent->next = sb->ent;
+ sb->ent = suspect->suspects;
+ suspect->suspects = NULL;
+ break;
}
}
+ blame_origin_decref(suspect);
- /* fallthrough */
- case REV_TREE_DIFFERENT:
- pp = &parent->next;
- if (!get_util(p)->pathname)
- get_util(p)->pathname =
- get_util(commit)->pathname;
- continue;
- }
- die("bad tree compare for commit %s",
- sha1_to_hex(commit->object.sha1));
+ if (sb->debug) /* sanity */
+ sanity_check_refcnt(sb);
}
- commit->object.flags |= TREECHANGE;
}
-struct commit_info
-{
- char *author;
- char *author_mail;
- unsigned long author_time;
- char *author_tz;
-
- /* filled only when asked for details */
- char *committer;
- char *committer_mail;
- unsigned long committer_time;
- char *committer_tz;
-
- char *summary;
-};
-
-static void get_ac_line(const char *inbuf, const char *what,
- int bufsz, char *person, char **mail,
- unsigned long *time, char **tz)
+static const char *get_next_line(const char *start, const char *end)
{
- int len;
- char *tmp, *endp;
-
- tmp = strstr(inbuf, what);
- if (!tmp)
- goto error_out;
- tmp += strlen(what);
- endp = strchr(tmp, '\n');
- if (!endp)
- len = strlen(tmp);
- else
- len = endp - tmp;
- if (bufsz <= len) {
- error_out:
- /* Ugh */
- person = *mail = *tz = "(unknown)";
- *time = 0;
- return;
- }
- memcpy(person, tmp, len);
-
- tmp = person;
- tmp += len;
- *tmp = 0;
- while (*tmp != ' ')
- tmp--;
- *tz = tmp+1;
-
- *tmp = 0;
- while (*tmp != ' ')
- tmp--;
- *time = strtoul(tmp, NULL, 10);
-
- *tmp = 0;
- while (*tmp != ' ')
- tmp--;
- *mail = tmp + 1;
- *tmp = 0;
+ const char *nl = memchr(start, '\n', end - start);
+ return nl ? nl + 1 : end;
}
-static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed)
+/*
+ * To allow quick access to the contents of nth line in the
+ * final image, prepare an index in the scoreboard.
+ */
+static int prepare_lines(struct blame_scoreboard *sb)
{
- int len;
- char *tmp, *endp;
- static char author_buf[1024];
- static char committer_buf[1024];
- static char summary_buf[1024];
-
- ret->author = author_buf;
- get_ac_line(commit->buffer, "\nauthor ",
- sizeof(author_buf), author_buf, &ret->author_mail,
- &ret->author_time, &ret->author_tz);
-
- if (!detailed)
- return;
+ const char *buf = sb->final_buf;
+ unsigned long len = sb->final_buf_size;
+ const char *end = buf + len;
+ const char *p;
+ int *lineno;
+ int num = 0;
- ret->committer = committer_buf;
- get_ac_line(commit->buffer, "\ncommitter ",
- sizeof(committer_buf), committer_buf, &ret->committer_mail,
- &ret->committer_time, &ret->committer_tz);
+ for (p = buf; p < end; p = get_next_line(p, end))
+ num++;
- ret->summary = summary_buf;
- tmp = strstr(commit->buffer, "\n\n");
- if (!tmp) {
- error_out:
- sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
- return;
- }
- tmp += 2;
- endp = strchr(tmp, '\n');
- if (!endp)
- goto error_out;
- len = endp - tmp;
- if (len >= sizeof(summary_buf))
- goto error_out;
- memcpy(summary_buf, tmp, len);
- summary_buf[len] = 0;
-}
+ ALLOC_ARRAY(sb->lineno, num + 1);
+ lineno = sb->lineno;
-static const char *format_time(unsigned long time, const char *tz_str,
- int show_raw_time)
-{
- static char time_buf[128];
- time_t t = time;
- int minutes, tz;
- struct tm *tm;
-
- if (show_raw_time) {
- sprintf(time_buf, "%lu %s", time, tz_str);
- return time_buf;
- }
+ for (p = buf; p < end; p = get_next_line(p, end))
+ *lineno++ = p - buf;
- tz = atoi(tz_str);
- minutes = tz < 0 ? -tz : tz;
- minutes = (minutes / 100)*60 + (minutes % 100);
- minutes = tz < 0 ? -minutes : minutes;
- t = time + minutes * 60;
- tm = gmtime(&t);
+ *lineno = len;
- strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
- strcat(time_buf, tz_str);
- return time_buf;
+ sb->num_lines = num;
+ return sb->num_lines;
}
-static void topo_setter(struct commit *c, void *data)
+static struct commit *find_single_final(struct rev_info *revs,
+ const char **name_p)
{
- struct util_info *util = c->util;
- util->topo_data = data;
+ int i;
+ struct commit *found = NULL;
+ const char *name = NULL;
+
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (found)
+ die("More than one commit to dig from %s and %s?",
+ revs->pending.objects[i].name, name);
+ found = (struct commit *)obj;
+ name = revs->pending.objects[i].name;
+ }
+ if (name_p)
+ *name_p = name;
+ return found;
}
-static void *topo_getter(struct commit *c)
+static struct commit *dwim_reverse_initial(struct rev_info *revs,
+ const char **name_p)
{
- struct util_info *util = c->util;
- return util->topo_data;
+ /*
+ * DWIM "git blame --reverse ONE -- PATH" as
+ * "git blame --reverse ONE..HEAD -- PATH" but only do so
+ * when it makes sense.
+ */
+ struct object *obj;
+ struct commit *head_commit;
+ struct object_id head_oid;
+
+ if (revs->pending.nr != 1)
+ return NULL;
+
+ /* Is that sole rev a committish? */
+ obj = revs->pending.objects[0].item;
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ return NULL;
+
+ /* Do we have HEAD? */
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
+ return NULL;
+ head_commit = lookup_commit_reference_gently(&head_oid, 1);
+ if (!head_commit)
+ return NULL;
+
+ /* Turn "ONE" into "ONE..HEAD" then */
+ obj->flags |= UNINTERESTING;
+ add_pending_object(revs, &head_commit->object, "HEAD");
+
+ if (name_p)
+ *name_p = revs->pending.objects[0].name;
+ return (struct commit *)obj;
}
-static int read_ancestry(const char *graft_file,
- unsigned char **start_sha1)
+static struct commit *find_single_initial(struct rev_info *revs,
+ const char **name_p)
{
- FILE *fp = fopen(graft_file, "r");
- char buf[1024];
- if (!fp)
- return -1;
- while (fgets(buf, sizeof(buf), fp)) {
- /* The format is just "Commit Parent1 Parent2 ...\n" */
- int len = strlen(buf);
- struct commit_graft *graft = read_graft_line(buf, len);
- register_commit_graft(graft, 0);
- if (!*start_sha1)
- *start_sha1 = graft->sha1;
+ int i;
+ struct commit *found = NULL;
+ const char *name = NULL;
+
+ /*
+ * There must be one and only one negative commit, and it must be
+ * the boundary.
+ */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (!(obj->flags & UNINTERESTING))
+ continue;
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (found)
+ die("More than one commit to dig up from, %s and %s?",
+ revs->pending.objects[i].name, name);
+ found = (struct commit *) obj;
+ name = revs->pending.objects[i].name;
}
- fclose(fp);
- return 0;
-}
-static int lineno_width(int lines)
-{
- int i, width;
+ if (!name)
+ found = dwim_reverse_initial(revs, &name);
+ if (!name)
+ die("No commit to dig up from?");
- for (width = 1, i = 10; i <= lines + 1; width++)
- i *= 10;
- return width;
+ if (name_p)
+ *name_p = name;
+ return found;
}
-static int find_orig_linenum(struct util_info *u, int lineno)
+void init_scoreboard(struct blame_scoreboard *sb)
{
- int i;
-
- for (i = 0; i < u->num_lines; i++)
- if (lineno == u->line_map[i])
- return i + 1;
- return 0;
+ memset(sb, 0, sizeof(struct blame_scoreboard));
+ sb->move_score = BLAME_DEFAULT_MOVE_SCORE;
+ sb->copy_score = BLAME_DEFAULT_COPY_SCORE;
}
-static void emit_meta(struct commit *c, int lno,
- int sha1_len, int compatibility, int porcelain,
- int show_name, int show_number, int show_raw_time,
- int longest_file, int longest_author,
- int max_digits, int max_orig_digits)
+void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blame_origin **orig)
{
- struct util_info *u;
- int lineno;
- struct commit_info ci;
-
- u = c->util;
- lineno = find_orig_linenum(u, lno);
-
- if (porcelain) {
- int group_size = -1;
- struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1];
- if (cc != c) {
- /* This is the beginning of this group */
- int i;
- for (i = lno + 1; i < num_blame_lines; i++)
- if (blame_lines[i] != c)
- break;
- group_size = i - lno;
- }
- if (0 < group_size)
- printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1),
- lineno, lno + 1, group_size);
- else
- printf("%s %d %d\n", sha1_to_hex(c->object.sha1),
- lineno, lno + 1);
- if (!u->meta_given) {
- get_commit_info(c, &ci, 1);
- printf("author %s\n", ci.author);
- printf("author-mail %s\n", ci.author_mail);
- printf("author-time %lu\n", ci.author_time);
- printf("author-tz %s\n", ci.author_tz);
- printf("committer %s\n", ci.committer);
- printf("committer-mail %s\n", ci.committer_mail);
- printf("committer-time %lu\n", ci.committer_time);
- printf("committer-tz %s\n", ci.committer_tz);
- printf("filename ");
- if (quote_c_style(u->pathname, NULL, NULL, 0))
- quote_c_style(u->pathname, NULL, stdout, 0);
- else
- fputs(u->pathname, stdout);
- printf("\nsummary %s\n", ci.summary);
-
- u->meta_given = 1;
- }
- putchar('\t');
- return;
+ const char *final_commit_name = NULL;
+ struct blame_origin *o;
+ struct commit *final_commit = NULL;
+ enum object_type type;
+
+ if (sb->reverse && sb->contents_from)
+ die(_("--contents and --reverse do not blend well."));
+
+ if (!sb->reverse) {
+ sb->final = find_single_final(sb->revs, &final_commit_name);
+ sb->commits.compare = compare_commits_by_commit_date;
+ } else {
+ sb->final = find_single_initial(sb->revs, &final_commit_name);
+ sb->commits.compare = compare_commits_by_reverse_commit_date;
}
- get_commit_info(c, &ci, 0);
- fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
- if (compatibility) {
- printf("\t(%10s\t%10s\t%d)", ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- lno + 1);
+ if (sb->final && sb->contents_from)
+ die(_("cannot use --contents with final commit object name"));
+
+ if (sb->reverse && sb->revs->first_parent_only)
+ sb->revs->children.name = NULL;
+
+ if (!sb->final) {
+ /*
+ * "--not A B -- path" without anything positive;
+ * do not default to HEAD, but use the working tree
+ * or "--contents".
+ */
+ setup_work_tree();
+ sb->final = fake_working_tree_commit(&sb->revs->diffopt,
+ path, sb->contents_from);
+ add_pending_object(sb->revs, &(sb->final->object), ":");
}
- else {
- if (show_name)
- printf(" %-*.*s", longest_file, longest_file,
- u->pathname);
- if (show_number)
- printf(" %*d", max_orig_digits,
- lineno);
- printf(" (%-*.*s %10s %*d) ",
- longest_author, longest_author, ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- max_digits, lno + 1);
+
+ if (sb->reverse && sb->revs->first_parent_only) {
+ final_commit = find_single_final(sb->revs, NULL);
+ if (!final_commit)
+ die(_("--reverse and --first-parent together require specified latest commit"));
}
-}
-int main(int argc, const char **argv)
-{
- int i;
- struct commit *initial = NULL;
- unsigned char sha1[20], *sha1_p = NULL;
-
- const char *filename = NULL, *commit = NULL;
- char filename_buf[256];
- int sha1_len = 8;
- int compatibility = 0;
- int show_raw_time = 0;
- int options = 1;
- struct commit *start_commit;
-
- const char *args[10];
- struct rev_info rev;
-
- struct commit_info ci;
- const char *buf;
- int max_digits, max_orig_digits;
- int longest_file, longest_author, longest_file_lines;
- int show_name = 0;
- int show_number = 0;
- int porcelain = 0;
-
- const char *prefix = setup_git_directory();
- git_config(git_default_config);
-
- for (i = 1; i < argc; i++) {
- if (options) {
- if (!strcmp(argv[i], "-h") ||
- !strcmp(argv[i], "--help"))
- usage(blame_usage);
- if (!strcmp(argv[i], "-l") ||
- !strcmp(argv[i], "--long")) {
- sha1_len = 40;
- continue;
- }
- if (!strcmp(argv[i], "-c") ||
- !strcmp(argv[i], "--compatibility")) {
- compatibility = 1;
- continue;
- }
- if (!strcmp(argv[i], "-t") ||
- !strcmp(argv[i], "--time")) {
- show_raw_time = 1;
- continue;
- }
- if (!strcmp(argv[i], "-S")) {
- if (i + 1 < argc &&
- !read_ancestry(argv[i + 1], &sha1_p)) {
- compatibility = 1;
- i++;
- continue;
- }
- usage(blame_usage);
- }
- if (!strcmp(argv[i], "-f") ||
- !strcmp(argv[i], "--show-name")) {
- show_name = 1;
- continue;
- }
- if (!strcmp(argv[i], "-n") ||
- !strcmp(argv[i], "--show-number")) {
- show_number = 1;
- continue;
- }
- if (!strcmp(argv[i], "-p") ||
- !strcmp(argv[i], "--porcelain")) {
- porcelain = 1;
- sha1_len = 40;
- show_raw_time = 1;
- continue;
- }
- if (!strcmp(argv[i], "--")) {
- options = 0;
- continue;
- }
- if (argv[i][0] == '-')
- usage(blame_usage);
- options = 0;
+ /*
+ * If we have bottom, this will mark the ancestors of the
+ * bottom commits we would reach while traversing as
+ * uninteresting.
+ */
+ if (prepare_revision_walk(sb->revs))
+ die(_("revision walk setup failed"));
+
+ if (sb->reverse && sb->revs->first_parent_only) {
+ struct commit *c = final_commit;
+
+ sb->revs->children.name = "children";
+ while (c->parents &&
+ oidcmp(&c->object.oid, &sb->final->object.oid)) {
+ struct commit_list *l = xcalloc(1, sizeof(*l));
+
+ l->item = c;
+ if (add_decoration(&sb->revs->children,
+ &c->parents->item->object, l))
+ die("BUG: not unique item in first-parent chain");
+ c = c->parents->item;
}
- if (!options) {
- if (!filename)
- filename = argv[i];
- else if (!commit)
- commit = argv[i];
- else
- usage(blame_usage);
- }
+ if (oidcmp(&c->object.oid, &sb->final->object.oid))
+ die(_("--reverse --first-parent together require range along first-parent chain"));
}
- if (!filename)
- usage(blame_usage);
- if (commit && sha1_p)
- usage(blame_usage);
- else if (!commit)
- commit = "HEAD";
-
- if (prefix)
- sprintf(filename_buf, "%s%s", prefix, filename);
- else
- strcpy(filename_buf, filename);
- filename = filename_buf;
-
- if (!sha1_p) {
- if (get_sha1(commit, sha1))
- die("get_sha1 failed, commit '%s' not found", commit);
- sha1_p = sha1;
- }
- start_commit = lookup_commit_reference(sha1_p);
- get_util(start_commit)->pathname = filename;
- if (fill_util_info(start_commit)) {
- printf("%s not found in %s\n", filename, commit);
- return 1;
+ if (is_null_oid(&sb->final->object.oid)) {
+ o = sb->final->util;
+ sb->final_buf = xmemdupz(o->file.ptr, o->file.size);
+ sb->final_buf_size = o->file.size;
}
+ else {
+ o = get_origin(sb->final, path);
+ if (fill_blob_sha1_and_mode(o))
+ die(_("no such path %s in %s"), path, final_commit_name);
+
+ if (DIFF_OPT_TST(&sb->revs->diffopt, ALLOW_TEXTCONV) &&
+ textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf,
+ &sb->final_buf_size))
+ ;
+ else
+ sb->final_buf = read_sha1_file(o->blob_oid.hash, &type,
+ &sb->final_buf_size);
- init_revisions(&rev, setup_git_directory());
- rev.remove_empty_trees = 1;
- rev.topo_order = 1;
- rev.prune_fn = simplify_commit;
- rev.topo_setter = topo_setter;
- rev.topo_getter = topo_getter;
- rev.parents = 1;
- rev.limited = 1;
-
- commit_list_insert(start_commit, &rev.commits);
-
- args[0] = filename;
- args[1] = NULL;
- diff_tree_setup_paths(args, &rev.pruning);
- prepare_revision_walk(&rev);
- process_commits(&rev, filename, &initial);
-
- for (i = 0; i < num_blame_lines; i++)
- if (!blame_lines[i])
- blame_lines[i] = initial;
-
- buf = blame_contents;
- max_digits = lineno_width(num_blame_lines);
-
- longest_file = 0;
- longest_author = 0;
- longest_file_lines = 0;
- for (i = 0; i < num_blame_lines; i++) {
- struct commit *c = blame_lines[i];
- struct util_info *u;
- u = c->util;
-
- if (!show_name && strcmp(filename, u->pathname))
- show_name = 1;
- if (longest_file < strlen(u->pathname))
- longest_file = strlen(u->pathname);
- if (longest_file_lines < u->num_lines)
- longest_file_lines = u->num_lines;
- get_commit_info(c, &ci, 0);
- if (longest_author < strlen(ci.author))
- longest_author = strlen(ci.author);
+ if (!sb->final_buf)
+ die(_("cannot read blob %s for path %s"),
+ oid_to_hex(&o->blob_oid),
+ path);
}
+ sb->num_read_blob++;
+ prepare_lines(sb);
- max_orig_digits = lineno_width(longest_file_lines);
-
- for (i = 0; i < num_blame_lines; i++) {
- emit_meta(blame_lines[i], i,
- sha1_len, compatibility, porcelain,
- show_name, show_number, show_raw_time,
- longest_file, longest_author,
- max_digits, max_orig_digits);
+ if (orig)
+ *orig = o;
+}
- if (i == num_blame_lines - 1) {
- fwrite(buf, blame_len - (buf - blame_contents),
- 1, stdout);
- if (blame_contents[blame_len-1] != '\n')
- putc('\n', stdout);
- }
- else {
- char *next_buf = strchr(buf, '\n') + 1;
- fwrite(buf, next_buf - buf, 1, stdout);
- buf = next_buf;
- }
- }
- if (DEBUG) {
- printf("num read blob: %d\n", num_read_blob);
- printf("num get patch: %d\n", num_get_patch);
- printf("num commits: %d\n", num_commits);
- printf("patch time: %f\n", patch_time / 1000000.0);
- printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
- }
- return 0;
+struct blame_entry *blame_entry_prepend(struct blame_entry *head,
+ long start, long end,
+ struct blame_origin *o)
+{
+ struct blame_entry *new_head = xcalloc(1, sizeof(struct blame_entry));
+ new_head->lno = start;
+ new_head->num_lines = end - start;
+ new_head->suspect = o;
+ new_head->s_lno = start;
+ new_head->next = head;
+ blame_origin_incref(o);
+ return new_head;
}