Skip to content

Commit 6784eb5

Browse files
committed
Merge branch 'mk/blame-first-parent'
"git blame" learnt to take "--first-parent" and "--reverse" at the same time when it makes sense. * mk/blame-first-parent: blame: allow blame --reverse --first-parent when it makes sense blame: extract find_single_final blame: test to describe use of blame --reverse --first-parent
2 parents a59d1d8 + 700fd28 commit 6784eb5

File tree

2 files changed

+83
-14
lines changed

2 files changed

+83
-14
lines changed

builtin/blame.c

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2402,16 +2402,11 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
24022402
return commit;
24032403
}
24042404

2405-
static char *prepare_final(struct scoreboard *sb)
2405+
static struct object_array_entry *find_single_final(struct rev_info *revs)
24062406
{
24072407
int i;
2408-
const char *final_commit_name = NULL;
2409-
struct rev_info *revs = sb->revs;
2408+
struct object_array_entry *found = NULL;
24102409

2411-
/*
2412-
* There must be one and only one positive commit in the
2413-
* revs->pending array.
2414-
*/
24152410
for (i = 0; i < revs->pending.nr; i++) {
24162411
struct object *obj = revs->pending.objects[i].item;
24172412
if (obj->flags & UNINTERESTING)
@@ -2420,14 +2415,24 @@ static char *prepare_final(struct scoreboard *sb)
24202415
obj = deref_tag(obj, NULL, 0);
24212416
if (obj->type != OBJ_COMMIT)
24222417
die("Non commit %s?", revs->pending.objects[i].name);
2423-
if (sb->final)
2418+
if (found)
24242419
die("More than one commit to dig from %s and %s?",
24252420
revs->pending.objects[i].name,
2426-
final_commit_name);
2427-
sb->final = (struct commit *) obj;
2428-
final_commit_name = revs->pending.objects[i].name;
2421+
found->name);
2422+
found = &(revs->pending.objects[i]);
2423+
}
2424+
return found;
2425+
}
2426+
2427+
static char *prepare_final(struct scoreboard *sb)
2428+
{
2429+
struct object_array_entry *found = find_single_final(sb->revs);
2430+
if (found) {
2431+
sb->final = (struct commit *) found->item;
2432+
return xstrdup(found->name);
2433+
} else {
2434+
return NULL;
24292435
}
2430-
return xstrdup_or_null(final_commit_name);
24312436
}
24322437

24332438
static char *prepare_initial(struct scoreboard *sb)
@@ -2503,6 +2508,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
25032508
long dashdash_pos, lno;
25042509
char *final_commit_name = NULL;
25052510
enum object_type type;
2511+
struct commit *final_commit = NULL;
25062512

25072513
static struct string_list range_list;
25082514
static int output_option = 0, opt = 0;
@@ -2692,11 +2698,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
26922698
}
26932699
else if (contents_from)
26942700
die("--contents and --reverse do not blend well.");
2695-
else if (revs.first_parent_only)
2696-
die("combining --first-parent and --reverse is not supported");
26972701
else {
26982702
final_commit_name = prepare_initial(&sb);
26992703
sb.commits.compare = compare_commits_by_reverse_commit_date;
2704+
if (revs.first_parent_only)
2705+
revs.children.name = NULL;
27002706
}
27012707

27022708
if (!sb.final) {
@@ -2713,6 +2719,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
27132719
else if (contents_from)
27142720
die("Cannot use --contents with final commit object name");
27152721

2722+
if (reverse && revs.first_parent_only) {
2723+
struct object_array_entry *entry = find_single_final(sb.revs);
2724+
if (!entry)
2725+
die("--reverse and --first-parent together require specified latest commit");
2726+
else
2727+
final_commit = (struct commit*) entry->item;
2728+
}
2729+
27162730
/*
27172731
* If we have bottom, this will mark the ancestors of the
27182732
* bottom commits we would reach while traversing as
@@ -2721,6 +2735,25 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
27212735
if (prepare_revision_walk(&revs))
27222736
die(_("revision walk setup failed"));
27232737

2738+
if (reverse && revs.first_parent_only) {
2739+
struct commit *c = final_commit;
2740+
2741+
sb.revs->children.name = "children";
2742+
while (c->parents &&
2743+
hashcmp(c->object.sha1, sb.final->object.sha1)) {
2744+
struct commit_list *l = xcalloc(1, sizeof(*l));
2745+
2746+
l->item = c;
2747+
if (add_decoration(&sb.revs->children,
2748+
&c->parents->item->object, l))
2749+
die("BUG: not unique item in first-parent chain");
2750+
c = c->parents->item;
2751+
}
2752+
2753+
if (hashcmp(c->object.sha1, sb.final->object.sha1))
2754+
die("--reverse --first-parent together require range along first-parent chain");
2755+
}
2756+
27242757
if (is_null_sha1(sb.final->object.sha1)) {
27252758
o = sb.final->util;
27262759
sb.final_buf = xmemdupz(o->file.ptr, o->file.size);

t/t8009-blame-vs-topicbranches.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/sh
2+
3+
test_description='blaming trough history with topic branches'
4+
. ./test-lib.sh
5+
6+
# Creates the history shown below. '*'s mark the first parent in the merges.
7+
# The only line of file.t is changed in commit B2
8+
#
9+
# +---C1
10+
# / \
11+
# A0--A1--*A2--*A3
12+
# \ /
13+
# B1-B2
14+
#
15+
test_expect_success setup '
16+
test_commit A0 file.t line0 &&
17+
test_commit A1 &&
18+
git reset --hard A0 &&
19+
test_commit B1 &&
20+
test_commit B2 file.t line0changed &&
21+
git reset --hard A1 &&
22+
test_merge A2 B2 &&
23+
git reset --hard A1 &&
24+
test_commit C1 &&
25+
git reset --hard A2 &&
26+
test_merge A3 C1
27+
'
28+
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 &&
33+
test_cmp expect actual
34+
'
35+
36+
test_done

0 commit comments

Comments
 (0)