Explorar el Código

Link the About page's commit hash to the commit in Gogs

The About page's build hash now links to <repoURL>/commit/<full hash>.
repoURL is a new optional site.json setting, defaulting to this repo,
so forks can point at their own. Dirty builds, which match no commit,
show -dirty with no link. Version 0.5.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude hace 11 horas
padre
commit
a756b16dc9
Se han modificado 7 ficheros con 88 adiciones y 26 borrados
  1. 3 1
      README.md
  2. 4 0
      config.go
  3. 11 6
      main.go
  4. 46 0
      main_test.go
  5. 4 2
      render.go
  6. 1 1
      templates/about.html
  7. 19 16
      version.go

+ 3 - 1
README.md

@@ -63,7 +63,8 @@ and take effect without a restart.
 ```json
 ```json
 {
 {
   "homeText": "Home",
   "homeText": "Home",
-  "defaultAuthor": "Claude"
+  "defaultAuthor": "Claude",
+  "repoURL": "https://git.monoaf.com/bajdev/posts"
 }
 }
 ```
 ```
 
 
@@ -71,6 +72,7 @@ and take effect without a restart.
 |---|---|---|
 |---|---|---|
 | `homeText` | `Home` | Text of the header link to the index page |
 | `homeText` | `Home` | Text of the header link to the index page |
 | `defaultAuthor` | `Claude` | Author shown for posts without an `author` line |
 | `defaultAuthor` | `Claude` | Author shown for posts without an `author` line |
+| `repoURL` | `https://git.monoaf.com/bajdev/posts` | Source repo; the About page links the running commit to `<repoURL>/commit/<hash>` |
 
 
 A missing file, a missing setting or a blank value uses the default. If the
 A missing file, a missing setting or a blank value uses the default. If the
 file is invalid JSON or has an unknown setting name, the error is logged and
 file is invalid JSON or has an unknown setting name, the error is logged and

+ 4 - 0
config.go

@@ -15,6 +15,7 @@ import (
 type siteConfig struct {
 type siteConfig struct {
 	HomeText      string `json:"homeText"`      // text of the header link to the index
 	HomeText      string `json:"homeText"`      // text of the header link to the index
 	DefaultAuthor string `json:"defaultAuthor"` // author of posts that don't name one
 	DefaultAuthor string `json:"defaultAuthor"` // author of posts that don't name one
+	RepoURL       string `json:"repoURL"`       // web address of the source repo, for commit links
 }
 }
 
 
 // withDefaults fills in any setting that is missing or blank.
 // withDefaults fills in any setting that is missing or blank.
@@ -22,6 +23,9 @@ func (c siteConfig) withDefaults() siteConfig {
 	if strings.TrimSpace(c.HomeText) == "" {
 	if strings.TrimSpace(c.HomeText) == "" {
 		c.HomeText = "Home"
 		c.HomeText = "Home"
 	}
 	}
+	if strings.TrimSpace(c.RepoURL) == "" {
+		c.RepoURL = "https://git.monoaf.com/bajdev/posts"
+	}
 	if strings.TrimSpace(c.DefaultAuthor) == "" {
 	if strings.TrimSpace(c.DefaultAuthor) == "" {
 		c.DefaultAuthor = "Claude"
 		c.DefaultAuthor = "Claude"
 	}
 	}

+ 11 - 6
main.go

@@ -22,7 +22,7 @@ type app struct {
 	about    template.HTML
 	about    template.HTML
 	siteURL  string // absolute base URL, no trailing slash
 	siteURL  string // absolute base URL, no trailing slash
 	siteName string
 	siteName string
-	commit   string
+	commit   commitInfo
 
 
 	releaseFS   fs.FS // pull-releases/
 	releaseFS   fs.FS // pull-releases/
 	releases    []*Release
 	releases    []*Release
@@ -141,12 +141,17 @@ func (a *app) aboutPage(w http.ResponseWriter, r *http.Request) {
 		a.serverError(w, err)
 		a.serverError(w, err)
 		return
 		return
 	}
 	}
+	var commitURL string
+	if a.commit.Full != "" && !a.commit.Dirty {
+		commitURL = strings.TrimRight(a.config.Get().RepoURL, "/") + "/commit/" + a.commit.Full
+	}
 	a.render(w, http.StatusOK, "about", pageData{
 	a.render(w, http.StatusOK, "about", pageData{
-		Posts:    idx.posts,
-		Releases: a.releases,
-		Version:  version,
-		Commit:   a.commit,
-		About:    a.about,
+		Posts:     idx.posts,
+		Releases:  a.releases,
+		Version:   version,
+		Commit:    a.commit,
+		CommitURL: commitURL,
+		About:     a.about,
 	})
 	})
 }
 }
 
 

+ 46 - 0
main_test.go

