From 718e2f881e61468f2243ae5d05722b5e1037a5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl-Einar=20Bergstr=C3=B8m?= Date: Sun, 11 Oct 2026 11:08:25 +0200 Subject: [PATCH] feat: link each changelog entry to its PR and commit Co-Authored-By: Claude Opus 5.5 (1M context) --- cliff.toml | 5 ++++- internal/update/update.go | 7 ++++++- internal/update/update_test.go | 7 +++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cliff.toml b/cliff.toml index 7b3e699..1978ee8 100644 --- a/cliff.toml +++ b/cliff.toml @@ -24,7 +24,8 @@ Hello world 👋 {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | striptags | trim }} {% for commit in commits %} -- {% if commit.breaking %}**Breaking:** {% endif %}{{ commit.message | split(pat="\n") | first | upper_first | trim }}\ +- {% if commit.breaking %}**Breaking:** {% endif %}{{ commit.message | split(pat="\n") | first | upper_first | trim }} \ +([{{ commit.id | truncate(length=7, end="") }}](https://git.kebcloud.no/keb/lazysecrets/commit/{{ commit.id }}))\ {% endfor %} {% endfor %}\n """ @@ -33,6 +34,8 @@ trim = true # Runs on each part (header, every release) separately. postprocessors = [ { pattern = "\n{3,}", replace = "\n\n" }, + # Squash commits end in the PR's number: link it. + { pattern = "\\(#([0-9]+)\\)", replace = "([#${1}](https://git.kebcloud.no/keb/lazysecrets/pulls/${1}))" }, ] [git] diff --git a/internal/update/update.go b/internal/update/update.go index 8c62532..d0e251e 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -22,6 +22,7 @@ import ( "os/exec" "path" "path/filepath" + "regexp" "runtime" "strings" "time" @@ -49,9 +50,13 @@ type Release struct { // every release ends with. func (r Release) Notes() string { notes, _, _ := strings.Cut(r.Body, "## Install") - return strings.TrimSpace(notes) + return mdLink.ReplaceAllString(strings.TrimSpace(notes), "$1") } +// mdLink is a Markdown link, [text](url): the notes link PRs and commits, +// and a terminal shows them as their text. +var mdLink = regexp.MustCompile(`\[([^\]]+)\]\((https?://[^)\s]+)\)`) + // Client talks to the release server. type Client struct { API string // the repository's API, DefaultAPI when empty diff --git a/internal/update/update_test.go b/internal/update/update_test.go index 61d9f2d..496e916 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -123,3 +123,10 @@ func TestInstallRefusesATamperedArchive(t *testing.T) { t.Error("replaced anyway") } } + +func TestNotesShowLinksAsText(t *testing.T) { + r := Release{Body: "### Added\n\n- Search ([#11](https://git.example.com/pulls/11)) ([65dc2d7](https://git.example.com/commit/65dc2d7abc))\n\n## Install\n\ncurl …"} + if got, want := r.Notes(), "### Added\n\n- Search (#11) (65dc2d7)"; got != want { + t.Errorf("notes %q, want %q", got, want) + } +}