Skip to content

Commit 62df03c

Browse files
committed
Merge branch 'jk/blame-contents-with-arbitrary-commit'
"git blame --contents=<file> <rev> -- <path>" used to be forbidden, but now it finds the origins of lines starting at <file> contents through the history that leads to <rev>. * jk/blame-contents-with-arbitrary-commit: blame: allow --contents to work with non-HEAD commit
2 parents 6dd9d96 + 1a3119e commit 62df03c

File tree

4 files changed

+46
-20
lines changed

4 files changed

+46
-20
lines changed

Documentation/blame-options.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ include::line-range-format.txt[]
6464
manual page.
6565

6666
--contents <file>::
67-
When <rev> is not specified, the command annotates the
68-
changes starting backwards from the working tree copy.
69-
This flag makes the command pretend as if the working
70-
tree copy has the contents of the named file (specify
71-
`-` to make the command read from the standard input).
67+
Pretend the file being annotated has a commit with the
68+
contents from the named file and a parent of <rev>,
69+
defaulting to HEAD when no <rev> is specified. You may
70+
specify '-' to make the command read from the standard
71+
input for the file contents.
7272

7373
--date <format>::
7474
Specifies the format used to output dates. If --date is not

Documentation/git-blame.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ SYNOPSIS
1212
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
1313
[--ignore-rev <rev>] [--ignore-revs-file <file>]
1414
[--color-lines] [--color-by-age] [--progress] [--abbrev=<n>]
15-
[<rev> | --contents <file> | --reverse <rev>..<rev>] [--] <file>
15+
[ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file>
1616

1717
DESCRIPTION
1818
-----------

blame.c

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,12 @@ static void set_commit_buffer_from_strbuf(struct repository *r,
177177
static struct commit *fake_working_tree_commit(struct repository *r,
178178
struct diff_options *opt,
179179
const char *path,
180-
const char *contents_from)
180+
const char *contents_from,
181+
struct object_id *oid)
181182
{
182183
struct commit *commit;
183184
struct blame_origin *origin;
184185
struct commit_list **parent_tail, *parent;
185-
struct object_id head_oid;
186186
struct strbuf buf = STRBUF_INIT;
187187
const char *ident;
188188
time_t now;
@@ -198,10 +198,7 @@ static struct commit *fake_working_tree_commit(struct repository *r,
198198
commit->date = now;
199199
parent_tail = &commit->parents;
200200

201-
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
202-
die("no such ref: HEAD");
203-
204-
parent_tail = append_parent(r, parent_tail, &head_oid);
201+
parent_tail = append_parent(r, parent_tail, oid);
205202
append_merge_parents(r, parent_tail);
206203
verify_working_tree_path(r, commit, path);
207204

@@ -2772,22 +2769,37 @@ void setup_scoreboard(struct blame_scoreboard *sb,
27722769
sb->commits.compare = compare_commits_by_reverse_commit_date;
27732770
}
27742771

2775-
if (sb->final && sb->contents_from)
2776-
die(_("cannot use --contents with final commit object name"));
2777-
27782772
if (sb->reverse && sb->revs->first_parent_only)
27792773
sb->revs->children.name = NULL;
27802774

2781-
if (!sb->final) {
2775+
if (sb->contents_from || !sb->final) {
2776+
struct object_id head_oid, *parent_oid;
2777+
27822778
/*
2783-
* "--not A B -- path" without anything positive;
2784-
* do not default to HEAD, but use the working tree
2785-
* or "--contents".
2779+
* Build a fake commit at the top of the history, when
2780+
* (1) "git blame [^A] --path", i.e. with no positive end
2781+
* of the history range, in which case we build such
2782+
* a fake commit on top of the HEAD to blame in-tree
2783+
* modifications.
2784+
* (2) "git blame --contents=file [A] -- path", with or
2785+
* without positive end of the history range but with
2786+
* --contents, in which case we pretend that there is
2787+
* a fake commit on top of the positive end (defaulting to
2788+
* HEAD) that has the given contents in the path.
27862789
*/
2790+
if (sb->final) {
2791+
parent_oid = &sb->final->object.oid;
2792+
} else {
2793+
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
2794+
die("no such ref: HEAD");
2795+
parent_oid = &head_oid;
2796+
}
2797+
27872798
setup_work_tree();
27882799
sb->final = fake_working_tree_commit(sb->repo,
27892800
&sb->revs->diffopt,
2790-
sb->path, sb->contents_from);
2801+
sb->path, sb->contents_from,
2802+
parent_oid);
27912803
add_pending_object(sb->revs, &(sb->final->object), ":");
27922804
}
27932805

t/annotate-tests.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ test_expect_success 'blame 1 author' '
7272
check_count A 2
7373
'
7474

75+
test_expect_success 'blame with --contents' '
76+
check_count --contents=file A 2
77+
'
78+
79+
test_expect_success 'blame with --contents changed' '
80+
echo "1A quick brown fox jumps over the" >contents &&
81+
echo "another lazy dog" >>contents &&
82+
check_count --contents=contents A 1 "Not Committed Yet" 1
83+
'
84+
7585
test_expect_success 'blame in a bare repo without starting commit' '
7686
git clone --bare . bare.git &&
7787
(
@@ -98,6 +108,10 @@ test_expect_success 'blame 2 authors' '
98108
check_count A 2 B 2
99109
'
100110

111+
test_expect_success 'blame with --contents and revision' '
112+
check_count -h testTag --contents=file A 2 "Not Committed Yet" 2
113+
'
114+
101115
test_expect_success 'setup B1 lines (branch1)' '
102116
git checkout -b branch1 main &&
103117
echo "3A slow green fox jumps into the" >>file &&

0 commit comments

Comments
 (0)