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.
61ebed5 parent: c1f6370 modified
Makefile +6 -1 | @@ -29,8 +29,13 @@ help: | ||
| 29 | 29 | dev: ## Drop into the Hugo dev shell (hugo, dart-sass) |
| 30 | 30 | 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 | 37 | build: ## Build the site (all languages) into ./result |
| 33 | - nix build | |
| 38 | + PAGE_COMMITS="$$(scripts/page-commits.sh)" nix build --impure | |
| 34 | 39 | |
| 35 | 40 | serve: ## Run the Hugo dev server with live reload |
| 36 | 41 | 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 develop | 30 | 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 ./result | 37 | build: ## Build the site (all languages) into ./result |
| 33 | - nix build | 38 | + PAGE_COMMITS="$$(scripts/page-commits.sh)" nix build --impure |
| 34 | 39 | ||
| 35 | serve: ## Run the Hugo dev server with live reload | 40 | serve: ## Run the Hugo dev server with live reload |
| 36 | nix develop --command hugo server --source blog | 41 | nix develop --command hugo server --source blog |
modified
blog/hugo.toml +14 -7 | @@ -84,16 +84,23 @@ weight = 5 | ||
| 84 | 84 | # flip to false (or delete the line) once the site is ready to be found. |
| 85 | 85 | noindex = true |
| 86 | 86 | |
| 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). | |
| 91 | 96 | repoURL = "https://rickub.com/mykiwi/mykiwi.blog" |
| 92 | 97 | repoContentPath = "blog/content" |
| 98 | +repoBranch = "main" | |
| 93 | 99 | # 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. | |
| 97 | 104 | repoCommit = "main" |
| 98 | 105 | |
| 99 | 106 | [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 = true | 85 | noindex = true |
| 86 | 86 | ||
| 87 | -# Footer "Improve this page" link: <repoURL>/blob/<repoCommit>/<repoContentPath>/<the | 87 | +# 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 under | 88 | +# 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 what | 89 | +# .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 the | 99 | # 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 | 100 | +# 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. `hugo | 101 | +# 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 @@ | ||
| 1 | 1 | <footer class="site-foot column"> |
| 2 | 2 | {{- /* |
| 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`. | |
| 8 | 11 | */}} |
| 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 }} | |
| 11 | 16 | {{- /* |
| 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. | |
| 16 | 27 | */}} |
| 17 | 28 | {{- with .File }} |
| 18 | 29 | {{- $source := path.Join site.Params.repoContentPath .Path }} |
| 30 | + {{- $stamp := or (index $pageCommits .Path) site.Params.repoCommit }} | |
| 19 | 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 | 33 | </span> |
| 22 | 34 | <span class="sep" aria-hidden="true">·</span> |
| 23 | 35 | {{- 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, see | 3 | + 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 to | 4 | + 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 resolves | 5 | + 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-character | 6 | + 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 rather | 17 | + 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 rendered | 18 | + 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 post | 19 | + 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">·</span> | 34 | <span class="sep" aria-hidden="true">·</span> |
| 23 | {{- end }} | 35 | {{- end }} |
modified
flake.nix +8 -0 | @@ -22,8 +22,16 @@ | ||
| 22 | 22 | # self.rev is the revision this flake was built from - it only exists |
| 23 | 23 | # when the tree is clean, so a local `nix build` with uncommitted |
| 24 | 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 | 32 | HUGO_PARAMS_NIXSTOREPATH="$out" \ |
| 26 | 33 | HUGO_PARAMS_REPOCOMMIT="${self.rev or "main"}" \ |
| 34 | + HUGO_PARAMS_PAGECOMMITS='${builtins.getEnv "PAGE_COMMITS"}' \ | |
| 27 | 35 | hugo --minify --destination "$out" |
| 28 | 36 | ''; |
| 29 | 37 | dontInstall = true; |
| @@ -22,8 +22,16 @@ | |||
| 22 | # self.rev is the revision this flake was built from - it only exists | 22 | # 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 uncommitted | 23 | # 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 '}' | ||