@ 261101.7

261101.5 · 4c2d0371693dc2f3c3f3293bd77a9e2267d527d3

git.go splits off the history pages

git.go held both running git and the pages built from it. The timeline, log and commit pages and their diffs move to historypage.go with their tests. git.go keeps running git, reading commits and the cache warm.

Declarations moved unchanged, as a sorted comparison of every line outside the import blocks confirms. git blame -C -C traces them.

reading declarations

Changes to git_test.go. All files

git_test.go

@@ -2,13 +2,11 @@ package main  import ( 	"fmt"-	"net/http" 	"os" 	"os/exec" 	"path/filepath" 	"strings" 	"testing"-	"time" )  func needGit(t *testing.T) {@@ -46,170 +44,6 @@ func repoTree(t *testing.T) (*server, string) { 	return s, dir } -// A tracked directory's listing opens its history from its first entry.-func TestListingLeadsToHistory(t *testing.T) {-	s, _ := repoTree(t)-	h := s.handler()-	entry := `<ul class="listing">` + "\n" + `<li class="log"><a href="?log">&lt;(git log)</a></li>`-	for _, p := range []string{"/", "/a/"} {-		if body := get(h, p).Body.String(); !strings.Contains(body, entry) {-			t.Errorf("%s: no history entry first:\n%s", p, body)-		}-	}-	if body := get(h, "/untracked/").Body.String(); strings.Contains(body, "?log") {-		t.Errorf("untracked directory leads to history:\n%s", body)-	}-}--// A directory's history covers its subtree; a file's covers the file.-func TestHistoryPage(t *testing.T) {-	s, dir := repoTree(t)-	h := s.handler()-	rec := get(h, "/?log")-	body := rec.Body.String()-	if rec.Code != http.StatusOK {-		t.Fatalf("status %d:\n%s", rec.Code, body)-	}-	add, ren := strings.Index(body, "Add the docs"), strings.Index(body, "Rename the package &lt;b&gt;")-	if add < 0 || ren < 0 || ren > add {-		t.Errorf("want both commits, newest first:\n%s", body)-	}-	if !strings.Contains(body, "<p>The first line wraps here.<br>- a list item<br>- another</p>\n<p>A second paragraph.</p>") {-		t.Errorf("body not in paragraphs:\n%s", body)-	}-	if !strings.Contains(body, `<span>&lt;(git log)</span>`) {-		t.Errorf("no history crumb:\n%s", body)-	}-	// Each commit folds; its diff loads from the commit page, which the-	// fold also links in its place.-	c := head(t, dir, "HEAD")-	for _, want := range []string{-		`<li><details data-diff="?commit=` + c + `">` + "\n<summary><span class=\"own lead\"><span class=\"date\" title=\"" + c[:7],-		`</span></span> <span class="subject">Rename the package &lt;b&gt;</span></summary>` + "\n" + `<p class="own links">commit <a href="?commit=` + c + `">` + c + `</a> · <a class="then"`,-		`<div class="diffs"><p class="own"><a href="?commit=` + c + `">diff</a></p></div>` + "\n</details></li>",-	} {-		if !strings.Contains(body, want) {-			t.Errorf("no %q:\n%s", want, body)-		}-	}-	if body := get(h, "/index.md?log").Body.String(); strings.Contains(body, "Rename") || !strings.Contains(body, "Add the docs") {-		t.Errorf("file history holds other files' commits:\n%s", body)-	}-	if body := get(h, "/.gitignore?log").Body.String(); !strings.Contains(body, `<a href="/.gitignore">.gitignore</a>`) {-		t.Errorf("dot file's crumb lost its dot:\n%s", body)-	}-	if body := get(h, "/a/b.go?log").Body.String(); !strings.Contains(body, "Rename") || !strings.Contains(body, `<a href="/a/b.go">b.go</a>`) {-		t.Errorf("file history:\n%s", body)-	}-	if body := get(h, "/untracked/?log").Body.String(); !strings.Contains(body, "No commit touches untracked/.") {-		t.Errorf("untracked history:\n%s", body)-	}-}--// A commit with no message still folds: its line holds the hash and date-// alone. A message without a blank line is all subject.-func TestHistoryOddMessages(t *testing.T) {-	s, dir := repoTree(t)-	os.WriteFile(filepath.Join(dir, "index.md"), []byte("# Changed\n"), 0o644)-	if out, err := exec.Command("git", "-C", dir, "-c", "user.name=t", "-c", "user.email=t@x",-		"commit", "-qa", "--allow-empty-message", "-m", "").CombinedOutput(); err != nil {-		t.Fatalf("git commit: %v\n%s", err, out)-	}-	os.WriteFile(filepath.Join(dir, "index.md"), []byte("# Again\n"), 0o644)-	commitAll(t, dir, "One paragraph\nthat runs on")-	body := get(s.handler(), "/index.md?log").Body.String()-	empty, long := head(t, dir, "HEAD~1"), head(t, dir, "HEAD")-	for _, want := range []string{-		`<details data-diff="?commit=` + empty + `">`,-		`</span></span> <span class="subject"></span></summary>`,-		`<details data-diff="?commit=` + long + `">`,-		`</span></span> <span class="subject">One paragraph that runs on</span></summary>`,-	} {-		if !strings.Contains(body, want) {-			t.Errorf("no %q:\n%s", want, body)-		}-	}-}--// A directory without its slash keeps ?log through the redirect.-func TestHistoryRedirectKeepsQuery(t *testing.T) {-	s, _ := repoTree(t)-	if rec := get(s.handler(), "/a?log"); rec.Header().Get("Location") != "/a/?log" {-		t.Errorf("redirect to %q", rec.Header().Get("Location"))-	}-}--// Doc and code pages say at the top whether they match their last-// commit, which they name on hover; an edit since makes them changed.-func TestPageTopNamesLastCommit(t *testing.T) {-	s, dir := repoTree(t)-	h := s.handler()-	for p, subject := range map[string]string{"/index.md": "Add the docs", "/a/b.go": "Rename the package &lt;b&gt;"} {-		body := get(h, p).Body.String()-		if !strings.Contains(body, `<details class="own latest" data-timeline="?timeline">`) || !strings.Contains(body, ` · `+subject+` · `) || !strings.Contains(body, `">current</span></summary>`) ||-			!strings.Contains(body, `<div class="versions"><a href="?log">&lt;(git log)</a></div>`) || strings.Contains(body, `class="own now"`) {-			t.Errorf("%s: no top line with %q:\n%s", p, subject, body)-		}-	}-	if id := head(t, dir, "HEAD:a/b.go"); blobID([]byte("package b\n"), len(id)) != id {-		t.Errorf("blob id %s, git says %s", blobID([]byte("package b\n"), len(id)), id)-	}-	os.WriteFile(filepath.Join(dir, "a", "b.go"), []byte("package c\n"), 0o644)-	if body := get(h, "/a/b.go").Body.String(); !strings.Contains(body, `">changed</span></summary>`) {-		t.Errorf("edited page is not changed:\n%s", body)-	}-	if body := get(h, "/untracked/x.txt").Body.String(); strings.Contains(body, "?log") {-		t.Errorf("untracked file has a top line:\n%s", body)-	}-}--// Outside a repo nothing leads to history, and the page says why.-func TestNoRepoNoHistory(t *testing.T) {-	needGit(t)-	s, _, _ := serveTree(t)-	h := s.handler()-	if body := get(h, "/").Body.String(); strings.Contains(body, "?log") {-		t.Errorf("history entry outside a repo:\n%s", body)-	}-	if rec := get(h, "/?log"); rec.Code != http.StatusNotFound || !strings.Contains(rec.Body.String(), "not a git repository") {-		t.Errorf("status %d:\n%s", rec.Code, rec.Body.String())-	}-}--// A share shows history without trailers, and only while other users-// could read the repo. Its errors name no path.-func TestShareShowsHistory(t *testing.T) {-	needGit(t)-	s := shareTree(t)-	dir := s.list()[0].dir-	if s.list()[0].secret {-		dir = s.list()[1].dir-	}-	exec.Command("git", "init", "-q", dir).Run()-	commitAll(t, dir, "Shared subject\n\nThe body.\n\nClaude-Session: https://claude.ai/code/session_x\nSigned-off-by: t <t@x>")-	gitDir := filepath.Join(dir, ".git")-	exec.Command("chmod", "-R", "go+rX", gitDir).Run()-	h := s.handler()-	if body := get(h, "/docs/").Body.String(); !strings.Contains(body, `href="?log"`) {-		t.Errorf("share listing leads to no history:\n%s", body)-	}-	body := get(h, "/docs/?log").Body.String()-	if !strings.Contains(body, "Shared subject") || !strings.Contains(body, "<p>The body.</p>") {-		t.Errorf("share history:\n%s", body)-	}-	if strings.Contains(body, "claude.ai") || strings.Contains(body, "t@x") {-		t.Errorf("share history shows trailers:\n%s", body)-	}-	os.Chmod(filepath.Join(gitDir, "objects"), 0o700)-	if body := get(h, "/docs/").Body.String(); strings.Contains(body, "?log") {-		t.Errorf("history of a closed repo:\n%s", body)-	}-	rec := get(h, "/docs/?log")-	if rec.Code != http.StatusNotFound || !strings.Contains(rec.Body.String(), "No git history here.") || strings.Contains(rec.Body.String(), dir) {-		t.Errorf("closed repo: %d\n%s", rec.Code, rec.Body.String())-	}-}- // Loopback keeps trailers. func TestLoopbackKeepsTrailers(t *testing.T) { 	s, dir := repoTree(t)@@ -236,20 +70,6 @@ func TestDropTrailers(t *testing.T) { 	} } -// A commit reloads an open history page.-func TestLiveReloadsOnCommit(t *testing.T) {-	needGit(t)-	s, _, dir, ts := liveTree(t)-	exec.Command("git", "init", "-q", dir).Run()-	commitAll(t, dir, "First")-	lines := stream(t, s, ts, "/a/")-	time.Sleep(50 * time.Millisecond)-	exec.Command("git", "-C", dir, "-c", "user.name=t", "-c", "user.email=t@x", "commit", "-q", "--allow-empty", "-m", "Second").Run()-	if !reloads(lines, 2*time.Second) {-		t.Fatal("no reload after a commit")-	}-}- // head is the hash of HEAD in dir. func head(t *testing.T, dir, rev string) string { 	t.Helper()@@ -260,104 +80,6 @@ func head(t *testing.T, dir, rev string) string { 	return strings.TrimSpace(string(out)) } -// A commit page shows the message and the diff of the path it came from,-// and links to the commit across the tree.-func TestCommitPage(t *testing.T) {-	s, dir := repoTree(t)-	h := s.handler()-	c := head(t, dir, "HEAD")-	body := get(h, "/a/b.go?commit="+c).Body.String()-	for _, want := range []string{-		"Rename the package &lt;b&gt;",-		`</span> · <span class="hash">` + c + `</span></p>`,-		`<span class="del">-func F() {}</span><span class="add">+package b</span>`,-		`<a href="/?commit=` + c + `">All files</a>`,-		`<p class="own file"><a href="/a/b.go">a/b.go</a></p>`,-		`<a href="/a/b.go?log">&lt;(git log)</a>`,-		"</div>\n<div class=\"diffs\">\n<p class=\"own\">Changes to a/b.go.",-		"</pre>\n</div>\n",-	} {-		if !strings.Contains(body, want) {-			t.Errorf("no %q:\n%s", want, body)-		}-	}-	if body := get(h, "/index.md?commit="+c).Body.String(); !strings.Contains(body, "It does not change index.md.") {-		t.Errorf("commit that skips the file:\n%s", body)-	}-	first := get(h, "/?commit="+head(t, dir, "HEAD~1")).Body.String()-	if !strings.Contains(first, `<a href="/index.md">index.md</a>`) || !strings.Contains(first, `<a href="/a/b.go">a/b.go</a>`) || strings.Contains(first, "All files") {-		t.Errorf("whole commit:\n%s", first)-	}-	for _, bad := range []string{"zz", "-p", head(t, dir, "HEAD:a/b.go")} {-		if rec := get(h, "/?commit="+bad); rec.Code != http.StatusNotFound {-			t.Errorf("%s: status %d:\n%s", bad, rec.Code, rec.Body.String())-		}-	}-}--func TestDiffName(t *testing.T) {-	for in, want := range map[string]string{-		"a/x.md b/x.md":                     "x.md",-		"a/two words b/two words":           "two words",-		`"a/q\"uote" "b/q\"uote"`:           `q"uote`,-		`"a/tab\there.md" "b/tab\there.md"`: "tab\there.md",-	} {-		if got, ok := diffName(in); !ok || got != want {-			t.Errorf("%q: %q %v, want %q", in, got, ok, want)-		}-	}-}--// A share's diffs leave out dot files.-func TestShareDiffSkipsDots(t *testing.T) {-	needGit(t)-	s := shareTree(t)-	dir := s.list()[0].dir-	if s.list()[0].secret {-		dir = s.list()[1].dir-	}-	exec.Command("git", "init", "-q", dir).Run()-	os.WriteFile(filepath.Join(dir, ".env"), []byte("TOKEN=hidden\n"), 0o644)-	commitAll(t, dir, "Shared")-	exec.Command("chmod", "-R", "go+rX", filepath.Join(dir, ".git")).Run()-	body := get(s.handler(), "/docs/?commit="+head(t, dir, "HEAD")).Body.String()-	if !strings.Contains(body, "+docs/pub.txt") || strings.Contains(body, "hidden") {-		t.Errorf("share diff:\n%s", body)-	}-}--// A history that git was slow to give names the command that writes-// changed-path filters, until the repo has them. A share never names it.-func TestSlowHistoryNamesCommitGraph(t *testing.T) {-	s, dir := repoTree(t)-	h := s.handler()-	defer func(d time.Duration) { slowGit = d }(slowGit)-	slowGit = -1-	hint := "git commit-graph write --reachable --changed-paths"-	if body := get(h, "/?log").Body.String(); !strings.Contains(body, hint) {-		t.Errorf("no hint without filters:\n%s", body)-	}-	if out, err := exec.Command("git", "-C", dir, "commit-graph", "write", "--reachable", "--changed-paths").CombinedOutput(); err != nil {-		t.Fatalf("commit-graph: %v\n%s", err, out)-	}-	if body := get(h, "/index.md?log").Body.String(); strings.Contains(body, hint) {-		t.Errorf("hint with filters:\n%s", body)-	}-	os.Remove(filepath.Join(dir, ".git", "objects", "info", "commit-graph"))-	if out, err := exec.Command("git", "-C", dir, "commit-graph", "write", "--reachable", "--split").CombinedOutput(); err != nil {-		t.Fatalf("commit-graph: %v\n%s", err, out)-	}-	if body := get(h, "/a/?log").Body.String(); !strings.Contains(body, hint) {-		t.Errorf("no hint for a split graph without filters:\n%s", body)-	}-	if out, err := exec.Command("git", "-C", dir, "commit-graph", "write", "--reachable", "--split=replace", "--changed-paths").CombinedOutput(); err != nil {-		t.Fatalf("commit-graph: %v\n%s", err, out)-	}-	if body := get(h, "/a/b.go?log").Body.String(); strings.Contains(body, hint) {-		t.Errorf("hint for a split graph with filters:\n%s", body)-	}-}- // A new tree's listing and README have their top lines cached before the // first visit. func TestWarmCachesFirstPages(t *testing.T) {