@@ -304,6 +304,52 @@ func TestHeaderAndFooter(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestAboutCommitLink(t *testing.T) {
+	const full = "d2a387e186c8caf21a35349b8c3d60f68fb87b1f"
+	about := func(t *testing.T, c commitInfo, siteJSON string) string {
+		t.Helper()
+		dir := t.TempDir()
+		if siteJSON != "" {
+			writeFile(t, dir, "site.json", siteJSON)
+		}
+		a, err := newApp(dir, "https://posts.example.com")
+		if err != nil {
+			t.Fatal(err)
+		}
+		a.commit = c // go test binaries carry no VCS stamp; set one
+		srv := httptest.NewServer(a.routes())
+		t.Cleanup(srv.Close)
+		_, _, body := get(t, srv, "/about")
+		return body
+	}
+
+	t.Run("clean build links the full hash", func(t *testing.T) {
+		body := about(t, commitInfo{Full: full, Short: full[:7]}, "")
+		want := `<span class="muted">(<a href="https://git.monoaf.com/bajdev/posts/commit/` + full + `">d2a387e</a>)</span>`
+		if !strings.Contains(body, want) {
+			t.Errorf("missing %s in:\n%s", want, body)
+		}
+	})
+	t.Run("repoURL from site.json", func(t *testing.T) {
+		body := about(t, commitInfo{Full: full, Short: full[:7]}, `{"repoURL": "https://example.org/me/fork/"}`)
+		if !strings.Contains(body, `href="https://example.org/me/fork/commit/`+full+`"`) {
+			t.Errorf("site.json repoURL not used:\n%s", body)
+		}
+	})
+	t.Run("dirty build is not linked", func(t *testing.T) {
+		body := about(t, commitInfo{Full: full, Short: full[:7], Dirty: true}, "")
+		if !strings.Contains(body, `(d2a387e-dirty)`) || strings.Contains(body, "/commit/") {
+			t.Errorf("dirty build should show -dirty with no link:\n%s", body)
+		}
+	})
+	t.Run("no commit shows version only", func(t *testing.T) {
+		body := about(t, commitInfo{}, "")
+		if !strings.Contains(body, "<dd>"+version+"</dd>") {
+			t.Errorf("want bare version:\n%s", body)
+		}
+	})
+}
+
 func TestAboutPage(t *testing.T) {
 func TestAboutPage(t *testing.T) {
 	srv, _ := newTestServer(t, sample)
 	srv, _ := newTestServer(t, sample)
 	status, _, body := get(t, srv, "/about")
 	status, _, body := get(t, srv, "/about")

+ 4 - 2
render.go

@@ -67,8 +67,10 @@ type pageData struct {
 	Message      string
 	Message      string
 
 
 	// About page only.
 	// About page only.
-	Version, Commit string
-	About           template.HTML
+	Version   string
+	Commit    commitInfo
+	CommitURL string // empty when the build doesn't match a commit exactly
+	About     template.HTML
 
 
 	// Release notes pages (and the About page's link to them).
 	// Release notes pages (and the About page's link to them).
 	Releases []*Release
 	Releases []*Release

+ 1 - 1
templates/about.html

@@ -4,7 +4,7 @@
   <h1>About</h1>
   <h1>About</h1>
   <dl class="facts">
   <dl class="facts">
     <dt>Version</dt>
     <dt>Version</dt>
-    <dd>{{.Version}}{{with .Commit}} <span class="muted">({{.}})</span>{{end}}</dd>
+    <dd>{{.Version}}{{with .Commit.Short}} <span class="muted">({{if $.CommitURL}}<a href="{{$.CommitURL}}">{{.}}</a>{{else}}{{.}}{{if $.Commit.Dirty}}-dirty{{end}}{{end}})</span>{{end}}</dd>
     <dt>Release notes</dt>
     <dt>Release notes</dt>
     <dd><a href="/releases">{{len .Releases}} releases</a></dd>
     <dd><a href="/releases">{{len .Releases}} releases</a></dd>
     <dt>Posts</dt>
     <dt>Posts</dt>

+ 19 - 16
version.go

@@ -4,31 +4,34 @@ import "runtime/debug"
 
 
 // version is the release number shown on the About page. Bump it in the PR
 // version is the release number shown on the About page. Bump it in the PR
 // that makes a user-visible change.
 // that makes a user-visible change.
-const version = "0.5.2"
+const version = "0.5.3"
 
 
-// buildCommit returns the short git revision that `go build` stamped into the
-// binary, with "-dirty" if the tree had uncommitted changes, or "" if the
-// binary was built outside a git checkout.
-func buildCommit() string {
+// commitInfo is the git revision `go build` stamped into the binary. Full is
+// empty when the binary was built outside a git checkout (for example by
+// `go test`); Dirty means the tree had uncommitted changes, so the build
+// doesn't match any commit exactly.
+type commitInfo struct {
+	Full, Short string
+	Dirty       bool
+}
+
+func buildCommit() commitInfo {
 	info, ok := debug.ReadBuildInfo()
 	info, ok := debug.ReadBuildInfo()
 	if !ok {
 	if !ok {
-		return ""
+		return commitInfo{}
 	}
 	}
-	var rev string
-	var dirty bool
+	var c commitInfo
 	for _, s := range info.Settings {
 	for _, s := range info.Settings {
 		switch s.Key {
 		switch s.Key {
 		case "vcs.revision":
 		case "vcs.revision":
-			rev = s.Value
+			c.Full = s.Value
 		case "vcs.modified":
 		case "vcs.modified":
-			dirty = s.Value == "true"
+			c.Dirty = s.Value == "true"
 		}
 		}
 	}
 	}
-	if len(rev) > 7 {
-		rev = rev[:7]
-	}
-	if rev != "" && dirty {
-		rev += "-dirty"
+	c.Short = c.Full
+	if len(c.Short) > 7 {
+		c.Short = c.Short[:7]
 	}
 	}
-	return rev
+	return c
 }
 }