diff --git a/blame.c b/blame.c index 798e61b8c7..f6c9cb7df9 100644 --- a/blame.c +++ b/blame.c @@ -4,6 +4,7 @@ #include "mergesort.h" #include "diff.h" #include "diffcore.h" +#include "tag.h" #include "blame.h" void blame_origin_decref(struct blame_origin *o) @@ -49,7 +50,7 @@ static struct blame_origin *make_origin(struct commit *commit, const char *path) * Locate an existing origin or create a new one. * This moves the origin to front position in the commit util list. */ -struct blame_origin *get_origin(struct commit *commit, const char *path) +static struct blame_origin *get_origin(struct commit *commit, const char *path) { struct blame_origin *o, *l; @@ -142,9 +143,9 @@ static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb) * Prepare a dummy commit that represents the work tree (or staged) item. * Note that annotating work tree item never works in the reverse. */ -struct commit *fake_working_tree_commit(struct diff_options *opt, - const char *path, - const char *contents_from) +static struct commit *fake_working_tree_commit(struct diff_options *opt, + const char *path, + const char *contents_from) { struct commit *commit; struct blame_origin *origin; @@ -410,6 +411,13 @@ void blame_sort_final(struct blame_scoreboard *sb) compare_blame_final); } +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); +} + /* * For debugging -- origin is refcounted, and this asserts that * we do not underflow. @@ -482,6 +490,32 @@ static void queue_blames(struct blame_scoreboard *sb, struct blame_origin *porig } } +/* + * 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. @@ -1574,3 +1608,240 @@ void assign_blame(struct blame_scoreboard *sb, int opt) sanity_check_refcnt(sb); } } + +static const char *get_next_line(const char *start, const char *end) +{ + const char *nl = memchr(start, '\n', end - start); + return nl ? nl + 1 : end; +} + +/* + * 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) +{ + 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; + + for (p = buf; p < end; p = get_next_line(p, end)) + num++; + + ALLOC_ARRAY(sb->lineno, num + 1); + lineno = sb->lineno; + + for (p = buf; p < end; p = get_next_line(p, end)) + *lineno++ = p - buf; + + *lineno = len; + + sb->num_lines = num; + return sb->num_lines; +} + +static struct commit *find_single_final(struct rev_info *revs, + const char **name_p) +{ + 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 struct commit *dwim_reverse_initial(struct rev_info *revs, + const char **name_p) +{ + /* + * 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; + unsigned char head_sha1[20]; + + 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_sha1, NULL)) + return NULL; + head_commit = lookup_commit_reference_gently(head_sha1, 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 struct commit *find_single_initial(struct rev_info *revs, + const char **name_p) +{ + 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; + } + + if (!name) + found = dwim_reverse_initial(revs, &name); + if (!name) + die("No commit to dig up from?"); + + if (name_p) + *name_p = name; + return found; +} + +void init_scoreboard(struct blame_scoreboard *sb) +{ + memset(sb, 0, sizeof(struct blame_scoreboard)); + sb->move_score = BLAME_DEFAULT_MOVE_SCORE; + sb->copy_score = BLAME_DEFAULT_COPY_SCORE; +} + +void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blame_origin **orig) +{ + 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; + } + + 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), ":"); + } + + 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")); + } + + /* + * 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 (oidcmp(&c->object.oid, &sb->final->object.oid)) + die(_("--reverse --first-parent together require range along first-parent chain")); + } + + 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); + + 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); + + if (orig) + *orig = o; +} diff --git a/blame.h b/blame.h index df824b2267..76fd8efe15 100644 --- a/blame.h +++ b/blame.h @@ -13,6 +13,9 @@ #define PICKAXE_BLAME_COPY_HARDER 04 #define PICKAXE_BLAME_COPY_HARDEST 010 +#define BLAME_DEFAULT_MOVE_SCORE 20 +#define BLAME_DEFAULT_COPY_SCORE 40 + /* * One blob in a commit that is being suspected */ @@ -158,14 +161,13 @@ static inline struct blame_origin *blame_origin_incref(struct blame_origin *o) } extern void blame_origin_decref(struct blame_origin *o); -extern struct blame_origin *get_origin(struct commit *commit, const char *path); - -extern struct commit *fake_working_tree_commit(struct diff_options *opt, const char *path, const char *contents_from); - extern void blame_coalesce(struct blame_scoreboard *sb); extern void blame_sort_final(struct blame_scoreboard *sb); extern unsigned blame_entry_score(struct blame_scoreboard *sb, struct blame_entry *e); extern void assign_blame(struct blame_scoreboard *sb, int opt); extern const char *blame_nth_line(struct blame_scoreboard *sb, long lno); +extern void init_scoreboard(struct blame_scoreboard *sb); +extern void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blame_origin **orig); + #endif /* BLAME_H */ diff --git a/builtin/blame.c b/builtin/blame.c index 461506c3cb..7d9e322d45 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -6,11 +6,8 @@ */ #include "cache.h" -#include "refs.h" #include "builtin.h" #include "commit.h" -#include "tag.h" -#include "tree-walk.h" #include "diff.h" #include "revision.h" #include "quote.h" @@ -60,8 +57,6 @@ static struct string_list mailmap = STRING_LIST_INIT_NODUP; static unsigned blame_move_score; static unsigned blame_copy_score; -#define BLAME_DEFAULT_MOVE_SCORE 20 -#define BLAME_DEFAULT_COPY_SCORE 40 /* Remember to update object flag allocation in object.h */ #define METAINFO_SHOWN (1u<<12) @@ -72,39 +67,6 @@ struct progress_info { int blamed_lines; }; -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); -} - -/* - * 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; -} - static const char *nth_line_cb(void *data, long lno) { return blame_nth_line((struct blame_scoreboard *)data, lno); @@ -512,40 +474,6 @@ static void output(struct blame_scoreboard *sb, int option) } } -static const char *get_next_line(const char *start, const char *end) -{ - const char *nl = memchr(start, '\n', end - start); - return nl ? nl + 1 : end; -} - -/* - * 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) -{ - 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; - - for (p = buf; p < end; p = get_next_line(p, end)) - num++; - - ALLOC_ARRAY(sb->lineno, num + 1); - lineno = sb->lineno; - - for (p = buf; p < end; p = get_next_line(p, end)) - *lineno++ = p - buf; - - *lineno = len; - - sb->num_lines = num; - return sb->num_lines; -} - /* * Add phony grafts for use with -S; this is primarily to * support git's cvsserver that wants to give a linear history @@ -687,104 +615,6 @@ static int git_blame_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -static struct commit *find_single_final(struct rev_info *revs, - const char **name_p) -{ - 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 struct commit *dwim_reverse_initial(struct rev_info *revs, - const char **name_p) -{ - /* - * 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; - unsigned char head_sha1[20]; - - 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_sha1, NULL)) - return NULL; - head_commit = lookup_commit_reference_gently(head_sha1, 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 struct commit *find_single_initial(struct rev_info *revs, - const char **name_p) -{ - int i; - const char *final_commit_name = NULL; - struct commit *found = 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, - final_commit_name); - found = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; - } - - if (!final_commit_name) - found = dwim_reverse_initial(revs, &final_commit_name); - if (!final_commit_name) - die("No commit to dig up from?"); - - if (name_p) - *name_p = final_commit_name; - return found; -} - static int blame_copy_callback(const struct option *option, const char *arg, int unset) { int *opt = option->value; @@ -818,112 +648,6 @@ static int blame_move_callback(const struct option *option, const char *arg, int return 0; } -void init_scoreboard(struct blame_scoreboard *sb) -{ - memset(sb, 0, sizeof(struct blame_scoreboard)); - sb->move_score = BLAME_DEFAULT_MOVE_SCORE; - sb->copy_score = BLAME_DEFAULT_COPY_SCORE; -} - -void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blame_origin **orig) -{ - 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; - } - - 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), ":"); - } - - 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")); - } - - /* - * 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 (oidcmp(&c->object.oid, &sb->final->object.oid)) - die(_("--reverse --first-parent together require range along first-parent chain")); - } - - 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); - - 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); - - if (orig) - *orig = o; -} - struct blame_entry *blame_entry_prepend(struct blame_entry *head, long start, long end, struct blame_origin *o)