@ -18,24 +18,16 @@
#include "cache-tree.h"
#include "cache-tree.h"
#include "path-list.h"
#include "path-list.h"
#include "mailmap.h"
#include "mailmap.h"
#include "parse-options.h"
static char blame_usage[] =
static char blame_usage[] = "git-blame [options] [rev-opts] [rev] [--] file";
"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
" -c Use the same output mode as git-annotate (Default: off)\n"
static const char *blame_opt_usage[] = {
" -b Show blank SHA-1 for boundary commits (Default: off)\n"
blame_usage,
" -l Show long commit SHA1 (Default: off)\n"
"",
" --root Do not treat root commits as boundaries (Default: off)\n"
"[rev-opts] are documented in git-rev-list(1)",
" -t Show raw timestamp (Default: off)\n"
NULL
" -f, --show-name Show original filename (Default: auto)\n"
};
" -n, --show-number Show original linenumber (Default: off)\n"
" -s Suppress author name and timestamp (Default: off)\n"
" -p, --porcelain Show in a format designed for machine consumption\n"
" -w Ignore whitespace differences\n"
" -L n,m Process only line range n,m, counting from 1\n"
" -M, -C Find line movements within and across files\n"
" --incremental Show blame entries as we find them, incrementally\n"
" --contents file Use <file>'s contents as the final image\n"
" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n";
static int longest_file;
static int longest_file;
static int longest_author;
static int longest_author;
@ -43,6 +35,7 @@ static int max_orig_digits;
static int max_digits;
static int max_digits;
static int max_score_digits;
static int max_score_digits;
static int show_root;
static int show_root;
static int reverse;
static int blank_boundary;
static int blank_boundary;
static int incremental;
static int incremental;
static int cmd_is_annotate;
static int cmd_is_annotate;
@ -91,7 +84,7 @@ struct origin {
* Given an origin, prepare mmfile_t structure to be used by the
* Given an origin, prepare mmfile_t structure to be used by the
* diff machinery
* diff machinery
*/
*/
static char *fill_origin_blob(struct origin *o, mmfile_t *file)
static void fill_origin_blob(struct origin *o, mmfile_t *file)
{
{
if (!o->file.ptr) {
if (!o->file.ptr) {
enum object_type type;
enum object_type type;
@ -106,7 +99,6 @@ static char *fill_origin_blob(struct origin *o, mmfile_t *file)
}
}
else
else
*file = o->file;
*file = o->file;
return file->ptr;
}
}
/*
/*
@ -178,7 +170,7 @@ struct blame_entry {
struct scoreboard {
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
/* the final commit (i.e. where we started digging from) */
struct commit *final;
struct commit *final;
struct rev_info *revs;
const char *path;
const char *path;
/*
/*
@ -1192,18 +1184,48 @@ static void pass_whole_blame(struct scoreboard *sb,
}
}
}
}
#define MAXPARENT 16
/*
* 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)
{
if (!reverse)
return commit->parents;
return lookup_decoration(&revs->children, &commit->object);
}
static int num_scapegoats(struct rev_info *revs, struct commit *commit)
{
int cnt;
struct commit_list *l = first_scapegoat(revs, commit);
for (cnt = 0; l; l = l->next)
cnt++;
return cnt;
}
#define MAXSG 16
static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
{
{
int i, pass;
struct rev_info *revs = sb->revs;
int i, pass, num_sg;
struct commit *commit = origin->commit;
struct commit *commit = origin->commit;
struct commit_list *parent;
struct commit_list *sg;
struct origin *parent_origin[MAXPARENT], *porigin;
struct origin *sg_buf[MAXSG];
struct origin *porigin, **sg_origin = sg_buf;
memset(parent_origin, 0, sizeof(parent_origin));
num_sg = num_scapegoats(revs, commit);
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
/*
* The first pass looks for unrenamed path to optimize for
* common cases, then we look for renames in the second pass.
* common cases, then we look for renames in the second pass.
*/
*/
for (pass = 0; pass < 2; pass++) {
for (pass = 0; pass < 2; pass++) {
@ -1211,13 +1233,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
struct commit *, struct origin *);
struct commit *, struct origin *);
find = pass ? find_rename : find_origin;
find = pass ? find_rename : find_origin;
for (i = 0, parent = commit->parents;
for (i = 0, sg = first_scapegoat(revs, commit);
i < MAXPARENT && parent;
i < num_sg && sg;
parent = parent->next, i++) {
sg = sg->next, i++) {
struct commit *p = parent->item;
struct commit *p = sg->item;
int j, same;
int j, same;
if (parent_origin[i])
if (sg_origin[i])
continue;
continue;
if (parse_commit(p))
if (parse_commit(p))
continue;
continue;
@ -1230,24 +1252,24 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
goto finish;
goto finish;
}
}
for (j = same = 0; j < i; j++)
for (j = same = 0; j < i; j++)
if (parent_origin[j] &&
if (sg_origin[j] &&
!hashcmp(parent_origin[j]->blob_sha1,
!hashcmp(sg_origin[j]->blob_sha1,
porigin->blob_sha1)) {
porigin->blob_sha1)) {
same = 1;
same = 1;
break;
break;
}
}
if (!same)
if (!same)
parent_origin[i] = porigin;
sg_origin[i] = porigin;
else
else
origin_decref(porigin);
origin_decref(porigin);
}
}
}
}
num_commits++;
num_commits++;
for (i = 0, parent = commit->parents;
for (i = 0, sg = first_scapegoat(revs, commit);
i < MAXPARENT && parent;
i < num_sg && sg;
parent = parent->next, i++) {
sg = sg->next, i++) {
struct origin *porigin = parent_origin[i];
struct origin *porigin = sg_origin[i];
if (!porigin)
if (!porigin)
continue;
continue;
if (pass_blame_to_parent(sb, origin, porigin))
if (pass_blame_to_parent(sb, origin, porigin))
@ -1258,10 +1280,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Optionally find moves in parents' files.
* Optionally find moves in parents' files.
*/
*/
if (opt & PICKAXE_BLAME_MOVE)
if (opt & PICKAXE_BLAME_MOVE)
for (i = 0, parent = commit->parents;
for (i = 0, sg = first_scapegoat(revs, commit);
i < MAXPARENT && parent;
i < num_sg && sg;
parent = parent->next, i++) {
sg = sg->next, i++) {
struct origin *porigin = parent_origin[i];
struct origin *porigin = sg_origin[i];
if (!porigin)
if (!porigin)
continue;
continue;
if (find_move_in_parent(sb, origin, porigin))
if (find_move_in_parent(sb, origin, porigin))
@ -1272,23 +1294,25 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Optionally find copies from parents' files.
* Optionally find copies from parents' files.
*/
*/
if (opt & PICKAXE_BLAME_COPY)
if (opt & PICKAXE_BLAME_COPY)
for (i = 0, parent = commit->parents;
for (i = 0, sg = first_scapegoat(revs, commit);
i < MAXPARENT && parent;
i < num_sg && sg;
parent = parent->next, i++) {
sg = sg->next, i++) {
struct origin *porigin = parent_origin[i];
struct origin *porigin = sg_origin[i];
if (find_copy_in_parent(sb, origin, parent->item,
if (find_copy_in_parent(sb, origin, sg->item,
porigin, opt))
porigin, opt))
goto finish;
goto finish;
}
}
finish:
finish:
for (i = 0; i < MAXPARENT; i++) {
for (i = 0; i < num_sg; i++) {
if (parent_origin[i]) {
if (sg_origin[i]) {
drop_origin_blob(parent_origin[i]);
drop_origin_blob(sg_origin[i]);
origin_decref(parent_origin[i]);
origin_decref(sg_origin[i]);
}
}
}
}
drop_origin_blob(origin);
drop_origin_blob(origin);
if (sg_buf != sg_origin)
free(sg_origin);
}
}
/*
/*
@ -1487,8 +1511,10 @@ static void found_guilty_entry(struct blame_entry *ent)
* is still unknown, pick one blame_entry, and allow its current
* is still unknown, pick one blame_entry, and allow its current
* suspect to pass blames to its parents.
* suspect to pass blames to its parents.
*/
*/
static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
static void assign_blame(struct scoreboard *sb, int opt)
{
{
struct rev_info *revs = sb->revs;
while (1) {
while (1) {
struct blame_entry *ent;
struct blame_entry *ent;
struct commit *commit;
struct commit *commit;
@ -1509,8 +1535,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
commit = suspect->commit;
commit = suspect->commit;
if (!commit->object.parsed)
if (!commit->object.parsed)
parse_commit(commit);
parse_commit(commit);
if (!(commit->object.flags & UNINTERESTING) &&
if (reverse ||
!(revs->max_age != -1 && commit->date < revs->max_age))
(!(commit->object.flags & UNINTERESTING) &&
!(revs->max_age != -1 && commit->date < revs->max_age)))
pass_blame(sb, suspect, opt);
pass_blame(sb, suspect, opt);
else {
else {
commit->object.flags |= UNINTERESTING;
commit->object.flags |= UNINTERESTING;
@ -2006,6 +2033,10 @@ 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);
}
}
/*
* 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(const char *path, const char *contents_from)
static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
{
{
struct commit *commit;
struct commit *commit;
@ -2122,6 +2153,108 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
return commit;
return commit;
}
}
static const char *prepare_final(struct scoreboard *sb)
{
int i;
const char *final_commit_name = NULL;
struct rev_info *revs = sb->revs;
/*
* There must be one and only one positive commit in the
* revs->pending array.
*/
for (i = 0; i < revs->pending.nr; i++) {
struct object *obj = revs->pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
while (obj->type == OBJ_TAG)
obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (sb->final)
die("More than one commit to dig from %s and %s?",
revs->pending.objects[i].name,
final_commit_name);
sb->final = (struct commit *) obj;
final_commit_name = revs->pending.objects[i].name;
}
return final_commit_name;
}
static const char *prepare_initial(struct scoreboard *sb)
{
int i;
const char *final_commit_name = NULL;
struct rev_info *revs = sb->revs;
/*
* 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;
while (obj->type == OBJ_TAG)
obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (sb->final)
die("More than one commit to dig down to %s and %s?",
revs->pending.objects[i].name,
final_commit_name);
sb->final = (struct commit *) obj;
final_commit_name = revs->pending.objects[i].name;
}
if (!final_commit_name)
die("No commit to dig down to?");
return final_commit_name;
}
static int blame_copy_callback(const struct option *option, const char *arg, int unset)
{
int *opt = option->value;
/*
* -C enables copy from removed files;
* -C -C enables copy from existing files, but only
* when blaming a new file;
* -C -C -C enables copy from existing files for
* everybody
*/
if (*opt & PICKAXE_BLAME_COPY_HARDER)
*opt |= PICKAXE_BLAME_COPY_HARDEST;
if (*opt & PICKAXE_BLAME_COPY)
*opt |= PICKAXE_BLAME_COPY_HARDER;
*opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
if (arg)
blame_copy_score = parse_score(arg);
return 0;
}
static int blame_move_callback(const struct option *option, const char *arg, int unset)
{
int *opt = option->value;
*opt |= PICKAXE_BLAME_MOVE;
if (arg)
blame_move_score = parse_score(arg);
return 0;
}
static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
{
const char **bottomtop = option->value;
if (!arg)
return -1;
if (*bottomtop)
die("More than one '-L n,m' option given");
*bottomtop = arg;
return 0;
}
int cmd_blame(int argc, const char **argv, const char *prefix)
int cmd_blame(int argc, const char **argv, const char *prefix)
{
{
struct rev_info revs;
struct rev_info revs;
@ -2129,102 +2262,66 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
struct scoreboard sb;
struct scoreboard sb;
struct origin *o;
struct origin *o;
struct blame_entry *ent;
struct blame_entry *ent;
int i, seen_dashdash, unk, opt;
long dashdash_pos, bottom, top, lno;
long bottom, top, lno;
int output_option = 0;
int show_stats = 0;
const char *revs_file = NULL;
const char *final_commit_name = NULL;
const char *final_commit_name = NULL;
enum object_type type;
enum object_type type;
const char *bottomtop = NULL;
const char *contents_from = NULL;
static const char *bottomtop = NULL;
static int output_option = 0, opt = 0;
static int show_stats = 0;
static const char *revs_file = NULL;
static const char *contents_from = NULL;
static const struct option options[] = {
OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
{ OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
{ OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
OPT_END()
};
struct parse_opt_ctx_t ctx;
cmd_is_annotate = !strcmp(argv[0], "annotate");
cmd_is_annotate = !strcmp(argv[0], "annotate");
git_config(git_blame_config, NULL);
git_config(git_blame_config, NULL);
init_revisions(&revs, NULL);
save_commit_buffer = 0;
save_commit_buffer = 0;
dashdash_pos = 0;
opt = 0;
seen_dashdash = 0;
parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
for (unk = i = 1; i < argc; i++) {
PARSE_OPT_KEEP_ARGV0);
const char *arg = argv[i];
for (;;) {
if (*arg != '-')
switch (parse_options_step(&ctx, options, blame_opt_usage)) {
break;
case PARSE_OPT_HELP:
else if (!strcmp("-b", arg))
exit(129);
blank_boundary = 1;
case PARSE_OPT_DONE:
else if (!strcmp("--root", arg))
if (ctx.argv[0])
show_root = 1;
dashdash_pos = ctx.cpidx;
else if (!strcmp(arg, "--show-stats"))
goto parse_done;
show_stats = 1;
else if (!strcmp("-c", arg))
output_option |= OUTPUT_ANNOTATE_COMPAT;
else if (!strcmp("-t", arg))
output_option |= OUTPUT_RAW_TIMESTAMP;
else if (!strcmp("-l", arg))
output_option |= OUTPUT_LONG_OBJECT_NAME;
else if (!strcmp("-s", arg))
output_option |= OUTPUT_NO_AUTHOR;
else if (!strcmp("-w", arg))
xdl_opts |= XDF_IGNORE_WHITESPACE;
else if (!strcmp("-S", arg) && ++i < argc)
revs_file = argv[i];
else if (!prefixcmp(arg, "-M")) {
opt |= PICKAXE_BLAME_MOVE;
blame_move_score = parse_score(arg+2);
}
else if (!prefixcmp(arg, "-C")) {
/*
* -C enables copy from removed files;
* -C -C enables copy from existing files, but only
* when blaming a new file;
* -C -C -C enables copy from existing files for
* everybody
*/
if (opt & PICKAXE_BLAME_COPY_HARDER)
opt |= PICKAXE_BLAME_COPY_HARDEST;
if (opt & PICKAXE_BLAME_COPY)
opt |= PICKAXE_BLAME_COPY_HARDER;
opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
blame_copy_score = parse_score(arg+2);
}
else if (!prefixcmp(arg, "-L")) {
if (!arg[2]) {
if (++i >= argc)
usage(blame_usage);
arg = argv[i];
}
else
arg += 2;
if (bottomtop)
die("More than one '-L n,m' option given");
bottomtop = arg;
}
}
else if (!strcmp("--contents", arg)) {
if (++i >= argc)
if (!strcmp(ctx.argv[0], "--reverse")) {
usage(blame_usage);
ctx.argv[0] = "--children";
contents_from = argv[i];
reverse = 1;
}
else if (!strcmp("--incremental", arg))
incremental = 1;
else if (!strcmp("--score-debug", arg))
output_option |= OUTPUT_SHOW_SCORE;
else if (!strcmp("-f", arg) ||
!strcmp("--show-name", arg))
output_option |= OUTPUT_SHOW_NAME;
else if (!strcmp("-n", arg) ||
!strcmp("--show-number", arg))
output_option |= OUTPUT_SHOW_NUMBER;
else if (!strcmp("-p", arg) ||
!strcmp("--porcelain", arg))
output_option |= OUTPUT_PORCELAIN;
else if (!strcmp("--", arg)) {
seen_dashdash = 1;
i++;
break;
}
}
else
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
argv[unk++] = arg;
}
}
parse_done:
argc = parse_options_end(&ctx);
if (!blame_move_score)
if (!blame_move_score)
blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
@ -2238,115 +2335,59 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
*
*
* The remaining are:
* The remaining are:
*
*
* (1) if seen_dashdash, its either
* (1) if dashdash_pos != 0, its either
* "-options -- <path>" or
* "blame [revisions] -- <path>" or
* "-options -- <path> <rev>".
* "blame -- <path> <rev>"
* but the latter is allowed only if there is no
* options that we passed to revision machinery.
*
*
* (2) otherwise, we may have "--" somewhere later and
* (2) otherwise, its one of the two:
* might be looking at the first one of multiple 'rev'
* "blame [revisions] <path>"
* parameters (e.g. " master ^next ^maint -- path").
* "blame <path> <rev>"
* See if there is a dashdash first, and give the
* arguments before that to revision machinery.
* After that there must be one 'path'.
*
*
* (3) otherwise, its one of the three:
* Note that we must strip out <path> from the arguments: we do not
* "-options <path> <rev>"
* want the path pruning but we may want "bottom" processing.
* "-options <rev> <path>"
* "-options <path>"
* but again the first one is allowed only if
* there is no options that we passed to revision
* machinery.
*/
*/
if (dashdash_pos) {
if (seen_dashdash) {
switch (argc - dashdash_pos - 1) {
/* (1) */
case 2: /* (1b) */
if (argc <= i)
if (argc != 4)
usage(blame_usage);
usage_with_options(blame_opt_usage, options);
path = add_prefix(prefix, argv[i]);
/* reorder for the new way: <rev> -- <path> */
if (i + 1 == argc - 1) {
argv[1] = argv[3];
if (unk != 1)
argv[3] = argv[2];
usage(blame_usage);
argv[2] = "--";
argv[unk++] = argv[i + 1];
/* FALLTHROUGH */
case 1: /* (1a) */
path = add_prefix(prefix, argv[--argc]);
argv[argc] = NULL;
break;
default:
usage_with_options(blame_opt_usage, options);
}
}
else if (i + 1 != argc)
} else {
/* garbage at end */
if (argc < 2)
usage(blame_usage);
usage_with_options(blame_opt_usage, options);
}
path = add_prefix(prefix, argv[argc - 1]);
else {
if (argc == 3 && !has_path_in_work_tree(path)) { /* (2b) */
int j;
path = add_prefix(prefix, argv[1]);
for (j = i; !seen_dashdash && j < argc; j++)
argv[1] = argv[2];
if (!strcmp(argv[j], "--"))
seen_dashdash = j;
if (seen_dashdash) {
/* (2) */
if (seen_dashdash + 1 != argc - 1)
usage(blame_usage);
path = add_prefix(prefix, argv[seen_dashdash + 1]);
for (j = i; j < seen_dashdash; j++)
argv[unk++] = argv[j];
}
}
else {
argv[argc - 1] = "--";
/* (3) */
if (argc <= i)
usage(blame_usage);
path = add_prefix(prefix, argv[i]);
if (i + 1 == argc - 1) {
final_commit_name = argv[i + 1];
/* if (unk == 1) we could be getting
* old-style
*/
if (unk == 1 && !has_path_in_work_tree(path)) {
path = add_prefix(prefix, argv[i + 1]);
final_commit_name = argv[i];
}
}
else if (i != argc - 1)
usage(blame_usage); /* garbage at end */
setup_work_tree();
setup_work_tree();
if (!has_path_in_work_tree(path))
if (!has_path_in_work_tree(path))
die("cannot stat path %s: %s",
die("cannot stat path %s: %s", path, strerror(errno));
path, strerror(errno));
}
}
}
if (final_commit_name)
setup_revisions(argc, argv, &revs, NULL);
argv[unk++] = final_commit_name;
/*
* Now we got rev and path. We do not want the path pruning
* but we may want "bottom" processing.
*/
argv[unk++] = "--"; /* terminate the rev name */
argv[unk] = NULL;
init_revisions(&revs, NULL);
setup_revisions(unk, argv, &revs, NULL);
memset(&sb, 0, sizeof(sb));
memset(&sb, 0, sizeof(sb));
/*
sb.revs = &revs;
* There must be one and only one positive commit in the
if (!reverse)
* revs->pending array.
final_commit_name = prepare_final(&sb);
*/
else if (contents_from)
for (i = 0; i < revs.pending.nr; i++) {
die("--contents and --children do not blend well.");
struct object *obj = revs.pending.objects[i].item;
else
if (obj->flags & UNINTERESTING)
final_commit_name = prepare_initial(&sb);
continue;
while (obj->type == OBJ_TAG)
obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?",
revs.pending.objects[i].name);
if (sb.final)
die("More than one commit to dig from %s and %s?",
revs.pending.objects[i].name,
final_commit_name);
sb.final = (struct commit *) obj;
final_commit_name = revs.pending.objects[i].name;
}
if (!sb.final) {
if (!sb.final) {
/*
/*
@ -2425,7 +2466,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
if (!incremental)
if (!incremental)
setup_pager();
setup_pager();
assign_blame(&sb, &revs, opt);
assign_blame(&sb, opt);
if (incremental)
if (incremental)
return 0;
return 0;