static int max_digits;
static int max_score_digits;
static int show_root;
+static int reverse;
static int blank_boundary;
static int incremental;
static int cmd_is_annotate;
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
-
+ struct rev_info *revs;
const char *path;
/*
* "parent" (and "porigin"), but what we mean is to find scapegoat to
* exonerate ourselves.
*/
-static struct commit_list *first_scapegoat(struct commit *commit)
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
{
- return commit->parents;
+ if (!reverse)
+ return commit->parents;
+ return lookup_decoration(&revs->children, &commit->object);
}
-static int num_scapegoats(struct commit *commit)
+static int num_scapegoats(struct rev_info *revs, struct commit *commit)
{
int cnt;
- struct commit_list *l = first_scapegoat(commit);
+ struct commit_list *l = first_scapegoat(revs, commit);
for (cnt = 0; l; l = l->next)
cnt++;
return cnt;
static void pass_blame(struct scoreboard *sb, struct 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 origin *sg_buf[MAXSG];
struct origin *porigin, **sg_origin = sg_buf;
- num_sg = num_scapegoats(commit);
+ num_sg = num_scapegoats(revs, commit);
if (!num_sg)
goto finish;
else if (num_sg < ARRAY_SIZE(sg_buf))
struct commit *, struct origin *);
find = pass ? find_rename : find_origin;
- for (i = 0, sg = first_scapegoat(commit);
+ for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct commit *p = sg->item;
}
num_commits++;
- for (i = 0, sg = first_scapegoat(commit);
+ for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
* Optionally find moves in parents' files.
*/
if (opt & PICKAXE_BLAME_MOVE)
- for (i = 0, sg = first_scapegoat(commit);
+ for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
* Optionally find copies from parents' files.
*/
if (opt & PICKAXE_BLAME_COPY)
- for (i = 0, sg = first_scapegoat(commit);
+ for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
* is still unknown, pick one blame_entry, and allow its current
* 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) {
struct blame_entry *ent;
struct commit *commit;
commit = suspect->commit;
if (!commit->object.parsed)
parse_commit(commit);
- if (!(commit->object.flags & UNINTERESTING) &&
- !(revs->max_age != -1 && commit->date < revs->max_age))
+ if (reverse ||
+ (!(commit->object.flags & UNINTERESTING) &&
+ !(revs->max_age != -1 && commit->date < revs->max_age)))
pass_blame(sb, suspect, opt);
else {
commit->object.flags |= UNINTERESTING;
return commit;
}
-static const char *prepare_final(struct scoreboard *sb, struct rev_info *revs)
+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
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;
+}
+
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
blank_boundary = 1;
else if (!strcmp("--root", arg))
show_root = 1;
+ else if (!strcmp("--reverse", arg)) {
+ argv[unk++] = "--children";
+ reverse = 1;
+ }
else if (!strcmp(arg, "--show-stats"))
show_stats = 1;
else if (!strcmp("-c", arg))
setup_revisions(unk, argv, &revs, NULL);
memset(&sb, 0, sizeof(sb));
- final_commit_name = prepare_final(&sb, &revs);
+ sb.revs = &revs;
+ if (!reverse)
+ final_commit_name = prepare_final(&sb);
+ else if (contents_from)
+ die("--contents and --children do not blend well.");
+ else
+ final_commit_name = prepare_initial(&sb);
+
if (!sb.final) {
/*
* "--not A B -- path" without anything positive;
if (!incremental)
setup_pager();
- assign_blame(&sb, &revs, opt);
+ assign_blame(&sb, opt);
if (incremental)
return 0;