Skip to content

Commit 992366e

Browse files
authored
Add support for Graphviz diagrams (#2052)
## Changes - This adds support for Graphviz diagrams and replaces the `.png`s in https://git-scm.com/about/distributed with scalable versions. ## Context Over in #2049, we discussed various options how to add elegant diagrams, and settled on Graphviz ones. I broke this out so that I don't have to derail the discussion about the cheat sheet anymore. The solution presented here uses [`viz.js`](https://viz-js.com/), a WebAssembly version of Graphviz. Since it weighs a bit much (1.4MB), this is used client-side only when developing the page locally; When the site gets deployed, the SVGs are "pre-rendered", i.e. generated and inserted in place of the `<pre class="graphviz">` blocks. To demonstrate this new feature, I replaced the diagrams in https://git-scm.com/about/distributed with Graphviz ones: | before | after | | - | - | | ![workflow-a as PNG](https://github.com/git/git-scm.com/blob/802cbb4d3b71960972e9a09a78bab439eb303c29/static/images/about/[email protected]?raw=true) | ![workflow-A](https://github.com/user-attachments/assets/c6f28d06-b64d-46f7-b220-dcb241e818f0) | | ![workflow-b as PNG](https://github.com/git/git-scm.com/blob/802cbb4d3b71960972e9a09a78bab439eb303c29/static/images/about/[email protected]?raw=true) | ![workflow-B](https://github.com/user-attachments/assets/6de3510d-818a-4118-96cf-043ba3bf3ab8) | | ![workflow-c as PNG](https://github.com/git/git-scm.com/blob/802cbb4d3b71960972e9a09a78bab439eb303c29/static/images/about/[email protected]?raw=true) | ![workflow-C](https://github.com/user-attachments/assets/89fc9699-6374-497f-bef6-aea715f0bb4f) | The changes of this PR can also be seen in action in my fork: https://dscho.github.io/git-scm.com/about/distributed
2 parents 802cbb4 + 352dbbf commit 992366e

File tree

12 files changed

+195
-4
lines changed

12 files changed

+195
-4
lines changed

.github/actions/deploy-to-github-pages/action.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ runs:
8181
find public/book public/docs -name \*.html -print0 |
8282
xargs -0r sed -i 's,http://git-scm\.com,https://git-scm.com,g'
8383
84+
- uses: actions/setup-node@v5
85+
- name: pre-render the Graphviz diagrams
86+
shell: bash
87+
run: |
88+
npm install node-html-parser &&
89+
node ./script/graphviz-ssr.js
90+
8491
- name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index
8592
shell: bash
8693
run: npx -y pagefind@${{ env.PAGEFIND_VERSION }} --site public --write-playground

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ jobs:
3232
exit 1
3333
fi
3434
35+
- uses: actions/setup-node@v5
36+
- name: pre-render the Graphviz diagrams
37+
run: |
38+
npm install node-html-parser &&
39+
node ./script/graphviz-ssr.js
40+
3541
- name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index
3642
run: npx -y pagefind@${{ env.PAGEFIND_VERSION }} --site public --write-playground
3743

assets/js/application.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,32 @@ var PostelizeAnchor = {
782782
},
783783
}
784784

