Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/actions/deploy-to-github-pages/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ runs:
find public/book public/docs -name \*.html -print0 |
xargs -0r sed -i 's,http://git-scm\.com,https://git-scm.com,g'

- uses: actions/setup-node@v5
- name: pre-render the Graphviz diagrams
shell: bash
run: |
npm install node-html-parser &&
node ./script/graphviz-ssr.js

- name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index
shell: bash
run: npx -y pagefind@${{ env.PAGEFIND_VERSION }} --site public --write-playground
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ jobs:
exit 1
fi

- uses: actions/setup-node@v5
- name: pre-render the Graphviz diagrams
run: |
npm install node-html-parser &&
node ./script/graphviz-ssr.js

- name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index
run: npx -y pagefind@${{ env.PAGEFIND_VERSION }} --site public --write-playground

Expand Down
26 changes: 26 additions & 0 deletions assets/js/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,32 @@ var PostelizeAnchor = {
},
}

var Graphviz = {
render: function() {
let vizInstance
[...document.querySelectorAll("pre[class=graphviz]")].forEach(async (x) => {
if (!vizInstance) vizInstance = await Viz.instance()
const options = {
format: "svg",
graphAttributes: {
bgcolor: "transparent",
},
engine: x.getAttribute("engine") || "dot",
}
const svg = vizInstance.renderString(x.innerText, options)
const img = document.createElement('img')
img.setAttribute(
'src',
`data:image/svg+xml;utf8,${encodeURIComponent(svg.substring(svg.indexOf('<svg')))}`
)
const alt = x.getAttribute("alt")
if (alt) img.setAttribute("alt", alt)
x.parentNode.insertBefore(img, x);
x.style.display = 'none'
});
}
}

