{"thread":{"id":"33841","subject":"[PATCH v4 01/15] decorate.c: compact table when growing","startedAt":"2013-05-16T15:32:26Z","lastAt":"2013-05-16T15:32:41Z","messageCount":16,"participants":["Kevin Bracey"],"isPatch":true,"patchVersion":4,"patchTotal":15},"messages":[{"id":"217610","messageId":"1368718361-27859-1-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":null,"subject":"[PATCH v4 00/15] History traversal refinements","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:26Z","receivedAt":"2013-05-16T15:32:26Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"No new functionality or bug fixes since v3, just tidying:\n\n* Tests now use Junio's parent-checking functionality\n* BOTTOM flags now set in a neater fashion (I think),\n  separating it out from the cmdline stuff.\n* Creation and use of BOTTOM flag now split into 4 separate\n  commits - last version was too much for one commit, I feel.\n* Finally decided that \"relevant\" is the word I was looking\n  for. Obvious, really.\n* On the subject of words, remove the only technical use\n  of \"uninteresting\" that I found in the Documentation - I know\n  that it always confused me until I read the source.\n\nThis sequence is based on my 2-commit ancestry-path \"...\" series,\nbut no longer depends on it due to the new way the BOTTOM flag\nis initialised. But they both touch the t6019 test, so\napplying this on top will avoid conflicts.\n\nJunio C Hamano (2):\n  t6111: allow checking the parents as well\n  t6012: update test for tweaked full-history traversal\n\nKevin Bracey (13):\n  decorate.c: compact table when growing\n  t6019: test file dropped in -s ours merge\n  t6111: new TREESAME test set\n  t6111: add parents to tests\n  rev-list-options.txt: correct TREESAME for P\n  Documentation: avoid \"uninteresting\"\n  revision.c: Make --full-history consider more merges\n  simplify-merges: never remove all TREESAME parents\n  simplify-merges: drop merge from irrelevant side branch\n  revision.c: add BOTTOM flag for commits\n  revision.c: discount side branches when computing TREESAME\n  revision.c: don't show all merges for --parents\n  revision.c: make default history consider bottom commits\n\n Documentation/rev-list-options.txt |  42 +--\n decorate.c                         |   2 +-\n revision.c                         | 539 ++++++++++++++++++++++++++++++++-----\n revision.h                         |   4 +-\n t/t6012-rev-list-simplify.sh       |  31 ++-\n t/t6019-rev-list-ancestry-path.sh  |  27 ++\n t/t6111-rev-list-treesame.sh       | 196 ++++++++++++++\n 7 files changed, 750 insertions(+), 91 deletions(-)\n create mode 100755 t/t6111-rev-list-treesame.sh\n\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217599","messageId":"1368718361-27859-2-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 01/15] decorate.c: compact table when growing","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:27Z","receivedAt":"2013-05-16T15:32:27Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"When growing the table, take the opportunity to \"compact\" it by removing\nentries with NULL decoration.\n\nUsers may have \"removed\" decorations by passing NULL to\ninsert_decoration. An object's table entry can't actually be removed\nduring normal operation, as it would break the linear hash collision\nsearch. But we can remove NULL decoration entries when rebuilding the\ntable.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n decorate.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/decorate.c b/decorate.c\nindex 2f8a63e..7cb5d29 100644\n--- a/decorate.c\n+++ b/decorate.c\n@@ -49,7 +49,7 @@ static void grow_decoration(struct decoration *n)\n \t\tconst struct object *base = old_hash[i].base;\n \t\tvoid *decoration = old_hash[i].decoration;\n \n-\t\tif (!base)\n+\t\tif (!decoration)\n \t\t\tcontinue;\n \t\tinsert_decoration(n, base, decoration);\n \t}\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217615","messageId":"1368718361-27859-3-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 02/15] t6019: test file dropped in -s ours merge","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:28Z","receivedAt":"2013-05-16T15:32:28Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"In preparation for upcoming TREESAME work, check the result for G.t,\nwhich is dropped in \"-s ours\" merge L. The default rev-list is empty, as\nexpected - it follows the first parent path where it never existed.\n\nUnfortunately, --ancestry-path is also empty. Merges H J and L are all\nTREESAME to 1 parent, so are treated as TREESAME and not shown. This is\nclearly undesirable in the case of merge L, which dropped our G.t by\ntaking the non-ancestry-path version. Document this as a known failure,\nand expect \"H J L\", the 3 merges along the path that had to chose G.t\nversions.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n t/t6019-rev-list-ancestry-path.sh | 19 +++++++++++++++++++\n 1 file changed, 19 insertions(+)\n\ndiff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh\nindex dd5b0e5..c3bc2e7 100755\n--- a/t/t6019-rev-list-ancestry-path.sh\n+++ b/t/t6019-rev-list-ancestry-path.sh\n@@ -16,6 +16,9 @@ test_description='--ancestry-path'\n #\n #  F...I                 == F G H I\n #  --ancestry-path F...I == F H I\n+#\n+#  G..M -- G.t                 == [nothing - was dropped in \"-s ours\" merge L]\n+#  --ancestry-path G..M -- G.t == H J L\n \n . ./test-lib.sh\n \n@@ -89,6 +92,22 @@ test_expect_success 'rev-list --ancestry-path F...I' '\n \ttest_cmp expect actual\n '\n \n+# G.t is dropped in an \"-s ours\" merge\n+test_expect_success 'rev-list G..M -- G.t' '\n+\t>expect &&\n+\tgit rev-list --format=%s G..M -- G.t |\n+\tsed -e \"/^commit /d\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_failure 'rev-list --ancestry-path G..M -- G.t' '\n+\tfor c in H J L; do echo $c; done >expect &&\n+\tgit rev-list --ancestry-path --format=%s G..M -- G.t |\n+\tsed -e \"/^commit /d\" |\n+\tsort >actual &&\n+\ttest_cmp expect actual\n+'\n+\n #   b---bc\n #  / \\ /\n # a   X\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217606","messageId":"1368718361-27859-4-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 03/15] t6111: new TREESAME test set","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:29Z","receivedAt":"2013-05-16T15:32:29Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"Some side branching and odd merging to illustrate various flaws in\nrevision list scans, particularly when limiting the list.\n\nMany expected failures, which will be gone by the end of the \"history\ntraversal refinements\" series.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n t/t6111-rev-list-treesame.sh | 184 +++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 184 insertions(+)\n create mode 100755 t/t6111-rev-list-treesame.sh\n\ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nnew file mode 100755\nindex 0000000..b2bca77\n--- /dev/null\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -0,0 +1,184 @@\n+#!/bin/sh\n+#\n+#        ,---E--.   *H----------.             * marks !TREESAME parent paths\n+#       /        \\ /             \\*\n+# *A--*B---D--*F-*G---------K-*L-*M\n+#   \\     /*       \\       /\n+#    `-C-'          `-*I-*J\n+#\n+# A creates \"file\", B and F change it.\n+# Odd merge G takes the old version from B.\n+# I changes it, but J reverts it, so K is TREESAME to both parents.\n+# H and L both change \"file\", and M merges those changes.\n+\n+test_description='TREESAME and limiting'\n+\n+. ./test-lib.sh\n+\n+note () {\n+\tgit tag \"$1\"\n+}\n+\n+unnote () {\n+\tgit name-rev --tags --stdin | sed -e \"s|$_x40 (tags/\\([^)]*\\)) |\\1 |g\"\n+}\n+\n+test_expect_success setup '\n+\ttest_commit \"Initial file\" file \"Hi there\" A &&\n+\tgit branch other-branch &&\n+\n+\ttest_commit \"file=Hello\" file \"Hello\" B &&\n+\tgit branch third-branch &&\n+\n+\tgit checkout other-branch &&\n+\ttest_commit \"Added other\" other \"Hello\" C &&\n+\n+\tgit checkout master &&\n+\ttest_merge D other-branch &&\n+\n+\tgit checkout third-branch &&\n+\ttest_commit \"Third file\" third \"Nothing\" E &&\n+\n+\tgit checkout master &&\n+\ttest_commit \"file=Blah\" file \"Blah\" F &&\n+\n+\ttest_tick && git merge --no-commit third-branch &&\n+\tgit checkout third-branch file &&\n+\tgit commit &&\n+\tnote G &&\n+\tgit branch fiddler-branch &&\n+\n+\tgit checkout -b part2-branch &&\n+\ttest_commit \"file=Part 2\" file \"Part 2\" H &&\n+\n+\tgit checkout fiddler-branch &&\n+\ttest_commit \"Bad commit\" file \"Silly\" I &&\n+\n+\ttest_tick && git revert I && note J &&\n+\n+\tgit checkout master &&\n+\ttest_tick && git merge --no-ff fiddler-branch &&\n+\tnote K\n+\n+\ttest_commit \"file=Part 1\" file \"Part 1\" L &&\n+\n+\ttest_tick && test_must_fail git merge part2-branch &&\n+\ttest_commit M file \"Parts 1+2\"\n+'\n+\n+FMT='tformat:%P \t%H | %s'\n+\n+# could we soup this up to optionally check parents? So \"(BA)C\" would check\n+# that C is shown and has parents B A.\n+check_outcome () {\n+\toutcome=$1\n+\tshift\n+\tfor c in $1\n+\tdo\n+\t\techo \"$c\"\n+\tdone >expect &&\n+\tshift &&\n+\tparam=\"$*\" &&\n+\ttest_expect_$outcome \"log $param\" '\n+\t\tgit log --format=\"$FMT\" $param |\n+\t\tunnote >actual &&\n+\t\tsed -e \"s/^.*\t\\([^ ]*\\) .*/\\1/\" >check <actual &&\n+\t\ttest_cmp expect check || {\n+\t\t\tcat actual\n+\t\t\tfalse\n+\t\t}\n+\t'\n+}\n+\n+check_result () {\n+\tcheck_outcome success \"$@\"\n+}\n+\n+# Odd merge G drops a change in F. Important that G is listed in all\n+# except the most basic list. Achieving this means normal merge D will also be\n+# shown in normal full-history, as we can't distinguish unless we do a\n+# simplification pass. After simplification, D is dropped but G remains.\n+check_result 'M L K J I H G F E D C B A'\n+check_result 'M H L K J I G E F D C B A' --topo-order\n+check_result 'M L H B A' -- file\n+check_result 'M L H B A' --parents -- file\n+check_outcome failure 'M L J I H G F D B A' --full-history -- file # drops G\n+check_result 'M L K J I H G F D B A' --full-history --parents -- file\n+check_outcome failure 'M H L J I G F B A' --simplify-merges -- file # drops G\n+check_result 'M L K G F D B A' --first-parent\n+check_result 'M L G F B A' --first-parent -- file\n+\n+# Check that odd merge G remains shown when F is the bottom.\n+check_result 'M L K J I H G E' F..M\n+check_result 'M H L K J I G E' F..M --topo-order\n+check_result 'M L H' F..M -- file\n+check_result 'M L H' F..M --parents -- file # L+H's parents rewritten to B, so more useful than it may seem\n+check_outcome failure 'M L J I H G' F..M --full-history -- file # drops G\n+check_result 'M L K J I H G' F..M --full-history --parents -- file\n+check_outcome failure 'M H L J I G' F..M --simplify-merges -- file # drops G\n+check_result 'M L K J I H G' F..M --ancestry-path\n+check_outcome failure 'M L J I H G' F..M --ancestry-path -- file # drops G\n+check_result 'M L K J I H G' F..M --ancestry-path --parents -- file\n+check_result 'M H L J I G' F..M --ancestry-path --simplify-merges -- file\n+check_result 'M L K G' F..M --first-parent\n+check_result 'M L G' F..M --first-parent -- file\n+\n+# Note that G is pruned when E is the bottom, even if it's the same commit list\n+# If we want history since E, then we're quite happy to ignore G that took E.\n+check_result 'M L K J I H G' E..M --ancestry-path\n+check_result 'M L J I H' E..M --ancestry-path -- file\n+check_outcome failure 'M L K J I H' E..M --ancestry-path --parents -- file # includes G\n+check_outcome failure 'M H L J I' E..M --ancestry-path --simplify-merges -- file # includes G\n+\n+# Should still be able to ignore I-J branch in simple log, despite limiting\n+# to G.\n+check_result 'M L K J I H' G..M\n+check_result 'M H L K J I' G..M --topo-order\n+check_outcome failure 'M L H' G..M -- file # includes J I\n+check_outcome failure 'M L H' G..M --parents -- file # includes J I\n+check_result 'M L J I H' G..M --full-history -- file\n+check_result 'M L K J I H' G..M --full-history --parents -- file\n+check_result 'M H L J I' G..M --simplify-merges -- file\n+check_result 'M L K J I H' G..M --ancestry-path\n+check_result 'M L J I H' G..M --ancestry-path -- file\n+check_result 'M L K J I H' G..M --ancestry-path --parents -- file\n+check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file\n+\n+# B..F should be able to simplify the merge D from irrelevant side branch C.\n+# Default log should also be free to follow B-D, and ignore C.\n+# But --full-history shouldn't drop D on its own - without simplification,\n+# we can't decide if the merge from INTERESTING commit C was sensible.\n+check_result 'F D C' B..F\n+check_result 'F' B..F -- file\n+check_outcome failure 'F' B..F --parents -- file # includes D\n+check_outcome failure 'F D' B..F --full-history -- file # drops D prematurely\n+check_result 'F D' B..F --full-history --parents -- file\n+check_result 'F' B..F --simplify-merges -- file\n+check_result 'F D' B..F --ancestry-path\n+check_result 'F' B..F --ancestry-path -- file\n+check_outcome failure 'F' B..F --ancestry-path --parents -- file # includes D\n+check_outcome failure 'F' B..F --ancestry-path --simplify-merges -- file # includes D\n+check_result 'F D' B..F --first-parent\n+check_result 'F' B..F --first-parent -- file\n+\n+# E...F should be equivalent to E F ^B, and be able to drop D as above.\n+check_result 'F' E F ^B -- file\n+check_result 'F' E...F -- file\n+\n+# Any sort of full history of C..F should show D, as it's the connection to C,\n+# and it differs from it.\n+check_result 'F D B' C..F\n+check_result 'F B' C..F -- file\n+check_result 'F B' C..F --parents -- file\n+check_outcome failure 'F D B' C..F --full-history -- file # drops D\n+check_result 'F D B' C..F --full-history --parents -- file\n+check_result 'F D B' C..F --simplify-merges -- file\n+check_result 'F D' C..F --ancestry-path\n+check_outcome failure 'F D' C..F --ancestry-path -- file # drops D\n+check_result 'F D' C..F --ancestry-path --parents -- file\n+check_result 'F D' C..F --ancestry-path --simplify-merges -- file\n+check_result 'F D B' C..F --first-parent\n+check_result 'F B' C..F --first-parent -- file\n+\n+\n+test_done\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217609","messageId":"1368718361-27859-5-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 04/15] t6111: allow checking the parents as well","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:30Z","receivedAt":"2013-05-16T15:32:30Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t6111-rev-list-treesame.sh | 30 +++++++++++++++++++++---------\n 1 file changed, 21 insertions(+), 9 deletions(-)\n\ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nindex b2bca77..1e4a550 100755\n--- a/t/t6111-rev-list-treesame.sh\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -20,7 +20,7 @@ note () {\n }\n \n unnote () {\n-\tgit name-rev --tags --stdin | sed -e \"s|$_x40 (tags/\\([^)]*\\)) |\\1 |g\"\n+\tgit name-rev --tags --stdin | sed -e \"s|$_x40 (tags/\\([^)]*\\))\\([ \t]\\)|\\1\\2|g\"\n }\n \n test_expect_success setup '\n@@ -66,23 +66,34 @@ test_expect_success setup '\n \ttest_commit M file \"Parts 1+2\"\n '\n \n-FMT='tformat:%P \t%H | %s'\n-\n # could we soup this up to optionally check parents? So \"(BA)C\" would check\n # that C is shown and has parents B A.\n check_outcome () {\n \toutcome=$1\n \tshift\n-\tfor c in $1\n-\tdo\n-\t\techo \"$c\"\n-\tdone >expect &&\n-\tshift &&\n+\n+\tcase \"$1\" in\n+\t*\"(\"*)\n+\t\tFMT=\"%P\t%H | %s\"\n+\t\tmunge_actual=\"\n+\t\t\ts/^\\([^\t]*\\)\t\\([^ ]*\\) .*/(\\1)\\2/\n+\t\t\ts/ //g\n+\t\t\ts/()//\n+\t\t\"\n+\t\t;;\n+\t*)\n+\t\tFMT=\"%H | %s\"\n+\t\tmunge_actual=\"s/^\\([^ ]*\\) .*/\\1/\"\n+\t\t;;\n+\tesac &&\n+\tprintf \"%s\\n\" $1 >expect &&\n+\tshift\n+\n \tparam=\"$*\" &&\n \ttest_expect_$outcome \"log $param\" '\n \t\tgit log --format=\"$FMT\" $param |\n \t\tunnote >actual &&\n-\t\tsed -e \"s/^.*\t\\([^ ]*\\) .*/\\1/\" >check <actual &&\n+\t\tsed -e \"$munge_actual\" <actual >check &&\n \t\ttest_cmp expect check || {\n \t\t\tcat actual\n \t\t\tfalse\n@@ -99,6 +110,7 @@ check_result () {\n # shown in normal full-history, as we can't distinguish unless we do a\n # simplification pass. After simplification, D is dropped but G remains.\n check_result 'M L K J I H G F E D C B A'\n+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'\n check_result 'M H L K J I G E F D C B A' --topo-order\n check_result 'M L H B A' -- file\n check_result 'M L H B A' --parents -- file\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217613","messageId":"1368718361-27859-6-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 05/15] t6111: add parents to tests","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:31Z","receivedAt":"2013-05-16T15:32:31Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"Signed-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n t/t6111-rev-list-treesame.sh | 38 +++++++++++++++++++-------------------\n 1 file changed, 19 insertions(+), 19 deletions(-)\n\ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nindex 1e4a550..4d74d3c 100755\n--- a/t/t6111-rev-list-treesame.sh\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -66,8 +66,6 @@ test_expect_success setup '\n \ttest_commit M file \"Parts 1+2\"\n '\n \n-# could we soup this up to optionally check parents? So \"(BA)C\" would check\n-# that C is shown and has parents B A.\n check_outcome () {\n \toutcome=$1\n \tshift\n@@ -109,14 +107,16 @@ check_result () {\n # except the most basic list. Achieving this means normal merge D will also be\n # shown in normal full-history, as we can't distinguish unless we do a\n # simplification pass. After simplification, D is dropped but G remains.\n+# Also, merge simplification of G should not drop the parent B that the default\n+# simple history follows.\n check_result 'M L K J I H G F E D C B A'\n 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'\n check_result 'M H L K J I G E F D C B A' --topo-order\n check_result 'M L H B A' -- file\n-check_result 'M L H B A' --parents -- file\n+check_result '(LH)M (B)L (B)H (A)B A' --parents -- file\n check_outcome failure 'M L J I H G F D B A' --full-history -- file # drops G\n-check_result 'M L K J I H G F D B A' --full-history --parents -- file\n-check_outcome failure 'M H L J I G F B A' --simplify-merges -- file # drops G\n+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\n+check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file # drops G\n check_result 'M L K G F D B A' --first-parent\n check_result 'M L G F B A' --first-parent -- file\n \n@@ -124,14 +124,14 @@ check_result 'M L G F B A' --first-parent -- file\n check_result 'M L K J I H G E' F..M\n check_result 'M H L K J I G E' F..M --topo-order\n check_result 'M L H' F..M -- file\n-check_result 'M L H' F..M --parents -- file # L+H's parents rewritten to B, so more useful than it may seem\n+check_result '(LH)M (B)L (B)H' --parents F..M -- file\n check_outcome failure 'M L J I H G' F..M --full-history -- file # drops G\n-check_result 'M L K J I H G' F..M --full-history --parents -- file\n-check_outcome failure 'M H L J I G' F..M --simplify-merges -- file # drops G\n+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G' F..M --full-history --parents -- file\n+check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file # drops G\n check_result 'M L K J I H G' F..M --ancestry-path\n check_outcome failure 'M L J I H G' F..M --ancestry-path -- file # drops G\n-check_result 'M L K J I H G' F..M --ancestry-path --parents -- file\n-check_result 'M H L J I G' F..M --ancestry-path --simplify-merges -- file\n+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G' F..M --ancestry-path --parents -- file\n+check_result '(LH)M (G)H (J)L (I)J (G)I (FE)G' F..M --ancestry-path --simplify-merges -- file\n check_result 'M L K G' F..M --first-parent\n check_result 'M L G' F..M --first-parent -- file\n \n@@ -139,15 +139,15 @@ check_result 'M L G' F..M --first-parent -- file\n # If we want history since E, then we're quite happy to ignore G that took E.\n check_result 'M L K J I H G' E..M --ancestry-path\n check_result 'M L J I H' E..M --ancestry-path -- file\n-check_outcome failure 'M L K J I H' E..M --ancestry-path --parents -- file # includes G\n-check_outcome failure 'M H L J I' E..M --ancestry-path --simplify-merges -- file # includes G\n+check_outcome failure '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file # includes G\n+check_outcome failure '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file # includes G\n \n # Should still be able to ignore I-J branch in simple log, despite limiting\n # to G.\n check_result 'M L K J I H' G..M\n check_result 'M H L K J I' G..M --topo-order\n check_outcome failure 'M L H' G..M -- file # includes J I\n-check_outcome failure 'M L H' G..M --parents -- file # includes J I\n+check_outcome failure '(LH)M (G)L (G)H' G..M --parents -- file # includes J I\n check_result 'M L J I H' G..M --full-history -- file\n check_result 'M L K J I H' G..M --full-history --parents -- file\n check_result 'M H L J I' G..M --simplify-merges -- file\n@@ -162,10 +162,10 @@ check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file\n # we can't decide if the merge from INTERESTING commit C was sensible.\n check_result 'F D C' B..F\n check_result 'F' B..F -- file\n-check_outcome failure 'F' B..F --parents -- file # includes D\n+check_outcome failure '(B)F' B..F --parents -- file # includes D\n check_outcome failure 'F D' B..F --full-history -- file # drops D prematurely\n-check_result 'F D' B..F --full-history --parents -- file\n-check_result 'F' B..F --simplify-merges -- file\n+check_result '(D)F (BA)D' B..F --full-history --parents -- file\n+check_result '(B)F' B..F --simplify-merges -- file\n check_result 'F D' B..F --ancestry-path\n check_result 'F' B..F --ancestry-path -- file\n check_outcome failure 'F' B..F --ancestry-path --parents -- file # includes D\n@@ -181,10 +181,10 @@ check_result 'F' E...F -- file\n # and it differs from it.\n check_result 'F D B' C..F\n check_result 'F B' C..F -- file\n-check_result 'F B' C..F --parents -- file\n+check_result '(B)F (A)B' C..F --parents -- file\n check_outcome failure 'F D B' C..F --full-history -- file # drops D\n-check_result 'F D B' C..F --full-history --parents -- file\n-check_result 'F D B' C..F --simplify-merges -- file\n+check_result '(D)F (BC)D (A)B' C..F --full-history --parents -- file\n+check_result '(D)F (BC)D (A)B' C..F --simplify-merges -- file\n check_result 'F D' C..F --ancestry-path\n check_outcome failure 'F D' C..F --ancestry-path -- file # drops D\n check_result 'F D' C..F --ancestry-path --parents -- file\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217608","messageId":"1368718361-27859-7-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 06/15] rev-list-options.txt: correct TREESAME for P","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:32Z","receivedAt":"2013-05-16T15:32:32Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"In the example given, P is not TREESAME to E. This doesn't affect the\ncurrent result, but it will matter when we change behaviour.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n Documentation/rev-list-options.txt | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex 3bdbf5e..50bbff7 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -367,8 +367,7 @@ each merge.  The commits are:\n   `N` and `D` to \"foobarbaz\"; i.e., it is not TREESAME to any parent.\n \n * `E` changes `quux` to \"xyzzy\", and its merge `P` combines the\n-  strings to \"quux xyzzy\".  Despite appearing interesting, `P` is\n-  TREESAME to all parents.\n+  strings to \"quux xyzzy\".  `P` is TREESAME to `O`, but not to `E`.\n \n 'rev-list' walks backwards through history, including or excluding\n commits based on whether '\\--full-history' and/or parent rewriting\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217612","messageId":"1368718361-27859-8-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 07/15] Documentation: avoid \"uninteresting\"","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:33Z","receivedAt":"2013-05-16T15:32:33Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"The documentation of --boundary uses the term \"uninteresting\", which is\nnot used or defined anywhere else in the documentation. This is\nunhelpful and confusing to anyone who hasn't seen the UNINTERESTING\nflag in the source code.\n\nChange to use \"excluded\", as per revisions.txt.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n Documentation/rev-list-options.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex 50bbff7..55ddf33 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -271,8 +271,8 @@ See also linkgit:git-reflog[1].\n \n --boundary::\n \n-\tOutput uninteresting commits at the boundary, which are usually\n-\tnot shown.\n+\tOutput excluded boundary commits. Boundary commits are\n+\tprefixed with `-`.\n \n --\n \n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217601","messageId":"1368718361-27859-9-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 08/15] revision.c: Make --full-history consider more merges","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:34Z","receivedAt":"2013-05-16T15:32:34Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"History simplification previously always treated merges as TREESAME\nif they were TREESAME to any parent.\n\nWhile this was consistent with the default behaviour, this could be\nextremely unhelpful when searching detailed history, and could not be\noverridden. For example, if a merge had ignored a change, as if by \"-s\nours\", then:\n\n  git log -m -p --full-history -Schange file\n\nwould successfully locate \"change\"'s addition but would not locate the\nmerge that resolved against it.\n\nFuther, simplify_merges could drop the actual parent that a commit\nwas TREESAME to, leaving it as a normal commit marked TREESAME that\nisn't actually TREESAME to its remaining parent.\n\nNow redefine a commit's TREESAME flag to be true only if a commit is\nTREESAME to _all_ of its parents. This doesn't affect either the default\nsimplify_history behaviour (because partially TREESAME merges are turned\ninto normal commits), or full-history with parent rewriting (because all\nmerges are output). But it does affect other modes. The clearest\ndifference is that --full-history will show more merges - sufficient to\nensure that -m -p --full-history log searches can really explain every\nchange to the file, including those changes' ultimate fate in merges.\n\nAlso modify simplify_merges to recalculate TREESAME after removing\na parent. This is achieved by storing per-parent TREESAME flags on the\ninitial scan, so the combined flag can be easily recomputed.\n\nThis fixes some t6111 failures, but creates a couple of new ones -\nwe are now showing some merges that don't need to be shown.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n Documentation/rev-list-options.txt |   6 +-\n revision.c                         | 241 ++++++++++++++++++++++++++++++++-----\n revision.h                         |   1 +\n t/t6019-rev-list-ancestry-path.sh  |   2 +-\n t/t6111-rev-list-treesame.sh       |  26 ++--\n 5 files changed, 229 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex 55ddf33..d166384 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -409,10 +409,10 @@ parent lines.\n \tthe example, we get\n +\n -----------------------------------------------------------------------\n-\tI  A  B  N  D  O\n+\tI  A  B  N  D  O  P\n -----------------------------------------------------------------------\n +\n-`P` and `M` were excluded because they are TREESAME to a parent.  `E`,\n+`M` was excluded because it is TREESAME to both parents.  `E`,\n `C` and `B` were all walked, but only `B` was !TREESAME, so the others\n do not appear.\n +\n@@ -440,7 +440,7 @@ themselves.  This results in\n Compare to '\\--full-history' without rewriting above.  Note that `E`\n was pruned away because it is TREESAME, but the parent list of P was\n rewritten to contain `E`'s parent `I`.  The same happened for `C` and\n-`N`.  Note also that `P` was included despite being TREESAME.\n+`N`.\n \n In addition to the above settings, you can change whether TREESAME\n affects inclusion:\ndiff --git a/revision.c b/revision.c\nindex 7f7a8ab..64b86ae 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -429,10 +429,100 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)\n \treturn retval >= 0 && (tree_difference == REV_TREE_SAME);\n }\n \n+struct treesame_state {\n+\tunsigned int nparents;\n+\tunsigned char treesame[FLEX_ARRAY];\n+};\n+\n+static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)\n+{\n+\tunsigned n = commit_list_count(commit->parents);\n+\tstruct treesame_state *st = xcalloc(1, sizeof(*st) + n);\n+\tst->nparents = n;\n+\tadd_decoration(&revs->treesame, &commit->object, st);\n+\treturn st;\n+}\n+\n+/*\n+ * Must be called immediately after removing the nth_parent from a commit's\n+ * parent list, if we are maintaining the per-parent treesame[] decoration.\n+ * This does not recalculate the master TREESAME flag - update_treesame()\n+ * should be called to update it after a sequence of treesame[] modifications\n+ * that may have affected it.\n+ */\n+static int compact_treesame(struct rev_info *revs, struct commit *commit, unsigned nth_parent)\n+{\n+\tstruct treesame_state *st;\n+\tint old_same;\n+\n+\tif (!commit->parents) {\n+\t\t/*\n+\t\t * Have just removed the only parent from a non-merge.\n+\t\t * Different handling, as we lack decoration.\n+\t\t */\n+\t\tif (nth_parent != 0)\n+\t\t\tdie(\"compact_treesame %u\", nth_parent);\n+\t\told_same = !!(commit->object.flags & TREESAME);\n+\t\tif (rev_same_tree_as_empty(revs, commit))\n+\t\t\tcommit->object.flags |= TREESAME;\n+\t\telse\n+\t\t\tcommit->object.flags &= ~TREESAME;\n+\t\treturn old_same;\n+\t}\n+\n+\tst = lookup_decoration(&revs->treesame, &commit->object);\n+\tif (!st || nth_parent >= st->nparents)\n+\t\tdie(\"compact_treesame %u\", nth_parent);\n+\n+\told_same = st->treesame[nth_parent];\n+\tmemmove(st->treesame + nth_parent,\n+\t\tst->treesame + nth_parent + 1,\n+\t\tst->nparents - nth_parent - 1);\n+\n+\t/*\n+\t * If we've just become a non-merge commit, update TREESAME\n+\t * immediately, and remove the no-longer-needed decoration.\n+\t * If still a merge, defer update until update_treesame().\n+\t */\n+\tif (--st->nparents == 1) {\n+\t\tif (commit->parents->next)\n+\t\t\tdie(\"compact_treesame parents mismatch\");\n+\t\tif (st->treesame[0] && revs->dense)\n+\t\t\tcommit->object.flags |= TREESAME;\n+\t\telse\n+\t\t\tcommit->object.flags &= ~TREESAME;\n+\t\tfree(add_decoration(&revs->treesame, &commit->object, NULL));\n+\t}\n+\n+\treturn old_same;\n+}\n+\n+static unsigned update_treesame(struct rev_info *revs, struct commit *commit)\n+{\n+\tif (commit->parents && commit->parents->next) {\n+\t\tunsigned n;\n+\t\tstruct treesame_state *st;\n+\n+\t\tst = lookup_decoration(&revs->treesame, &commit->object);\n+\t\tif (!st)\n+\t\t\tdie(\"update_treesame %s\", sha1_to_hex(commit->object.sha1));\n+\t\tcommit->object.flags |= TREESAME;\n+\t\tfor (n = 0; n < st->nparents; n++) {\n+\t\t\tif (!st->treesame[n]) {\n+\t\t\t\tcommit->object.flags &= ~TREESAME;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn commit->object.flags & TREESAME;\n+}\n+\n static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n {\n \tstruct commit_list **pp, *parent;\n-\tint tree_changed = 0, tree_same = 0, nth_parent = 0;\n+\tstruct treesame_state *ts = NULL;\n+\tint tree_changed = 0, nth_parent;\n \n \t/*\n \t * If we don't do pruning, everything is interesting\n@@ -456,25 +546,43 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \tif (!revs->dense && !commit->parents->next)\n \t\treturn;\n \n-\tpp = &commit->parents;\n-\twhile ((parent = *pp) != NULL) {\n+\tfor (pp = &commit->parents, nth_parent = 0;\n+\t     (parent = *pp) != NULL;\n+\t     pp = &parent->next, nth_parent++) {\n \t\tstruct commit *p = parent->item;\n \n-\t\t/*\n-\t\t * Do not compare with later parents when we care only about\n-\t\t * the first parent chain, in order to avoid derailing the\n-\t\t * traversal to follow a side branch that brought everything\n-\t\t * in the path we are limited to by the pathspec.\n-\t\t */\n-\t\tif (revs->first_parent_only && nth_parent++)\n-\t\t\tbreak;\n+\t\tif (nth_parent == 1) {\n+\t\t\t/*\n+\t\t\t * This our second loop iteration - so we now know\n+\t\t\t * we're dealing with a merge.\n+\t\t\t *\n+\t\t\t * Do not compare with later parents when we care only about\n+\t\t\t * the first parent chain, in order to avoid derailing the\n+\t\t\t * traversal to follow a side branch that brought everything\n+\t\t\t * in the path we are limited to by the pathspec.\n+\t\t\t */\n+\t\t\tif (revs->first_parent_only)\n+\t\t\t\tbreak;\n+\t\t\t/*\n+\t\t\t * If this will remain a potentially-simplifiable\n+\t\t\t * merge, remember per-parent treesame if needed.\n+\t\t\t * Initialise the array with the comparison from our\n+\t\t\t * first iteration.\n+\t\t\t */\n+\t\t\tif (revs->treesame.name &&\n+\t\t\t    !revs->simplify_history &&\n+\t\t\t    !(commit->object.flags & UNINTERESTING)) {\n+\t\t\t\tts = initialise_treesame(revs, commit);\n+\t\t\t\tif (!tree_changed)\n+\t\t\t\t\tts->treesame[0] = 1;\n+\t\t\t}\n+\t\t}\n \t\tif (parse_commit(p) < 0)\n \t\t\tdie(\"cannot simplify commit %s (because of %s)\",\n \t\t\t    sha1_to_hex(commit->object.sha1),\n \t\t\t    sha1_to_hex(p->object.sha1));\n \t\tswitch (rev_compare_tree(revs, p, commit)) {\n \t\tcase REV_TREE_SAME:\n-\t\t\ttree_same = 1;\n \t\t\tif (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {\n \t\t\t\t/* Even if a merge with an uninteresting\n \t\t\t\t * side branch brought the entire change\n@@ -482,7 +590,8 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \t\t\t\t * to lose the other branches of this\n \t\t\t\t * merge, so we just keep going.\n \t\t\t\t */\n-\t\t\t\tpp = &parent->next;\n+\t\t\t\tif (ts)\n+\t\t\t\t\tts->treesame[nth_parent] = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tparent->next = NULL;\n@@ -511,12 +620,11 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \t\tcase REV_TREE_OLD:\n \t\tcase REV_TREE_DIFFERENT:\n \t\t\ttree_changed = 1;\n-\t\t\tpp = &parent->next;\n \t\t\tcontinue;\n \t\t}\n \t\tdie(\"bad tree compare for commit %s\", sha1_to_hex(commit->object.sha1));\n \t}\n-\tif (tree_changed && !tree_same)\n+\tif (tree_changed)\n \t\treturn;\n \tcommit->object.flags |= TREESAME;\n }\n@@ -1947,28 +2055,32 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi\n \tl->next = add_decoration(&revs->children, &parent->object, l);\n }\n \n-static int remove_duplicate_parents(struct commit *commit)\n+static int remove_duplicate_parents(struct rev_info *revs, struct commit *commit)\n {\n+\tstruct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);\n \tstruct commit_list **pp, *p;\n \tint surviving_parents;\n \n \t/* Examine existing parents while marking ones we have seen... */\n \tpp = &commit->parents;\n+\tsurviving_parents = 0;\n \twhile ((p = *pp) != NULL) {\n \t\tstruct commit *parent = p->item;\n \t\tif (parent->object.flags & TMP_MARK) {\n \t\t\t*pp = p->next;\n+\t\t\tif (ts)\n+\t\t\t\tcompact_treesame(revs, commit, surviving_parents);\n \t\t\tcontinue;\n \t\t}\n \t\tparent->object.flags |= TMP_MARK;\n+\t\tsurviving_parents++;\n \t\tpp = &p->next;\n \t}\n-\t/* count them while clearing the temporary mark */\n-\tsurviving_parents = 0;\n+\t/* clear the temporary mark */\n \tfor (p = commit->parents; p; p = p->next) {\n \t\tp->item->object.flags &= ~TMP_MARK;\n-\t\tsurviving_parents++;\n \t}\n+\t/* no update_treesame() - removing duplicates can't affect TREESAME */\n \treturn surviving_parents;\n }\n \n@@ -1988,6 +2100,70 @@ static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs,\n \treturn st;\n }\n \n+static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)\n+{\n+\tstruct commit_list *h = reduce_heads(commit->parents);\n+\tint i = 0, marked = 0;\n+\tstruct commit_list *po, *pn;\n+\n+\t/* Want these for sanity-checking only */\n+\tint orig_cnt = commit_list_count(commit->parents);\n+\tint cnt = commit_list_count(h);\n+\n+\t/*\n+\t * Not ready to remove items yet, just mark them for now, based\n+\t * on the output of reduce_heads(). reduce_heads outputs the reduced\n+\t * set in its original order, so this isn't too hard.\n+\t */\n+\tpo = commit->parents;\n+\tpn = h;\n+\twhile (po) {\n+\t\tif (pn && po->item == pn->item) {\n+\t\t\tpn = pn->next;\n+\t\t\ti++;\n+\t\t} else {\n+\t\t\tpo->item->object.flags |= TMP_MARK;\n+\t\t\tmarked++;\n+\t\t}\n+\t\tpo=po->next;\n+\t}\n+\n+\tif (i != cnt || cnt+marked != orig_cnt)\n+\t\tdie(\"mark_redundant_parents %d %d %d %d\", orig_cnt, cnt, i, marked);\n+\n+\tfree_commit_list(h);\n+\n+\treturn marked;\n+}\n+\n+static int remove_marked_parents(struct rev_info *revs, struct commit *commit)\n+{\n+\tstruct commit_list **pp, *p;\n+\tint nth_parent, removed = 0;\n+\n+\tpp = &commit->parents;\n+\tnth_parent = 0;\n+\twhile ((p = *pp) != NULL) {\n+\t\tstruct commit *parent = p->item;\n+\t\tif (parent->object.flags & TMP_MARK) {\n+\t\t\tparent->object.flags &= ~TMP_MARK;\n+\t\t\t*pp = p->next;\n+\t\t\tfree(p);\n+\t\t\tremoved++;\n+\t\t\tcompact_treesame(revs, commit, nth_parent);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tpp = &p->next;\n+\t\tnth_parent++;\n+\t}\n+\n+\t/* Removing parents can only increase TREESAMEness */\n+\tif (removed && !(commit->object.flags & TREESAME))\n+\t\tupdate_treesame(revs, commit);\n+\n+\treturn nth_parent;\n+}\n+\n static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)\n {\n \tstruct commit_list *p;\n@@ -2032,7 +2208,9 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c\n \t}\n \n \t/*\n-\t * Rewrite our list of parents.\n+\t * Rewrite our list of parents. Note that this cannot\n+\t * affect our TREESAME flags in any way - a commit is\n+\t * always TREESAME to its simplification.\n \t */\n \tfor (p = commit->parents; p; p = p->next) {\n \t\tpst = locate_simplify_state(revs, p->item);\n@@ -2044,31 +2222,30 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c\n \tif (revs->first_parent_only)\n \t\tcnt = 1;\n \telse\n-\t\tcnt = remove_duplicate_parents(commit);\n+\t\tcnt = remove_duplicate_parents(revs, commit);\n \n \t/*\n \t * It is possible that we are a merge and one side branch\n \t * does not have any commit that touches the given paths;\n-\t * in such a case, the immediate parents will be rewritten\n-\t * to different commits.\n+\t * in such a case, the immediate parent from that branch\n+\t * will be rewritten to be the merge base.\n \t *\n \t *      o----X\t\tX: the commit we are looking at;\n \t *     /    /\t\to: a commit that touches the paths;\n \t * ---o----'\n \t *\n-\t * Further reduce the parents by removing redundant parents.\n+\t * Detect and simplify this case.\n \t */\n \tif (1 < cnt) {\n-\t\tstruct commit_list *h = reduce_heads(commit->parents);\n-\t\tcnt = commit_list_count(h);\n-\t\tfree_commit_list(commit->parents);\n-\t\tcommit->parents = h;\n+\t\tint marked = mark_redundant_parents(revs, commit);\n+\t\tif (marked)\n+\t\t\tcnt = remove_marked_parents(revs, commit);\n \t}\n \n \t/*\n \t * A commit simplifies to itself if it is a root, if it is\n \t * UNINTERESTING, if it touches the given paths, or if it is a\n-\t * merge and its parents simplifies to more than one commits\n+\t * merge and its parents simplify to more than one commit\n \t * (the first two cases are already handled at the beginning of\n \t * this function).\n \t *\n@@ -2176,6 +2353,10 @@ int prepare_revision_walk(struct rev_info *revs)\n \tif (!revs->leak_pending)\n \t\tfree(list);\n \n+\t/* Signal whether we need per-parent treesame decoration */\n+\tif (revs->simplify_merges)\n+\t\trevs->treesame.name = \"treesame\";\n+\n \tif (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)\n \t\tcommit_list_sort_by_date(&revs->commits);\n \tif (revs->no_walk)\n@@ -2235,7 +2416,7 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n \t\t}\n \t\tpp = &parent->next;\n \t}\n-\tremove_duplicate_parents(commit);\n+\tremove_duplicate_parents(revs, commit);\n \treturn 0;\n }\n \ndiff --git a/revision.h b/revision.h\nindex 878a555..7d5763b 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -168,6 +168,7 @@ struct rev_info {\n \tstruct reflog_walk_info *reflog_info;\n \tstruct decoration children;\n \tstruct decoration merge_simplification;\n+\tstruct decoration treesame;\n \n \t/* notes-specific options: which refs to show */\n \tstruct display_notes_opt notes_opt;\ndiff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh\nindex c3bc2e7..5ad96e3 100755\n--- a/t/t6019-rev-list-ancestry-path.sh\n+++ b/t/t6019-rev-list-ancestry-path.sh\n@@ -100,7 +100,7 @@ test_expect_success 'rev-list G..M -- G.t' '\n \ttest_cmp expect actual\n '\n \n-test_expect_failure 'rev-list --ancestry-path G..M -- G.t' '\n+test_expect_success 'rev-list --ancestry-path G..M -- G.t' '\n \tfor c in H J L; do echo $c; done >expect &&\n \tgit rev-list --ancestry-path --format=%s G..M -- G.t |\n \tsed -e \"/^commit /d\" |\ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nindex 4d74d3c..0047b54 100755\n--- a/t/t6111-rev-list-treesame.sh\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -114,9 +114,9 @@ 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'\n check_result 'M H L K J I G E F D C B A' --topo-order\n check_result 'M L H B A' -- file\n check_result '(LH)M (B)L (B)H (A)B A' --parents -- file\n-check_outcome failure 'M L J I H G F D B A' --full-history -- file # drops G\n+check_result 'M L J I H G F D B A' --full-history -- file\n 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\n-check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file # drops G\n+check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file # drops parent B from G\n check_result 'M L K G F D B A' --first-parent\n check_result 'M L G F B A' --first-parent -- file\n \n@@ -125,11 +125,11 @@ check_result 'M L K J I H G E' F..M\n check_result 'M H L K J I G E' F..M --topo-order\n check_result 'M L H' F..M -- file\n check_result '(LH)M (B)L (B)H' --parents F..M -- file\n-check_outcome failure 'M L J I H G' F..M --full-history -- file # drops G\n+check_result 'M L J I H G' F..M --full-history -- file\n check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G' F..M --full-history --parents -- file\n-check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file # drops G\n+check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file # drops parent B from G\n check_result 'M L K J I H G' F..M --ancestry-path\n-check_outcome failure 'M L J I H G' F..M --ancestry-path -- file # drops G\n+check_result 'M L J I H G' F..M --ancestry-path -- file\n check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G' F..M --ancestry-path --parents -- file\n check_result '(LH)M (G)H (J)L (I)J (G)I (FE)G' F..M --ancestry-path --simplify-merges -- file\n check_result 'M L K G' F..M --first-parent\n@@ -138,7 +138,7 @@ check_result 'M L G' F..M --first-parent -- file\n # Note that G is pruned when E is the bottom, even if it's the same commit list\n # If we want history since E, then we're quite happy to ignore G that took E.\n check_result 'M L K J I H G' E..M --ancestry-path\n-check_result 'M L J I H' E..M --ancestry-path -- file\n+check_outcome failure 'M L J I H' E..M --ancestry-path -- file # includes G\n check_outcome failure '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file # includes G\n check_outcome failure '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file # includes G\n \n@@ -161,32 +161,32 @@ check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file\n # But --full-history shouldn't drop D on its own - without simplification,\n # we can't decide if the merge from INTERESTING commit C was sensible.\n check_result 'F D C' B..F\n-check_result 'F' B..F -- file\n+check_outcome failure 'F' B..F -- file # includes D\n check_outcome failure '(B)F' B..F --parents -- file # includes D\n-check_outcome failure 'F D' B..F --full-history -- file # drops D prematurely\n+check_result 'F D' B..F --full-history -- file\n check_result '(D)F (BA)D' B..F --full-history --parents -- file\n check_result '(B)F' B..F --simplify-merges -- file\n check_result 'F D' B..F --ancestry-path\n-check_result 'F' B..F --ancestry-path -- file\n+check_outcome failure 'F' B..F --ancestry-path -- file # includes D\n check_outcome failure 'F' B..F --ancestry-path --parents -- file # includes D\n check_outcome failure 'F' B..F --ancestry-path --simplify-merges -- file # includes D\n check_result 'F D' B..F --first-parent\n check_result 'F' B..F --first-parent -- file\n \n # E...F should be equivalent to E F ^B, and be able to drop D as above.\n-check_result 'F' E F ^B -- file\n-check_result 'F' E...F -- file\n+check_outcome failure 'F' E F ^B -- file # includes D\n+check_outcome failure 'F' E...F -- file # includes D\n \n # Any sort of full history of C..F should show D, as it's the connection to C,\n # and it differs from it.\n check_result 'F D B' C..F\n check_result 'F B' C..F -- file\n check_result '(B)F (A)B' C..F --parents -- file\n-check_outcome failure 'F D B' C..F --full-history -- file # drops D\n+check_result 'F D B' C..F --full-history -- file\n check_result '(D)F (BC)D (A)B' C..F --full-history --parents -- file\n check_result '(D)F (BC)D (A)B' C..F --simplify-merges -- file\n check_result 'F D' C..F --ancestry-path\n-check_outcome failure 'F D' C..F --ancestry-path -- file # drops D\n+check_result 'F D' C..F --ancestry-path -- file\n check_result 'F D' C..F --ancestry-path --parents -- file\n check_result 'F D' C..F --ancestry-path --simplify-merges -- file\n check_result 'F D B' C..F --first-parent\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217600","messageId":"1368718361-27859-10-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 09/15] t6012: update test for tweaked full-history traversal","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:35Z","receivedAt":"2013-05-16T15:32:35Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t6012-rev-list-simplify.sh | 29 +++++++++++++++++++++++------\n 1 file changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh\nindex dd6dc84..4e55872 100755\n--- a/t/t6012-rev-list-simplify.sh\n+++ b/t/t6012-rev-list-simplify.sh\n@@ -14,21 +14,24 @@ unnote () {\n \n test_expect_success setup '\n \techo \"Hi there\" >file &&\n-\tgit add file &&\n-\ttest_tick && git commit -m \"Initial file\" &&\n+\techo \"initial\" >lost &&\n+\tgit add file lost &&\n+\ttest_tick && git commit -m \"Initial file and lost\" &&\n \tnote A &&\n \n \tgit branch other-branch &&\n \n \techo \"Hello\" >file &&\n-\tgit add file &&\n-\ttest_tick && git commit -m \"Modified file\" &&\n+\techo \"second\" >lost &&\n+\tgit add file lost &&\n+\ttest_tick && git commit -m \"Modified file and lost\" &&\n \tnote B &&\n \n \tgit checkout other-branch &&\n \n \techo \"Hello\" >file &&\n-\tgit add file &&\n+\t>lost &&\n+\tgit add file lost &&\n \ttest_tick && git commit -m \"Modified the file identically\" &&\n \tnote C &&\n \n@@ -37,7 +40,9 @@ test_expect_success setup '\n \ttest_tick && git commit -m \"Add another file\" &&\n \tnote D &&\n \n-\ttest_tick && git merge -m \"merge\" master &&\n+\ttest_tick &&\n+\ttest_must_fail git merge -m \"merge\" master &&\n+\t>lost && git commit -a -m \"merge\" &&\n \tnote E &&\n \n \techo \"Yet another\" >elif &&\n@@ -110,4 +115,16 @@ check_result 'I B A' -- file\n check_result 'I B A' --topo-order -- file\n check_result 'H' --first-parent -- another-file\n \n+check_result 'E C B A' --full-history E -- lost\n+test_expect_success 'full history simplification without parent' '\n+\tprintf \"%s\\n\" E C B A >expect &&\n+\tgit log --pretty=\"$FMT\" --full-history E -- lost |\n+\tunnote >actual &&\n+\tsed -e \"s/^.*\t\\([^ ]*\\) .*/\\1/\" >check <actual &&\n+\ttest_cmp expect check || {\n+\t\tcat actual\n+\t\tfalse\n+\t}\n+'\n+\n test_done\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217607","messageId":"1368718361-27859-11-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 10/15] simplify-merges: never remove all TREESAME parents","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:36Z","receivedAt":"2013-05-16T15:32:36Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"When simplifying an odd merge, such as one that used \"-s ours\", we may\nfind ourselves TREESAME to apparently redundant parents. Prevent\nsimplify_merges() from removing every TREESAME parent; if this would\nhappen reinstate the first TREESAME parent - the one that the default\nlog would have followed.\n\nThis avoids producing a totally disjoint history from the default log\nwhen the default log is a better explanation of the end result, and aids\nvisualisation of odd merges.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n Documentation/rev-list-options.txt |  3 +-\n revision.c                         | 69 ++++++++++++++++++++++++++++++++++++++\n t/t6111-rev-list-treesame.sh       |  4 +--\n 3 files changed, 73 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex d166384..f41e865 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -471,7 +471,8 @@ history according to the following rules:\n +\n * Replace each parent `P` of `C'` with its simplification `P'`.  In\n   the process, drop parents that are ancestors of other parents, and\n-  remove duplicates.\n+  remove duplicates, but take care to never drop all parents that\n+  we are TREESAME to.\n +\n * If after this parent rewriting, `C'` is a root or merge commit (has\n   zero or >1 parents), a boundary commit, or !TREESAME, it remains.\ndiff --git a/revision.c b/revision.c\nindex 64b86ae..62f399c 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2136,6 +2136,73 @@ static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)\n \treturn marked;\n }\n \n+/*\n+ * Awkward naming - this means one parent we are TREESAME to.\n+ * cf mark_treesame_root_parents: root parents that are TREESAME (to an\n+ * empty tree). Better name suggestions?\n+ */\n+static int leave_one_treesame_to_parent(struct rev_info *revs, struct commit *commit)\n+{\n+\tstruct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);\n+\tstruct commit *unmarked = NULL, *marked = NULL;\n+\tstruct commit_list *p;\n+\tunsigned n;\n+\n+\tfor (p = commit->parents, n = 0; p; p = p->next, n++) {\n+\t\tif (ts->treesame[n]) {\n+\t\t\tif (p->item->object.flags & TMP_MARK) {\n+\t\t\t\tif (!marked)\n+\t\t\t\t\tmarked = p->item;\n+\t\t\t} else {\n+\t\t\t\tif (!unmarked) {\n+\t\t\t\t\tunmarked = p->item;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * If we are TREESAME to a marked-for-deletion parent, but not to any\n+\t * unmarked parents, unmark the first TREESAME parent. This is the\n+\t * parent that the default simplify_history==1 scan would have followed,\n+\t * and it doesn't make sense to omit that path when asking for a\n+\t * simplified full history. Retaining it improves the chances of\n+\t * understanding odd missed merges that took an old version of a file.\n+\t *\n+\t * Example:\n+\t *\n+\t *   I--------*X       A modified the file, but mainline merge X used\n+\t *    \\       /        \"-s ours\", so took the version from I. X is\n+\t *     `-*A--'         TREESAME to I and !TREESAME to A.\n+\t *\n+\t * Default log from X would produce \"I\". Without this check,\n+\t * --full-history --simplify-merges would produce \"I-A-X\", showing\n+\t * the merge commit X and that it changed A, but not making clear that\n+\t * it had just taken the I version. With this check, the topology above\n+\t * is retained.\n+\t *\n+\t * Note that it is possible that the simplification chooses a different\n+\t * TREESAME parent from the default, in which case this test doesn't\n+\t * activate, and we _do_ drop the default parent. Example:\n+\t *\n+\t *   I------X         A modified the file, but it was reverted in B,\n+\t *    \\    /          meaning mainline merge X is TREESAME to both\n+\t *    *A-*B           parents.\n+\t *\n+\t * Default log would produce \"I\" by following the first parent;\n+\t * --full-history --simplify-merges will produce \"I-A-B\". But this is a\n+\t * reasonable result - it presents a logical full history leading from\n+\t * I to X, and X is not an important merge.\n+\t */\n+\tif (!unmarked && marked) {\n+\t\tmarked->object.flags &= ~TMP_MARK;\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int remove_marked_parents(struct rev_info *revs, struct commit *commit)\n {\n \tstruct commit_list **pp, *p;\n@@ -2239,6 +2306,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c\n \tif (1 < cnt) {\n \t\tint marked = mark_redundant_parents(revs, commit);\n \t\tif (marked)\n+\t\t\tmarked -= leave_one_treesame_to_parent(revs, commit);\n+\t\tif (marked)\n \t\t\tcnt = remove_marked_parents(revs, commit);\n \t}\n \ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nindex 0047b54..484b69b 100755\n--- a/t/t6111-rev-list-treesame.sh\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -116,7 +116,7 @@ check_result 'M L H B A' -- file\n check_result '(LH)M (B)L (B)H (A)B A' --parents -- file\n check_result 'M L J I H G F D B A' --full-history -- file\n 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\n-check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file # drops parent B from G\n+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file\n check_result 'M L K G F D B A' --first-parent\n check_result 'M L G F B A' --first-parent -- file\n \n@@ -127,7 +127,7 @@ check_result 'M L H' F..M -- file\n check_result '(LH)M (B)L (B)H' --parents F..M -- file\n check_result 'M L J I H G' F..M --full-history -- file\n check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G' F..M --full-history --parents -- file\n-check_outcome failure '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file # drops parent B from G\n+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file\n check_result 'M L K J I H G' F..M --ancestry-path\n check_result 'M L J I H G' F..M --ancestry-path -- file\n check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G' F..M --ancestry-path --parents -- file\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217604","messageId":"1368718361-27859-12-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 11/15] simplify-merges: drop merge from irrelevant side branch","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:37Z","receivedAt":"2013-05-16T15:32:37Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"Reimplement commit 4b7f53da on top of the new simplify-merges\ninfrastructure, tightening the condition to only consider root parents;\nthe original version incorrectly dropped parents that were TREESAME to\nanything.\n\nOriginal log message follows.\n\nThe merge simplification rule stated in 6546b59 (revision traversal:\nshow full history with merge simplification, 2008-07-31) still\ntreated merge commits too specially.  Namely, in a history with this\nshape:\n\n\t---o---o---M\n\t          /\n         x---x---x\n\nwhere three 'x' were on a history completely unrelated to the main\nhistory 'o' and do not touch any of the paths we are following, we\nstill said that after simplifying all of the parents of M, 'x'\n(which is the leftmost 'x' that rightmost 'x simplifies down to) and\n'o' (which would be the last commit on the main history that touches\nthe paths we are following) are independent from each other, and\nboth need to be kept.\n\nThat is incorrect; when the side branch 'x' never touches the paths,\nit should be removed to allow M to simplify down to the last commit\non the main history that touches the paths.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n Documentation/rev-list-options.txt | 34 +++++++++++++++++++++-------------\n revision.c                         | 26 +++++++++++++++++++++++++-\n t/t6012-rev-list-simplify.sh       |  2 +-\n 3 files changed, 47 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex f41e865..b462f17 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -342,13 +342,13 @@ In the following, we will always refer to the same example history to\n illustrate the differences between simplification settings.  We assume\n that you are filtering for a file `foo` in this commit graph:\n -----------------------------------------------------------------------\n-\t  .-A---M---N---O---P\n-\t /     /   /   /   /\n-\tI     B   C   D   E\n-\t \\   /   /   /   /\n-\t  `-------------'\n+\t  .-A---M---N---O---P---Q\n+\t /     /   /   /   /   /\n+\tI     B   C   D   E   Y\n+\t \\   /   /   /   /   /\n+\t  `-------------'   X\n -----------------------------------------------------------------------\n-The horizontal line of history A---P is taken to be the first parent of\n+The horizontal line of history A---Q is taken to be the first parent of\n each merge.  The commits are:\n \n * `I` is the initial commit, in which `foo` exists with contents\n@@ -369,6 +369,10 @@ each merge.  The commits are:\n * `E` changes `quux` to \"xyzzy\", and its merge `P` combines the\n   strings to \"quux xyzzy\".  `P` is TREESAME to `O`, but not to `E`.\n \n+* `X` is an indpendent root commit that added a new file `side`, and `Y`\n+  modified it. `Y` is TREESAME to `X`. Its merge `Q` added `side` to `P`, and\n+  `Q` is TREESAME to `P`, but not to `Y`.\n+\n 'rev-list' walks backwards through history, including or excluding\n commits based on whether '\\--full-history' and/or parent rewriting\n (via '\\--parents' or '\\--children') are used.  The following settings\n@@ -409,7 +413,7 @@ parent lines.\n \tthe example, we get\n +\n -----------------------------------------------------------------------\n-\tI  A  B  N  D  O  P\n+\tI  A  B  N  D  O  P  Q\n -----------------------------------------------------------------------\n +\n `M` was excluded because it is TREESAME to both parents.  `E`,\n@@ -430,7 +434,7 @@ Along each parent, prune away commits that are not included\n themselves.  This results in\n +\n -----------------------------------------------------------------------\n-\t  .-A---M---N---O---P\n+\t  .-A---M---N---O---P---Q\n \t /     /   /   /   /\n \tI     B   /   D   /\n \t \\   /   /   /   /\n@@ -440,7 +444,7 @@ themselves.  This results in\n Compare to '\\--full-history' without rewriting above.  Note that `E`\n was pruned away because it is TREESAME, but the parent list of P was\n rewritten to contain `E`'s parent `I`.  The same happened for `C` and\n-`N`.\n+`N`, and `X`, `Y` and `Q`.\n \n In addition to the above settings, you can change whether TREESAME\n affects inclusion:\n@@ -470,9 +474,9 @@ history according to the following rules:\n * Set `C'` to `C`.\n +\n * Replace each parent `P` of `C'` with its simplification `P'`.  In\n-  the process, drop parents that are ancestors of other parents, and\n-  remove duplicates, but take care to never drop all parents that\n-  we are TREESAME to.\n+  the process, drop parents that are ancestors of other parents or that are\n+  root commits TREESAME to an empty tree, and remove duplicates, but take care\n+  to never drop all parents that we are TREESAME to.\n +\n * If after this parent rewriting, `C'` is a root or merge commit (has\n   zero or >1 parents), a boundary commit, or !TREESAME, it remains.\n@@ -490,7 +494,7 @@ The effect of this is best shown by way of comparing to\n \t  `---------'\n -----------------------------------------------------------------------\n +\n-Note the major differences in `N` and `P` over '--full-history':\n+Note the major differences in `N`, `P` and `Q` over '--full-history':\n +\n --\n * `N`'s parent list had `I` removed, because it is an ancestor of the\n@@ -498,6 +502,10 @@ Note the major differences in `N` and `P` over '--full-history':\n +\n * `P`'s parent list similarly had `I` removed.  `P` was then\n   removed completely, because it had one parent and is TREESAME.\n++\n+* `Q`'s parent list had `Y` simplified to `X`. `X` was then removed, because it\n+  was a TREESAME root. `Q` was then removed completely, because it had one\n+  parent and is TREESAME.\n --\n \n Finally, there is a fifth simplification mode available:\ndiff --git a/revision.c b/revision.c\nindex 62f399c..4f7446c 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2136,6 +2136,22 @@ static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)\n \treturn marked;\n }\n \n+static int mark_treesame_root_parents(struct rev_info *revs, struct commit *commit)\n+{\n+\tstruct commit_list *p;\n+\tint marked = 0;\n+\n+\tfor (p = commit->parents; p; p = p->next) {\n+\t\tstruct commit *parent = p->item;\n+\t\tif (!parent->parents && (parent->object.flags & TREESAME)) {\n+\t\t\tparent->object.flags |= TMP_MARK;\n+\t\t\tmarked++;\n+\t\t}\n+\t}\n+\n+\treturn marked;\n+}\n+\n /*\n  * Awkward naming - this means one parent we are TREESAME to.\n  * cf mark_treesame_root_parents: root parents that are TREESAME (to an\n@@ -2301,10 +2317,18 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c\n \t *     /    /\t\to: a commit that touches the paths;\n \t * ---o----'\n \t *\n-\t * Detect and simplify this case.\n+\t * Further, a merge of an independent branch that doesn't\n+\t * touch the path will reduce to a treesame root parent:\n+\t *\n+\t *  ----o----X\t\tX: the commit we are looking at;\n+\t *          /\t\to: a commit that touches the paths;\n+\t *         r\t\tr: a root commit not touching the paths\n+\t *\n+\t * Detect and simplify both cases.\n \t */\n \tif (1 < cnt) {\n \t\tint marked = mark_redundant_parents(revs, commit);\n+\t\tmarked += mark_treesame_root_parents(revs, commit);\n \t\tif (marked)\n \t\t\tmarked -= leave_one_treesame_to_parent(revs, commit);\n \t\tif (marked)\ndiff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh\nindex 4e55872..57ce239 100755\n--- a/t/t6012-rev-list-simplify.sh\n+++ b/t/t6012-rev-list-simplify.sh\n@@ -110,7 +110,7 @@ check_result 'L K J I H G F E D C B A' --full-history\n check_result 'K I H E C B A' --full-history -- file\n check_result 'K I H E C B A' --full-history --topo-order -- file\n check_result 'K I H E C B A' --full-history --date-order -- file\n-check_outcome failure 'I E C B A' --simplify-merges -- file\n+check_result 'I E C B A' --simplify-merges -- file\n check_result 'I B A' -- file\n check_result 'I B A' --topo-order -- file\n check_result 'H' --first-parent -- another-file\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217603","messageId":"1368718361-27859-13-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 12/15] revision.c: add BOTTOM flag for commits","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:38Z","receivedAt":"2013-05-16T15:32:38Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"When performing edge-based operations on the revision graph, it can be\nuseful to be able to identify the INTERESTING graph's connection(s) to\nthe bottom commit(s) specified by the user.\n\nConceptually when the user specifies \"A..B\" (== B ^A), they are asking\nfor the history from A to B. The first connection from A onto the\nINTERESTING graph is part of that history, and should be considered. If\nwe consider only INTERESTING nodes and their connections, then we're\nreally only considering the history from A's immediate descendants to B.\n\nThis patch does not change behaviour, but adds a new BOTTOM flag to\nindicate the bottom commits specified by the user, ready to be used by\nfollowing patches.\n\nWe immediately use the BOTTOM flag to return collect_bottom_commits() to\nits original approach of examining the pending commit list rather than\nthe command line. This will ensure alignment of the definition of\n\"bottom\" with future patches.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n revision.c | 34 ++++++++++++++++------------------\n revision.h |  3 ++-\n 2 files changed, 18 insertions(+), 19 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 4f7446c..6607dab 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -909,16 +909,12 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li\n  * to filter the result of \"A..B\" further to the ones that can actually\n  * reach A.\n  */\n-static struct commit_list *collect_bottom_commits(struct rev_info *revs)\n+static struct commit_list *collect_bottom_commits(struct commit_list *list)\n {\n-\tstruct commit_list *bottom = NULL;\n-\tint i;\n-\tfor (i = 0; i < revs->cmdline.nr; i++) {\n-\t\tstruct rev_cmdline_entry *elem = &revs->cmdline.rev[i];\n-\t\tif ((elem->flags & UNINTERESTING) &&\n-\t\t    elem->item->type == OBJ_COMMIT)\n-\t\t\tcommit_list_insert((struct commit *)elem->item, &bottom);\n-\t}\n+\tstruct commit_list *elem, *bottom = NULL;\n+\tfor (elem = list; elem; elem = elem->next)\n+\t\tif (elem->item->object.flags & BOTTOM)\n+\t\t\tcommit_list_insert(elem->item, &bottom);\n \treturn bottom;\n }\n \n@@ -949,7 +945,7 @@ static int limit_list(struct rev_info *revs)\n \tstruct commit_list *bottom = NULL;\n \n \tif (revs->ancestry_path) {\n-\t\tbottom = collect_bottom_commits(revs);\n+\t\tbottom = collect_bottom_commits(list);\n \t\tif (!bottom)\n \t\t\tdie(\"--ancestry-path given but there are no bottom commits\");\n \t}\n@@ -1121,7 +1117,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)\n \tconst char *arg = arg_;\n \n \tif (*arg == '^') {\n-\t\tflags ^= UNINTERESTING;\n+\t\tflags ^= UNINTERESTING | BOTTOM;\n \t\targ++;\n \t}\n \tif (get_sha1_committish(arg, sha1))\n@@ -1213,8 +1209,8 @@ static void prepare_show_merge(struct rev_info *revs)\n \tadd_pending_object(revs, &head->object, \"HEAD\");\n \tadd_pending_object(revs, &other->object, \"MERGE_HEAD\");\n \tbases = get_merge_bases(head, other, 1);\n-\tadd_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING);\n-\tadd_pending_commit_list(revs, bases, UNINTERESTING);\n+\tadd_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);\n+\tadd_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);\n \tfree_commit_list(bases);\n \thead->object.flags |= SYMMETRIC_LEFT;\n \n@@ -1250,13 +1246,15 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi\n \tint cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;\n \tunsigned get_sha1_flags = 0;\n \n+\tflags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;\n+\n \tdotdot = strstr(arg, \"..\");\n \tif (dotdot) {\n \t\tunsigned char from_sha1[20];\n \t\tconst char *next = dotdot + 2;\n \t\tconst char *this = arg;\n \t\tint symmetric = *next == '.';\n-\t\tunsigned int flags_exclude = flags ^ UNINTERESTING;\n+\t\tunsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);\n \t\tstatic const char head_by_default[] = \"HEAD\";\n \t\tunsigned int a_flags;\n \n@@ -1332,13 +1330,13 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi\n \tdotdot = strstr(arg, \"^!\");\n \tif (dotdot && !dotdot[2]) {\n \t\t*dotdot = 0;\n-\t\tif (!add_parents_only(revs, arg, flags ^ UNINTERESTING))\n+\t\tif (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))\n \t\t\t*dotdot = '^';\n \t}\n \n \tlocal_flags = 0;\n \tif (*arg == '^') {\n-\t\tlocal_flags = UNINTERESTING;\n+\t\tlocal_flags = UNINTERESTING | BOTTOM;\n \t\targ++;\n \t}\n \n@@ -1815,7 +1813,7 @@ static int handle_revision_pseudo_opt(const char *submodule,\n \t\thandle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);\n \t} else if (!strcmp(arg, \"--bisect\")) {\n \t\thandle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);\n-\t\thandle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);\n+\t\thandle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);\n \t\trevs->bisect = 1;\n \t} else if (!strcmp(arg, \"--tags\")) {\n \t\thandle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);\n@@ -1841,7 +1839,7 @@ static int handle_revision_pseudo_opt(const char *submodule,\n \t} else if (!strcmp(arg, \"--reflog\")) {\n \t\thandle_reflog(revs, *flags);\n \t} else if (!strcmp(arg, \"--not\")) {\n-\t\t*flags ^= UNINTERESTING;\n+\t\t*flags ^= UNINTERESTING | BOTTOM;\n \t} else if (!strcmp(arg, \"--no-walk\")) {\n \t\trevs->no_walk = REVISION_WALK_NO_WALK_SORTED;\n \t} else if (!prefixcmp(arg, \"--no-walk=\")) {\ndiff --git a/revision.h b/revision.h\nindex 7d5763b..1e2c95c 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -15,7 +15,8 @@\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n #define PATCHSAME\t(1u<<9)\n-#define ALL_REV_FLAGS\t((1u<<10)-1)\n+#define BOTTOM\t\t(1u<<10)\n+#define ALL_REV_FLAGS\t((1u<<11)-1)\n \n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217605","messageId":"1368718361-27859-14-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 13/15] revision.c: discount side branches when computing TREESAME","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:39Z","receivedAt":"2013-05-16T15:32:39Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"Use the BOTTOM flag to define relevance for pruning. Relevant commits\nare those that are !UNINTERESTING or BOTTOM, and this allows us to\nidentify irrelevant side branches (UNINTERESTING && !BOTTOM).\n\nIf a merge has relevant parents, and it is TREESAME to them, then do not\nlet irrelevant parents cause the merge to be treated as !TREESAME.\n\nWhen considering simplification, don't always include all merges -\nmerges with exactly one relevant parent can be simplified, if TREESAME\naccording to the above rule.\n\nThese two changes greatly increase simplification in limited, pruned\nrevision lists.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n revision.c                        | 171 +++++++++++++++++++++++++++++++++-----\n t/t6019-rev-list-ancestry-path.sh |  12 ++-\n t/t6111-rev-list-treesame.sh      |   8 +-\n 3 files changed, 164 insertions(+), 27 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 6607dab..1c75070 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -333,6 +333,80 @@ static int everybody_uninteresting(struct commit_list *orig)\n }\n \n /*\n+ * A definition of \"relevant\" commit that we can use to simplify limited graphs\n+ * by eliminating side branches.\n+ *\n+ * A \"relevant\" commit is one that is !UNINTERESTING (ie we are including it\n+ * in our list), or that is a specified BOTTOM commit. Then after computing\n+ * a limited list, during processing we can generally ignore boundary merges\n+ * coming from outside the graph, (ie from irrelevant parents), and treat\n+ * those merges as if they were single-parent. TREESAME is defined to consider\n+ * only relevant parents, if any. If we are TREESAME to our on-graph parents,\n+ * we don't care if we were !TREESAME to non-graph parents.\n+ *\n+ * Treating bottom commits as relevant ensures that a limited graph's\n+ * connection to the actual bottom commit is not viewed as a side branch, but\n+ * treated as part of the graph. For example:\n+ *\n+ *   ....Z...A---X---o---o---B\n+ *        .     /\n+ *         W---Y\n+ *\n+ * When computing \"A..B\", the A-X connection is at least as important as\n+ * Y-X, despite A being flagged UNINTERESTING.\n+ *\n+ * And when computing --ancestry-path \"A..B\", the A-X connection is more\n+ * important than Y-X, despite both A and Y being flagged UNINTERESTING.\n+ */\n+static inline int relevant_commit(struct commit *commit)\n+{\n+\treturn (commit->object.flags & (UNINTERESTING | BOTTOM)) != UNINTERESTING;\n+}\n+\n+/*\n+ * Return a single relevant commit from a parent list. If we are a TREESAME\n+ * commit, and this selects one of our parents, then we can safely simplify to\n+ * that parent.\n+ */\n+static struct commit *one_relevant_parent(const struct rev_info *revs,\n+\t\t\t\t\t  struct commit_list *orig)\n+{\n+\tstruct commit_list *list = orig;\n+\tstruct commit *relevant = NULL;\n+\n+\tif (!orig)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * For 1-parent commits, or if first-parent-only, then return that\n+\t * first parent (even if not \"relevant\" by the above definition).\n+\t * TREESAME will have been set purely on that parent.\n+\t */\n+\tif (revs->first_parent_only || !orig->next)\n+\t\treturn orig->item;\n+\n+\t/*\n+\t * For multi-parent commits, identify a sole relevant parent, if any.\n+\t * If we have only one relevant parent, then TREESAME will be set purely\n+\t * with regard to that parent, and we can simplify accordingly.\n+\t *\n+\t * If we have more than one relevant parent, or no relevant parents\n+\t * (and multiple irrelevant ones), then we can't select a parent here\n+\t * and return NULL.\n+\t */\n+\twhile (list) {\n+\t\tstruct commit *commit = list->item;\n+\t\tlist = list->next;\n+\t\tif (relevant_commit(commit)) {\n+\t\t\tif (relevant)\n+\t\t\t\treturn NULL;\n+\t\t\trelevant = commit;\n+\t\t}\n+\t}\n+\treturn relevant;\n+}\n+\n+/*\n  * The goal is to get REV_TREE_NEW as the result only if the\n  * diff consists of all '+' (and no other changes), REV_TREE_OLD\n  * if the whole diff is removal of old data, and otherwise\n@@ -502,27 +576,52 @@ static unsigned update_treesame(struct rev_info *revs, struct commit *commit)\n \tif (commit->parents && commit->parents->next) {\n \t\tunsigned n;\n \t\tstruct treesame_state *st;\n+\t\tstruct commit_list *p;\n+\t\tunsigned relevant_parents;\n+\t\tunsigned relevant_change, irrelevant_change;\n \n \t\tst = lookup_decoration(&revs->treesame, &commit->object);\n \t\tif (!st)\n \t\t\tdie(\"update_treesame %s\", sha1_to_hex(commit->object.sha1));\n-\t\tcommit->object.flags |= TREESAME;\n-\t\tfor (n = 0; n < st->nparents; n++) {\n-\t\t\tif (!st->treesame[n]) {\n-\t\t\t\tcommit->object.flags &= ~TREESAME;\n-\t\t\t\tbreak;\n-\t\t\t}\n+\t\trelevant_parents = 0;\n+\t\trelevant_change = irrelevant_change = 0;\n+\t\tfor (p = commit->parents, n = 0; p; n++, p = p->next) {\n+\t\t\tif (relevant_commit(p->item)) {\n+\t\t\t\trelevant_change |= !st->treesame[n];\n+\t\t\t\trelevant_parents++;\n+\t\t\t} else\n+\t\t\t\tirrelevant_change |= !st->treesame[n];\n \t\t}\n+\t\tif (relevant_parents ? relevant_change : irrelevant_change)\n+\t\t\tcommit->object.flags &= ~TREESAME;\n+\t\telse\n+\t\t\tcommit->object.flags |= TREESAME;\n \t}\n \n \treturn commit->object.flags & TREESAME;\n }\n \n+static inline int limiting_can_increase_treesame(const struct rev_info *revs)\n+{\n+\t/*\n+\t * TREESAME is irrelevant unless prune && dense;\n+\t * if simplify_history is set, we can't have a mixture of TREESAME and\n+\t *    !TREESAME INTERESTING parents (and we don't have treesame[]\n+\t *    decoration anyway);\n+\t * if first_parent_only is set, then the TREESAME flag is locked\n+\t *    against the first parent (and again we lack treesame[] decoration).\n+\t */\n+\treturn revs->prune && revs->dense &&\n+\t       !revs->simplify_history &&\n+\t       !revs->first_parent_only;\n+}\n+\n static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n {\n \tstruct commit_list **pp, *parent;\n \tstruct treesame_state *ts = NULL;\n-\tint tree_changed = 0, nth_parent;\n+\tint relevant_change = 0, irrelevant_change = 0;\n+\tint relevant_parents, nth_parent;\n \n \t/*\n \t * If we don't do pruning, everything is interesting\n@@ -546,10 +645,12 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \tif (!revs->dense && !commit->parents->next)\n \t\treturn;\n \n-\tfor (pp = &commit->parents, nth_parent = 0;\n+\tfor (pp = &commit->parents, nth_parent = 0, relevant_parents = 0;\n \t     (parent = *pp) != NULL;\n \t     pp = &parent->next, nth_parent++) {\n \t\tstruct commit *p = parent->item;\n+\t\tif (relevant_commit(p))\n+\t\t\trelevant_parents++;\n \n \t\tif (nth_parent == 1) {\n \t\t\t/*\n@@ -573,7 +674,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \t\t\t    !revs->simplify_history &&\n \t\t\t    !(commit->object.flags & UNINTERESTING)) {\n \t\t\t\tts = initialise_treesame(revs, commit);\n-\t\t\t\tif (!tree_changed)\n+\t\t\t\tif (!(irrelevant_change || relevant_change))\n \t\t\t\t\tts->treesame[0] = 1;\n \t\t\t}\n \t\t}\n@@ -619,14 +720,27 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \t\t/* fallthrough */\n \t\tcase REV_TREE_OLD:\n \t\tcase REV_TREE_DIFFERENT:\n-\t\t\ttree_changed = 1;\n+\t\t\tif (relevant_commit(p))\n+\t\t\t\trelevant_change = 1;\n+\t\t\telse\n+\t\t\t\tirrelevant_change = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tdie(\"bad tree compare for commit %s\", sha1_to_hex(commit->object.sha1));\n \t}\n-\tif (tree_changed)\n-\t\treturn;\n-\tcommit->object.flags |= TREESAME;\n+\n+\t/*\n+\t * TREESAME is straightforward for single-parent commits. For merge\n+\t * commits, it is most useful to define it so that \"irrelevant\"\n+\t * parents cannot make us !TREESAME - if we have any relevant\n+\t * parents, then we only consider TREESAMEness with respect to them,\n+\t * allowing irrelevant merges from uninteresting branches to be\n+\t * simplified away. Only if we have only irrelevant parents do we\n+\t * base TREESAME on them. Note that this logic is replicated in\n+\t * update_treesame, which should be kept in sync.\n+\t */\n+\tif (relevant_parents ? !relevant_change : !irrelevant_change)\n+\t\tcommit->object.flags |= TREESAME;\n }\n \n static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,\n@@ -998,6 +1112,18 @@ static int limit_list(struct rev_info *revs)\n \t\tfree_commit_list(bottom);\n \t}\n \n+\t/*\n+\t * Check if any commits have become TREESAME by some of their parents\n+\t * becoming UNINTERESTING.\n+\t */\n+\tif (limiting_can_increase_treesame(revs))\n+\t\tfor (list = newlist; list; list = list->next) {\n+\t\t\tstruct commit *c = list->item;\n+\t\t\tif (c->object.flags & (UNINTERESTING | TREESAME))\n+\t\t\t\tcontinue;\n+\t\t\tupdate_treesame(revs, c);\n+\t\t}\n+\n \trevs->commits = newlist;\n \treturn 0;\n }\n@@ -2248,6 +2374,7 @@ static int remove_marked_parents(struct rev_info *revs, struct commit *commit)\n static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)\n {\n \tstruct commit_list *p;\n+\tstruct commit *parent;\n \tstruct merge_simplify_state *st, *pst;\n \tint cnt;\n \n@@ -2336,19 +2463,20 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c\n \t/*\n \t * A commit simplifies to itself if it is a root, if it is\n \t * UNINTERESTING, if it touches the given paths, or if it is a\n-\t * merge and its parents simplify to more than one commit\n+\t * merge and its parents don't simplify to one relevant commit\n \t * (the first two cases are already handled at the beginning of\n \t * this function).\n \t *\n-\t * Otherwise, it simplifies to what its sole parent simplifies to.\n+\t * Otherwise, it simplifies to what its sole relevant parent\n+\t * simplifies to.\n \t */\n \tif (!cnt ||\n \t    (commit->object.flags & UNINTERESTING) ||\n \t    !(commit->object.flags & TREESAME) ||\n-\t    (1 < cnt))\n+\t    (parent = one_relevant_parent(revs, commit->parents)) == NULL)\n \t\tst->simplified = commit;\n \telse {\n-\t\tpst = locate_simplify_state(revs, commit->parents->item);\n+\t\tpst = locate_simplify_state(revs, parent);\n \t\tst->simplified = pst->simplified;\n \t}\n \treturn tail;\n@@ -2445,7 +2573,8 @@ int prepare_revision_walk(struct rev_info *revs)\n \t\tfree(list);\n \n \t/* Signal whether we need per-parent treesame decoration */\n-\tif (revs->simplify_merges)\n+\tif (revs->simplify_merges ||\n+\t    (revs->limited && limiting_can_increase_treesame(revs)))\n \t\trevs->treesame.name = \"treesame\";\n \n \tif (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)\n@@ -2479,15 +2608,15 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp\n \t\tif (!revs->limited)\n \t\t\tif (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)\n \t\t\t\treturn rewrite_one_error;\n-\t\tif (p->parents && p->parents->next)\n-\t\t\treturn rewrite_one_ok;\n \t\tif (p->object.flags & UNINTERESTING)\n \t\t\treturn rewrite_one_ok;\n \t\tif (!(p->object.flags & TREESAME))\n \t\t\treturn rewrite_one_ok;\n \t\tif (!p->parents)\n \t\t\treturn rewrite_one_noparents;\n-\t\t*pp = p->parents->item;\n+\t\tif ((p = one_relevant_parent(revs, p->parents)) == NULL)\n+\t\t\treturn rewrite_one_ok;\n+\t\t*pp = p;\n \t}\n }\n \ndiff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh\nindex 5ad96e3..dabebae 100755\n--- a/t/t6019-rev-list-ancestry-path.sh\n+++ b/t/t6019-rev-list-ancestry-path.sh\n@@ -18,7 +18,8 @@ test_description='--ancestry-path'\n #  --ancestry-path F...I == F H I\n #\n #  G..M -- G.t                 == [nothing - was dropped in \"-s ours\" merge L]\n-#  --ancestry-path G..M -- G.t == H J L\n+#  --ancestry-path G..M -- G.t == L\n+#  --ancestry-path --simplify-merges G^..M -- G.t == G L\n \n . ./test-lib.sh\n \n@@ -101,8 +102,15 @@ test_expect_success 'rev-list G..M -- G.t' '\n '\n \n test_expect_success 'rev-list --ancestry-path G..M -- G.t' '\n-\tfor c in H J L; do echo $c; done >expect &&\n+\techo L >expect &&\n \tgit rev-list --ancestry-path --format=%s G..M -- G.t |\n+\tsed -e \"/^commit /d\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rev-list --ancestry-path --simplify-merges G^..M -- G.t' '\n+\tfor c in G L; do echo $c; done >expect &&\n+\tgit rev-list --ancestry-path --simplify-merges --format=%s G^..M -- G.t |\n \tsed -e \"/^commit /d\" |\n \tsort >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nindex 484b69b..e32b373 100755\n--- a/t/t6111-rev-list-treesame.sh\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -138,9 +138,9 @@ check_result 'M L G' F..M --first-parent -- file\n # Note that G is pruned when E is the bottom, even if it's the same commit list\n # If we want history since E, then we're quite happy to ignore G that took E.\n check_result 'M L K J I H G' E..M --ancestry-path\n-check_outcome failure 'M L J I H' E..M --ancestry-path -- file # includes G\n+check_result 'M L J I H' E..M --ancestry-path -- file\n check_outcome failure '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file # includes G\n-check_outcome failure '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file # includes G\n+check_result '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file\n \n # Should still be able to ignore I-J branch in simple log, despite limiting\n # to G.\n@@ -167,9 +167,9 @@ check_result 'F D' B..F --full-history -- file\n check_result '(D)F (BA)D' B..F --full-history --parents -- file\n check_result '(B)F' B..F --simplify-merges -- file\n check_result 'F D' B..F --ancestry-path\n-check_outcome failure 'F' B..F --ancestry-path -- file # includes D\n+check_result 'F' B..F --ancestry-path -- file\n check_outcome failure 'F' B..F --ancestry-path --parents -- file # includes D\n-check_outcome failure 'F' B..F --ancestry-path --simplify-merges -- file # includes D\n+check_result 'F' B..F --ancestry-path --simplify-merges -- file\n check_result 'F D' B..F --first-parent\n check_result 'F' B..F --first-parent -- file\n \n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217602","messageId":"1368718361-27859-15-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 14/15] revision.c: don't show all merges for --parents","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:40Z","receivedAt":"2013-05-16T15:32:40Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"When using --parents or --children, get_commit_action() previously showed\nall merges, even if TREESAME to both parents.\n\nThis was intended to tie together the topology of the rewritten parents,\nbut it was excessive - in fact we only need to show merges that have two\nor more relevant parents. Merges at the boundary do not necessarily need\nto be shown.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n revision.c                   | 22 +++++++++++++++-------\n t/t6111-rev-list-treesame.sh |  4 ++--\n 2 files changed, 17 insertions(+), 9 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 1c75070..edb7e1c 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2760,10 +2760,7 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi\n \tif (revs->min_age != -1 && (commit->date > revs->min_age))\n \t\treturn commit_ignore;\n \tif (revs->min_parents || (revs->max_parents >= 0)) {\n-\t\tint n = 0;\n-\t\tstruct commit_list *p;\n-\t\tfor (p = commit->parents; p; p = p->next)\n-\t\t\tn++;\n+\t\tint n = commit_list_count(commit->parents);\n \t\tif ((n < revs->min_parents) ||\n \t\t    ((revs->max_parents >= 0) && (n > revs->max_parents)))\n \t\t\treturn commit_ignore;\n@@ -2773,12 +2770,23 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi\n \tif (revs->prune && revs->dense) {\n \t\t/* Commit without changes? */\n \t\tif (commit->object.flags & TREESAME) {\n+\t\t\tint n;\n+\t\t\tstruct commit_list *p;\n \t\t\t/* drop merges unless we want parenthood */\n \t\t\tif (!want_ancestry(revs))\n \t\t\t\treturn commit_ignore;\n-\t\t\t/* non-merge - always ignore it */\n-\t\t\tif (!commit->parents || !commit->parents->next)\n-\t\t\t\treturn commit_ignore;\n+\t\t\t/*\n+\t\t\t * If we want ancestry, then need to keep any merges\n+\t\t\t * between relevant commits to tie together topology.\n+\t\t\t * For consistency with TREESAME and simplification\n+\t\t\t * use \"relevant\" here rather than just INTERESTING,\n+\t\t\t * to treat bottom commit(s) as part of the topology.\n+\t\t\t */\n+\t\t\tfor (n = 0, p = commit->parents; p; p = p->next)\n+\t\t\t\tif (relevant_commit(p->item))\n+\t\t\t\t\tif (++n >= 2)\n+\t\t\t\t\t\treturn commit_show;\n+\t\t\treturn commit_ignore;\n \t\t}\n \t}\n \treturn commit_show;\ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nindex e32b373..25cc8ad 100755\n--- a/t/t6111-rev-list-treesame.sh\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -139,7 +139,7 @@ check_result 'M L G' F..M --first-parent -- file\n # If we want history since E, then we're quite happy to ignore G that took E.\n check_result 'M L K J I H G' E..M --ancestry-path\n check_result 'M L J I H' E..M --ancestry-path -- file\n-check_outcome failure '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file # includes G\n+check_result '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file\n check_result '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file\n \n # Should still be able to ignore I-J branch in simple log, despite limiting\n@@ -168,7 +168,7 @@ check_result '(D)F (BA)D' B..F --full-history --parents -- file\n check_result '(B)F' B..F --simplify-merges -- file\n check_result 'F D' B..F --ancestry-path\n check_result 'F' B..F --ancestry-path -- file\n-check_outcome failure 'F' B..F --ancestry-path --parents -- file # includes D\n+check_result 'F' B..F --ancestry-path --parents -- file\n check_result 'F' B..F --ancestry-path --simplify-merges -- file\n check_result 'F D' B..F --first-parent\n check_result 'F' B..F --first-parent -- file\n-- \n1.8.3.rc0.28.g4b02ef5\n"},{"id":"217611","messageId":"1368718361-27859-16-git-send-email-kevin@bracey.fi","threadId":"33841","inReplyTo":"1368718361-27859-1-git-send-email-kevin@bracey.fi","subject":"[PATCH v4 15/15] revision.c: make default history consider bottom commits","fromName":"Kevin Bracey","fromEmail":"kevin@bracey.fi","sentAt":"2013-05-16T15:32:41Z","receivedAt":"2013-05-16T15:32:41Z","isPatch":true,"sender":{"key":"kevin@bracey.fi","avatar":"https://avatars.githubusercontent.com/u/96079793?v=4"},"body":"Previously, the default history treated bottom commits the same as any\nother UNINTERESTING commit, which could force it down side branches.\n\nConsider the following history:\n\n   *A--*B---D--*F         * marks !TREESAME parent paths\n     \\     /*\n      `-C-'\n\nWhen requesting \"B..F\", B is UNINTERESTING but TREESAME to D. C is\n!UNINTERESTING.\n\nSo default following would go from D into the irrelevant side branch C\nto A, rather than to B.  Note also that if there had been an extra\n!UNINTERESTING commit B1 between B and D, it wouldn't have gone down C.\n\nChange the default following to test relevant_commit() instead of\n!UNINTERESTING, so it can proceed straight from D to B, thus finishing\nthe traversal of that path.\n\nSigned-off-by: Kevin Bracey <kevin@bracey.fi>\n---\n revision.c                   |  2 +-\n t/t6111-rev-list-treesame.sh | 12 ++++++------\n 2 files changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex edb7e1c..914ac78 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -684,7 +684,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n \t\t\t    sha1_to_hex(p->object.sha1));\n \t\tswitch (rev_compare_tree(revs, p, commit)) {\n \t\tcase REV_TREE_SAME:\n-\t\t\tif (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {\n+\t\t\tif (!revs->simplify_history || !relevant_commit(p)) {\n \t\t\t\t/* Even if a merge with an uninteresting\n \t\t\t\t * side branch brought the entire change\n \t\t\t\t * we are interested in, we do not want\ndiff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh\nindex 25cc8ad..88b84df 100755\n--- a/t/t6111-rev-list-treesame.sh\n+++ b/t/t6111-rev-list-treesame.sh\n@@ -146,8 +146,8 @@ check_result '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges\n # to G.\n check_result 'M L K J I H' G..M\n check_result 'M H L K J I' G..M --topo-order\n-check_outcome failure 'M L H' G..M -- file # includes J I\n-check_outcome failure '(LH)M (G)L (G)H' G..M --parents -- file # includes J I\n+check_result 'M L H' G..M -- file\n+check_result '(LH)M (G)L (G)H' G..M --parents -- file\n check_result 'M L J I H' G..M --full-history -- file\n check_result 'M L K J I H' G..M --full-history --parents -- file\n check_result 'M H L J I' G..M --simplify-merges -- file\n@@ -161,8 +161,8 @@ check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file\n # But --full-history shouldn't drop D on its own - without simplification,\n # we can't decide if the merge from INTERESTING commit C was sensible.\n check_result 'F D C' B..F\n-check_outcome failure 'F' B..F -- file # includes D\n-check_outcome failure '(B)F' B..F --parents -- file # includes D\n+check_result 'F' B..F -- file\n+check_result '(B)F' B..F --parents -- file\n check_result 'F D' B..F --full-history -- file\n check_result '(D)F (BA)D' B..F --full-history --parents -- file\n check_result '(B)F' B..F --simplify-merges -- file\n@@ -174,8 +174,8 @@ check_result 'F D' B..F --first-parent\n check_result 'F' B..F --first-parent -- file\n \n # E...F should be equivalent to E F ^B, and be able to drop D as above.\n-check_outcome failure 'F' E F ^B -- file # includes D\n-check_outcome failure 'F' E...F -- file # includes D\n+check_result 'F' E F ^B -- file # includes D\n+check_result 'F' E...F -- file # includes D\n \n # Any sort of full history of C..F should show D, as it's the connection to C,\n # and it differs from it.\n-- \n1.8.3.rc0.28.g4b02ef5\n"}]}