提交 b27a79d1 编写于 作者: J Junio C Hamano

Merge branch 'kb/full-history-compute-treesame-carefully-2'

Major update to the revision traversal logic to improve culling of
irrelevant parents while traversing a mergy history.

* kb/full-history-compute-treesame-carefully-2:
  revision.c: make default history consider bottom commits
  revision.c: don't show all merges for --parents
  revision.c: discount side branches when computing TREESAME
  revision.c: add BOTTOM flag for commits
  simplify-merges: drop merge from irrelevant side branch
  simplify-merges: never remove all TREESAME parents
  t6012: update test for tweaked full-history traversal
  revision.c: Make --full-history consider more merges
  Documentation: avoid "uninteresting"
  rev-list-options.txt: correct TREESAME for P
  t6111: add parents to tests
  t6111: allow checking the parents as well
  t6111: new TREESAME test set
  t6019: test file dropped in -s ours merge
  decorate.c: compact table when growing
...@@ -271,8 +271,8 @@ See also linkgit:git-reflog[1]. ...@@ -271,8 +271,8 @@ See also linkgit:git-reflog[1].
--boundary:: --boundary::
Output uninteresting commits at the boundary, which are usually Output excluded boundary commits. Boundary commits are
not shown. prefixed with `-`.
-- --
...@@ -342,13 +342,13 @@ In the following, we will always refer to the same example history to ...@@ -342,13 +342,13 @@ In the following, we will always refer to the same example history to
illustrate the differences between simplification settings. We assume illustrate the differences between simplification settings. We assume
that you are filtering for a file `foo` in this commit graph: that you are filtering for a file `foo` in this commit graph:
----------------------------------------------------------------------- -----------------------------------------------------------------------
.-A---M---N---O---P .-A---M---N---O---P---Q
/ / / / / / / / / / /
I B C D E I B C D E Y
\ / / / / \ / / / / /
`-------------' `-------------' X
----------------------------------------------------------------------- -----------------------------------------------------------------------
The horizontal line of history A---P is taken to be the first parent of The horizontal line of history A---Q is taken to be the first parent of
each merge. The commits are: each merge. The commits are:
* `I` is the initial commit, in which `foo` exists with contents * `I` is the initial commit, in which `foo` exists with contents
...@@ -367,8 +367,11 @@ each merge. The commits are: ...@@ -367,8 +367,11 @@ each merge. The commits are:
`N` and `D` to "foobarbaz"; i.e., it is not TREESAME to any parent. `N` and `D` to "foobarbaz"; i.e., it is not TREESAME to any parent.
* `E` changes `quux` to "xyzzy", and its merge `P` combines the * `E` changes `quux` to "xyzzy", and its merge `P` combines the
strings to "quux xyzzy". Despite appearing interesting, `P` is strings to "quux xyzzy". `P` is TREESAME to `O`, but not to `E`.
TREESAME to all parents.
* `X` is an indpendent root commit that added a new file `side`, and `Y`
modified it. `Y` is TREESAME to `X`. Its merge `Q` added `side` to `P`, and
`Q` is TREESAME to `P`, but not to `Y`.
'rev-list' walks backwards through history, including or excluding 'rev-list' walks backwards through history, including or excluding
commits based on whether '\--full-history' and/or parent rewriting commits based on whether '\--full-history' and/or parent rewriting
...@@ -410,10 +413,10 @@ parent lines. ...@@ -410,10 +413,10 @@ parent lines.
the example, we get the example, we get
+ +
----------------------------------------------------------------------- -----------------------------------------------------------------------
I A B N D O I A B N D O P Q
----------------------------------------------------------------------- -----------------------------------------------------------------------
+ +
`P` and `M` were excluded because they are TREESAME to a parent. `E`, `M` was excluded because it is TREESAME to both parents. `E`,
`C` and `B` were all walked, but only `B` was !TREESAME, so the others `C` and `B` were all walked, but only `B` was !TREESAME, so the others
do not appear. do not appear.
+ +
...@@ -431,7 +434,7 @@ Along each parent, prune away commits that are not included ...@@ -431,7 +434,7 @@ Along each parent, prune away commits that are not included
themselves. This results in themselves. This results in
+ +
----------------------------------------------------------------------- -----------------------------------------------------------------------
.-A---M---N---O---P .-A---M---N---O---P---Q
/ / / / / / / / / /
I B / D / I B / D /
\ / / / / \ / / / /
...@@ -441,7 +444,7 @@ themselves. This results in ...@@ -441,7 +444,7 @@ themselves. This results in
Compare to '\--full-history' without rewriting above. Note that `E` Compare to '\--full-history' without rewriting above. Note that `E`
was pruned away because it is TREESAME, but the parent list of P was was pruned away because it is TREESAME, but the parent list of P was
rewritten to contain `E`'s parent `I`. The same happened for `C` and rewritten to contain `E`'s parent `I`. The same happened for `C` and
`N`. Note also that `P` was included despite being TREESAME. `N`, and `X`, `Y` and `Q`.
In addition to the above settings, you can change whether TREESAME In addition to the above settings, you can change whether TREESAME
affects inclusion: affects inclusion:
...@@ -471,8 +474,9 @@ history according to the following rules: ...@@ -471,8 +474,9 @@ history according to the following rules:
* Set `C'` to `C`. * Set `C'` to `C`.
+ +
* Replace each parent `P` of `C'` with its simplification `P'`. In * Replace each parent `P` of `C'` with its simplification `P'`. In
the process, drop parents that are ancestors of other parents, and the process, drop parents that are ancestors of other parents or that are
remove duplicates. root commits TREESAME to an empty tree, and remove duplicates, but take care
to never drop all parents that we are TREESAME to.
+ +
* If after this parent rewriting, `C'` is a root or merge commit (has * If after this parent rewriting, `C'` is a root or merge commit (has
zero or >1 parents), a boundary commit, or !TREESAME, it remains. zero or >1 parents), a boundary commit, or !TREESAME, it remains.
...@@ -490,7 +494,7 @@ The effect of this is best shown by way of comparing to ...@@ -490,7 +494,7 @@ The effect of this is best shown by way of comparing to
`---------' `---------'
----------------------------------------------------------------------- -----------------------------------------------------------------------
+ +
Note the major differences in `N` and `P` over '--full-history': Note the major differences in `N`, `P` and `Q` over '--full-history':
+ +
-- --
* `N`'s parent list had `I` removed, because it is an ancestor of the * `N`'s parent list had `I` removed, because it is an ancestor of the
...@@ -498,6 +502,10 @@ Note the major differences in `N` and `P` over '--full-history': ...@@ -498,6 +502,10 @@ Note the major differences in `N` and `P` over '--full-history':
+ +
* `P`'s parent list similarly had `I` removed. `P` was then * `P`'s parent list similarly had `I` removed. `P` was then
removed completely, because it had one parent and is TREESAME. removed completely, because it had one parent and is TREESAME.
+
* `Q`'s parent list had `Y` simplified to `X`. `X` was then removed, because it
was a TREESAME root. `Q` was then removed completely, because it had one
parent and is TREESAME.
-- --
Finally, there is a fifth simplification mode available: Finally, there is a fifth simplification mode available:
......
...@@ -49,7 +49,7 @@ static void grow_decoration(struct decoration *n) ...@@ -49,7 +49,7 @@ static void grow_decoration(struct decoration *n)
const struct object *base = old_hash[i].base; const struct object *base = old_hash[i].base;
void *decoration = old_hash[i].decoration; void *decoration = old_hash[i].decoration;
if (!base) if (!decoration)
continue; continue;
insert_decoration(n, base, decoration); insert_decoration(n, base, decoration);
} }
......
此差异已折叠。
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
#define ADDED (1u<<7) /* Parents already parsed and added? */ #define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8) #define SYMMETRIC_LEFT (1u<<8)
#define PATCHSAME (1u<<9) #define PATCHSAME (1u<<9)
#define ALL_REV_FLAGS ((1u<<10)-1) #define BOTTOM (1u<<10)
#define ALL_REV_FLAGS ((1u<<11)-1)
#define DECORATE_SHORT_REFS 1 #define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2 #define DECORATE_FULL_REFS 2
...@@ -169,6 +170,7 @@ struct rev_info { ...@@ -169,6 +170,7 @@ struct rev_info {
struct reflog_walk_info *reflog_info; struct reflog_walk_info *reflog_info;
struct decoration children; struct decoration children;
struct decoration merge_simplification; struct decoration merge_simplification;
struct decoration treesame;
/* notes-specific options: which refs to show */ /* notes-specific options: which refs to show */
struct display_notes_opt notes_opt; struct display_notes_opt notes_opt;
......
...@@ -14,21 +14,24 @@ unnote () { ...@@ -14,21 +14,24 @@ unnote () {
test_expect_success setup ' test_expect_success setup '
echo "Hi there" >file && echo "Hi there" >file &&
git add file && echo "initial" >lost &&
test_tick && git commit -m "Initial file" && git add file lost &&
test_tick && git commit -m "Initial file and lost" &&
note A && note A &&
git branch other-branch && git branch other-branch &&
echo "Hello" >file && echo "Hello" >file &&
git add file && echo "second" >lost &&
test_tick && git commit -m "Modified file" && git add file lost &&
test_tick && git commit -m "Modified file and lost" &&
note B && note B &&
git checkout other-branch && git checkout other-branch &&
echo "Hello" >file && echo "Hello" >file &&
git add file && >lost &&
git add file lost &&
test_tick && git commit -m "Modified the file identically" && test_tick && git commit -m "Modified the file identically" &&
note C && note C &&
...@@ -37,7 +40,9 @@ test_expect_success setup ' ...@@ -37,7 +40,9 @@ test_expect_success setup '
test_tick && git commit -m "Add another file" && test_tick && git commit -m "Add another file" &&
note D && note D &&
test_tick && git merge -m "merge" master && test_tick &&
test_must_fail git merge -m "merge" master &&
>lost && git commit -a -m "merge" &&
note E && note E &&
echo "Yet another" >elif && echo "Yet another" >elif &&
...@@ -105,9 +110,21 @@ check_result 'L K J I H G F E D C B A' --full-history ...@@ -105,9 +110,21 @@ 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 -- 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 --topo-order -- file
check_result 'K I H E C B A' --full-history --date-order -- file check_result 'K I H E C B A' --full-history --date-order -- file
check_outcome failure 'I E C B A' --simplify-merges -- file check_result 'I E C B A' --simplify-merges -- file
check_result 'I B A' -- file check_result 'I B A' -- file
check_result 'I B A' --topo-order -- file check_result 'I B A' --topo-order -- file
check_result 'H' --first-parent -- another-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_done test_done
...@@ -16,6 +16,10 @@ test_description='--ancestry-path' ...@@ -16,6 +16,10 @@ test_description='--ancestry-path'
# #
# F...I == F G H I # F...I == F G H I
# --ancestry-path F...I == F H I # --ancestry-path F...I == F H I
#
# G..M -- G.t == [nothing - was dropped in "-s ours" merge L]
# --ancestry-path G..M -- G.t == L
# --ancestry-path --simplify-merges G^..M -- G.t == G L
. ./test-lib.sh . ./test-lib.sh
...@@ -89,6 +93,29 @@ test_expect_success 'rev-list --ancestry-path F...I' ' ...@@ -89,6 +93,29 @@ test_expect_success 'rev-list --ancestry-path F...I' '
test_cmp expect actual test_cmp expect actual
' '
# G.t is dropped in an "-s ours" merge
test_expect_success 'rev-list G..M -- G.t' '
>expect &&
git rev-list --format=%s G..M -- G.t |
sed -e "/^commit /d" >actual &&
test_cmp expect actual
'
test_expect_success 'rev-list --ancestry-path G..M -- G.t' '
echo L >expect &&
git rev-list --ancestry-path --format=%s G..M -- G.t |
sed -e "/^commit /d" >actual &&
test_cmp expect actual
'
test_expect_success 'rev-list --ancestry-path --simplify-merges G^..M -- G.t' '
for c in G L; do echo $c; done >expect &&
git rev-list --ancestry-path --simplify-merges --format=%s G^..M -- G.t |
sed -e "/^commit /d" |
sort >actual &&
test_cmp expect actual
'
# b---bc # b---bc
# / \ / # / \ /
# a X # a X
......
#!/bin/sh
#
# ,---E--. *H----------. * marks !TREESAME parent paths
# / \ / \*
# *A--*B---D--*F-*G---------K-*L-*M
# \ /* \ /
# `-C-' `-*I-*J
#
# A creates "file", B and F change it.
# Odd merge G takes the old version from B.
# I changes it, but J reverts it, so K is TREESAME to both parents.
# H and L both change "file", and M merges those changes.
test_description='TREESAME and limiting'
. ./test-lib.sh
note () {
git tag "$1"
}
unnote () {
git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\))\([ ]\)|\1\2|g"
}
test_expect_success setup '
test_commit "Initial file" file "Hi there" A &&
git branch other-branch &&
test_commit "file=Hello" file "Hello" B &&
git branch third-branch &&
git checkout other-branch &&
test_commit "Added other" other "Hello" C &&
git checkout master &&
test_merge D other-branch &&
git checkout third-branch &&
test_commit "Third file" third "Nothing" E &&
git checkout master &&
test_commit "file=Blah" file "Blah" F &&
test_tick && git merge --no-commit third-branch &&
git checkout third-branch file &&
git commit &&
note G &&
git branch fiddler-branch &&
git checkout -b part2-branch &&
test_commit "file=Part 2" file "Part 2" H &&
git checkout fiddler-branch &&
test_commit "Bad commit" file "Silly" I &&
test_tick && git revert I && note J &&
git checkout master &&
test_tick && git merge --no-ff fiddler-branch &&
note K
test_commit "file=Part 1" file "Part 1" L &&
test_tick && test_must_fail git merge part2-branch &&
test_commit M file "Parts 1+2"
'
check_outcome () {
outcome=$1
shift
case "$1" in
*"("*)
FMT="%P %H | %s"
munge_actual="
s/^\([^ ]*\) \([^ ]*\) .*/(\1)\2/
s/ //g
s/()//
"
;;
*)
FMT="%H | %s"
munge_actual="s/^\([^ ]*\) .*/\1/"
;;
esac &&
printf "%s\n" $1 >expect &&
shift
param="$*" &&
test_expect_$outcome "log $param" '
git log --format="$FMT" $param |
unnote >actual &&
sed -e "$munge_actual" <actual >check &&
test_cmp expect check || {
cat actual
false
}
'
}
check_result () {
check_outcome success "$@"
}
# Odd merge G drops a change in F. Important that G is listed in all
# except the most basic list. Achieving this means normal merge D will also be
# shown in normal full-history, as we can't distinguish unless we do a
# simplification pass. After simplification, D is dropped but G remains.
# Also, merge simplification of G should not drop the parent B that the default
# simple history follows.
check_result 'M L K J I H G F E D C B A'
check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G (D)F (B)E (BC)D (A)C (A)B A'
check_result 'M H L K J I G E F D C B A' --topo-order
check_result 'M L H B A' -- file
check_result '(LH)M (B)L (B)H (A)B A' --parents -- file
check_result 'M L J I H G F D B A' --full-history -- file
check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G (D)F (BA)D (A)B A' --full-history --parents -- file
check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file
check_result 'M L K G F D B A' --first-parent
check_result 'M L G F B A' --first-parent -- file
# Check that odd merge G remains shown when F is the bottom.
check_result 'M L K J I H G E' F..M
check_result 'M H L K J I G E' F..M --topo-order
check_result 'M L H' F..M -- file
check_result '(LH)M (B)L (B)H' --parents F..M -- file
check_result 'M L J I H G' F..M --full-history -- file
check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G' F..M --full-history --parents -- file
check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file
check_result 'M L K J I H G' F..M --ancestry-path
check_result 'M L J I H G' F..M --ancestry-path -- file
check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G' F..M --ancestry-path --parents -- file
check_result '(LH)M (G)H (J)L (I)J (G)I (FE)G' F..M --ancestry-path --simplify-merges -- file
check_result 'M L K G' F..M --first-parent
check_result 'M L G' F..M --first-parent -- file
# Note that G is pruned when E is the bottom, even if it's the same commit list
# If we want history since E, then we're quite happy to ignore G that took E.
check_result 'M L K J I H G' E..M --ancestry-path
check_result 'M L J I H' E..M --ancestry-path -- file
check_result '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file
check_result '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file
# Should still be able to ignore I-J branch in simple log, despite limiting
# to G.
check_result 'M L K J I H' G..M
check_result 'M H L K J I' G..M --topo-order
check_result 'M L H' G..M -- file
check_result '(LH)M (G)L (G)H' G..M --parents -- file
check_result 'M L J I H' G..M --full-history -- file
check_result 'M L K J I H' G..M --full-history --parents -- file
check_result 'M H L J I' G..M --simplify-merges -- file
check_result 'M L K J I H' G..M --ancestry-path
check_result 'M L J I H' G..M --ancestry-path -- file
check_result 'M L K J I H' G..M --ancestry-path --parents -- file
check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file
# B..F should be able to simplify the merge D from irrelevant side branch C.
# Default log should also be free to follow B-D, and ignore C.
# But --full-history shouldn't drop D on its own - without simplification,
# we can't decide if the merge from INTERESTING commit C was sensible.
check_result 'F D C' B..F
check_result 'F' B..F -- file
check_result '(B)F' B..F --parents -- file
check_result 'F D' B..F --full-history -- file
check_result '(D)F (BA)D' B..F --full-history --parents -- file
check_result '(B)F' B..F --simplify-merges -- file
check_result 'F D' B..F --ancestry-path
check_result 'F' B..F --ancestry-path -- file
check_result 'F' B..F --ancestry-path --parents -- file
check_result 'F' B..F --ancestry-path --simplify-merges -- file
check_result 'F D' B..F --first-parent
check_result 'F' B..F --first-parent -- file
# E...F should be equivalent to E F ^B, and be able to drop D as above.
check_result 'F' E F ^B -- file # includes D
check_result 'F' E...F -- file # includes D
# Any sort of full history of C..F should show D, as it's the connection to C,
# and it differs from it.
check_result 'F D B' C..F
check_result 'F B' C..F -- file
check_result '(B)F (A)B' C..F --parents -- file
check_result 'F D B' C..F --full-history -- file
check_result '(D)F (BC)D (A)B' C..F --full-history --parents -- file
check_result '(D)F (BC)D (A)B' C..F --simplify-merges -- file
check_result 'F D' C..F --ancestry-path
check_result 'F D' C..F --ancestry-path -- file
check_result 'F D' C..F --ancestry-path --parents -- file
check_result 'F D' C..F --ancestry-path --simplify-merges -- file
check_result 'F D B' C..F --first-parent
check_result 'F B' C..F --first-parent -- file
test_done
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册