Prechádzať zdrojové kódy

Merge branch 'feature/commit-link' of bajdev/posts into main

bajdev 13 hodín pred
rodič
commit
ff84dfea9c

+ 3 - 1
README.md

@@ -63,7 +63,8 @@ and take effect without a restart.
 ```json
 {
   "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 |
 | `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
 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 {
 	HomeText      string `json:"homeText"`      // text of the header link to the index
 	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.
@@ -22,6 +23,9 @@ func (c siteConfig) withDefaults() siteConfig {
 	if strings.TrimSpace(c.HomeText) == "" {
 		c.HomeText = "Home"
 	}
+	if strings.TrimSpace(c.RepoURL) == "" {
+		c.RepoURL = "https://git.monoaf.com/bajdev/posts"
+	}
 	if strings.TrimSpace(c.DefaultAuthor) == "" {
 		c.DefaultAuthor = "Claude"
 	}

+ 11 - 6
main.go

@@ -22,7 +22,7 @@ type app struct {
 	about    template.HTML
 	siteURL  string // absolute base URL, no trailing slash
 	siteName string
-	commit   string
+	commit   commitInfo
 
 	releaseFS   fs.FS // pull-releases/
 	releases    []*Release
@@ -141,12 +141,17 @@ func (a *app) aboutPage(w http.ResponseWriter, r *http.Request) {
 		a.serverError(w, err)
 		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{
-		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) {
 	srv, _ := newTestServer(t, sample)
 	status, _, body := get(t, srv, "/about")

+ 36 - 0
pull-releases/010-commit-link/README.md

@@ -0,0 +1,36 @@
+# Link the About page's commit hash to the commit
+
+Branch `feature/commit-link` · version 0.5.3
+
+## What changed
+
+- **Linked hash:** the About page's build hash, e.g. `0.5.3 (d2a387e)`, now links to that commit in Gogs. The link uses the full hash: `https://git.monoaf.com/bajdev/posts/commit/<full hash>`.
+- **New `repoURL` setting:** optional, in `content/site.json`, defaulting to `https://git.monoaf.com/bajdev/posts`. Anyone running a fork of the code can point the link at their own repo.
+- **Dirty builds aren't linked.** A binary built from uncommitted changes matches no commit, so it still shows `(abc1234-dirty)` as plain text. Pipeline builds always come from a clean checkout, so the live site always has the link.
+- **README:** documents `repoURL`.
+- **Version:** 0.5.3.
+
+## Notes
+
+- Since Phase 7, the live site is always built from the tip of `main`, so the link goes to the merge commit that's running.
+- The hash display is unchanged; only the link is new.
+
+## Testing
+
+- All tests pass with the race detector (`go test -race ./...`).
+- New test covering four cases: a clean build links the full hash, `repoURL` in `site.json` overrides the link, a dirty build shows `-dirty` with no link, and no commit at all shows the bare version.
+- Built from a clean local commit and checked the About page HTML: the link went to `/commit/<full hash>` of that build. Gogs commit URLs return 200 for the full hash.
+
+| About page |
+|---|
+| [![About page](about-link.png)](about-link.png) |
+
+## Screenshots
+
+From the branch's build running locally. The home page doesn't change.
+
+| Desktop | Mobile |
+|---|---|
+| [![Desktop](desktop.png)](desktop.png) | [![Mobile](mobile.png)](mobile.png) |
+
+🤖 Generated with [Claude Code](https://claude.com/claude-code)

BIN
pull-releases/010-commit-link/about-link.png


BIN
pull-releases/010-commit-link/desktop.png


BIN
pull-releases/010-commit-link/mobile.png


+ 1 - 0
pull-releases/README.md

@@ -36,3 +36,4 @@ screenshot, or its `README.md` doesn't load.
 | [007](007-pinned-posts/) | Pinned posts |
 | [008](008-tab-size/) | Tabs in code blocks 4 columns wide |
 | [009](009-pager-order/) | Previous post on the left, next on the right |
+| [010](010-commit-link/) | About page commit hash links to the commit |

+ 4 - 2
render.go

@@ -67,8 +67,10 @@ type pageData struct {
 	Message      string
 
 	// 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).
 	Releases []*Release

+ 1 - 1
templates/about.html

@@ -4,7 +4,7 @@
   <h1>About</h1>
   <dl class="facts">
     <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>
     <dd><a href="/releases">{{len .Releases}} releases</a></dd>
     <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
 // 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()
 	if !ok {
-		return ""
+		return commitInfo{}
 	}
-	var rev string
-	var dirty bool
+	var c commitInfo
 	for _, s := range info.Settings {
 		switch s.Key {
 		case "vcs.revision":
-			rev = s.Value
+			c.Full = s.Value
 		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
 }