mykiwi/mykiwi.blogpublic Fork
⑂ 61ebed5
Commits
⬇ Clone ▾
git clone https://git.rickub.com/mykiwi/mykiwi.blog.git
git clone ssh://git@rickub.com/mykiwi/mykiwi.blog.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Name each page's own last commit, and link to the branchUnverified

The footer named the revision the whole site was built from, which is
the wrong fact about a single page: it moves with every deploy, says
nothing about when the page last changed, and - being a revision -
only resolves once that commit has been pushed, so a deploy that ran
ahead of a push left every link pointing at a 404.

Name the commit the page was last changed in instead, and point the
link at the branch. The label is then provenance for what you just
read, and the link is what it claims to be: an invitation to edit,
landing on the version you would actually edit, at a ref the forge can
always resolve. The two disagree only while a change is pushed but not
deployed, and in that window the label is right about the page and the
link is right about the file.

Per-page history is not in the build's reach: the derivation's source
is ./blog, a flake's source never carries .git, and so Hugo's
--enableGitInfo has nothing to read in the sandbox. scripts/page-commits.sh
builds the map out here where git is, and `make build` passes it in
through the environment - which is why that target now runs `nix build
--impure`. The build stops being a function of its source alone, which
is the honest cost of a page knowing its own history.

Both fallbacks are quiet. A plain `nix build` sees no map and names
the site's revision; a page git has never seen (a post written but not
committed) does the same; and when there is no revision either - `hugo
server`, or a dirty tree - the label stands alone as "Improve this
page". Only a real 40-character hash is ever printed as one.
Romain Gautier committed 2026-09-20T15:21:08+02:00 Browse files
61ebed5 parent: c1f6370
modified Makefile +6 -1
@@ -29,8 +29,13 @@ help:
2929 dev: ## Drop into the Hugo dev shell (hugo, dart-sass)
3030 nix develop
3131
32+# --impure so the derivation can read PAGE_COMMITS (see flake.nix): the footer
33+# names the commit each page was last changed in, which is history, not source,
34+# and so cannot be derived from the tree the sandbox is handed. `nix build` on
35+# its own still works - it just builds pages that name the site's revision
36+# instead of their own.
3237 build: ## Build the site (all languages) into ./result
33- nix build
38+ PAGE_COMMITS="$$(scripts/page-commits.sh)" nix build --impure
3439
3540 serve: ## Run the Hugo dev server with live reload
3641 nix develop --command hugo server --source blog
@@ -29,8 +29,13 @@ help:
29 dev: ## Drop into the Hugo dev shell (hugo, dart-sass)29 dev: ## Drop into the Hugo dev shell (hugo, dart-sass)
30 nix develop30 nix develop
31 31
32+# --impure so the derivation can read PAGE_COMMITS (see flake.nix): the footer
33+# names the commit each page was last changed in, which is history, not source,
34+# and so cannot be derived from the tree the sandbox is handed. `nix build` on
35+# its own still works - it just builds pages that name the site's revision
36+# instead of their own.
32 build: ## Build the site (all languages) into ./result37 build: ## Build the site (all languages) into ./result
33- nix build38+ PAGE_COMMITS="$$(scripts/page-commits.sh)" nix build --impure
34 39
35 serve: ## Run the Hugo dev server with live reload40 serve: ## Run the Hugo dev server with live reload
36 nix develop --command hugo server --source blog41 nix develop --command hugo server --source blog
modified blog/hugo.toml +14 -7
@@ -84,16 +84,23 @@ weight = 5
8484 # flip to false (or delete the line) once the site is ready to be found.
8585 noindex = true
8686
87-# Footer "Improve this page" link: <repoURL>/blob/<repoCommit>/<repoContentPath>/<the
88-# page's own source file>. The forge serves a file at a given revision under
89-# /blob/<ref>/<path>, and .File.Path is relative to blog/content/, which is what
90-# repoContentPath prepends.
87+# Footer "Improve this page @ <hash>" link: <repoURL>/blob/<repoBranch>/<repoContentPath>/<the
88+# page's own source file>. The forge serves a file under /blob/<ref>/<path>, and
89+# .File.Path is relative to blog/content/, which is what repoContentPath prepends.
90+#
91+# The link deliberately points at the branch, not at a pinned revision: it is an
92+# invitation to edit the file, so it should land on the version you would edit,
93+# and a branch is also something the forge can always resolve. The hash beside
94+# the label is the commit the page was last changed in - provenance for what you
95+# just read - and comes from HUGO_PARAMS_PAGECOMMITS (see scripts/page-commits.sh).
9196 repoURL = "https://rickub.com/mykiwi/mykiwi.blog"
9297 repoContentPath = "blog/content"
98+repoBranch = "main"
9399 # Overridden by the Nix build (HUGO_PARAMS_REPOCOMMIT, see flake.nix) with the
94-# revision the site was built from, so a deployed page links to the source it was
95-# actually rendered from rather than to wherever main has moved since. `hugo
96-# server` and builds from a dirty tree have no revision and keep the branch below.
100+# revision the site was built from. Only a fallback now: pages git has no history
101+# for - a post written but not yet committed - name the build instead of
102+# themselves. `hugo server` and dirty-tree builds have no revision at all and
103+# keep the branch below, which is not a hash and so is never shown as one.
97104 repoCommit = "main"
98105
99106 [params.profile]
@@ -84,16 +84,23 @@ weight = 5
84 # flip to false (or delete the line) once the site is ready to be found.84 # flip to false (or delete the line) once the site is ready to be found.
85 noindex = true85 noindex = true
86 86
87-# Footer "Improve this page" link: <repoURL>/blob/<repoCommit>/<repoContentPath>/<the87+# Footer "Improve this page @ <hash>" link: <repoURL>/blob/<repoBranch>/<repoContentPath>/<the
88-# page's own source file>. The forge serves a file at a given revision under88+# page's own source file>. The forge serves a file under /blob/<ref>/<path>, and
89-# /blob/<ref>/<path>, and .File.Path is relative to blog/content/, which is what89+# .File.Path is relative to blog/content/, which is what repoContentPath prepends.
90-# repoContentPath prepends.90+#
91+# The link deliberately points at the branch, not at a pinned revision: it is an
92+# invitation to edit the file, so it should land on the version you would edit,
93+# and a branch is also something the forge can always resolve. The hash beside
94+# the label is the commit the page was last changed in - provenance for what you
95+# just read - and comes from HUGO_PARAMS_PAGECOMMITS (see scripts/page-commits.sh).
91 repoURL = "https://rickub.com/mykiwi/mykiwi.blog"96 repoURL = "https://rickub.com/mykiwi/mykiwi.blog"
92 repoContentPath = "blog/content"97 repoContentPath = "blog/content"
98+repoBranch = "main"
93 # Overridden by the Nix build (HUGO_PARAMS_REPOCOMMIT, see flake.nix) with the99 # Overridden by the Nix build (HUGO_PARAMS_REPOCOMMIT, see flake.nix) with the
94-# revision the site was built from, so a deployed page links to the source it was100+# revision the site was built from. Only a fallback now: pages git has no history
95-# actually rendered from rather than to wherever main has moved since. `hugo101+# for - a post written but not yet committed - name the build instead of
96-# server` and builds from a dirty tree have no revision and keep the branch below.102+# themselves. `hugo server` and dirty-tree builds have no revision at all and
103+# keep the branch below, which is not a hash and so is never shown as one.
97 repoCommit = "main"104 repoCommit = "main"
98 105
99 [params.profile]106 [params.profile]
modified blog/layouts/partials/site-foot.html +24 -12
@@ -1,23 +1,35 @@
11 <footer class="site-foot column">
22 {{- /*
3- The revision the site was built from (site.Params.repoCommit, see
4- hugo.toml). It falls back to a branch name when there is no revision to
5- name - `hugo server`, or a build over a dirty tree - which still resolves
6- in the link below but is nothing to print, so only a real 40-character
7- hash is shown.
3+ Content file -> the commit it was last changed in, handed to the build in
4+ HUGO_PARAMS_PAGECOMMITS because the sandbox has no git to ask (see
5+ scripts/page-commits.sh). Empty for a plain `nix build`, and missing an
6+ entry for any page git has not seen yet, so every lookup needs a fallback:
7+ site.Params.repoCommit, the revision the site itself was built from. That
8+ in turn is a branch name when there was no revision to name, which is not a
9+ hash and is not shown as one - hence the length test rather than a bare
10+ `with`.
811 */}}
9- {{- $rev := site.Params.repoCommit }}
10- {{- $builtFrom := eq (len $rev) 40 }}
12+ {{- $pageCommits := dict }}
13+ {{- with site.Params.pagecommits }}
14+ {{- $pageCommits = transform.Unmarshal . }}
15+ {{- end }}
1116 {{- /*
12- Source link for the page you are reading, pointing at that revision rather
13- than at the branch, so it shows the source this very page was rendered
14- from. Pages Hugo generates without a content file - the home page, the post
15- list - have no .File, and get no link.
17+ Source link for the page you are reading. It points at the branch rather
18+ than at a revision: "improve this page" is an invitation to edit the file,
19+ so it lands on the version you would actually edit, and the branch is
20+ something the forge can always resolve - a revision only resolves once it
21+ has been pushed. The hash beside it names the commit this page was last
22+ changed in, which is what the reader is looking at. The two can disagree
23+ for as long as a change is pushed but not deployed.
24+
25+ Pages Hugo generates without a content file - the home page, the post list
26+ - have no .File, and get no link.
1627 */}}
1728 {{- with .File }}
1829 {{- $source := path.Join site.Params.repoContentPath .Path }}
30+ {{- $stamp := or (index $pageCommits .Path) site.Params.repoCommit }}
1931 <span>
20- <a href="{{ printf "%s/blob/%s/%s" site.Params.repoURL $rev $source }}">{{ i18n "improvePage" }}{{ if $builtFrom }} @ {{ substr $rev 0 7 }}{{ end }}</a>
32+ <a href="{{ printf "%s/blob/%s/%s" site.Params.repoURL site.Params.repoBranch $source }}">{{ i18n "improvePage" }}{{ if eq (len $stamp) 40 }} @ {{ substr $stamp 0 7 }}{{ end }}</a>
2133 </span>
2234 <span class="sep" aria-hidden="true">&middot;</span>
2335 {{- end }}
@@ -1,23 +1,35 @@
1 <footer class="site-foot column">1 <footer class="site-foot column">
2 {{- /*2 {{- /*
3- The revision the site was built from (site.Params.repoCommit, see3+ Content file -> the commit it was last changed in, handed to the build in
4- hugo.toml). It falls back to a branch name when there is no revision to4+ HUGO_PARAMS_PAGECOMMITS because the sandbox has no git to ask (see
5- name - `hugo server`, or a build over a dirty tree - which still resolves5+ scripts/page-commits.sh). Empty for a plain `nix build`, and missing an
6- in the link below but is nothing to print, so only a real 40-character6+ entry for any page git has not seen yet, so every lookup needs a fallback:
7- hash is shown.7+ site.Params.repoCommit, the revision the site itself was built from. That
8+ in turn is a branch name when there was no revision to name, which is not a
9+ hash and is not shown as one - hence the length test rather than a bare
10+ `with`.
8 */}}11 */}}
9- {{- $rev := site.Params.repoCommit }}12+ {{- $pageCommits := dict }}
10- {{- $builtFrom := eq (len $rev) 40 }}13+ {{- with site.Params.pagecommits }}
14+ {{- $pageCommits = transform.Unmarshal . }}
15+ {{- end }}
11 {{- /*16 {{- /*
12- Source link for the page you are reading, pointing at that revision rather17+ Source link for the page you are reading. It points at the branch rather
13- than at the branch, so it shows the source this very page was rendered18+ than at a revision: "improve this page" is an invitation to edit the file,
14- from. Pages Hugo generates without a content file - the home page, the post19+ so it lands on the version you would actually edit, and the branch is
15- list - have no .File, and get no link.20+ something the forge can always resolve - a revision only resolves once it
21+ has been pushed. The hash beside it names the commit this page was last
22+ changed in, which is what the reader is looking at. The two can disagree
23+ for as long as a change is pushed but not deployed.
24+
25+ Pages Hugo generates without a content file - the home page, the post list
26+ - have no .File, and get no link.
16 */}}27 */}}
17 {{- with .File }}28 {{- with .File }}
18 {{- $source := path.Join site.Params.repoContentPath .Path }}29 {{- $source := path.Join site.Params.repoContentPath .Path }}
30+ {{- $stamp := or (index $pageCommits .Path) site.Params.repoCommit }}
19 <span>31 <span>
20- <a href="{{ printf "%s/blob/%s/%s" site.Params.repoURL $rev $source }}">{{ i18n "improvePage" }}{{ if $builtFrom }} @ {{ substr $rev 0 7 }}{{ end }}</a>32+ <a href="{{ printf "%s/blob/%s/%s" site.Params.repoURL site.Params.repoBranch $source }}">{{ i18n "improvePage" }}{{ if eq (len $stamp) 40 }} @ {{ substr $stamp 0 7 }}{{ end }}</a>
21 </span>33 </span>
22 <span class="sep" aria-hidden="true">&middot;</span>34 <span class="sep" aria-hidden="true">&middot;</span>
23 {{- end }}35 {{- end }}
modified flake.nix +8 -0
@@ -22,8 +22,16 @@
2222 # self.rev is the revision this flake was built from - it only exists
2323 # when the tree is clean, so a local `nix build` with uncommitted
2424 # changes falls back to the branch, same as `hugo server` does.
25+ #
26+ # PAGE_COMMITS is scripts/page-commits.sh's map of content file ->
27+ # last commit to touch it, which `make build` puts in the environment
28+ # (hence its `nix build --impure`). Reading it here is what makes the
29+ # build depend on git history rather than on the source alone, so a
30+ # plain `nix build` - pure, and blind to the environment - gets an
31+ # empty map and pages fall back to naming the build's own revision.
2532 HUGO_PARAMS_NIXSTOREPATH="$out" \
2633 HUGO_PARAMS_REPOCOMMIT="${self.rev or "main"}" \
34+ HUGO_PARAMS_PAGECOMMITS='${builtins.getEnv "PAGE_COMMITS"}' \
2735 hugo --minify --destination "$out"
2836 '';
2937 dontInstall = true;
@@ -22,8 +22,16 @@
22 # self.rev is the revision this flake was built from - it only exists22 # self.rev is the revision this flake was built from - it only exists
23 # when the tree is clean, so a local `nix build` with uncommitted23 # when the tree is clean, so a local `nix build` with uncommitted
24 # changes falls back to the branch, same as `hugo server` does.24 # changes falls back to the branch, same as `hugo server` does.
25+ #
26+ # PAGE_COMMITS is scripts/page-commits.sh's map of content file ->
27+ # last commit to touch it, which `make build` puts in the environment
28+ # (hence its `nix build --impure`). Reading it here is what makes the
29+ # build depend on git history rather than on the source alone, so a
30+ # plain `nix build` - pure, and blind to the environment - gets an
31+ # empty map and pages fall back to naming the build's own revision.
25 HUGO_PARAMS_NIXSTOREPATH="$out" \32 HUGO_PARAMS_NIXSTOREPATH="$out" \
26 HUGO_PARAMS_REPOCOMMIT="${self.rev or "main"}" \33 HUGO_PARAMS_REPOCOMMIT="${self.rev or "main"}" \
34+ HUGO_PARAMS_PAGECOMMITS='${builtins.getEnv "PAGE_COMMITS"}' \
27 hugo --minify --destination "$out"35 hugo --minify --destination "$out"
28 '';36 '';
29 dontInstall = true;37 dontInstall = true;
added scripts/page-commits.sh +25 -0
new file mode 100755
@@ -0,0 +1,25 @@
1+#!/usr/bin/env bash
2+# Last commit to touch each content file, as JSON keyed by the path Hugo's
3+# .File.Path reports (relative to blog/content/), for the footer's "Improve
4+# this page @ <hash>" label.
5+#
6+# This exists because the build cannot ask git itself: the derivation's source
7+# is ./blog, and a flake's source never carries .git, so Hugo's own
8+# --enableGitInfo has no history to read inside the sandbox. The map is built
9+# out here, where git is, and passed in through the environment.
10+#
11+# Files git has never seen (a post written but not yet committed) get no entry,
12+# and the template falls back to the revision the site was built from.
13+set -euo pipefail
14+
15+cd "$(git rev-parse --show-toplevel)"
16+
17+printf '{'
18+sep=''
19+while IFS= read -r file; do
20+ sha=$(git log -1 --format=%H -- "$file")
21+ [ -n "$sha" ] || continue
22+ printf '%s"%s":"%s"' "$sep" "${file#blog/content/}" "$sha"
23+ sep=','
24+done < <(git ls-files blog/content)
25+printf '}'
new file mode 100755
@@ -0,0 +1,25 @@
1+#!/usr/bin/env bash
2+# Last commit to touch each content file, as JSON keyed by the path Hugo's
3+# .File.Path reports (relative to blog/content/), for the footer's "Improve
4+# this page @ <hash>" label.
5+#
6+# This exists because the build cannot ask git itself: the derivation's source
7+# is ./blog, and a flake's source never carries .git, so Hugo's own
8+# --enableGitInfo has no history to read inside the sandbox. The map is built
9+# out here, where git is, and passed in through the environment.
10+#
11+# Files git has never seen (a post written but not yet committed) get no entry,
12+# and the template falls back to the revision the site was built from.
13+set -euo pipefail
14+
15+cd "$(git rev-parse --show-toplevel)"
16+
17+printf '{'
18+sep=''
19+while IFS= read -r file; do
20+ sha=$(git log -1 --format=%H -- "$file")
21+ [ -n "$sha" ] || continue
22+ printf '%s"%s":"%s"' "$sep" "${file#blog/content/}" "$sha"
23+ sep=','
24+done < <(git ls-files blog/content)
25+printf '}'