{"thread":{"id":"65435","subject":"[RFC] Show empty root commits when using --simplify-by-decoration","startedAt":"2026-04-04T15:17:49Z","lastAt":"2026-04-04T20:05:09Z","messageCount":3,"participants":["Fernando Ramos","Pablo"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"540893","messageId":"adErl-EdiaXopVx-@x395.localdomain","threadId":"65435","inReplyTo":null,"subject":"[RFC] Show empty root commits when using --simplify-by-decoration","fromName":"Fernando Ramos","fromEmail":"greenfoo@u92.eu","sentAt":"2026-04-04T15:17:43Z","receivedAt":"2026-04-04T15:17:49Z","isPatch":false,"body":"Regarding \"--simplify-by-decoration\", I found a somewhat unexpected behavior.\n\nLet's say we have a repository with two branches (\"master\" and \"branch\") and\nthis history structure:\n\n         a * <master>       b * <branch>\n           |                  |\n         2 *------------------'\n           |\n         1 *\n           |\n    start  *\n\nIf I now run this...\n\n    $ git log --graph --all --oneline --simplify-by-decoration\n\n...I get this (which is expected and ok):\n\n    * 0f3892d (branch) b\n    | * 0c37d8c (master) a\n    |/\n    * d783631 Start\n\nHOWEVER, if the root commit (\"start\") is an empty commit, then the same command\nwill print this instead:\n\n    * 7abc10a (branch) b\n    * 14d51e1 (master) a\n\n...which is confusing for two reasons:\n\n1. The graph is not showing a common origin.\n2. Because \"root\" commits use an \"*\", it looks like \"a\" is the parent of \"b\",\n   when the reality is that the graph is showing two independen origins.\n\nIssue (1) seems to be caused by the fact that empty root commits are marked as\nTREESAME and thus discarded when showing the graph.  This can be fixed with\npatch #1 at the end of this email.  Note, however, that this patch partially\nbreaks what Taylor did on 1343c893138 (I say \"partially\" because it only happens\nwhen using \"--simplify-by-decoration\").  I have CC'ed Taylor in case he has any\ncomments about it.\n\nIssue (2) can be \"fixed\" by using a diferent character (for example \"I\") for\nroot commits (ie. those without parents), which can be done with patch #2 at the\nend of this email.\n\nWith those two patches applied, this is what the output looks like in both cases\n(with the root commit being empty or not):\n\n    * 7abc10a (branch) b\n    | * 14d51e1 (master) a\n    |/\n    I c0db521 Start\n\nLet me know what you think and whether you want me to send a proper patch that\nincludes (1) or (2) or (1)+(2).\n\nThanks!\n\nPS: Path #3 at the end of this emails includes a dummy script to quickly\nrecreate the two scenarios described above.\n\n\n\n--------------------------------------------------------------------------------\nPatch #1\n--------------------------------------------------------------------------------\n\ndiff --git a/revision.c b/revision.c\nindex c9b8bfd09f..0e7bf2ed3c 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -978,15 +978,17 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n                return;\n\n        if (!commit->parents) {\n-               /*\n-                * Pretend as if we are comparing ourselves to the\n-                * (non-existent) first parent of this commit object. Even\n-                * though no such parent exists, its changed-path Bloom filter\n-                * (if one exists) is relative to the empty tree, using Bloom\n-                * filters is allowed here.\n-                */\n-               if (rev_same_tree_as_empty(revs, commit, 0))\n-                       commit->object.flags |= TREESAME;\n+               if (!revs->simplify_by_decoration) {\n+                       /*\n+                        * Pretend as if we are comparing ourselves to the\n+                        * (non-existent) first parent of this commit object. Even\n+                        * though no such parent exists, its changed-path Bloom filter\n+                        * (if one exists) is relative to the empty tree, using Bloom\n+                        * filters is allowed here.\n+                        */\n+                       if (rev_same_tree_as_empty(revs, commit, 0))\n+                               commit->object.flags |= TREESAME;\n+               }\n                return;\n        }\n\n\n--------------------------------------------------------------------------------\nPatch #2\n--------------------------------------------------------------------------------\n\ndiff --git a/revision.c b/revision.c\nindex 31808e3df0..c9b8bfd09f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -4565,7 +4565,7 @@ const char *get_revision_mark(const struct rev_info *revs, const struct commit *\n                else\n                        return \">\";\n        } else if (revs->graph)\n-               return \"*\";\n+               return commit->parents ? \"*\" : \"I\";\n        else if (revs->cherry_mark)\n                return \"+\";\n        return \"\";\n\n\n--------------------------------------------------------------------------------\nPatch #3\n--------------------------------------------------------------------------------\n\ndiff --git a/build_example.sh b/build_example.sh\nnew file mode 100755\nindex 0000000000..60f63c5344\n--- /dev/null\n+++ b/build_example.sh\n@@ -0,0 +1,61 @@\n+#!/bin/bash\n+\n+################################################################################\n+# Example #1\n+################################################################################\n+#\n+# This will create this a repo with this history structure:\n+#\n+#       a * (master)       b * (branch)\n+#         |                  |\n+#       2 * -----------------'\n+#         |\n+#       1 *\n+#         |\n+#   start *\n+\n+rm -rf EXAMPLE1 || true; mkdir -p EXAMPLE1; cd EXAMPLE1\n+\n+touch start.txt; git init; git add start.txt; git commit -m 'Start'\n+touch 1.txt; git add 1.txt; git commit -m '1'\n+touch 2.txt; git add 2.txt; git commit -m '2'\n+touch a.txt; git add a.txt; git commit -m 'a'\n+git checkout -b branch HEAD^\n+touch b.txt; git add b.txt; git commit -m 'b'\n+\n+git log --graph --all --oneline --simplify-by-decoration\n+# The previous command will print this, which is what we expected:\n+#\n+#   * 0f3892d (HEAD -> branch) b\n+#   | * 0c37d8c (master) a\n+#   |/\n+#   * d783631 Start\n+\n+cd ..\n+\n+\n+\n+################################################################################\n+# Example #2\n+################################################################################\n+#\n+# This will create this a repo with the same structure as before, but this time\n+# the root commit (\"start\") is an empty one.\n+\n+rm -rf EXAMPLE2 || true; mkdir -p EXAMPLE2; cd EXAMPLE2\n+\n+git init; git commit --allow-empty -m 'Start'\n+touch 1.txt; git add 1.txt; git commit -m '1'\n+touch 2.txt; git add 2.txt; git commit -m '2'\n+touch a.txt; git add a.txt; git commit -m 'a'\n+git checkout -b branch HEAD^\n+touch b.txt; git add b.txt; git commit -m 'b'\n+\n+git log --graph --all --oneline --simplify-by-decoration\n+# The previous command will print this, which is unexpected:\n+#\n+#   * 7abc10a (HEAD -> branch) b\n+#   * 14d51e1 (master) a\n+\n+cd ..\n+\n\n"},{"id":"540898","messageId":"CAN5EUNTUDT7FMNYTOnTA_GJJXa2eZZL=RhJHfA6PfJvFSSHL-g@mail.gmail.com","threadId":"65435","inReplyTo":"adErl-EdiaXopVx-@x395.localdomain","subject":"Re: [RFC] Show empty root commits when using --simplify-by-decoration","fromName":"Pablo","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-04-04T16:08:25Z","receivedAt":"2026-04-04T16:08:38Z","isPatch":false,"body":"El sáb, 4 abr 2026 a las 17:18, Fernando Ramos (<greenfoo@u92.eu>) escribió:\n\nHi Fernando,\nI think I can bring some clarity to your second issue.\n\n>\n> Regarding \"--simplify-by-decoration\", I found a somewhat unexpected behavior.\n>\n> Let's say we have a repository with two branches (\"master\" and \"branch\") and\n> this history structure:\n>\n>          a * <master>       b * <branch>\n>            |                  |\n>          2 *------------------'\n>            |\n>          1 *\n>            |\n>     start  *\n>\n> If I now run this...\n>\n>     $ git log --graph --all --oneline --simplify-by-decoration\n>\n> ...I get this (which is expected and ok):\n>\n>     * 0f3892d (branch) b\n>     | * 0c37d8c (master) a\n>     |/\n>     * d783631 Start\n>\n> HOWEVER, if the root commit (\"start\") is an empty commit, then the same command\n> will print this instead:\n>\n>     * 7abc10a (branch) b\n>     * 14d51e1 (master) a\n>\n> ...which is confusing for two reasons:\n>\n> 1. The graph is not showing a common origin.\n> 2. Because \"root\" commits use an \"*\", it looks like \"a\" is the parent of \"b\",\n>    when the reality is that the graph is showing two independen origins.\n>\n> Issue (1) seems to be caused by the fact that empty root commits are marked as\n> TREESAME and thus discarded when showing the graph.  This can be fixed with\n> patch #1 at the end of this email.  Note, however, that this patch partially\n> breaks what Taylor did on 1343c893138 (I say \"partially\" because it only happens\n> when using \"--simplify-by-decoration\").  I have CC'ed Taylor in case he has any\n> comments about it.\n>\n> Issue (2) can be \"fixed\" by using a diferent character (for example \"I\") for\n> root commits (ie. those without parents), which can be done with patch #2 at the\n> end of this email.\n\nThis has actually been discussed before and it was ruled out some years ago.\n  https://lore.kernel.org/git/xmqqwnwajbuj.fsf@gitster.c.googlers.com/\n\nYou would need more symbols for different roots, --boundary, --left-right and\nwhat if a commit that is not a root has its parents excluded, would you\nchange the symbol even tho it is not a root?\n\nYou might find interesting a patch I'm working on that tackles that\nconfusing look when two \"parentless\" commits are stacked.\n  https://lore.kernel.org/git/20260404092425.550346-1-pabloosabaterr@gmail.com/\n\nThis answer from junio might be helpful as well:\n  https://lore.kernel.org/git/xmqqbjfzn6ku.fsf@gitster.g/\n\n>\n> With those two patches applied, this is what the output looks like in both cases\n> (with the root commit being empty or not):\n>\n>     * 7abc10a (branch) b\n>     | * 14d51e1 (master) a\n>     |/\n>     I c0db521 Start\n>\n> Let me know what you think and whether you want me to send a proper patch that\n> includes (1) or (2) or (1)+(2).\n>\n> Thanks!\n>\n> PS: Path #3 at the end of this emails includes a dummy script to quickly\n> recreate the two scenarios described above.\n>\n>\n>\n> --------------------------------------------------------------------------------\n> Patch #1\n> --------------------------------------------------------------------------------\n>\n> diff --git a/revision.c b/revision.c\n> index c9b8bfd09f..0e7bf2ed3c 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -978,15 +978,17 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)\n>                 return;\n>\n>         if (!commit->parents) {\n> -               /*\n> -                * Pretend as if we are comparing ourselves to the\n> -                * (non-existent) first parent of this commit object. Even\n> -                * though no such parent exists, its changed-path Bloom filter\n> -                * (if one exists) is relative to the empty tree, using Bloom\n> -                * filters is allowed here.\n> -                */\n> -               if (rev_same_tree_as_empty(revs, commit, 0))\n> -                       commit->object.flags |= TREESAME;\n> +               if (!revs->simplify_by_decoration) {\n> +                       /*\n> +                        * Pretend as if we are comparing ourselves to the\n> +                        * (non-existent) first parent of this commit object. Even\n> +                        * though no such parent exists, its changed-path Bloom filter\n> +                        * (if one exists) is relative to the empty tree, using Bloom\n> +                        * filters is allowed here.\n> +                        */\n> +                       if (rev_same_tree_as_empty(revs, commit, 0))\n> +                               commit->object.flags |= TREESAME;\n> +               }\n>                 return;\n>         }\n>\n>\n> --------------------------------------------------------------------------------\n> Patch #2\n> --------------------------------------------------------------------------------\n>\n> diff --git a/revision.c b/revision.c\n> index 31808e3df0..c9b8bfd09f 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -4565,7 +4565,7 @@ const char *get_revision_mark(const struct rev_info *revs, const struct commit *\n>                 else\n>                         return \">\";\n>         } else if (revs->graph)\n> -               return \"*\";\n> +               return commit->parents ? \"*\" : \"I\";\n\nThis only focuses on real roots, what about commits with excluded parents.\nfor example:\n\n    O---A\n\n    X---Y\n\n    If you'd run 'git log --graph O..A Y', it shows A, Y, X but\nbecause it only look for roots it\n    ends up looking like:\n\n      * A <- not a root but O is excluded so it seems like one\n      * Y\n      I X\n\nThis would make A and Y look related, also the \"I\" makes it very\nconfusing because\nthe vertical edges are very similar \"|\".\n\n\n>         else if (revs->cherry_mark)\n>                 return \"+\";\n>         return \"\";\n>\n>\n> --------------------------------------------------------------------------------\n> Patch #3\n> --------------------------------------------------------------------------------\n>\n> diff --git a/build_example.sh b/build_example.sh\n> new file mode 100755\n> index 0000000000..60f63c5344\n> --- /dev/null\n> +++ b/build_example.sh\n> @@ -0,0 +1,61 @@\n> +#!/bin/bash\n> +\n> +################################################################################\n> +# Example #1\n> +################################################################################\n> +#\n> +# This will create this a repo with this history structure:\n> +#\n> +#       a * (master)       b * (branch)\n> +#         |                  |\n> +#       2 * -----------------'\n> +#         |\n> +#       1 *\n> +#         |\n> +#   start *\n> +\n> +rm -rf EXAMPLE1 || true; mkdir -p EXAMPLE1; cd EXAMPLE1\n> +\n> +touch start.txt; git init; git add start.txt; git commit -m 'Start'\n> +touch 1.txt; git add 1.txt; git commit -m '1'\n> +touch 2.txt; git add 2.txt; git commit -m '2'\n> +touch a.txt; git add a.txt; git commit -m 'a'\n> +git checkout -b branch HEAD^\n> +touch b.txt; git add b.txt; git commit -m 'b'\n> +\n> +git log --graph --all --oneline --simplify-by-decoration\n> +# The previous command will print this, which is what we expected:\n> +#\n> +#   * 0f3892d (HEAD -> branch) b\n> +#   | * 0c37d8c (master) a\n> +#   |/\n> +#   * d783631 Start\n> +\n> +cd ..\n> +\n> +\n> +\n> +################################################################################\n> +# Example #2\n> +################################################################################\n> +#\n> +# This will create this a repo with the same structure as before, but this time\n> +# the root commit (\"start\") is an empty one.\n> +\n> +rm -rf EXAMPLE2 || true; mkdir -p EXAMPLE2; cd EXAMPLE2\n> +\n> +git init; git commit --allow-empty -m 'Start'\n> +touch 1.txt; git add 1.txt; git commit -m '1'\n> +touch 2.txt; git add 2.txt; git commit -m '2'\n> +touch a.txt; git add a.txt; git commit -m 'a'\n> +git checkout -b branch HEAD^\n> +touch b.txt; git add b.txt; git commit -m 'b'\n> +\n> +git log --graph --all --oneline --simplify-by-decoration\n> +# The previous command will print this, which is unexpected:\n> +#\n> +#   * 7abc10a (HEAD -> branch) b\n> +#   * 14d51e1 (master) a\n> +\n> +cd ..\n> +\n>\n>\n\nOne more thing, even if it's an RFC it would be much easier for reviewers if the\npatches were sent as git format-patch (one each email) with a cover letter,\ndiscussing your RFC, so anyone can use git am and b4 to apply the patches.\n\nHope this helps,\nPablo\n"},{"id":"540930","messageId":"adFu7wLmro5A9NDr@x395.localdomain","threadId":"65435","inReplyTo":"CAN5EUNTUDT7FMNYTOnTA_GJJXa2eZZL=RhJHfA6PfJvFSSHL-g@mail.gmail.com","subject":"Re: [RFC] Show empty root commits when using --simplify-by-decoration","fromName":"Fernando Ramos","fromEmail":"greenfoo@u92.eu","sentAt":"2026-04-04T20:05:03Z","receivedAt":"2026-04-04T20:05:09Z","isPatch":false,"body":"On 26/04/04 06:08PM, Pablo wrote:\n> >\n> > Issue (2) can be \"fixed\" by using a diferent character (for example \"I\") for\n> > root commits...\n> \n> This has actually been discussed before and it was ruled out some years ago.\n>   https://lore.kernel.org/git/xmqqwnwajbuj.fsf@gitster.c.googlers.com/\n>\n> You might find interesting a patch I'm working on that tackles that\n> confusing look when two \"parentless\" commits are stacked.\n>   https://lore.kernel.org/git/20260404092425.550346-1-pabloosabaterr@gmail.com/\n> \n> This answer from junio might be helpful as well:\n>   https://lore.kernel.org/git/xmqqbjfzn6ku.fsf@gitster.g/\n\nUps... I completely missed those on the very quick search I did on the topic.\nSorry.  I will ignore \"Issue (2)\" then and wait for your patch :)\n\n\n> One more thing, even if it's an RFC it would be much easier for reviewers if\n> the patches were sent as git format-patch (one each email) with a cover\n> letter, discussing your RFC, so anyone can use git am and b4 to apply the\n> patches.\n\nThanks, I was not sure whether I should send individual emails for this,\nconsidering that, after the feedback, they might be dropped.  But now I know.\n\nOnce I receive freedback on whether the \"fix\" for \"Issue (1)\" is a good idea or\nnot, I will resubmit a proper patch.\n\nThanks.\n"}]}