// Scroll to Top
$('#scrollToTop').removeClass('no-js');
$(window).on('scroll', function() {
Expand Down
85 changes: 82 additions & 3 deletions content/about/distributed.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,101 @@ <h4>Subversion-Style Workflow</h4>
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.
</p>
<p class="center">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the commit message:

With this change, the text in those diagrams is copy/paste-able.

@dscho That no longer seems to be the case now <svg> is no longer is inlined. It's a nitpick on the commit message, I don't consider this an issue that it's not the case. Do you think it worthy to edit the commit message?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I force-pushed a fix:

Range-diff
  • 1: a2c59d6 ! 1: 352dbbf Replace the diagrams in about/distributed with Graphviz versions

    @@ Commit message
         Replace the diagrams in `about/distributed` with Graphviz versions
     
         This commit recreates those diagrams that have been provided as `.png`
    -    files before.
    -
    -    With this change, the text in those diagrams is copy/paste-able. But of
    -    course that's not the real reason I made this change, the real reason is
    +    files before. Of course, this is not strictly necessary because the
    +    previous `.png` diagrams served their purpose well enough. But I wanted
         to show off that we can now use Graphviz diagrams on git-scm.com.
     
    +    Note: When developing this patch, I had considered inlining the SVGs as
    +    `<svg>` instead of `<img>`. That would have had the following
    +    advantages:
    +
    +    - The text in those diagrams would have been copy/paste-able.
    +
    +    - We could have defined explicit colors for dark mode, as the `<style>`
    +      element would have been able to refer to the HTML document's `:root`
    +      element's dark mode definitions.
    +
    +    However, having SVGs embedded in `<img>` elements have a few benefits:
    +
    +    - Current browsers allow to `Open Image in New Tab` (where the text
    +      would be copy/paste-able again).
    +
    +    - Current browsers allow to `Save Image` (even if there is no way to
    +      specify a filename, see https://github.com/whatwg/html/issues/2722).
    +
    +    - By using `<img>` elements, we can reuse the previous work on images in
    +      dark mode.
    +
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## content/about/distributed.html ##

<img src="{{< relurl "images/about/[email protected]" >}}" width="415" height="209" alt="Workflow A">
{{< graphviz alt="workflow A" >}}
digraph dev_workflow {
layout=neato;
overlap=false;
sep="+10";
node [shape=box, style="filled,rounded", penwidth=0, fontname="Helvetica"];
edge [penwidth=3, color="gray", arrowsize=0.5, dir=both];

shared_repo [label="shared repository", fillcolor=teal, fontcolor=white, pos="2,1.5!"];

developer1 [label="developer", fillcolor=lightblue, pos="0,0!"];
developer2 [label="developer", fillcolor=lightblue, pos="2,0!"];
developer3 [label="developer", fillcolor=lightblue, pos="4,0!"];

developer1 -> shared_repo;
developer2 -> shared_repo;
developer3 -> shared_repo;
}
{{< /graphviz >}}
</p>
<h4>Integration Manager Workflow</h4>
<p>
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.
</p>
<p class="center">
<img src="{{< relurl "images/about/[email protected]" >}}" width="407" height="164" alt="Workflow B">
{{< graphviz alt="workflow B" >}}
digraph workflow {
layout=neato;
overlap=false;
node [shape=box, style="filled,rounded", penwidth=0, fontname="Helvetica"];
edge [penwidth=3, color="gray", arrowsize=0.5];

// Nodes
blessed_repo [label="blessed\nrepository", fillcolor=teal, fontcolor=white, margin="0.3", pos="0,3!"];
integration_manager [label="integration\nmanager", fillcolor=orange, fontcolor=white, pos="0,1!"];

dev_pub1 [label="developer\npublic", fillcolor="#CCAA00", fontcolor=black, pos="2,3!"];
dev_pub2 [label="developer\npublic", fillcolor="#CCAA00", fontcolor=black, pos="4,3!"];

dev_priv1 [label="developer\nprivate", fillcolor="#CCAA00", fontcolor=black, pos="2,1!"];
dev_priv2 [label="developer\nprivate", fillcolor="#CCAA00", fontcolor=black, pos="4,1!"];

// Edges with labels
dev_priv1 -> dev_pub1;
dev_priv2 -> dev_pub2;
integration_manager -> blessed_repo;

dev_pub1 -> integration_manager [color="lightgray"];
dev_pub2 -> integration_manager [color="lightgray"];
blessed_repo -> dev_priv1 [color="lightgray"];
blessed_repo -> dev_priv2 [color="lightgray"];
}
{{< /graphviz >}}
</p>
<h4>Dictator and Lieutenants Workflow</h4>
<p>
For more massive projects, a development workflow like that of the Linux kernel is often effective.
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.
</p>
<p class="center">
<img src="{{< relurl "images/about/[email protected]" >}}" width="562" height="303" alt="Workflow C">
{{< graphviz alt="workflow C" >}}
digraph workflow {
layout=neato;
overlap=false;
node [shape=box, style="filled,rounded", penwidth=0, fontname="Helvetica"];
edge [penwidth=3, color="gray", arrowsize=0.5];

// Nodes
blessed_repo [label="blessed\nrepository", fillcolor=teal, fontcolor=white, margin="0.3", pos="6,4!"];
dictator [label="dictator", fillcolor=red, fontcolor=white, pos="0,4!"];

lieutenant1 [label="lieutenant", fillcolor="#CCAA00", fontcolor=white, pos="0,2.5!"];
lieutenant2 [label="lieutenant", fillcolor="#CCAA00", fontcolor=white, pos="2,3!"];

developer1 [label="developer", fillcolor=lightblue, fontcolor=black, pos="0,1!"];
developer2 [label="developer", fillcolor=lightblue, fontcolor=black, pos="2,1!"];
developer3 [label="developer", fillcolor=lightblue, fontcolor=black, pos="4,1!"];
developer4 [label="developer", fillcolor=lightblue, fontcolor=black, pos="6,1!"];

// Edges
dictator -> blessed_repo;
lieutenant1 -> dictator;
lieutenant2 -> dictator;

developer1 -> lieutenant1;
developer2 -> lieutenant1;
developer3 -> lieutenant2;
developer4 -> lieutenant2;

blessed_repo -> developer1;
blessed_repo -> developer2;
blessed_repo -> developer3;
blessed_repo -> developer4;
}
{{< /graphviz >}}
</p>
<div class="bottom-nav" style="display: block;">
<a href="{{< relurl "about/small-and-fast" >}}" class="previous" data-section-id="small-and-fast">← Small and Fast</a>
Expand Down
4 changes: 4 additions & 0 deletions content/diagram-list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
outputs:
- json
---
4 changes: 4 additions & 0 deletions hugo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ markup:
goldmark:
renderer:
unsafe: true
mediaTypes:
application/json:
suffixes:
- json
module:
mounts:
- source: content
Expand Down
4 changes: 4 additions & 0 deletions layouts/_default/baseof.html
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,9 @@ <h1 data-pagefind-meta="title">About{{ if (isset .Params "subtitle") }} - {{ .Pa
{{ end }}

</body>
{{ if .Page.Store.Get "hasGraphviz" -}}
<script src="{{ relURL "js/viz-global.js" }}"> </script>
<script type="text/javascript">Graphviz.render();</script>
{{ end -}}
</html>
{{ end }}
2 changes: 2 additions & 0 deletions layouts/_default/single.json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{{- $diagram_list := where .Site.Pages "RawContent" "like" "{{< graphviz" -}}
{{- $diagram_list | jsonify -}}
4 changes: 4 additions & 0 deletions layouts/shortcodes/graphviz.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<pre class="graphviz"{{ if (ne "" (.Get "engine")) }} engine="{{(.Get "engine")}}"{{ end }}{{ if (ne "" (.Get "alt")) }} alt="{{ (.Get "alt") }}"{{ end }}>
{{ .Inner | htmlEscape | safeHTML }}
</pre>
{{ .Page.Store.Set "hasGraphviz" true }}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"license": "MIT",
"devDependencies": {
"@playwright/test": "^1.47.2",
"@types/node": "^22.5.4"
"@types/node": "^22.5.4",
"node-html-parser": "^7.0.1"
}
}
45 changes: 45 additions & 0 deletions script/graphviz-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env node

import { readFileSync, writeFileSync } from "node:fs"
import { parse } from "node-html-parser"
import Viz from "../static/js/viz-global.js"

/*
* This script post-processes the site as generated via Hugo, replacing the
* `<pre class="graphviz">` elements with inline `<svg>` ones, pre-rendered
* via `viz-js`.
*/
;(async () => {
for (const { Path: pathInPublic } of JSON.parse(readFileSync("public/diagram-list.json", "utf-8"))) {
const path = `public${pathInPublic}.html`
const contents = readFileSync(path, "utf-8")
const html = parse(contents)
const vizImport = html.querySelector('script[src$="viz-global.js"]')
if (!vizImport) {
console.error(`No 'viz-global.js' import found in ${path}; skipping`)
continue
}
vizImport.nextElementSibling.remove() // remove the inline script
vizImport.remove() // remove the import

for (const pre of html.querySelectorAll("pre.graphviz")) {
const engine = pre.getAttribute("engine") || "dot"
const svg = (await Viz.instance()).renderString(pre.textContent, {
format: "svg",
graphAttributes: {
bgcolor: "transparent",
},
engine,
})
const alt = pre.getAttribute("alt")
const altAttr = !alt ? '' : ` alt='${alt.replaceAll("&", "&amp;").replaceAll("'", "&#39;")}'`
const dataURL = `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`
pre.replaceWith(`<img${altAttr} src="${dataURL}" />`)
}
console.log(`Rewriting ${path}`)
writeFileSync(`${path}`, html.toString())
}
})().catch((e) => {
console.error(e)
process.exitCode = 1
})
9 changes: 9 additions & 0 deletions static/js/viz-global.js

Large diffs are not rendered by default.

Loading