Signed-off-by: Jeff Smith <whydoubt@xxxxxxxxx> --- builtin/blame.c | 274 -------------------------------------------------------- scoreboard.c | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ scoreboard.h | 2 + 3 files changed, 275 insertions(+), 274 deletions(-) diff --git a/builtin/blame.c b/builtin/blame.c index 3bde5a6..4e3079f 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" @@ -25,7 +22,6 @@ #include "dir.h" #include "progress.h" #include "origin.h" -#include "commit-fake.h" #include "scoreboard.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -72,39 +68,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 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 nth_line((struct scoreboard *)data, lno); @@ -512,40 +475,6 @@ static void output(struct 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 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 @@ -706,209 +635,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; - 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 scoreboard *sb) -{ - memset(sb, 0, sizeof(struct scoreboard)); - sb->blame_move_score = BLAME_DEFAULT_MOVE_SCORE; - sb->blame_copy_score = BLAME_DEFAULT_COPY_SCORE; -} - -void setup_scoreboard(struct scoreboard *sb, const char *path, struct origin **orig) -{ - const char *final_commit_name = NULL; - struct 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; -} - static int blame_copy_callback(const struct option *option, const char *arg, int unset) { int *opt = option->value; diff --git a/scoreboard.c b/scoreboard.c index 0463688..01add1d 100644 --- a/scoreboard.c +++ b/scoreboard.c @@ -1,6 +1,9 @@ +#include "refs.h" +#include "tag.h" #include "mergesort.h" #include "diff.h" #include "diffcore.h" +#include "commit-fake.h" #include "scoreboard.h" static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, @@ -151,6 +154,13 @@ void blame_sort_final(struct scoreboard *sb) sb->ent = blame_sort(sb->ent, 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); +} + /* * If two blame entries that are next to each other came from * contiguous lines in the same origin (i.e. <commit, path> pair), @@ -201,6 +211,32 @@ static void queue_blames(struct scoreboard *sb, struct origin *porigin, } /* + * 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 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. */ @@ -1294,3 +1330,240 @@ void assign_blame(struct scoreboard *sb, int opt) sb->sanity_check(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 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 scoreboard *sb) +{ + memset(sb, 0, sizeof(struct scoreboard)); + sb->blame_move_score = BLAME_DEFAULT_MOVE_SCORE; + sb->blame_copy_score = BLAME_DEFAULT_COPY_SCORE; +} + +void setup_scoreboard(struct scoreboard *sb, const char *path, struct origin **orig) +{ + const char *final_commit_name = NULL; + struct 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/scoreboard.h b/scoreboard.h index 42d9aac..fb573a5 100644 --- a/scoreboard.h +++ b/scoreboard.h @@ -71,5 +71,7 @@ void blame_sort_final(struct scoreboard *sb); unsigned ent_score(struct scoreboard *sb, struct blame_entry *e); void assign_blame(struct scoreboard *sb, int opt); const char *nth_line(struct scoreboard *sb, long lno); +void init_scoreboard(struct scoreboard *sb); +void setup_scoreboard(struct scoreboard *sb, const char *path, struct origin **orig); #endif /* SCOREBOARD_H */ -- 2.9.3