{"thread":{"id":"4770","subject":"[PATCH] Additional merge-base tests (revised)","startedAt":"2006-07-05T00:35:20Z","lastAt":"2006-07-11T14:26:06Z","messageCount":8,"participants":["A Large Angry SCM","Junio C Hamano","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"23262","messageId":"44AB0948.9070606@gmail.com","threadId":"4770","inReplyTo":null,"subject":"[PATCH] Additional merge-base tests (revised)","fromName":"A Large Angry SCM","fromEmail":"gitzilla@gmail.com","sentAt":"2006-07-05T00:35:20Z","receivedAt":"2006-07-05T00:35:20Z","isPatch":true,"sender":{"key":"gitzilla@gmail.com","avatar":"https://gravatar.com/avatar/354625c442439908ff3dd99757dee330e29e9df7847472384faf7a00add247fb?d=mp&s=160"},"body":"Signed-off-by: A Large Angry SCM <gitzilla@gmail.com>\n---\nThis demonstrates a problem with git-merge-base.\n\nThis is a slightly revised version of the same patch as before.\n\n t/t6010-merge-base.sh |   45 +++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 45 insertions(+)\n\ndiff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh\nindex 1dce123..2b7b51c 100755\n--- a/t/t6010-merge-base.sh\n+++ b/t/t6010-merge-base.sh\n@@ -44,6 +44,43 @@ A=$(doit 1 A $B)\n G=$(doit 7 G $B $E)\n H=$(doit 8 H $A $F)\n \n+# Setup for second test to demonstrate that relying on timestamps in a\n+# distributed SCM to provide a _consistent_ partial ordering of commits\n+# leads to insanity.\n+#\n+#               Relative\n+# Structure     timestamps\n+#\n+#   PL  PR        +4  +4\n+#  /  \\/  \\      /  \\/  \\\n+# L2  C2  R2    +3  -1  +3\n+# |   |   |     |   |   |\n+# L1  C1  R1    +2  -2  +2\n+# |   |   |     |   |   |\n+# L0  C0  R0    +1  -3  +1\n+#   \\ |  /        \\ |  /\n+#     S             0\n+#\n+# The left and right chains of commits can be of any length and complexity as\n+# long as all of the timestamps are greater than that of S.\n+\n+S=$(doit  0 S)\n+\n+C0=$(doit -3 C0 $S)\n+C1=$(doit -2 C1 $C0)\n+C2=$(doit -1 C2 $C1)\n+\n+L0=$(doit  1 L0 $S)\n+L1=$(doit  2 L1 $L0)\n+L2=$(doit  3 L2 $L1)\n+\n+R0=$(doit  1 R0 $S)\n+R1=$(doit  2 R1 $R0)\n+R2=$(doit  3 R2 $R1)\n+\n+PL=$(doit  4 PL $L2 $C2)\n+PR=$(doit  4 PR $C2 $R2)\n+\n test_expect_success 'compute merge-base (single)' \\\n     'MB=$(git-merge-base G H) &&\n      expr \"$(git-name-rev \"$MB\")\" : \"[0-9a-f]* tags/B\"'\n@@ -56,4 +93,12 @@ test_expect_success 'compute merge-base \n     'MB=$(git-show-branch --merge-base G H) &&\n      expr \"$(git-name-rev \"$MB\")\" : \"[0-9a-f]* tags/B\"'\n \n+test_expect_success 'compute merge-base (single)' \\\n+    'MB=$(git-merge-base PL PR) &&\n+     expr \"$(git-name-rev \"$MB\")\" : \"[0-9a-f]* tags/C2\"'\n+\n+test_expect_success 'compute merge-base (all)' \\\n+    'MB=$(git-merge-base --all PL PR) &&\n+     expr \"$(git-name-rev \"$MB\")\" : \"[0-9a-f]* tags/C2\"'\n+\n test_done\n"},{"id":"23264","messageId":"7vy7v8dctz.fsf@assigned-by-dhcp.cox.net","threadId":"4770","inReplyTo":"44AB0948.9070606@gmail.com","subject":"merge-base: update the clean-up postprocessing","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-07-05T02:07:04Z","receivedAt":"2006-07-05T02:07:04Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is \"for concepts\" only -- it still seems to have bugs\nsomewhere to break other tests, although it passes your new\ntests.\n-- >8 --\nThis removes the \"contaminate the well even more\" approach\ntaken in the current merge-base postprosessing code.  Instead,\nwhen there are more than one merge-base results, we compute the\nmerge-base between them and see if one is a fast-forward of the\nother, in which case the ancestor is removed from the result.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\n * This is on top of Johannes/Rene's merge-base work (in \"next\").\n\n commit.c |  279 ++++++++++++++++++++------------------------------------------\n 1 files changed, 89 insertions(+), 190 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex c6bf10d..1bd6dca 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -854,6 +854,7 @@ void sort_in_topological_order_fn(struct\n #define PARENT1\t\t(1u<< 8)\n #define PARENT2\t\t(1u<< 9)\n #define STALE\t\t(1u<<10)\n+#define RESULT\t\t(1u<<11)\n \n static struct commit *interesting(struct commit_list *list)\n {\n@@ -867,183 +868,42 @@ static struct commit *interesting(struct\n \treturn NULL;\n }\n \n-/*\n- * A pathological example of how this thing works.\n- *\n- * Suppose we had this commit graph, where chronologically\n- * the timestamp on the commit are A <= B <= C <= D <= E <= F\n- * and we are trying to figure out the merge base for E and F\n- * commits.\n- *\n- *                  F\n- *                 / \\\n- *            E   A   D\n- *             \\ /   /\n- *              B   /\n- *               \\ /\n- *                C\n- *\n- * First we push E and F to list to be processed.  E gets bit 1\n- * and F gets bit 2.  The list becomes:\n- *\n- *     list=F(2) E(1), result=empty\n- *\n- * Then we pop F, the newest commit, from the list.  Its flag is 2.\n- * We scan its parents, mark them reachable from the side that F is\n- * reachable from, and push them to the list:\n- *\n- *     list=E(1) D(2) A(2), result=empty\n- *\n- * Next pop E and do the same.\n- *\n- *     list=D(2) B(1) A(2), result=empty\n- *\n- * Next pop D and do the same.\n- *\n- *     list=C(2) B(1) A(2), result=empty\n- *\n- * Next pop C and do the same.\n- *\n- *     list=B(1) A(2), result=empty\n- *\n- * Now it is B's turn.  We mark its parent, C, reachable from B's side,\n- * and push it to the list:\n- *\n- *     list=C(3) A(2), result=empty\n- *\n- * Now pop C and notice it has flags==3.  It is placed on the result list,\n- * and the list now contains:\n- *\n- *     list=A(2), result=C(3)\n- *\n- * We pop A and do the same.\n- *\n- *     list=B(3), result=C(3)\n- *\n- * Next, we pop B and something very interesting happens.  It has flags==3\n- * so it is also placed on the result list, and its parents are marked\n- * stale, retroactively, and placed back on the list:\n- *\n- *    list=C(7), result=C(7) B(3)\n- *\n- * Now, list does not have any interesting commit.  So we find the newest\n- * commit from the result list that is not marked stale.  Which is\n- * commit B.\n- *\n- *\n- * Another pathological example how this thing used to fail to mark an\n- * ancestor of a merge base as STALE before we introduced the\n- * postprocessing phase (mark_reachable_commits).\n- *\n- *\t\t  2\n- *\t\t  H\n- *\t    1    / \\\n- *\t    G   A   \\\n- *\t    |\\ /     \\\n- *\t    | B       \\\n- *\t    |  \\       \\\n- *\t     \\  C       F\n- *\t      \\  \\     /\n- *\t       \\  D   /\n- *\t\t\\ |  /\n- *\t\t \\| /\n- *\t\t  E\n- *\n- *\t list\t\t\tA B C D E F G H\n- *\t G1 H2\t\t\t- - - - - - 1 2\n- *\t H2 E1 B1\t\t- 1 - - 1 - 1 2\n- *\t F2 E1 B1 A2\t\t2 1 - - 1 2 1 2\n- *\t E3 B1 A2\t\t2 1 - - 3 2 1 2\n- *\t B1 A2\t\t\t2 1 - - 3 2 1 2\n- *\t C1 A2\t\t\t2 1 1 - 3 2 1 2\n- *\t D1 A2\t\t\t2 1 1 1 3 2 1 2\n- *\t A2\t\t\t2 1 1 1 3 2 1 2\n- *\t B3\t\t\t2 3 1 1 3 2 1 2\n- *\t C7\t\t\t2 3 7 1 3 2 1 2\n- *\n- * At this point, unfortunately, everybody in the list is\n- * stale, so we fail to complete the following two\n- * steps to fully marking stale commits.\n- *\n- *\t D7\t\t\t2 3 7 7 3 2 1 2\n- *\t E7\t\t\t2 3 7 7 7 2 1 2\n- *\n- * and we ended up showing E as an interesting merge base.\n- * The postprocessing phase re-injects C and continues traversal\n- * to contaminate D and E.\n- */\n-\n-static void mark_reachable_commits(struct commit_list *result,\n-\t\t\t\t   struct commit_list *list)\n-{\n-\tstruct commit_list *tmp;\n-\n-\t/*\n-\t * Postprocess to fully contaminate the well.\n-\t */\n-\tfor (tmp = result; tmp; tmp = tmp->next) {\n-\t\tstruct commit *c = tmp->item;\n-\t\t/* Reinject stale ones to list,\n-\t\t * so we can scan their parents.\n-\t\t */\n-\t\tif (c->object.flags & STALE)\n-\t\t\tcommit_list_insert(c, &list);\n-\t}\n-\twhile (list) {\n-\t\tstruct commit *c = list->item;\n-\t\tstruct commit_list *parents;\n-\n-\t\ttmp = list;\n-\t\tlist = list->next;\n-\t\tfree(tmp);\n-\n-\t\t/* Anything taken out of the list is stale, so\n-\t\t * mark all its parents stale.  We do not\n-\t\t * parse new ones (we already parsed all the relevant\n-\t\t * ones).\n-\t\t */\n-\t\tparents = c->parents;\n-\t\twhile (parents) {\n-\t\t\tstruct commit *p = parents->item;\n-\t\t\tparents = parents->next;\n-\t\t\tif (!(p->object.flags & STALE)) {\n-\t\t\t\tp->object.flags |= STALE;\n-\t\t\t\tcommit_list_insert(p, &list);\n-\t\t\t}\n-\t\t}\n-\t}\n-}\n-\n-struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2,\n-                                    int cleanup)\n+static struct commit_list *merge_bases(struct commit *one, struct commit *two)\n {\n \tstruct commit_list *list = NULL;\n \tstruct commit_list *result = NULL;\n-\tstruct commit_list *tmp = NULL;\n \n-\tif (rev1 == rev2)\n-\t\treturn commit_list_insert(rev1, &result);\n+\tif (one == two)\n+\t\t/* We do not mark this even with RESULT so we do not\n+\t\t * have to clean it up.\n+\t\t */\n+\t\treturn commit_list_insert(one, &result);\n \n-\tparse_commit(rev1);\n-\tparse_commit(rev2);\n+\tparse_commit(one);\n+\tparse_commit(two);\n \n-\trev1->object.flags |= PARENT1;\n-\trev2->object.flags |= PARENT2;\n-\tinsert_by_date(rev1, &list);\n-\tinsert_by_date(rev2, &list);\n+\tone->object.flags |= PARENT1;\n+\ttwo->object.flags |= PARENT2;\n+\tinsert_by_date(one, &list);\n+\tinsert_by_date(two, &list);\n \n \twhile (interesting(list)) {\n-\t\tstruct commit *commit = list->item;\n+\t\tstruct commit *commit;\n \t\tstruct commit_list *parents;\n-\t\tint flags = commit->object.flags\n-\t\t\t& (PARENT1 | PARENT2 | STALE);\n+\t\tstruct commit_list *n;\n+\t\tint flags;\n \n-\t\ttmp = list;\n-\t\tlist = list->next;\n-\t\tfree(tmp);\n-\t\tif (flags == (PARENT1 | PARENT2)) {\n-\t\t\tinsert_by_date(commit, &result);\n+\t\tcommit = list->item;\n+\t\tn = list->next;\n+\t\tfree(list);\n+\t\tlist = n;\n \n+\t\tflags = commit->object.flags & (PARENT1 | PARENT2 | STALE);\n+\t\tif (flags == (PARENT1 | PARENT2)) {\n+\t\t\tif (!(commit->object.flags & RESULT)) {\n+\t\t\t\tcommit->object.flags |= RESULT;\n+\t\t\t\tinsert_by_date(commit, &result);\n+\t\t\t}\n \t\t\t/* Mark parents of a found merge stale */\n \t\t\tflags |= STALE;\n \t\t}\n@@ -1059,35 +919,74 @@ struct commit_list *get_merge_bases(stru\n \t\t}\n \t}\n \n-\tif (!result)\n-\t\tgoto finish;\n-\n-\tif (result->next && list)\n-\t\tmark_reachable_commits(result, list);\n+\t/* Clean up the result to remove stale ones */\n+\tlist = result; result = NULL;\n+\twhile (list) {\n+\t\tstruct commit_list *n = list->next;\n+\t\tif (!(list->item->object.flags & STALE))\n+\t\t\tinsert_by_date(list->item, &result);\n+\t\tfree(list);\n+\t\tlist = n;\n+\t}\n+\treturn result;\n+}\n \n-\t/* cull duplicates */\n-\tfor (tmp = result, list = NULL; tmp; ) {\n-\t\tstruct commit *commit = tmp->item;\n-\t\tstruct commit_list *next = tmp->next;\n-\t\tif (commit->object.flags & STALE) {\n-\t\t\tif (list != NULL)\n-\t\t\t\tlist->next = next;\n-\t\t\tfree(tmp);\n-\t\t} else {\n-\t\t\tif (list == NULL)\n-\t\t\t\tresult = tmp;\n-\t\t\tlist = tmp;\n-\t\t\tcommit->object.flags |= STALE;\n+struct commit_list *get_merge_bases(struct commit *one,\n+\t\t\t\t    struct commit *two,\n+                                    int cleanup)\n+{\n+\tstruct commit_list *result = merge_bases(one, two);\n+\tstruct commit_list *list;\n+\tstruct commit **rslt;\n+\tunsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);\n+\tint cnt, i, j;\n+\n+\tif (one == two)\n+\t\treturn result;\n+\tif (!result || !result->next) {\n+\t\tif (cleanup) {\n+\t\t\tclear_commit_marks(one, all_flags);\n+\t\t\tclear_commit_marks(two, all_flags);\n \t\t}\n-\n-\t\ttmp = next;\n+\t\treturn result;\n \t}\n \n- finish:\n-\tif (cleanup) {\n-\t\tclear_commit_marks(rev1, PARENT1 | PARENT2 | STALE);\n-\t\tclear_commit_marks(rev2, PARENT1 | PARENT2 | STALE);\n+\t/* There are more than one */\n+\tcnt = 0;\n+\tlist = result;\n+\twhile (list) {\n+\t\tlist = list->next;\n+\t\tcnt++;\n+\t}\n+\trslt = xcalloc(cnt, sizeof(*rslt));\n+\tfor (list = result, i = 0; list; list = list->next)\n+\t\trslt[i] = list->item;\n+\tfree_commit_list(result);\n+\n+\tclear_commit_marks(one, all_flags);\n+\tclear_commit_marks(two, all_flags);\n+\tfor (i = 0; i < cnt - 1; i++) {\n+\t\tfor (j = i+1; j < cnt; j++) {\n+\t\t\tif (!rslt[i] || !rslt[j])\n+\t\t\t\tcontinue;\n+\t\t\tresult = merge_bases(rslt[i], rslt[j]);\n+\t\t\tclear_commit_marks(rslt[i], all_flags);\n+\t\t\tclear_commit_marks(rslt[j], all_flags);\n+\t\t\tfor (list = result; list; list = list->next) {\n+\t\t\t\tif (rslt[i] == list->item)\n+\t\t\t\t\trslt[i] = NULL;\n+\t\t\t\tif (rslt[j] == list->item)\n+\t\t\t\t\trslt[j] = NULL;\n+\t\t\t}\n+\t\t}\n \t}\n \n+\t/* Surviving ones in rslt[] are the independent results */\n+\tresult = NULL;\n+\tfor (i = 0; i < cnt; i++) {\n+\t\tif (rslt[i])\n+\t\t\tinsert_by_date(rslt[i], &result);\n+\t}\n+\tfree(rslt);\n \treturn result;\n }\n-- \n1.4.1.g7993\n"},{"id":"23267","messageId":"Pine.LNX.4.63.0607050946390.29667@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"4770","inReplyTo":"7vy7v8dctz.fsf@assigned-by-dhcp.cox.net","subject":"Re: merge-base: update the clean-up postprocessing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-07-05T07:50:53Z","receivedAt":"2006-07-05T07:50:53Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 4 Jul 2006, Junio C Hamano wrote:\n\n> This is \"for concepts\" only -- it still seems to have bugs\n> somewhere to break other tests, although it passes your new\n> tests.\n\nDoesn't this introduce a nasty O(n*m) performance (where m is the \nnumber of merge bases, and n the number of traversed commits)? I think \npossibly many commits are traversed multiple times.\n\nBTW ALAS' argument about grafts not only shot down my maximumSkew, but \nAFAICT also the generation number thing. Besides, the generation number \ncould be manipulated by a mean-spirited person also.\n\nCiao,\nDscho\n"},{"id":"23268","messageId":"7vejx0cwwj.fsf@assigned-by-dhcp.cox.net","threadId":"4770","inReplyTo":"7vy7v8dctz.fsf@assigned-by-dhcp.cox.net","subject":"Re: merge-base: update the clean-up postprocessing","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-07-05T07:51:08Z","receivedAt":"2006-07-05T07:51:08Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"A fixed up version of this patch, along with your updated test,\nis at the tip of \"pu\".\n\nIt does affect the processing time for cases where there are\nmore than one merge bases negatively.  To compute all merge-base\nfor the 23 merges in the kernel reporitory, the old code with\nthe \"contaminate the well a bit more\" clean-up phase takes 2.5\nseconds, while the new code takes 3.9 seconds.\n\nProcessing all 2215 merges in the kernel repository (the other\n2192 merges have one merge-base between the parents) takes 160\nseconds either way.  In other words, multi merge-base merges are\nrelatively rare and a bit more time spent to clean-up with the\nnew code is lost in the noise.\n\nThe numbers are taken from /usr/bin/time on an Athron 64X2 3800.\n"},{"id":"23293","messageId":"7v4pxvbpw0.fsf@assigned-by-dhcp.cox.net","threadId":"4770","inReplyTo":"Pine.LNX.4.63.0607050946390.29667@wbgn013.biozentrum.uni-wuerzburg.de","subject":"Re: merge-base: update the clean-up postprocessing","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-07-05T23:20:15Z","receivedAt":"2006-07-05T23:20:15Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Tue, 4 Jul 2006, Junio C Hamano wrote:\n>\n>> This is \"for concepts\" only -- it still seems to have bugs\n>> somewhere to break other tests, although it passes your new\n>> tests.\n>\n> Doesn't this introduce a nasty O(n*m) performance (where m is the \n> number of merge bases, and n the number of traversed commits)? I think \n> possibly many commits are traversed multiple times.\n\nIn practice m is small and the recomputation of bases between\nbases does not require the minimalization so O(m^2).  I've given\nthe numbers from a small real world example on the fixed code.\n"},{"id":"23294","messageId":"7vzmfnaba4.fsf@assigned-by-dhcp.cox.net","threadId":"4770","inReplyTo":"Pine.LNX.4.63.0607050946390.29667@wbgn013.biozentrum.uni-wuerzburg.de","subject":"Re: merge-base: update the clean-up postprocessing","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-07-05T23:21:07Z","receivedAt":"2006-07-05T23:21:07Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> BTW ALAS' argument about grafts not only shot down my maximumSkew, but \n> AFAICT also the generation number thing. Besides, the generation number \n> could be manipulated by a mean-spirited person also.\n\nI think with the fixed-up version of \"concepts only\" patch,\ngeneration number approach is already moot.\n"},{"id":"23622","messageId":"7vpsgc4kze.fsf@assigned-by-dhcp.cox.net","threadId":"4770","inReplyTo":"7vejx0cwwj.fsf@assigned-by-dhcp.cox.net","subject":"Re: merge-base: update the clean-up postprocessing","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-07-11T08:13:57Z","receivedAt":"2006-07-11T08:13:57Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> A fixed up version of this patch, along with your updated test,\n> is at the tip of \"pu\".\n>\n> It does affect the processing time for cases where there are\n> more than one merge bases negatively.  To compute all merge-base\n> for the 23 merges in the kernel reporitory, the old code with\n> the \"contaminate the well a bit more\" clean-up phase takes 2.5\n> seconds, while the new code takes 3.9 seconds.\n>\n> Processing all 2215 merges in the kernel repository (the other\n> 2192 merges have one merge-base between the parents) takes 160\n> seconds either way.  In other words, multi merge-base merges are\n> relatively rare and a bit more time spent to clean-up with the\n> new code is lost in the noise.\n>\n> The numbers are taken from /usr/bin/time on an Athron 64X2 3800.\n\nI did a similar test on git.git repository.  Numbers are\ninteresting.\n\n * I have 941 two-head merges.  89 of them are multi-base\n   merges.  This is unproportionally higher compared to the\n   kernel repository.\n\n * Both the version in \"master\" (i.e. the one with the horizon\n   effect) and this version with updated clean-up code produces\n   identical set of merge bases for all 941 two-head merges.\n\n * The difference in processing time for 941 two-head merges\n   with both versions is lost within margin of error.\n"},{"id":"23639","messageId":"Pine.LNX.4.63.0607111625440.29667@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"4770","inReplyTo":"7vpsgc4kze.fsf@assigned-by-dhcp.cox.net","subject":"Re: merge-base: update the clean-up postprocessing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-07-11T14:26:06Z","receivedAt":"2006-07-11T14:26:06Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 11 Jul 2006, Junio C Hamano wrote:\n\n>  * The difference in processing time for 941 two-head merges\n>    with both versions is lost within margin of error.\n\nYou convinced me.\n\nCiao,\nDscho\n"}]}