2008-08-04 00:47:16 +00:00
|
|
|
#!/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 &&
|
2013-05-16 15:32:35 +00:00
|
|
|
echo "initial" >lost &&
|
|
|
|
git add file lost &&
|
|
|
|
test_tick && git commit -m "Initial file and lost" &&
|
2008-08-04 00:47:16 +00:00
|
|
|
note A &&
|
|
|
|
|
|
|
|
git branch other-branch &&
|
|
|
|
|
|
|
|
echo "Hello" >file &&
|
2013-05-16 15:32:35 +00:00
|
|
|
echo "second" >lost &&
|
|
|
|
git add file lost &&
|
|
|
|
test_tick && git commit -m "Modified file and lost" &&
|
2008-08-04 00:47:16 +00:00
|
|
|
note B &&
|
|
|
|
|
|
|
|
git checkout other-branch &&
|
|
|
|
|
|
|
|
echo "Hello" >file &&
|
2013-05-16 15:32:35 +00:00
|
|
|
>lost &&
|
|
|
|
git add file lost &&
|
2008-08-04 00:47:16 +00:00
|
|
|
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 &&
|
|
|
|
|
2013-05-16 15:32:35 +00:00
|
|
|
test_tick &&
|
|
|
|
test_must_fail git merge -m "merge" master &&
|
|
|
|
>lost && git commit -a -m "merge" &&
|
2008-08-04 00:47:16 +00:00
|
|
|
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" &&
|
2013-01-17 22:23:03 +00:00
|
|
|
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
|
2008-08-04 00:47:16 +00:00
|
|
|
'
|
|
|
|
|
|
|
|
FMT='tformat:%P %H | %s'
|
|
|
|
|
2013-04-08 20:10:27 +00:00
|
|
|
check_outcome () {
|
|
|
|
outcome=$1
|
|
|
|
shift
|
2008-08-04 00:47:16 +00:00
|
|
|
for c in $1
|
|
|
|
do
|
|
|
|
echo "$c"
|
|
|
|
done >expect &&
|
|
|
|
shift &&
|
|
|
|
param="$*" &&
|
2013-04-08 20:10:27 +00:00
|
|
|
test_expect_$outcome "log $param" '
|
2008-08-04 00:47:16 +00:00
|
|
|
git log --pretty="$FMT" --parents $param |
|
|
|
|
unnote >actual &&
|
|
|
|
sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual &&
|
2015-03-20 10:07:52 +00:00
|
|
|
test_cmp expect check
|
2008-08-04 00:47:16 +00:00
|
|
|
'
|
|
|
|
}
|
|
|
|
|
2013-04-08 20:10:27 +00:00
|
|
|
check_result () {
|
|
|
|
check_outcome success "$@"
|
|
|
|
}
|
|
|
|
|
2013-01-17 22:23:03 +00:00
|
|
|
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
|
2013-05-16 15:32:37 +00:00
|
|
|
check_result 'I E C B A' --simplify-merges -- file
|
2008-08-04 00:47:16 +00:00
|
|
|
check_result 'I B A' -- file
|
|
|
|
check_result 'I B A' --topo-order -- file
|
Making pathspec limited log play nicer with --first-parent
In a topic branch workflow, you often want to find the latest commit that
merged a side branch that touched a particular area of the system, so that
a new topic branch to work on that area can be forked from that commit.
For example, I wanted to find an appropriate fork-point to queue Luke's
changes related to git-p4 in contrib/fast-import/.
"git log --first-parent" traverses the first-parent chain, and "-m --stat"
shows the list of paths touched by commits including merge commits. We
could ask the question this way:
# What is the latest commit that touched that path?
$ git log --first-parent --oneline -m --stat master |
sed -e '/^ contrib\/fast-import\/git-p4 /q' | tail
The above finds that 8cbfc11 (Merge branch 'pw/p4-view-updates',
2012-01-06) was such a commit.
But a more natural way to spell this question is this:
$ git log --first-parent --oneline -m --stat -1 master -- \
contrib/fast-import/git-p4
Unfortunately, this does not work. It finds ecb7cf9 (git-p4: rewrite view
handling, 2012-01-02). This commit is a part of the merged topic branch
and is _not_ on the first-parent path from the 'master':
$ git show-branch 8cbfc11 ecb7cf9
! [8cbfc11] Merge branch 'pw/p4-view-updates'
! [ecb7cf9] git-p4: rewrite view handling
--
- [8cbfc11] Merge branch 'pw/p4-view-updates'
+ [8cbfc11^2] git-p4: view spec documentation
++ [ecb7cf9] git-p4: rewrite view handling
The problem is caused by the merge simplification logic when it inspects
the merge commit 8cbfc11. In this case, the history leading to the tip of
'master' did not touch git-p4 since 'pw/p4-view-updates' topic forked, and
the result of the merge is simply a copy from the tip of the topic branch
in the view limited by the given pathspec. The merge simplification logic
discards the history on the mainline side of the merge, and pretends as if
the sole parent of the merge is its second parent, i.e. the tip of the
topic. While this simplification is correct in the general case, it is at
least surprising if not outright wrong when the user explicitly asked to
show the first-parent history.
Here is an attempt to fix this issue, by not allowing us to compare the
merge result with anything but the first parent when --first-parent is in
effect, to avoid the history traversal veering off to the side branch.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-01-19 19:58:45 +00:00
|
|
|
check_result 'H' --first-parent -- another-file
|
2008-08-04 00:47:16 +00:00
|
|
|
|
2013-05-16 15:32:35 +00:00
|
|
|
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 &&
|
2015-03-20 10:07:52 +00:00
|
|
|
test_cmp expect check
|
2013-05-16 15:32:35 +00:00
|
|
|
'
|
|
|
|
|
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-07-31 20:13:20 +00:00
|
|
|
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
|
|
|
|
'
|
|
|
|
|
2008-08-04 00:47:16 +00:00
|
|
|
test_done
|