Diffs
When one block is an evolution of another, you don’t have to paste the
code twice and hand-maintain a changeset. Give each block a unique
#+NAME: and add a #+begin_diff block that references them; on export
the build replaces it with a box that toggles between a GitHub-style diff
and the full source of the evolved block. Both views are syntax-highlighted
and line-numbered; in the diff, added lines are blocked out in green and
removed lines in red. Any named block works as a reference — src,
example, or a special block like shader — and the two may live in
different subtrees of the same file.
#+NAME: greet-v1
#+begin_src sh
#!/bin/sh
echo "starting up"
echo "hello"
echo "done"
#+end_src
#+NAME: greet-v2
#+begin_src sh
#!/bin/sh
echo "starting up"
name="world"
echo "hello, $name"
echo "done"
#+end_src
#+attr_diff: :from greet-v1 :to greet-v2
#+begin_diff
#+end_diff
#+attr_diff takes :from and :to (the two block names) plus a few
optional settings:
:context N— unchanged lines of context around each change (default 3; use a large value to show the whole block as one changeset).:view diff|source— which view renders by default (defaultdiff). This is also the static view shown with JavaScript disabled.:lang LANG— override the language used to highlight the source view (otherwise inferred from the:toblock;shaderblocks useglsl).
The header reads Diff: from → to or Source: to to match the view, and
a button in the top-right toggles between them (it folds away when
JavaScript is off, leaving the static :view). The build fails loudly
if either name is missing or cannot be resolved, so a typo never silently
ships an empty diff. The example above renders:
#!/bin/sh
echo "starting up"
echo "hello"
echo "done"
#!/bin/sh
echo "starting up"
name="world"
echo "hello, $name"
echo "done"
--- greet-v1
+++ greet-v2
@@ -1,4 +1,5 @@
#!/bin/sh
echo "starting up"
-echo "hello"
+name="world"
+echo "hello, $name"
echo "done"
|
|
#!/bin/sh
echo "starting up"
echo "hello"
echo "done"The same two blocks can be referenced again — a named block is not
consumed by a diff. Here is the very same pair with :view source, so the
box opens on the evolved source instead of the changeset:
--- greet-v1
+++ greet-v2
@@ -1,4 +1,5 @@
#!/bin/sh
echo "starting up"
-echo "hello"
+name="world"
+echo "hello, $name"
echo "done"
|
|
#!/bin/sh
echo "starting up"
echo "hello"
echo "done"By default the diff shows only three lines of context around each change,
so a longer file whose edits are far apart renders as separate hunks
with the unchanged stretch between them collapsed behind a @@ … @@
header. This pair edits a line near the top and another near the bottom:
#!/bin/sh
# greeting service v1
greeting="hello"
main() {
name="$1"
echo "$greeting, $name"
log "$name"
}
log() {
printf '%s\n' "$1" >> guests.txt
}
main "$@"
#!/bin/sh
# greeting service v2
greeting="hi"
main() {
name="$1"
echo "$greeting, $name"
log "$name"
}
log() {
printf '%s\n' "$1" >> visitors.log
}
main "$@"
--- srv-v1
+++ srv-v2
@@ -1,6 +1,6 @@
#!/bin/sh
-# greeting service v1
-greeting="hello"
+# greeting service v2
+greeting="hi"
main() {
name="$1"
@@ -9,7 +9,7 @@
}
log() {
- printf '%s\n' "$1" >> guests.txt
+ printf '%s\n' "$1" >> visitors.log
}
main "$@"
|
|
#!/bin/sh
# greeting service v1
greeting="hello"
main() {
name="$1"
echo "$greeting, $name"
log "$name"
}
log() {
printf '%s\n' "$1" >> guests.txt
}
main "$@"To fill the gap and show the whole file as one changeset, pass a :context
at least as large as the file — exactly what the whirlpool note below does
with :context 99.
You don’t always need two named blocks. Reference a single block with
:from (or :to) and write the change directly in the diff block’s body;
that body becomes the other side of the diff. This is handy for a one-off
edit you don’t want to give its own named block. :label names the inline
side (default edited). For example, against greet-v1 from above:
#+attr_diff: :from greet-v1 :label timestamped
#+begin_diff
#!/bin/sh
echo "starting up"
echo "$(date): hello"
echo "done"
#+end_diff
which renders:
--- greet-v1
+++ timestamped
@@ -1,4 +1,4 @@
#!/bin/sh
echo "starting up"
-echo "hello"
+echo "$(date): hello"
echo "done"
|
|
#!/bin/sh
echo "starting up"
echo "hello"
echo "done"With :from the body is the new (right-hand) side; with :to it is the
original instead. The same :context, :view, and :lang settings apply.
A #+begin_shader block can carry the same #+attr_diff: :from NAME line:
the shader still runs live, but its source box becomes a diffbox against
NAME. The shader body is written once — run and diffed from a single
block — so there is no duplicated copy to keep in sync. The
whirlpool note does exactly this.
The diff block is DRY: only the named blocks hold the source, so the
changeset can never drift from the code it describes. See it used in the
whirlpool shader note, which shows the whole shader as a changeset
from the step-function shader on the previous page.
The diff and source are handed to the renderer verbatim, so the referenced
code must not contain a literal Hugo shortcode opener — a {{ immediately
followed by < (or by %). Ordinary {{ braces on their own are fine.