git/t/t8013-blame-ignore-revs.sh
Barret Rhoden 8934ac8c92 blame: add config options for the output of ignored or unblamable lines
When ignoring commits, the commit that is blamed might not be
responsible for the change, due to the inaccuracy of our heuristic.
Users might want to know when a particular line has a potentially
inaccurate blame.

Furthermore, guess_line_blames() may fail to find any parent commit for
a given line touched by an ignored commit.  Those 'unblamable' lines
remain blamed on an ignored commit.  Users might want to know if a line
is unblamable so that they do not spend time investigating a commit they
know is uninteresting.

This patch adds two config options to mark these two types of lines in
the output of blame.

The first option can identify ignored lines by specifying
blame.markIgnoredLines.  When this option is set, each blame line that
was blamed on a commit other than the ignored commit is marked with a
'?'.

For example:
	278b6158d6fdb (Barret Rhoden  2016-04-11 13:57:54 -0400 26)
appears as:
	?278b6158d6fd (Barret Rhoden  2016-04-11 13:57:54 -0400 26)

where the '?' is placed before the commit, and the hash has one fewer
characters.

Sometimes we are unable to even guess at what ancestor commit touched a
line.  These lines are 'unblamable.'  The second option,
blame.markUnblamableLines, will mark the line with '*'.

For example, say we ignore e5e8d36d04cbe, yet we are unable to blame
this line on another commit:
	e5e8d36d04cbe (Barret Rhoden  2016-04-11 13:57:54 -0400 26)
appears as:
	*e5e8d36d04cb (Barret Rhoden  2016-04-11 13:57:54 -0400 26)

When these config options are used together, every line touched by an
ignored commit will be marked with either a '?' or a '*'.

Signed-off-by: Barret Rhoden <brho@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-05-16 11:36:23 +09:00

274 lines
7.8 KiB
Bash
Executable file

