All of lore.kernel.org
 help / color / mirror / Atom feed
From: Derrick Stolee <stolee@gmail.com>
To: git@vger.kernel.org
Cc: emilyshaffer@google.com, me@jramsay.com.au,
	Derrick Stolee <dstolee@microsoft.com>
Subject: [Git Developer Blog] [Blog Post] Updates to the Git Commit Graph Feature
Date: Mon, 21 Oct 2019 16:42:53 -0400	[thread overview]
Message-ID: <20191021204253.10196-1-dstolee@microsoft.com> (raw)

In this blog post, we discuss updates in to the Git commit-graph
feature since it was announced shortly after Git 2.18.0. This
answers the following:

1. What is the commit-graph?
2. Why should I enable the commit-graph?
3. How do I enable it now, or disable in time for 2.24.0?
4. How do I write it during fetch?
5. What is a commit-graph chain?

I intended the post to start at a level that any experienced Git
user could understand, but then it ramps up in detail by the end
to include some very deep technical details. I expect most readers
to give up after the first couple sections, but those who stick
to the end will learn something valuable.

Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
---

Inspired by Emily's first contribution in the space [1], I now finished this
post that I've been working on here-and-there since Git 2.23.0. Now that 2.24.0
is imminent, the post is even more relevant. This is available as a Merge
Request [2] on the git-blog repo. You should be able to see the markdown style
and the SVG better there.

To create the image, I used Inkscape, then SVGMinify.com to reduce the file
size, which hopefully makes this patch less obnoxious.

[1] https://public-inbox.org/git/20191019002045.148579-1-emilyshaffer@google.com/

[2] https://gitlab.com/git-scm/blog/merge_requests/5/diffs#241fcd5d0d81901ba224af0d603905f593aaf821

 .../post/2019-10-31-commit-graph-updates.md   | 268 ++++++++++++++++++
 content/post/img/2019-commit-graph-chain.svg  |  85 ++++++
 2 files changed, 353 insertions(+)
 create mode 100644 content/post/2019-10-31-commit-graph-updates.md
 create mode 100644 content/post/img/2019-commit-graph-chain.svg

