Synthesising Git Repos for Tutorial Walkthroughs
Over the last few years, I’ve run several scout camps called “Scout Hack”, during which we teach kids basic HTML, CSS, Bootstrap, and enough Python to build a very basic Flask website.
While trying to write the material for this course, I was reflecting on the best way to present the material such that:
- It was obvious and deterministic as to what each step was and why we did it
- We could show the resulting web page after each step
- I could easily make tweaks to earlier steps without having to re-write intermediate example code for demonstration purposes
I eventually settled on a synthesised git repository in which each commit was a step in the documentation. This meant that I could use the commit diff to show exactly what code changes participants had to make, and I could store documentation explaining each change in the commit message.
For bonus points, I wrote each commit message as a title with a body in Markdown format, and wrote a script to convert a git repo of this type into a Markdown-formatted document that could then be rendered as a HTML page or a RevealJS slide deck, both complete with code diffs.
Let’s take a look at how this works in practice.
First, we create a repository with a predictable, empty first commit:
$ git init
Initialized empty Git repository in /tmp/course/.git/
$ touch .gitignore
$ git add .gitignore
$ git commit -m "Initial commit"
[main (root-commit) 9b8af25] Initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 .gitignore
Then we have our first instructional commit:
$ cat index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
$ git commit -m 'Create a basic HTML document
Start by creating a text file called `index.html`.
This file contains a `DOCTYPE` to declare itself as a html document, a couple
of meta tags to declare its encoding and viewport, a placeholder `<title>` tag,
and an empty `<body>`.'
[main f25f07c] Create a basic HTML document
1 file changed, 11 insertions(+)
create mode 100644 index.html
OK, next instruction:
diff --git i/index.html w/index.html
index bd2bde6..0873343 100644
--- i/index.html
+++ w/index.html
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
+ <title>My First Page</title>
</head>
<body>
$ git add index.html
$ git commit -m 'Set a page title
Edit the `<title>` element to choose your own title.
Make **sure** you save your file before testing this.'
[main a487f57] Set a page title
1 file changed, 1 insertion(+), 1 deletion(-)
OK, let’s take a look at what we’ve got:
$ git log --reverse
commit 9b8af25
Author: Matthew Cengia <mattcen@mattcen.com>
Date: Thu, 3 Oct 2024 19:02:17 +1000
Initial commit
commit f25f07c
Author: Matthew Cengia <mattcen@mattcen.com>
Date: Thu, 3 Oct 2024 19:15:14 +1000
Create a basic HTML document
Start by creating a text file called `index.html`.
This file contains a `DOCTYPE` to declare itself as a html document, a couple
of meta tags to declare its encoding and viewport, a placeholder `<title>` tag,
and an empty `<body>`.
commit a487f57 (HEAD -> main)
Author: Matthew Cengia <mattcen@mattcen.com>
Date: Thu, 3 Oct 2024 19:21:26 +1000
Set a page title
Edit the `<title>` element to choose your own title.
Make **sure** you save your file before testing this.
Looks good! If we’d made any errors, we could use git commit --fixup <previous-commit-id>
to add anything we’ve forgotten, and/or use git rebase -i --root
to edit any historical code or commit messages and
re-order/squash/fixup those commits. I’m not going to explain how to use git rebase -i
here, but there is plenty of documentation online.
Once we’re satisfied with our commit history, we can format it as Markdown:
$ repo_path=https://git.mattcen.com/mattcen/synth-git-repo-tutorial
$ git log -p --reverse --format='```%n%n</details>%n%n# %s%n[%h]('"$repo_path"'/commit/%H)%n%n%b%n%n<details open><summary>diff</summary>%n%n```diff {.numberLines}' |
sed \
-e '1,4d;$a```\n\n</details>' \
-e '/```diff/{N;s/\n//}' > walkthrough.md
Let’s step through that. We do a git log
in reverse (to show the first commit
at the top), with -p
to show the diff, and with --format
to customise the
output format. The format string is interpreted as follows:
</details>
# COMMIT_MESSAGE_SUBJECT
[COMMIT_SHORT_HASH]($repo_path/commit/COMMIT_HASH)
COMMIT_MESSAGE_BODY
<details open><summary>diff</summary>
```diff {.numberLines}
Then, as a result of the -p
, git outputs the diff
for the commit, followed
by, usually, the next commit message (which contains the closing </details>
tag for the one opened before the previous diff).
We pipe all this through sed
to remove the first (unnecessary) </details>
and blank lines from the start, add an equivalent onto the very end to close
off the final diff, and delete the undesired blank line at the top of each
diff.
The whole $repo_path
thing is so that each step can have a hyperlink to its
respective git commit on GitHub or elsewhere.
If we instead wanted to generate Markdown suitable for use with Reveal.js, we could do this:
git log -p --reverse --format='```%n%n---%n%n# %s [*]('"$repo_path"'/commit/%H)%n%n%b%n%n```diff []' |
sed \
-e '1,4d;$a```' \
-e '/```diff/{N;s/\n//}' > walkthrough_slides.md
Which translates to:
---
# COMMIT_MESSAGE_SUBJECT [*]($repo_path/commit/COMMIT_HASH)
COMMIT_MESSAGE_BODY
```diff []
And again with a similar sed
to shuffle around the bits surrounding the code
diff and remove extra blank line.
This would produce slides with a title equal to the commit subject, followed by an asterisk linking to the published git commit, a paragraph showing the commit message body, and finally a formatted diff. This wouldn’t all likely fit on one slide, so would need some manual tweaking.
And we’re done! It’s a little hacky, but it works rather well! See below for the resulting Markdown and rendered HTML.
Resulting Markdown
# Initial commit
[9b8af25](https://git.mattcen.com/mattcen/synth-git-repo-tutorial/commit/9b8af25720ce020bc1a2b39966113bd741c38f3e)
<details open><summary>diff</summary>
```diff {.numberLines}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e69de29
```
</details>
# Create a basic HTML document
[f25f07c](https://git.mattcen.com/mattcen/synth-git-repo-tutorial/commit/f25f07c77a0384044e0295db97f8dfc97a5b23bf)
Start by creating a text file called `index.html`.
This file contains a `DOCTYPE` to declare itself as a html document, a couple
of meta tags to declare its encoding and viewport, a placeholder `<title>` tag,
and an empty `<body>`.
<details open><summary>diff</summary>
```diff {.numberLines}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..bd2bde6
--- /dev/null
+++ b/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Document</title>
+</head>
+<body>
+
+</body>
+</html>
```
</details>
# Set a page title
[a487f57](https://git.mattcen.com/mattcen/synth-git-repo-tutorial/commit/a487f57c35d64574416d073964849ebef0faa07e)
Edit the `<title>` element to choose your own title.
Make **sure** you save your file before testing this.
<details open><summary>diff</summary>
```diff {.numberLines}
diff --git a/index.html b/index.html
index bd2bde6..0873343 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
+ <title>My First Page</title>
</head>
<body>
```
</details>
Which renders as follows:
Initial commit
diff
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e69de29
Create a basic HTML document
Start by creating a text file called index.html
.
This file contains a DOCTYPE
to declare itself as a html document, a couple
of meta tags to declare its encoding and viewport, a placeholder <title>
tag,
and an empty <body>
.
diff
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..bd2bde6
--- /dev/null
+++ b/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Document</title>
+</head>
+<body>
+
+</body>
+</html>
Set a page title
Edit the <title>
element to choose your own title.
Make sure you save your file before testing this.
diff
diff --git a/index.html b/index.html
index bd2bde6..0873343 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
+ <title>My First Page</title>
</head>
<body>