git/t/t6012-rev-list-simplify.sh
Thomas Rast 53d00b39ce log: use true parents for diff even when rewriting
When using pathspec filtering in combination with diff-based log
output, parent simplification happens before the diff is computed.
The diff is therefore against the *simplified* parents.

This works okay, arguably by accident, in the normal case:
simplification reduces to one parent as long as the commit is TREESAME
to it.  So the simplified parent of any given commit must have the
same tree contents on the filtered paths as its true (unfiltered)
parent.

However, --full-diff breaks this guarantee, and indeed gives pretty
spectacular results when comparing the output of

  git log --graph --stat ...
  git log --graph --full-diff --stat ...

(--graph internally kicks in parent simplification, much like
--parents).

To fix it, store a copy of the parent list before simplification (in a
slab) whenever --full-diff is in effect.  Then use the stored parents
instead of the simplified ones in the commit display code paths.  The
latter do not actually check for --full-diff to avoid duplicated code;
they just grab the original parents if save_parents() has not been
called for this revision walk.

For ordinary commits it should be obvious that this is the right thing
to do.

Merge commits are a bit subtle.  Observe that with default
simplification, merge simplification is an all-or-nothing decision:
either the merge is TREESAME to one parent and disappears, or it is
different from all parents and the parent list remains intact.
Redundant parents are not pruned, so the existing code also shows them
as a merge.

So if we do show a merge commit, the parent list just consists of the
rewrite result on each parent.  Running, e.g., --cc on this in
--full-diff mode is not very useful: if any commits were skipped, some
hunks will disagree with all sides of the merge (with one side,
because commits were skipped; with the others, because they didn't
have those changes in the first place).  This triggers --cc showing
these hunks spuriously.

Therefore I believe that even for merge commits it is better to show
the diffs wrt. the original parents.

Reported-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
Signed-off-by: Thomas Rast <trast@inf.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-01 10:25:48 -07:00

137 lines
3.0 KiB
Bash
Executable File

#!/bin/sh
test_description='merge simplification'
. ./test-lib.sh
note () {
git tag "$1"
}
unnote () {
git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g"
}
test_expect_success setup '
echo "Hi there" >file &&
echo "initial" >lost &&
git add file lost &&
test_tick && git commit -m "Initial file and lost" &&
note A &&
git branch other-branch &&
echo "Hello" >file &&
echo "second" >lost &&
git add file lost &&
test_tick && git commit -m "Modified file and lost" &&
note B &&
git checkout other-branch &&
echo "Hello" >file &&
>lost &&
git add file lost &&
test_tick && git commit -m "Modified the file identically" &&
note C &&
echo "This is a stupid example" >another-file &&
git add another-file &&
test_tick && git commit -m "Add another file" &&
note D &&
test_tick &&
test_must_fail git merge -m "merge" master &&
>lost && git commit -a -m "merge" &&
note E &&
echo "Yet another" >elif &&
git add elif &&
test_tick && git commit -m "Irrelevant change" &&
note F &&
git checkout master &&
echo "Yet another" >elif &&
git add elif &&
test_tick && git commit -m "Another irrelevant change" &&
note G &&
test_tick && git merge -m "merge" other-branch &&
note H &&
echo "Final change" >file &&
test_tick && git commit -a -m "Final change" &&
note I &&
git symbolic-ref HEAD refs/heads/unrelated &&
git rm -f "*" &&
echo "Unrelated branch" >side &&
git add side &&
test_tick && git commit -m "Side root" &&
note J &&
git checkout master &&
test_tick && git merge -m "Coolest" unrelated &&
note K &&
echo "Immaterial" >elif &&
git add elif &&
test_tick && git commit -m "Last" &&
note L
'
FMT='tformat:%P %H | %s'
check_outcome () {
outcome=$1
shift
for c in $1
do
echo "$c"
done >expect &&
shift &&
param="$*" &&
test_expect_$outcome "log $param" '
git log --pretty="$FMT" --parents $param |
unnote >actual &&
sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual &&
test_cmp expect check || {
cat actual
false
}
'
}
check_result () {
check_outcome success "$@"
}
check_result 'L K J I H G F E D C B A' --full-history
check_result 'K I H E C B A' --full-history -- file
check_result 'K I H E C B A' --full-history --topo-order -- file
check_result 'K I H E C B A' --full-history --date-order -- file
check_result 'I E C B A' --simplify-merges -- file
check_result 'I B A' -- file
check_result 'I B A' --topo-order -- file
check_result 'H' --first-parent -- another-file
check_result 'E C B A' --full-history E -- lost
test_expect_success 'full history simplification without parent' '
printf "%s\n" E C B A >expect &&
git log --pretty="$FMT" --full-history E -- lost |
unnote >actual &&
sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual &&
test_cmp expect check || {
cat actual
false
}
'
test_expect_success '--full-diff is not affected by --parents' '
git log -p --pretty="%H" --full-diff -- file >expected &&
git log -p --pretty="%H" --full-diff --parents -- file >actual &&
test_cmp expected actual
'
test_done