diff --git a/content/post/2019-10-31-commit-graph-updates.md b/content/post/2019-10-31-commit-graph-updates.md
new file mode 100644
index 0000000..abf6a14
--- /dev/null
+++ b/content/post/2019-10-31-commit-graph-updates.md
@@ -0,0 +1,268 @@
+---
+title: Updates to the Git Commit Graph Feature
+author: Derrick Stolee, Microsoft
+date: '2019-10-31'
+draft: true
+categories:
+  - Feature Announcement
+tags:
+  - commit-graph
+  - performance
+  - features
+---
+
+In [a previous blog series](https://devblogs.microsoft.com/devops/supercharging-the-git-commit-graph/),
+we announced that Git has a new _commit graph_ feature, and described some
+future directions. Since then, the commit-graph feature has grown and evolved.
+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)
+Today, we discuss what you should know about the feature, and what you can
+expect when you upgrade.
+
+# What is the commit-graph, and what is it good for?
+
+The commit-graph file is a binary file format that creates a structured
+representation of Git's commit history. At minimum, the commit-graph file
+is [faster to parse](https://github.com/git/git/blob/master/commit-graph.c#L606-L668)
+than decompressing commit files and [parsing them](https://github.com/git/git/blob/master/commit.c#L395-L455)
+to find their parents and root trees. This faster parsing can lead to 10x
+performance improvements.
+
+To get even more performance benefits, Git does not just use the commit-graph
+file to parse commits faster, but the commit-graph includes extra information
+to help avoid parsing some commits altogether. The critical idea is that an
+extra value -- the [generation number](https://devblogs.microsoft.com/devops/supercharging-the-git-commit-graph-iii-generations/)
+of a commit -- can significantly reduce the number of commits we need to walk
+to decide reachability. Since Git 2.19.0, the commit-graph
+[stores generation numbers](https://github.com/git/git/commit/3258c66332abaf6e3e8fd81cab07ae804760cd08).
+
+Since then, multiple algorithms were introduced to speed up Git commands such as
+[force push](https://github.com/git/git/commit/1e3497a24cf13fe907b247d1b93a997d6537cca1) or
+[fetch negotiation](https://github.com/git/git/commit/4fbcca4effc1c6f8431120f88f5a4bd1c8e38ca3).
+
+Finally, the most immediately visible improvement is the time it takes to sort
+commits by topological order. This algorithm is the critical path for `git log
+--graph`. Before the commit-graph, Git needed to walk every reachable commit
+before returning a single result.
+
+For example, here is a run of `git log --graph` in the Linux repository without
+the commit-graph feature, timing how long it takes to return ten results:
+
+```
+$ time git -c core.commitGraph=false log --graph --oneline -10 >/dev/null
+
+real    0m6.103s
+user    0m5.803s
+sys     0m0.300s
+```
+
+The reason it takes so long is because [Kahn's algorithm](https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm)
+computes the "in-degrees" of every reachable commit before it can start to
+select commits of in-degree zero for output. When the commit-graph is present
+with generation numbers, Git now [uses an iterative version of Kahn's algorithm](https://github.com/git/git/commit/b45424181e9e8b2284a48c6db7b8db635bbfccc8)
+to avoid walking too far before knowing that some of the commits have in-degree
+zero and can be sent to output.
+
+Here is that same command again, this time with the commit-graph feature enabled:
+
+```
+$ time git -c core.commitGraph=true log --graph --oneline -10 >/dev/null
+
+real    0m0.009s
+user    0m0.000s
+sys     0m0.008s
+```
+
+Six seconds to nine milliseconds is a 650x speedup! Since most users asking
+for `git log --graph` actually see the result in a paged terminal window,
+this allows Git to load the first page of results almost instantaneously, and
+the next pages are available as you scroll through the history.
+
+The incremental nature of the algorithm can really be seen in real-time by
+using `git log --graph -- <file>` to investigate file history. When asking for
+a file that is not edited frequently, Git will still need to walk many commits
+to return the few results. For example, the Linux kernel `README` was edited
+in 46 of the 870,000+ commits of the repo. Running `git log --graph -- README`
+takes 0.33 seconds, but even in that time you can see the commits filling the
+page one-by-one. Without the commit-graph, the command pauses while it computes,
+then all results fill the page at the same time.
+
+# Sounds Great! What do I need to do?
+
+If you are using Git 2.23.0 or later, then you have all of these benefits
+available to you! You just need to enable the following config settings:
+
+* `git config --global core.commitGraph true`: this enables every Git repo to
+  use the commit-graph file, if present.
+
+* `git config --global gc.writeCommitGraph true`: this setting tells the `git gc`
+  command to write the commit-graph file whenever you do non-trivial garbage
+  collection. Rewriting the commit-graph file is a relatively small operation
+  compared to a full GC operation.
+
+* `git commit-graph write --reachable`: this command will update your
+  commit-graph file to contain all reachable commits. You can run this to create
+  the commit-graph file immediately, instead of waiting for your first GC operation.
+
+In the upcoming Git version 2.24.0, `core.commitGraph` and `gc.writeCommitGraph` 
+are [on by default](https://github.com/git/git/commit/31b1de6a09bad59cc0d88419925486afc7add277),
+so you don't need to set the config manually. If you _don't_ want commit-graph
+files, then explicitly disable these settings.
+
+# Write during fetch
+
+The point of the `gc.writeCommitGraph` is to keep your commit-graph updated
+with some frequency. As you add commits to your repo, the commit-graph gets
+further and further behind. That means your commit walks will parse more
+commits the old-fashioned way until finally reaching the commits in the
+commit-graph file.
+
+When working in a Git repo with many collaborators, the primary source of
+commits is not your own `git commit` calls, but your `git fetch` calls.
+However, if your repo is large enough, writing the commit-graph after each
+fetch can actually make a significant impact on your performance. Perhaps
+you downloaded a thousand new commits, but your repo has a million total
+commits. Writing the full commit-graph operates on the size of your repo,
+not on the size of your fetch, so writing those million commits is costly.
+
+During garbage collection, you are already paying for a full repack of all
+of your Git objects. That operation is already on the scale of your entire
+repository, so adding a full commit-graph write on top of that is not
+a problem.
+
+There is a solution: don't write the whole commit-graph every time! We'll
+go into how this works in more detail in the next section, but first you
+can enable the [`fetch.writeCommitGraph`](https://github.com/git/git/commit/50f26bd035816c2bb79582b834d59b49292502a9#diff-ec15845924b3ae854680823745518271)
+config setting to write the commit-graph after every `git fetch` command:
+
+```
+git config --global fetch.writeCommitGraph true
+```
+
+This ensures that your commit-graph is updated frequently and your Git
+commands are always as fast as possible.
+
+# Incremental Commit-Graph Format
+
+Before getting too far into the inremental file format, we need to refresh some
+details about the commit-graph file itself.
+
+## A Single Commit-Graph File
+
+The [commit-graph file format](https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt)
+stores commit data in a set of tables.
+
+One table is a sorted list of commit IDs. This row number of a commit ID in this
+table defines the _lexicographical position_ -- _lex_ position for short -- of
+a commit in the file.
+
+Another table contains metadata about the commits. The _n_th row of the metadata
+table corresponds to the commit with lex position _n_. This table contains the
+root tree ID, commit time, generation number, and information on the first two
+parents of the commit. We use special constants to say "this commit does not
+have a second parent", and use a pointer to a third "extra edges" table in the
+case of octopus merges.
+
+The two parent columns are stored as integers, and this is very important! If
+we store parents as commit IDs, then we waste a lot of space. Further, if we
+only have a commit ID, then we need to perform a binary search on the commit
+list to find the lex position. By storing the position of a parent, we can
+navigate to the metadata row for that parent as a random-access lookup.
+
+For that reason, the commit-graph file is _closed under reachability_, meaning
+that if a commit is in the file, then so is its parent. Otherwise, we could not
+refer to the parent using an integer.
+
+Before incremental writes, Git stored the commit-graph file as
+`.git/objects/info/commit-graph`. Git looks for that file, and parses the data
+there if it exists.
+
+## Multiple Commit-Graph Files
+
+If the single `.git/objects/info/commit-graph` file does not exist, Git looks
+for a file called `.git/objects/info/commit-graphs/commit-graph-chain`. This file
+contains a list of SHA-1 hashes separated by newlines. To demonstrate, we
+will use this list of placeholders:
+
+```
+{hash0}
+{hash1}
+{hash2}
+```
+
+These hashes correspond to files named
+`.git/objects/info/commit-graphs/graph-{hash0}.graph`. The chain of the three
+files combine to describe a set of commits.
+
+The first graph file, `graph-{hash0}.graph`, is a normal commit-graph file. It
+does not refer to any other commit-graph and is closed under reachability.
+
+The second graph file, `graph-{hash1}.graph` is no longer a normal commit-graph
+file. To start, it contains a pointer to `graph-{hash0}.graph` by storing an
+extra "base graphs" table containing only "{hash0}". Second, the parents of the
+commits in `graph-{hash1}.graph` may exist in that file _or_ in `graph-{hash0}.graph`.
+Each graph file stores the commits in lexicographic order, but we now need a
+second term for the position of a commit in the combined order.
+
+We say the _graph position_ of a commit in the commit-graph chain is the lex
+position of a commit in the sorted list plus the number of commits in the base
+commmit-graph files. We now modify our definition of a parent position to use
+the graph position. This allows the `graph-{hash1}.graph` file to not be closed
+under reachability: the parents can exist in either file.
+
+Extending to `graph-{hash2}.graph`, the parents of those commits can be in any
+of the three commit-graph files. The figure below shows this stack of files and
+how one commit row in `graph-{hash2}.graph` can have parents in `graph-{hash1}.graph`
+and `graph-{hash0}.graph`.
+
+![A chain of three commit-graphs](img/2019-commit-graph-chain.svg)
+
+To create your own commit-graph chain, you can start with an existing commit-graph
+file (created by `git commit-graph write --reachable`, for instance) then create
+new commits and run `git commit-graph write --reachable --split`. The `--split`
+option enables creating a chain of commit-graph files. If you ever run the command
+without the `--split` option, then the chain will merge into a single file.
+
+If you enable `fetch.writeCommitGraph`, then Git will write a commit-graph
+chain after every `git fetch` command. This is much faster than rewriting the
+entire file, since the top layer of the chain can consist of only the new
+commits. At least, it will _usually_ be faster.
+
+The figure above hints at the sizes of the commit-graph files in a chain. The
+base file is large and contains most of the commits. As we look higher in the
+chain, the sizes should shrink.
+
+There is a problem, though. What happens when we fetch 100 times? Will we get
+a chain of 100 commit-graph files? Will our commit lookups suddenly get much
+slower? The way to avoid this is to occasionally merge layers of the chain.
+This results in better [amortized time](https://en.wikipedia.org/wiki/Amortized_analysis),
+but will sometimes result in a full rewrite of the entire commit-graph file.
+
+## Merging Commit-Graph Files
+
+To ensure that the commit-graph chain does not get too long, Git will occasionally
+merge layers of the chain. This merge operation is always due to some number of
+incoming commits causing the "top" of the chain to be too large. There are two
+reasons Git would merge layers, given by these options to `git commit-graph
+write`:
+
+1. `--size-multiple=<X>`: Ensure that a commit-graph file is `X` times larger
+   than any commit-graph file "above" it. `X` defaults to 2.
+
+2. `--max-commits=<M>`: When specified, make sure that only the base layer
+   has more than `M` commits.
+
+The size-multiple option ensures that the commit-graph chain never has more
+than log(_N_) layers, where _N_ is total number of commits in the repo. If those
+chains seem to be too long, the max-commits setting (in conjunction with size-multiple)
+guarantees that there are a constant number of possible layers.
+
+In all, you should not see the incremental commit-graph taking very long during
+a fetch. You are more likely to see the automatic garbage collection trigger, and
+that will cause your commit-graph chain to collapse to a single layer.
+
+# Try it Yourself!
+
+We would love your feedback on the feature! Please test out the `--split` option
+for writing commit-graphs in Git 2.23.0 or later, and the `fetch.writeCommitGraph`
+option in Git 2.24.0. Release candidates for Git 2.24.0 are out now, and ready
+for testing!
diff --git a/content/post/img/2019-commit-graph-chain.svg b/content/post/img/2019-commit-graph-chain.svg
new file mode 100644
index 0000000..b426853
--- /dev/null
+++ b/content/post/img/2019-commit-graph-chain.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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#">
+<defs>
+<marker id="b" overflow="visible" orient="auto">
+<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"/>
+</marker>
+<marker id="g" overflow="visible" orient="auto">
+<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"/>
+</marker>
+<marker id="c" overflow="visible" orient="auto">
+<path transform="scale(.8)" d="m0 5.65v-11.3" fill="none" stroke="#000" stroke-width="1pt"/>
+</marker>
+<marker id="d" overflow="visible" orient="auto">
+<path transform="scale(.8)" d="m0 5.65v-11.3" fill="none" stroke="#000" stroke-width="1pt"/>
+</marker>
+<marker id="e" overflow="visible" orient="auto">
+<path transform="scale(.8)" d="m0 5.65v-11.3" fill="none" stroke="#000" stroke-width="1pt"/>
+</marker>
+<marker id="f" overflow="visible" orient="auto">
+<path transform="scale(.8)" d="m0 5.65v-11.3" fill="none" stroke="#000" stroke-width="1pt"/>
+</marker>
+<marker id="a" overflow="visible" orient="auto">
+<path transform="scale(.8)" d="m0 5.65v-11.3" fill="none" stroke="#000" stroke-width="1pt"/>
+</marker>
+</defs>
+<metadata>
+<rdf:RDF>
+<cc:Work rdf:about="">
+<dc:format>image/svg+xml</dc:format>
+<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+<dc:title/>
+</cc:Work>
+</rdf:RDF>
+</metadata>
+<g transform="translate(4.2812 -168.05)">
+<rect x="-4.21" y="168.04" width="144.45" height="133.58" fill="#fff" stroke="#fff" stroke-width=".31055"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<path d="m26.414 178.35h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 183.64h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 201.63h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 206.93h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 212.22h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 217.51h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 222.8h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 240.79h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 246.08h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 251.38h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 256.67h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 261.96h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 267.25h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 272.54h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 277.83h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 283.13h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 288.42h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m26.414 293.71h95.909" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m21.281 188.13v-9.7292" fill="none" marker-end="url(#a)" marker-start="url(#a)" stroke="#000" stroke-width=".265"/>
+<path d="m8.0517 299.26v-120.85" fill="none" marker-end="url(#e)" marker-start="url(#f)" stroke="#000" stroke-width=".265"/>
+<path d="m21.281 299.26v-103.39" fill="none" marker-end="url(#c)" marker-start="url(#d)" stroke="#000" stroke-width=".265"/>
+<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>
+<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>
+<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>
+<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>
+<rect x="26.414" y="178.35" width="95.909" height="5.2917" fill="#fca" stroke="#000" stroke-width=".3026"/>
+<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>
+<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>
+<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>
+<path d="m56.847 173.34v15.348" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m81.718 173.34v15.348" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m56.847 196.62v30.694" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m81.718 196.62v30.694" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m56.847 235.78v63.502" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m81.718 235.78v63.502" fill="none" stroke="#000" stroke-width=".26458px"/>
+<path d="m103.41 173.34v15.348" fill="none" stroke="#000" stroke-dasharray="2.1199999, 2.1199999" stroke-width=".265"/>
+<path d="m103.41 196.62v31.223" fill="none" stroke="#000" stroke-dasharray="2.1199999, 2.1199999" stroke-width=".265"/>
+<path d="m103.41 235.78v62.973" fill="none" stroke="#000" stroke-dasharray="2.1199999, 2.1199999" stroke-width=".265"/>
+<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>
+<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>
+<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>
+<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>
+</g>
+</svg>
-- 
2.23.0.vfs.1.1.87.g1f3c9be


                 reply	other threads:[~2019-10-21 20:43 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191021204253.10196-1-dstolee@microsoft.com \
    --to=stolee@gmail.com \
    --cc=dstolee@microsoft.com \
    --cc=emilyshaffer@google.com \
    --cc=git@vger.kernel.org \
    --cc=me@jramsay.com.au \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.