785+
var Graphviz = {
786+
render: function() {
787+
let vizInstance
788+
[...document.querySelectorAll("pre[class=graphviz]")].forEach(async (x) => {
789+
if (!vizInstance) vizInstance = await Viz.instance()
790+
const options = {
791+
format: "svg",
792+
graphAttributes: {
793+
bgcolor: "transparent",
794+
},
795+
engine: x.getAttribute("engine") || "dot",
796+
}
797+
const svg = vizInstance.renderString(x.innerText, options)
798+
const img = document.createElement('img')
799+
img.setAttribute(
800+
'src',
801+
`data:image/svg+xml;utf8,${encodeURIComponent(svg.substring(svg.indexOf('<svg')))}`
802+
)
803+
const alt = x.getAttribute("alt")
804+
if (alt) img.setAttribute("alt", alt)
805+
x.parentNode.insertBefore(img, x);
806+
x.style.display = 'none'
807+
});
808+
}
809+
}
810+
785811
// Scroll to Top
786812
$('#scrollToTop').removeClass('no-js');
787813
$(window).on('scroll', function() {

content/about/distributed.html

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,101 @@ <h4>Subversion-Style Workflow</h4>
2727
A centralized workflow is very common, especially from people transitioning from a centralized system. Git will not allow you to push if someone has pushed since the last time you fetched, so a centralized model where all developers push to the same server works just fine.
2828
</p>
2929
<p class="center">
30-
<img src="{{< relurl "images/about/[email protected]" >}}" width="415" height="209" alt="Workflow A">
30+
{{< graphviz alt="workflow A" >}}
31+
digraph dev_workflow {
32+
layout=neato;
33+
overlap=false;
34+
sep="+10";
35+
node [shape=box, style="filled,rounded", penwidth=0, fontname="Helvetica"];
36+
edge [penwidth=3, color="gray", arrowsize=0.5, dir=both];
37+
38+
shared_repo [label="shared repository", fillcolor=teal, fontcolor=white, pos="2,1.5!"];
39+
40+
developer1 [label="developer", fillcolor=lightblue, pos="0,0!"];
41+
developer2 [label="developer", fillcolor=lightblue, pos="2,0!"];
42+
developer3 [label="developer", fillcolor=lightblue, pos="4,0!"];
43+
44+
developer1 -> shared_repo;
45+
developer2 -> shared_repo;
46+
developer3 -> shared_repo;
47+
}
48+
{{< /graphviz >}}
3149
</p>
3250
<h4>Integration Manager Workflow</h4>
3351
<p>
3452
Another common Git workflow involves an integration manager — a single person who commits to the 'blessed' repository. A number of developers then clone from that repository, push to their own independent repositories, and ask the integrator to pull in their changes. This is the type of development model often seen with open source or GitHub repositories.
3553
</p>
3654
<p class="center">
37-
<img src="{{< relurl "images/about/[email protected]" >}}" width="407" height="164" alt="Workflow B">
55+
{{< graphviz alt="workflow B" >}}
56+
digraph workflow {
57+
layout=neato;
58+
overlap=false;
59+
node [shape=box, style="filled,rounded", penwidth=0, fontname="Helvetica"];
60+
edge [penwidth=3, color="gray", arrowsize=0.5];
61+
62+
// Nodes
63+
blessed_repo [label="blessed\nrepository", fillcolor=teal, fontcolor=white, margin="0.3", pos="0,3!"];
64+
integration_manager [label="integration\nmanager", fillcolor=orange, fontcolor=white, pos="0,1!"];
65+
66+
dev_pub1 [label="developer\npublic", fillcolor="#CCAA00", fontcolor=black, pos="2,3!"];
67+
dev_pub2 [label="developer\npublic", fillcolor="#CCAA00", fontcolor=black, pos="4,3!"];
68+
69+
dev_priv1 [label="developer\nprivate", fillcolor="#CCAA00", fontcolor=black, pos="2,1!"];
70+
dev_priv2 [label="developer\nprivate", fillcolor="#CCAA00", fontcolor=black, pos="4,1!"];
71+
72+
// Edges with labels
73+
dev_priv1 -> dev_pub1;
74+
dev_priv2 -> dev_pub2;
75+
integration_manager -> blessed_repo;
76+
77+
dev_pub1 -> integration_manager [color="lightgray"];
78+
dev_pub2 -> integration_manager [color="lightgray"];
79+
blessed_repo -> dev_priv1 [color="lightgray"];
80+
blessed_repo -> dev_priv2 [color="lightgray"];
81+
}
82+
{{< /graphviz >}}
3883
</p>
3984
<h4>Dictator and Lieutenants Workflow</h4>
4085
<p>
4186
For more massive projects, a development workflow like that of the Linux kernel is often effective.
4287
In this model, some people ('lieutenants') are in charge of a specific subsystem of the project and they merge in all changes related to that subsystem. Another integrator (the 'dictator') can pull changes from only his/her lieutenants and then push to the 'blessed' repository that everyone then clones from again.
4388
</p>
4489
<p class="center">
45-
<img src="{{< relurl "images/about/[email protected]" >}}" width="562" height="303" alt="Workflow C">
90+
{{< graphviz alt="workflow C" >}}
91+
digraph workflow {
92+
layout=neato;
93+
overlap=false;
94+
node [shape=box, style="filled,rounded", penwidth=0, fontname="Helvetica"];
95+
edge [penwidth=3, color="gray", arrowsize=0.5];
96+
97+
// Nodes
98+
blessed_repo [label="blessed\nrepository", fillcolor=teal, fontcolor=white, margin="0.3", pos="6,4!"];
99+
dictator [label="dictator", fillcolor=red, fontcolor=white, pos="0,4!"];
100+
101+
lieutenant1 [label="lieutenant", fillcolor="#CCAA00", fontcolor=white, pos="0,2.5!"];
102+
lieutenant2 [label="lieutenant", fillcolor="#CCAA00", fontcolor=white, pos="2,3!"];
103+
104+
developer1 [label="developer", fillcolor=lightblue, fontcolor=black, pos="0,1!"];
105+
developer2 [label="developer", fillcolor=lightblue, fontcolor=black, pos="2,1!"];
106+
developer3 [label="developer", fillcolor=lightblue, fontcolor=black, pos="4,1!"];
107+
developer4 [label="developer", fillcolor=lightblue, fontcolor=black, pos="6,1!"];
108+
109+
// Edges
110+
dictator -> blessed_repo;
111+
lieutenant1 -> dictator;
112+
lieutenant2 -> dictator;
113+
114+
developer1 -> lieutenant1;
115+
developer2 -> lieutenant1;
116+
developer3 -> lieutenant2;
117+
developer4 -> lieutenant2;
118+
119+
blessed_repo -> developer1;
120+
blessed_repo -> developer2;
121+
blessed_repo -> developer3;
122+
blessed_repo -> developer4;
123+
}
124+
{{< /graphviz >}}
46125
</p>
47126
<div class="bottom-nav" style="display: block;">
48127
<a href="{{< relurl "about/small-and-fast" >}}" class="previous" data-section-id="small-and-fast">← Small and Fast</a>

content/diagram-list.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
outputs:
3+
- json
4+
---

hugo.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ markup:
1212
goldmark:
1313
renderer:
1414
unsafe: true
15+
mediaTypes:
16+
application/json:
17+
suffixes:
18+
- json
1519
module:
1620
mounts:
1721
- source: content

layouts/_default/baseof.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,5 +191,9 @@ <h1 data-pagefind-meta="title">About{{ if (isset .Params "subtitle") }} - {{ .Pa
191191
{{ end }}
192192

193193
</body>
194+
{{ if .Page.Store.Get "hasGraphviz" -}}
195+
<script src="{{ relURL "js/viz-global.js" }}"> </script>
196+
<script type="text/javascript">Graphviz.render();</script>
197+
{{ end -}}
194198
</html>
195199
{{ end }}

layouts/_default/single.json.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{{- $diagram_list := where .Site.Pages "RawContent" "like" "{{< graphviz" -}}
2+
{{- $diagram_list | jsonify -}}

layouts/shortcodes/graphviz.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<pre class="graphviz"{{ if (ne "" (.Get "engine")) }} engine="{{(.Get "engine")}}"{{ end }}{{ if (ne "" (.Get "alt")) }} alt="{{ (.Get "alt") }}"{{ end }}>
2+
{{ .Inner | htmlEscape | safeHTML }}
3+
</pre>
4+
{{ .Page.Store.Set "hasGraphviz" true }}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"license": "MIT",
66
"devDependencies": {
77
"@playwright/test": "^1.47.2",
8-
"@types/node": "^22.5.4"
8+
"@types/node": "^22.5.4",
9+
"node-html-parser": "^7.0.1"
910
}
1011
}

0 commit comments

Comments
 (0)