{"thread":{"id":"52086","subject":"[Git Developer Blog] [Blog Post] Updates to the Git Commit Graph Feature","startedAt":"2019-10-21T20:43:08Z","lastAt":"2019-10-21T20:43:08Z","messageCount":1,"participants":["Derrick Stolee"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"384564","messageId":"20191021204253.10196-1-dstolee@microsoft.com","threadId":"52086","inReplyTo":null,"subject":"[Git Developer Blog] [Blog Post] Updates to the Git Commit Graph Feature","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-21T20:42:53Z","receivedAt":"2019-10-21T20:43:08Z","isPatch":false,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"In this blog post, we discuss updates in to the Git commit-graph\nfeature since it was announced shortly after Git 2.18.0. This\nanswers the following:\n\n1. What is the commit-graph?\n2. Why should I enable the commit-graph?\n3. How do I enable it now, or disable in time for 2.24.0?\n4. How do I write it during fetch?\n5. What is a commit-graph chain?\n\nI intended the post to start at a level that any experienced Git\nuser could understand, but then it ramps up in detail by the end\nto include some very deep technical details. I expect most readers\nto give up after the first couple sections, but those who stick\nto the end will learn something valuable.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n\nInspired by Emily's first contribution in the space [1], I now finished this\npost that I've been working on here-and-there since Git 2.23.0. Now that 2.24.0\nis imminent, the post is even more relevant. This is available as a Merge\nRequest [2] on the git-blog repo. You should be able to see the markdown style\nand the SVG better there.\n\nTo create the image, I used Inkscape, then SVGMinify.com to reduce the file\nsize, which hopefully makes this patch less obnoxious.\n\n[1] https://public-inbox.org/git/20191019002045.148579-1-emilyshaffer@google.com/\n\n[2] https://gitlab.com/git-scm/blog/merge_requests/5/diffs#241fcd5d0d81901ba224af0d603905f593aaf821\n\n .../post/2019-10-31-commit-graph-updates.md   | 268 ++++++++++++++++++\n content/post/img/2019-commit-graph-chain.svg  |  85 ++++++\n 2 files changed, 353 insertions(+)\n create mode 100644 content/post/2019-10-31-commit-graph-updates.md\n create mode 100644 content/post/img/2019-commit-graph-chain.svg\n\ndiff --git a/content/post/2019-10-31-commit-graph-updates.md b/content/post/2019-10-31-commit-graph-updates.md\nnew file mode 100644\nindex 0000000..abf6a14\n--- /dev/null\n+++ b/content/post/2019-10-31-commit-graph-updates.md\n@@ -0,0 +1,268 @@\n+---\n+title: Updates to the Git Commit Graph Feature\n+author: Derrick Stolee, Microsoft\n+date: '2019-10-31'\n+draft: true\n+categories:\n+  - Feature Announcement\n+tags:\n+  - commit-graph\n+  - performance\n+  - features\n+---\n+\n+In [a previous blog series](https://devblogs.microsoft.com/devops/supercharging-the-git-commit-graph/),\n+we announced that Git has a new _commit graph_ feature, and described some\n+future directions. Since then, the commit-graph feature has grown and evolved.\n+In the upcoming Git version 2.24.0, [the commit-graph will be enabled by default!](https://github.com/git/git/commit/31b1de6a09bad59cc0d88419925486afc7add277#diff-ec15845924b3ae854680823745518271)\n+Today, we discuss what you should know about the feature, and what you can\n+expect when you upgrade.\n+\n+# What is the commit-graph, and what is it good for?\n+\n+The commit-graph file is a binary file format that creates a structured\n+representation of Git's commit history. At minimum, the commit-graph file\n+is [faster to parse](https://github.com/git/git/blob/master/commit-graph.c#L606-L668)\n+than decompressing commit files and [parsing them](https://github.com/git/git/blob/master/commit.c#L395-L455)\n+to find their parents and root trees. This faster parsing can lead to 10x\n+performance improvements.\n+\n+To get even more performance benefits, Git does not just use the commit-graph\n+file to parse commits faster, but the commit-graph includes extra information\n+to help avoid parsing some commits altogether. The critical idea is that an\n+extra value -- the [generation number](https://devblogs.microsoft.com/devops/supercharging-the-git-commit-graph-iii-generations/)\n+of a commit -- can significantly reduce the number of commits we need to walk\n+to decide reachability. Since Git 2.19.0, the commit-graph\n+[stores generation numbers](https://github.com/git/git/commit/3258c66332abaf6e3e8fd81cab07ae804760cd08).\n+\n+Since then, multiple algorithms were introduced to speed up Git commands such as\n+[force push](https://github.com/git/git/commit/1e3497a24cf13fe907b247d1b93a997d6537cca1) or\n+[fetch negotiation](https://github.com/git/git/commit/4fbcca4effc1c6f8431120f88f5a4bd1c8e38ca3).\n+\n+Finally, the most immediately visible improvement is the time it takes to sort\n+commits by topological order. This algorithm is the critical path for `git log\n+--graph`. Before the commit-graph, Git needed to walk every reachable commit\n+before returning a single result.\n+\n+For example, here is a run of `git log --graph` in the Linux repository without\n+the commit-graph feature, timing how long it takes to return ten results:\n+\n+```\n+$ time git -c core.commitGraph=false log --graph --oneline -10 >/dev/null\n+\n+real    0m6.103s\n+user    0m5.803s\n+sys     0m0.300s\n+```\n+\n+The reason it takes so long is because [Kahn's algorithm](https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm)\n+computes the \"in-degrees\" of every reachable commit before it can start to\n+select commits of in-degree zero for output. When the commit-graph is present\n+with generation numbers, Git now [uses an iterative version of Kahn's algorithm](https://github.com/git/git/commit/b45424181e9e8b2284a48c6db7b8db635bbfccc8)\n+to avoid walking too far before knowing that some of the commits have in-degree\n+zero and can be sent to output.\n+\n+Here is that same command again, this time with the commit-graph feature enabled:\n+\n+```\n+$ time git -c core.commitGraph=true log --graph --oneline -10 >/dev/null\n+\n+real    0m0.009s\n+user    0m0.000s\n+sys     0m0.008s\n+```\n+\n+Six seconds to nine milliseconds is a 650x speedup! Since most users asking\n+for `git log --graph` actually see the result in a paged terminal window,\n+this allows Git to load the first page of results almost instantaneously, and\n+the next pages are available as you scroll through the history.\n+\n+The incremental nature of the algorithm can really be seen in real-time by\n+using `git log --graph -- <file>` to investigate file history. When asking for\n+a file that is not edited frequently, Git will still need to walk many commits\n+to return the few results. For example, the Linux kernel `README` was edited\n+in 46 of the 870,000+ commits of the repo. Running `git log --graph -- README`\n+takes 0.33 seconds, but even in that time you can see the commits filling the\n+page one-by-one. Without the commit-graph, the command pauses while it computes,\n+then all results fill the page at the same time.\n+\n+# Sounds Great! What do I need to do?\n+\n+If you are using Git 2.23.0 or later, then you have all of these benefits\n+available to you! You just need to enable the following config settings:\n+\n+* `git config --global core.commitGraph true`: this enables every Git repo to\n+  use the commit-graph file, if present.\n+\n+* `git config --global gc.writeCommitGraph true`: this setting tells the `git gc`\n+  command to write the commit-graph file whenever you do non-trivial garbage\n+  collection. Rewriting the commit-graph file is a relatively small operation\n+  compared to a full GC operation.\n+\n+* `git commit-graph write --reachable`: this command will update your\n+  commit-graph file to contain all reachable commits. You can run this to create\n+  the commit-graph file immediately, instead of waiting for your first GC operation.\n+\n+In the upcoming Git version 2.24.0, `core.commitGraph` and `gc.writeCommitGraph` \n+are [on by default](https://github.com/git/git/commit/31b1de6a09bad59cc0d88419925486afc7add277),\n+so you don't need to set the config manually. If you _don't_ want commit-graph\n+files, then explicitly disable these settings.\n+\n+# Write during fetch\n+\n+The point of the `gc.writeCommitGraph` is to keep your commit-graph updated\n+with some frequency. As you add commits to your repo, the commit-graph gets\n+further and further behind. That means your commit walks will parse more\n+commits the old-fashioned way until finally reaching the commits in the\n+commit-graph file.\n+\n+When working in a Git repo with many collaborators, the primary source of\n+commits is not your own `git commit` calls, but your `git fetch` calls.\n+However, if your repo is large enough, writing the commit-graph after each\n+fetch can actually make a significant impact on your performance. Perhaps\n+you downloaded a thousand new commits, but your repo has a million total\n+commits. Writing the full commit-graph operates on the size of your repo,\n+not on the size of your fetch, so writing those million commits is costly.\n+\n+During garbage collection, you are already paying for a full repack of all\n+of your Git objects. That operation is already on the scale of your entire\n+repository, so adding a full commit-graph write on top of that is not\n+a problem.\n+\n+There is a solution: don't write the whole commit-graph every time! We'll\n+go into how this works in more detail in the next section, but first you\n+can enable the [`fetch.writeCommitGraph`](https://github.com/git/git/commit/50f26bd035816c2bb79582b834d59b49292502a9#diff-ec15845924b3ae854680823745518271)\n+config setting to write the commit-graph after every `git fetch` command:\n+\n+```\n+git config --global fetch.writeCommitGraph true\n+```\n+\n+This ensures that your commit-graph is updated frequently and your Git\n+commands are always as fast as possible.\n+\n+# Incremental Commit-Graph Format\n+\n+Before getting too far into the inremental file format, we need to refresh some\n+details about the commit-graph file itself.\n+\n+## A Single Commit-Graph File\n+\n+The [commit-graph file format](https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt)\n+stores commit data in a set of tables.\n+\n+One table is a sorted list of commit IDs. This row number of a commit ID in this\n+table defines the _lexicographical position_ -- _lex_ position for short -- of\n+a commit in the file.\n+\n+Another table contains metadata about the commits. The _n_th row of the metadata\n+table corresponds to the commit with lex position _n_. This table contains the\n+root tree ID, commit time, generation number, and information on the first two\n+parents of the commit. We use special constants to say \"this commit does not\n+have a second parent\", and use a pointer to a third \"extra edges\" table in the\n+case of octopus merges.\n+\n+The two parent columns are stored as integers, and this is very important! If\n+we store parents as commit IDs, then we waste a lot of space. Further, if we\n+only have a commit ID, then we need to perform a binary search on the commit\n+list to find the lex position. By storing the position of a parent, we can\n+navigate to the metadata row for that parent as a random-access lookup.\n+\n+For that reason, the commit-graph file is _closed under reachability_, meaning\n+that if a commit is in the file, then so is its parent. Otherwise, we could not\n+refer to the parent using an integer.\n+\n+Before incremental writes, Git stored the commit-graph file as\n+`.git/objects/info/commit-graph`. Git looks for that file, and parses the data\n+there if it exists.\n+\n+## Multiple Commit-Graph Files\n+\n+If the single `.git/objects/info/commit-graph` file does not exist, Git looks\n+for a file called `.git/objects/info/commit-graphs/commit-graph-chain`. This file\n+contains a list of SHA-1 hashes separated by newlines. To demonstrate, we\n+will use this list of placeholders:\n+\n+```\n+{hash0}\n+{hash1}\n+{hash2}\n+```\n+\n+These hashes correspond to files named\n+`.git/objects/info/commit-graphs/graph-{hash0}.graph`. The chain of the three\n+files combine to describe a set of commits.\n+\n+The first graph file, `graph-{hash0}.graph`, is a normal commit-graph file. It\n+does not refer to any other commit-graph and is closed under reachability.\n+\n+The second graph file, `graph-{hash1}.graph` is no longer a normal commit-graph\n+file. To start, it contains a pointer to `graph-{hash0}.graph` by storing an\n+extra \"base graphs\" table containing only \"{hash0}\". Second, the parents of the\n+commits in `graph-{hash1}.graph` may exist in that file _or_ in `graph-{hash0}.graph`.\n+Each graph file stores the commits in lexicographic order, but we now need a\n+second term for the position of a commit in the combined order.\n+\n+We say the _graph position_ of a commit in the commit-graph chain is the lex\n+position of a commit in the sorted list plus the number of commits in the base\n+commmit-graph files. We now modify our definition of a parent position to use\n+the graph position. This allows the `graph-{hash1}.graph` file to not be closed\n+under reachability: the parents can exist in either file.\n+\n+Extending to `graph-{hash2}.graph`, the parents of those commits can be in any\n+of the three commit-graph files. The figure below shows this stack of files and\n+how one commit row in `graph-{hash2}.graph` can have parents in `graph-{hash1}.graph`\n+and `graph-{hash0}.graph`.\n+\n+![A chain of three commit-graphs](img/2019-commit-graph-chain.svg)\n+\n+To create your own commit-graph chain, you can start with an existing commit-graph\n+file (created by `git commit-graph write --reachable`, for instance) then create\n+new commits and run `git commit-graph write --reachable --split`. The `--split`\n+option enables creating a chain of commit-graph files. If you ever run the command\n+without the `--split` option, then the chain will merge into a single file.\n+\n+If you enable `fetch.writeCommitGraph`, then Git will write a commit-graph\n+chain after every `git fetch` command. This is much faster than rewriting the\n+entire file, since the top layer of the chain can consist of only the new\n+commits. At least, it will _usually_ be faster.\n+\n+The figure above hints at the sizes of the commit-graph files in a chain. The\n+base file is large and contains most of the commits. As we look higher in the\n+chain, the sizes should shrink.\n+\n+There is a problem, though. What happens when we fetch 100 times? Will we get\n+a chain of 100 commit-graph files? Will our commit lookups suddenly get much\n+slower? The way to avoid this is to occasionally merge layers of the chain.\n+This results in better [amortized time](https://en.wikipedia.org/wiki/Amortized_analysis),\n+but will sometimes result in a full rewrite of the entire commit-graph file.\n+\n+## Merging Commit-Graph Files\n+\n+To ensure that the commit-graph chain does not get too long, Git will occasionally\n+merge layers of the chain. This merge operation is always due to some number of\n+incoming commits causing the \"top\" of the chain to be too large. There are two\n+reasons Git would merge layers, given by these options to `git commit-graph\n+write`:\n+\n+1. `--size-multiple=<X>`: Ensure that a commit-graph file is `X` times larger\n+   than any commit-graph file \"above\" it. `X` defaults to 2.\n+\n+2. `--max-commits=<M>`: When specified, make sure that only the base layer\n+   has more than `M` commits.\n+\n+The size-multiple option ensures that the commit-graph chain never has more\n+than log(_N_) layers, where _N_ is total number of commits in the repo. If those\n+chains seem to be too long, the max-commits setting (in conjunction with size-multiple)\n+guarantees that there are a constant number of possible layers.\n+\n+In all, you should not see the incremental commit-graph taking very long during\n+a fetch. You are more likely to see the automatic garbage collection trigger, and\n+that will cause your commit-graph chain to collapse to a single layer.\n+\n+# Try it Yourself!\n+\n+We would love your feedback on the feature! Please test out the `--split` option\n+for writing commit-graphs in Git 2.23.0 or later, and the `fetch.writeCommitGraph`\n+option in Git 2.24.0. Release candidates for Git 2.24.0 are out now, and ready\n+for testing!\ndiff --git a/content/post/img/2019-commit-graph-chain.svg b/content/post/img/2019-commit-graph-chain.svg\nnew file mode 100644\nindex 0000000..b426853\n--- /dev/null\n+++ b/content/post/img/2019-commit-graph-chain.svg\n@@ -0,0 +1,85 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n+<svg width=\"5.6894in\" height=\"5.2459in\" version=\"1.1\" viewBox=\"0 0 144.51104 133.24566\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n+<defs>\n+<marker id=\"b\" overflow=\"visible\" orient=\"auto\">\n+<path transform=\"matrix(-.4 0 0 -.4 -4 0)\" d=\"m0 0 5-5-17.5 5 17.5 5z\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1pt\"/>\n+</marker>\n+<marker id=\"g\" overflow=\"visible\" orient=\"auto\">\n+<path transform=\"matrix(-.4 0 0 -.4 -4 0)\" d=\"m0 0 5-5-17.5 5 17.5 5z\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1pt\"/>\n+</marker>\n+<marker id=\"c\" overflow=\"visible\" orient=\"auto\">\n+<path transform=\"scale(.8)\" d=\"m0 5.65v-11.3\" fill=\"none\" stroke=\"#000\" stroke-width=\"1pt\"/>\n+</marker>\n+<marker id=\"d\" overflow=\"visible\" orient=\"auto\">\n+<path transform=\"scale(.8)\" d=\"m0 5.65v-11.3\" fill=\"none\" stroke=\"#000\" stroke-width=\"1pt\"/>\n+</marker>\n+<marker id=\"e\" overflow=\"visible\" orient=\"auto\">\n+<path transform=\"scale(.8)\" d=\"m0 5.65v-11.3\" fill=\"none\" stroke=\"#000\" stroke-width=\"1pt\"/>\n+</marker>\n+<marker id=\"f\" overflow=\"visible\" orient=\"auto\">\n+<path transform=\"scale(.8)\" d=\"m0 5.65v-11.3\" fill=\"none\" stroke=\"#000\" stroke-width=\"1pt\"/>\n+</marker>\n+<marker id=\"a\" overflow=\"visible\" orient=\"auto\">\n+<path transform=\"scale(.8)\" d=\"m0 5.65v-11.3\" fill=\"none\" stroke=\"#000\" stroke-width=\"1pt\"/>\n+</marker>\n+</defs>\n+<metadata>\n+<rdf:RDF>\n+<cc:Work rdf:about=\"\">\n+<dc:format>image/svg+xml</dc:format>\n+<dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\"/>\n+<dc:title/>\n+</cc:Work>\n+</rdf:RDF>\n+</metadata>\n+<g transform=\"translate(4.2812 -168.05)\">\n+<rect x=\"-4.21\" y=\"168.04\" width=\"144.45\" height=\"133.58\" fill=\"#fff\" stroke=\"#fff\" stroke-width=\".31055\"/>\n+<path d=\"m121.93 180.78h5.6241c1.662 0 3 1.338 3 3v28.341c0 1.662-1.338 3-3 3h-3.5074\" fill=\"none\" marker-end=\"url(#g)\" stroke=\"#000\" stroke-width=\".3\"/>\n+<path d=\"m121.93 180.78h8.7991c1.662 0 3 1.338 3 3v62.208c0 1.662-1.338 3-3 3h-6.6824\" fill=\"none\" marker-end=\"url(#b)\" stroke=\"#000\" stroke-width=\".3\"/>\n+<path d=\"m27.217 173.08h94.37c0.554 0 1 0.446 1 1v13.612c0 0.554-0.446 1-1 1h-94.37c-0.554 0-1-0.446-1-1v-13.612c0-0.554 0.446-1 1-1z\" fill=\"#f2f2f2\" stroke=\"#000\"/>\n+<path d=\"m27.217 196.28h94.37c0.554 0 1 0.446 1 1v29.487c0 0.554-0.446 1-1 1h-94.37c-0.554 0-1-0.446-1-1v-29.487c0-0.554 0.446-1 1-1z\" fill=\"#f2f2f2\" stroke=\"#000\"/>\n+<path d=\"m27.217 235.44h94.37c0.554 0 1 0.446 1 1v61.766c0 0.554-0.446 1-1 1h-94.37c-0.554 0-1-0.446-1-1v-61.766c0-0.554 0.446-1 1-1z\" fill=\"#f2f2f2\" stroke=\"#000\"/>\n+<path d=\"m26.414 178.35h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 183.64h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 201.63h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 206.93h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 212.22h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 217.51h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 222.8h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 240.79h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 246.08h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 251.38h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 256.67h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 261.96h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 267.25h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 272.54h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 277.83h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 283.13h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 288.42h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m26.414 293.71h95.909\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m21.281 188.13v-9.7292\" fill=\"none\" marker-end=\"url(#a)\" marker-start=\"url(#a)\" stroke=\"#000\" stroke-width=\".265\"/>\n+<path d=\"m8.0517 299.26v-120.85\" fill=\"none\" marker-end=\"url(#e)\" marker-start=\"url(#f)\" stroke=\"#000\" stroke-width=\".265\"/>\n+<path d=\"m21.281 299.26v-103.39\" fill=\"none\" marker-end=\"url(#c)\" marker-start=\"url(#d)\" stroke=\"#000\" stroke-width=\".265\"/>\n+<text transform=\"rotate(-90)\" x=\"-230.33467\" y=\"6.1092191\" fill=\"#000000\" font-family=\"Arial\" font-size=\"7.0556px\" letter-spacing=\"0px\" stroke-width=\".26458\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"-230.33467\" y=\"6.1092191\" font-size=\"5.6444px\" stroke-width=\".26458\">graph position</tspan></text>\n+<text transform=\"rotate(-90)\" x=\"-239.95189\" y=\"19.898293\" fill=\"#000000\" font-family=\"Arial\" font-size=\"7.0556px\" letter-spacing=\"0px\" stroke-width=\".26458\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"-239.95189\" y=\"19.898293\" font-size=\"5.6444px\" stroke-width=\".26458\">number of base commits</tspan></text>\n+<text transform=\"rotate(-90)\" x=\"-178.97917\" y=\"18.012175\" fill=\"#000000\" font-family=\"Arial\" font-size=\"7.0556px\" letter-spacing=\"0px\" stroke-width=\".26458\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"-178.97917\" y=\"18.012175\" font-size=\"5.6444px\" stroke-width=\".26458\" style=\"line-height:0\">position</tspan></text>\n+<text transform=\"rotate(-90)\" x=\"-184.98222\" y=\"13.381416\" fill=\"#000000\" font-family=\"Arial\" font-size=\"7.0556px\" letter-spacing=\"0px\" stroke-width=\".26458\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"-184.98222\" y=\"13.381416\" font-size=\"5.6444px\" stroke-width=\".26458\" style=\"line-height:0\">lex</tspan></text>\n+<rect x=\"26.414\" y=\"178.35\" width=\"95.909\" height=\"5.2917\" fill=\"#fca\" stroke=\"#000\" stroke-width=\".3026\"/>\n+<text x=\"40.151272\" y=\"182.74898\" fill=\"#000000\" font-family=\"Arial\" font-size=\"4.9389px\" letter-spacing=\"0px\" stroke-width=\".26458\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"40.151272\" y=\"182.74898\" font-size=\"4.9389px\" stroke-width=\".26458\" style=\"line-height:0\">commit id</tspan></text>\n+<text x=\"69.255447\" y=\"182.74898\" fill=\"#000000\" font-family=\"Arial\" font-size=\"4.9389px\" letter-spacing=\"0px\" stroke-width=\".26458\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"69.255447\" y=\"182.74898\" font-size=\"4.9389px\" stroke-width=\".26458\" style=\"line-height:0\">tree id</tspan></text>\n+<text x=\"101.99261\" y=\"182.67987\" fill=\"#000000\" font-family=\"Arial\" font-size=\"4.9389px\" letter-spacing=\"0px\" stroke-width=\".26458\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"101.99261\" y=\"182.67987\" font-size=\"4.9389px\" stroke-width=\".26458\" style=\"line-height:0\">parent positions</tspan></text>\n+<path d=\"m56.847 173.34v15.348\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m81.718 173.34v15.348\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m56.847 196.62v30.694\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m81.718 196.62v30.694\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m56.847 235.78v63.502\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m81.718 235.78v63.502\" fill=\"none\" stroke=\"#000\" stroke-width=\".26458px\"/>\n+<path d=\"m103.41 173.34v15.348\" fill=\"none\" stroke=\"#000\" stroke-dasharray=\"2.1199999, 2.1199999\" stroke-width=\".265\"/>\n+<path d=\"m103.41 196.62v31.223\" fill=\"none\" stroke=\"#000\" stroke-dasharray=\"2.1199999, 2.1199999\" stroke-width=\".265\"/>\n+<path d=\"m103.41 235.78v62.973\" fill=\"none\" stroke=\"#000\" stroke-dasharray=\"2.1199999, 2.1199999\" stroke-width=\".265\"/>\n+<text x=\"44.008171\" y=\"171.36497\" fill=\"#000000\" font-family=\"Arial\" font-size=\"4.4781px\" letter-spacing=\"0px\" stroke-width=\".16793\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"44.008171\" y=\"171.36497\" font-size=\"3.5825px\" stroke-width=\".16793\" style=\"line-height:0\">graph-{hash2}.graph</tspan></text>\n+<text x=\"44.008171\" y=\"171.36497\" fill=\"#000000\" font-family=\"Arial\" font-size=\"4.4781px\" letter-spacing=\"0px\" stroke-width=\".16793\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"44.008171\" y=\"171.36497\" font-size=\"3.5825px\" stroke-width=\".16793\" style=\"line-height:0\">graph-{hash2}.graph</tspan></text>\n+<text x=\"44.008171\" y=\"195.17746\" fill=\"#000000\" font-family=\"Arial\" font-size=\"4.4781px\" letter-spacing=\"0px\" stroke-width=\".16793\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"44.008171\" y=\"195.17746\" font-size=\"3.5825px\" stroke-width=\".16793\" style=\"line-height:0\">graph-{hash1}.graph</tspan></text>\n+<text x=\"44.008171\" y=\"234.3358\" fill=\"#000000\" font-family=\"Arial\" font-size=\"4.4781px\" letter-spacing=\"0px\" stroke-width=\".16793\" text-align=\"center\" text-anchor=\"middle\" word-spacing=\"0px\" style=\"line-height:1.25\" xml:space=\"preserve\"><tspan x=\"44.008171\" y=\"234.3358\" font-size=\"3.5825px\" stroke-width=\".16793\" style=\"line-height:0\">graph-{hash0}.graph</tspan></text>\n+</g>\n+</svg>\n-- \n2.23.0.vfs.1.1.87.g1f3c9be\n\n"}]}