#!/bin/sh
test_description='ignore revisions when blaming'
. ./test-lib.sh
# Creates:
# A--B--X
# A added line 1 and B added line 2. X makes changes to those lines. Sanity
# check that X is blamed for both lines.
test_expect_success setup '
test_commit A file line1 &&
echo line2 >>file &&
git add file &&
test_tick &&
git commit -m B &&
git tag B &&
test_write_lines line-one line-two >file &&
git add file &&
test_tick &&
git commit -m X &&
git tag X &&
git blame --line-porcelain file >blame_raw &&
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse X >expect &&
test_cmp expect actual &&
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse X >expect &&
test_cmp expect actual
'
# Ignore X, make sure A is blamed for line 1 and B for line 2.
test_expect_success ignore_rev_changing_lines '
git blame --line-porcelain --ignore-rev X file >blame_raw &&
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse A >expect &&
test_cmp expect actual &&
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual
'
# For ignored revs that have added 'unblamable' lines, attribute those to the
# ignored commit.
# A--B--X--Y
# Where Y changes lines 1 and 2, and adds lines 3 and 4. The added lines ought
# to have nothing in common with "line-one" or "line-two", to keep any
# heuristics from matching them with any lines in the parent.
test_expect_success ignore_rev_adding_unblamable_lines '
test_write_lines line-one-change line-two-changed y3 y4 >file &&
git add file &&
test_tick &&
git commit -m Y &&
git tag Y &&
git rev-parse Y >expect &&
git blame --line-porcelain file --ignore-rev Y >blame_raw &&
grep -E "^[0-9a-f]+ [0-9]+ 3" blame_raw | sed -e "s/ .*//" >actual &&
test_cmp expect actual &&
grep -E "^[0-9a-f]+ [0-9]+ 4" blame_raw | sed -e "s/ .*//" >actual &&
test_cmp expect actual
'
# Ignore X and Y, both in separate files. Lines 1 == A, 2 == B.
test_expect_success ignore_revs_from_files '
git rev-parse X >ignore_x &&
git rev-parse Y >ignore_y &&
git blame --line-porcelain file --ignore-revs-file ignore_x --ignore-revs-file ignore_y >blame_raw &&
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse A >expect &&
test_cmp expect actual &&
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual
'
# Ignore X from the config option, Y from a file.
test_expect_success ignore_revs_from_configs_and_files '
git config --add blame.ignoreRevsFile ignore_x &&
git blame --line-porcelain file --ignore-revs-file ignore_y >blame_raw &&
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse A >expect &&
test_cmp expect actual &&
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual
'
# Override blame.ignoreRevsFile (ignore_x) with an empty string. X should be
# blamed now for lines 1 and 2, since we are no longer ignoring X.
test_expect_success override_ignore_revs_file '
git blame --line-porcelain file --ignore-revs-file "" --ignore-revs-file ignore_y >blame_raw &&
git rev-parse X >expect &&
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
test_cmp expect actual &&
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
test_cmp expect actual
'
test_expect_success bad_files_and_revs '
test_must_fail git blame file --ignore-rev NOREV 2>err &&
test_i18ngrep "cannot find revision NOREV to ignore" err &&
test_must_fail git blame file --ignore-revs-file NOFILE 2>err &&
test_i18ngrep "could not open.*: NOFILE" err &&
echo NOREV >ignore_norev &&
test_must_fail git blame file --ignore-revs-file ignore_norev 2>err &&
test_i18ngrep "invalid object name: NOREV" err
'
# For ignored revs that have added 'unblamable' lines, mark those lines with a
# '*'
# A--B--X--Y
# Lines 3 and 4 are from Y and unblamable. This was set up in
# ignore_rev_adding_unblamable_lines.
test_expect_success mark_unblamable_lines '
git config --add blame.markUnblamableLines true &&
git blame --ignore-rev Y file >blame_raw &&
echo "*" >expect &&
sed -n "3p" blame_raw | cut -c1 >actual &&
test_cmp expect actual &&
sed -n "4p" blame_raw | cut -c1 >actual &&
test_cmp expect actual
'
# Commit Z will touch the first two lines. Y touched all four.
# A--B--X--Y--Z
# The blame output when ignoring Z should be:
# ?Y ... 1)
# ?Y ... 2)
# Y ... 3)
# Y ... 4)
# We're checking only the first character
test_expect_success mark_ignored_lines '
git config --add blame.markIgnoredLines true &&
test_write_lines line-one-Z line-two-Z y3 y4 >file &&
git add file &&
test_tick &&
git commit -m Z &&
git tag Z &&
git blame --ignore-rev Z file >blame_raw &&
echo "?" >expect &&
sed -n "1p" blame_raw | cut -c1 >actual &&
test_cmp expect actual &&
sed -n "2p" blame_raw | cut -c1 >actual &&
test_cmp expect actual &&
sed -n "3p" blame_raw | cut -c1 >actual &&
! test_cmp expect actual &&
sed -n "4p" blame_raw | cut -c1 >actual &&
! test_cmp expect actual
'
# For ignored revs that added 'unblamable' lines and more recent commits changed
# the blamable lines, mark the unblamable lines with a
# '*'
# A--B--X--Y--Z
# Lines 3 and 4 are from Y and unblamable, as set up in
# ignore_rev_adding_unblamable_lines. Z changed lines 1 and 2.
test_expect_success mark_unblamable_lines_intermediate '
git config --add blame.markUnblamableLines true &&
git blame --ignore-rev Y file >blame_raw 2>stderr &&
echo "*" >expect &&
sed -n "3p" blame_raw | cut -c1 >actual &&
test_cmp expect actual &&
sed -n "4p" blame_raw | cut -c1 >actual &&
test_cmp expect actual
'
# The heuristic called by guess_line_blames() tries to find the size of a
# blame_entry 'e' in the parent's address space. Those calculations need to
# check for negative or zero values for when a blame entry is completely outside
# the window of the parent's version of a file.
#
# This happens when one commit adds several lines (commit B below). A later
# commit (C) changes one line in the middle of B's change. Commit C gets blamed
# for its change, and that breaks up B's change into multiple blame entries.
# When processing B, one of the blame_entries is outside A's window (which was
# zero - it had no lines added on its side of the diff).
#
# A--B--C, ignore B to test the ignore heuristic's boundary checks.
test_expect_success ignored_chunk_negative_parent_size '
rm -rf .git/ &&
git init &&
test_write_lines L1 L2 L7 L8 L9 >file &&
git add file &&
test_tick &&
git commit -m A &&
git tag A &&
test_write_lines L1 L2 L3 L4 L5 L6 L7 L8 L9 >file &&
git add file &&
test_tick &&
git commit -m B &&
git tag B &&
test_write_lines L1 L2 L3 L4 xxx L6 L7 L8 L9 >file &&
git add file &&
test_tick &&
git commit -m C &&
git tag C &&
git blame file --ignore-rev B >blame_raw
'
# Resetting the repo and creating:
#
# A--B--M
# \ /
# C-+
#
# 'A' creates a file. B changes line 1, and C changes line 9. M merges.
test_expect_success ignore_merge '
rm -rf .git/ &&
git init &&
test_write_lines L1 L2 L3 L4 L5 L6 L7 L8 L9 >file &&
git add file &&
test_tick &&
git commit -m A &&
git tag A &&
test_write_lines BB L2 L3 L4 L5 L6 L7 L8 L9 >file &&
git add file &&
test_tick &&
git commit -m B &&
git tag B &&
git reset --hard A &&
test_write_lines L1 L2 L3 L4 L5 L6 L7 L8 CC >file &&
git add file &&
test_tick &&
git commit -m C &&
git tag C &&
test_merge M B &&
git blame --line-porcelain file --ignore-rev M >blame_raw &&
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual &&
grep -E "^[0-9a-f]+ [0-9]+ 9" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse C >expect &&
test_cmp expect actual
'
test_done