blame: allow blame --reverse --first-parent when it makes sense

Allow combining --reverse and --first-parent if initial commit of specified range is at the first-parent chain starting from the final commit. Disable the prepare_revision_walk()'s builtin children collection, instead picking only the ones which are along the first parent chain. Signed-off-by: Max Kirillov <max@max630.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Max Kirillov committed Oct 30, 2015 at 07:01 UTC 700fd28e4f3ecd6e7b5f8f2098e62ffef9ab958b
2 files changed +31 -3
builtin/blame.c
+30 -2
@@ -2502,6 +2502,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
2502 long dashdash_pos, lno;
2503 char *final_commit_name = NULL;
2504 enum object_type type;
2505 + struct commit *final_commit = NULL;
2506
2507 static struct string_list range_list;
2508 static int output_option = 0, opt = 0;
@@ -2689,11 +2690,11 @@ parse_done:
2690 }
2691 else if (contents_from)
2692 die("--contents and --children do not blend well.");
2692 - else if (revs.first_parent_only)
2693 - die("combining --first-parent and --reverse is not supported");
2693 else {
2694 final_commit_name = prepare_initial(&sb);
2695 sb.commits.compare = compare_commits_by_reverse_commit_date;
2696 + if (revs.first_parent_only)
2697 + revs.children.name = NULL;
2698 }
2699
2700 if (!sb.final) {
@@ -2710,6 +2711,14 @@ parse_done:
2711 else if (contents_from)
2712 die("Cannot use --contents with final commit object name");
2713
2714 + if (reverse && revs.first_parent_only) {
2715 + struct object_array_entry *entry = find_single_final(sb.revs);
2716 + if (!entry)
2717 + die("--reverse and --first-parent together require specified latest commit");
2718 + else
2719 + final_commit = (struct commit*) entry->item;
2720 + }
2721 +
2722 /*
2723 * If we have bottom, this will mark the ancestors of the
2724 * bottom commits we would reach while traversing as
@@ -2718,6 +2727,25 @@ parse_done:
2727 if (prepare_revision_walk(&revs))
2728 die(_("revision walk setup failed"));
2729
2730 + if (reverse && revs.first_parent_only) {
2731 + struct commit *c = final_commit;
2732 +
2733 + sb.revs->children.name = "children";
2734 + while (c->parents &&
2735 + hashcmp(c->object.sha1, sb.final->object.sha1)) {
2736 + struct commit_list *l = xcalloc(1, sizeof(*l));
2737 +
2738 + l->item = c;
2739 + if (add_decoration(&sb.revs->children,
2740 + &c->parents->item->object, l))
2741 + die("BUG: not unique item in first-parent chain");
2742 + c = c->parents->item;
2743 + }
2744 +
2745 + if (hashcmp(c->object.sha1, sb.final->object.sha1))
2746 + die("--reverse --first-parent together require range along first-parent chain");
2747 + }
2748 +
2749 if (is_null_sha1(sb.final->object.sha1)) {
2750 o = sb.final->util;
2751 sb.final_buf = xmemdupz(o->file.ptr, o->file.size);
t/t8009-blame-vs-topicbranches.sh
+1 -1
@@ -26,7 +26,7 @@ test_expect_success setup '
26 test_merge A3 C1
27 '
28
29 -test_expect_failure 'blame --reverse --first-parent finds A1' '
29 +test_expect_success 'blame --reverse --first-parent finds A1' '
30 git blame --porcelain --reverse --first-parent A0..A3 -- file.t >actual_full &&
31 head -n 1 <actual_full | sed -e "s/ .*//" >actual &&
32 git rev-parse A1 >expect &&