mirror of
https://github.com/git/git.git
synced 2024-05-09 19:36:10 +02:00
blame: move scoreboard setup to libgit
Signed-off-by: Jeff Smith <whydoubt@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
b543bb1cdf
commit
09002f1b31
279
blame.c
279
blame.c
|
@ -4,6 +4,7 @@
|
||||||
#include "mergesort.h"
|
#include "mergesort.h"
|
||||||
#include "diff.h"
|
#include "diff.h"
|
||||||
#include "diffcore.h"
|
#include "diffcore.h"
|
||||||
|
#include "tag.h"
|
||||||
#include "blame.h"
|
#include "blame.h"
|
||||||
|
|
||||||
void blame_origin_decref(struct blame_origin *o)
|
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.
|
* Locate an existing origin or create a new one.
|
||||||
* This moves the origin to front position in the commit util list.
|
* 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;
|
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.
|
* Prepare a dummy commit that represents the work tree (or staged) item.
|
||||||
* Note that annotating work tree item never works in the reverse.
|
* Note that annotating work tree item never works in the reverse.
|
||||||
*/
|
*/
|
||||||
struct commit *fake_working_tree_commit(struct diff_options *opt,
|
static struct commit *fake_working_tree_commit(struct diff_options *opt,
|
||||||
const char *path,
|
const char *path,
|
||||||
const char *contents_from)
|
const char *contents_from)
|
||||||
{
|
{
|
||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
struct blame_origin *origin;
|
struct blame_origin *origin;
|
||||||
|
@ -410,6 +411,13 @@ void blame_sort_final(struct blame_scoreboard *sb)
|
||||||
compare_blame_final);
|
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
|
* For debugging -- origin is refcounted, and this asserts that
|
||||||
* we do not underflow.
|
* 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
|
* We have an origin -- check if the same path exists in the
|
||||||
* parent and return an origin structure to represent it.
|
* 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);
|
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;
|
||||||
|
}
|
||||||
|
|
10
blame.h
10
blame.h
|
@ -13,6 +13,9 @@
|
||||||
#define PICKAXE_BLAME_COPY_HARDER 04
|
#define PICKAXE_BLAME_COPY_HARDER 04
|
||||||
#define PICKAXE_BLAME_COPY_HARDEST 010
|
#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
|
* 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 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_coalesce(struct blame_scoreboard *sb);
|
||||||
extern void blame_sort_final(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 unsigned blame_entry_score(struct blame_scoreboard *sb, struct blame_entry *e);
|
||||||
extern void assign_blame(struct blame_scoreboard *sb, int opt);
|
extern void assign_blame(struct blame_scoreboard *sb, int opt);
|
||||||
extern const char *blame_nth_line(struct blame_scoreboard *sb, long lno);
|
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 */
|
#endif /* BLAME_H */
|
||||||
|
|
276
builtin/blame.c
276
builtin/blame.c
|
@ -6,11 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "refs.h"
|
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
#include "tag.h"
|
|
||||||
#include "tree-walk.h"
|
|
||||||
#include "diff.h"
|
#include "diff.h"
|
||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "quote.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_move_score;
|
||||||
static unsigned blame_copy_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 */
|
/* Remember to update object flag allocation in object.h */
|
||||||
#define METAINFO_SHOWN (1u<<12)
|
#define METAINFO_SHOWN (1u<<12)
|
||||||
|
@ -72,39 +67,6 @@ struct progress_info {
|
||||||
int blamed_lines;
|
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)
|
static const char *nth_line_cb(void *data, long lno)
|
||||||
{
|
{
|
||||||
return blame_nth_line((struct blame_scoreboard *)data, 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
|
* Add phony grafts for use with -S; this is primarily to
|
||||||
* support git's cvsserver that wants to give a linear history
|
* 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);
|
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)
|
static int blame_copy_callback(const struct option *option, const char *arg, int unset)
|
||||||
{
|
{
|
||||||
int *opt = option->value;
|
int *opt = option->value;
|
||||||
|
@ -818,112 +648,6 @@ static int blame_move_callback(const struct option *option, const char *arg, int
|
||||||
return 0;
|
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,
|
struct blame_entry *blame_entry_prepend(struct blame_entry *head,
|
||||||
long start, long end,
|
long start, long end,
|
||||||
struct blame_origin *o)
|
struct blame_origin *o)
|
||||||
|
|
Loading…
Reference in New Issue