Commit de058c1

Nick Faro committed on
Avoid republishing unchanged site assets
commit de058c1996818b05085a54195fa995f7536a0fc0 parent 4ff6b40
6 changed files +64−15
Modifiedcmd/dumbforge/main.go +1−0
@@ -129,6 +129,7 @@ func siteCommand(ctx context.Context, args []string) (err error) {
129 fmt.Printf("dumbforge: "+format+"\n", args...) 129 fmt.Printf("dumbforge: "+format+"\n", args...)
130 } 130 }
131 generator.SetBranches(metadata.BranchNames(), strings.TrimPrefix(metadata.Head, "refs/heads/")) 131 generator.SetBranches(metadata.BranchNames(), strings.TrimPrefix(metadata.Head, "refs/heads/"))
132 generator.SetAssetsCurrent(metadata.SiteVersion == generator.AssetVersion())
132 build, err := generator.BuildBranch(ctx, *branch, "", oid, metadata.Head == "refs/heads/"+*branch) 133 build, err := generator.BuildBranch(ctx, *branch, "", oid, metadata.Head == "refs/heads/"+*branch)
133 if err != nil { 134 if err != nil {
134 return err 135 return err
Modifiedinternal/helper/helper.go +4−0
@@ -149,6 +149,7 @@ func publishSite(ctx context.Context, publisher *publish.Publisher, result publi
149 fmt.Fprintf(stderr, "dumbforge: "+format+"\n", args...) 149 fmt.Fprintf(stderr, "dumbforge: "+format+"\n", args...)
150 } 150 }
151 generator.SetBranches(result.Branches, strings.TrimPrefix(result.Head, "refs/heads/")) 151 generator.SetBranches(result.Branches, strings.TrimPrefix(result.Head, "refs/heads/"))
152 generator.SetAssetsCurrent(result.SiteVersion == generator.AssetVersion())
152 var deletedBranches []string 153 var deletedBranches []string
153 navigationChanged := false 154 navigationChanged := false
154 for _, update := range result.Updates { 155 for _, update := range result.Updates {
@@ -167,6 +168,9 @@ func publishSite(ctx context.Context, publisher *publish.Publisher, result publi
167 build, err := generator.BuildBranch(ctx, branch, update.OldOID, update.NewOID, update.Dst == result.Head) 168 build, err := generator.BuildBranch(ctx, branch, update.OldOID, update.NewOID, update.Dst == result.Head)
168 if err == nil { 169 if err == nil {
169 err = generator.Publish(ctx, publisher.Store, build) 170 err = generator.Publish(ctx, publisher.Store, build)
171 if err == nil {
172 generator.SetAssetsCurrent(true)
173 }
170 } 174 }
171 if err != nil { 175 if err != nil {
172 fmt.Fprintf(stderr, "dumbforge: warning: site publication failed: %v\n", err) 176 fmt.Fprintf(stderr, "dumbforge: warning: site publication failed: %v\n", err)
Modifiedinternal/publish/metadata.go +18−6
@@ -18,9 +18,10 @@ type Ref struct {
18 } 18 }
19 19
20 type Metadata struct { 20 type Metadata struct {
21 Head string 21 Head string
22 Refs map[string]Ref 22 Refs map[string]Ref
23 Packs map[string]struct{} 23 Packs map[string]struct{}
24 SiteVersion string
24 } 25 }
25 26
26 func (m Metadata) BranchNames() []string { 27 func (m Metadata) BranchNames() []string {
@@ -39,10 +40,10 @@ func LoadMetadata(ctx context.Context, store *s3store.Store) (Metadata, error) {
39 Refs: map[string]Ref{}, 40 Refs: map[string]Ref{},
40 Packs: map[string]struct{}{}, 41 Packs: map[string]struct{}{},
41 } 42 }
42 var refsObject, headObject, packObject s3store.Object 43 var refsObject, headObject, packObject, siteVersionObject s3store.Object
43 var refsErr, headErr, packErr error 44 var refsErr, headErr, packErr, siteVersionErr error
44 var requests sync.WaitGroup 45 var requests sync.WaitGroup
45 requests.Add(3) 46 requests.Add(4)
46 go func() { 47 go func() {
47 defer requests.Done() 48 defer requests.Done()
48 refsObject, refsErr = store.Get(ctx, "info/refs") 49 refsObject, refsErr = store.Get(ctx, "info/refs")
@@ -55,6 +56,10 @@ func LoadMetadata(ctx context.Context, store *s3store.Store) (Metadata, error) {
55 defer requests.Done() 56 defer requests.Done()
56 packObject, packErr = store.Get(ctx, "objects/info/packs") 57 packObject, packErr = store.Get(ctx, "objects/info/packs")
57 }() 58 }()
59 go func() {
60 defer requests.Done()
61 siteVersionObject, siteVersionErr = store.Get(ctx, ".dumbforge/site-version")
62 }()
58 requests.Wait() 63 requests.Wait()
59 64
60 err := refsErr 65 err := refsErr
@@ -83,6 +88,13 @@ func LoadMetadata(ctx context.Context, store *s3store.Store) (Metadata, error) {
83 if err == nil { 88 if err == nil {
84 parsePackList(packObject.Data, metadata.Packs) 89 parsePackList(packObject.Data, metadata.Packs)
85 } 90 }
91 err = siteVersionErr
92 if err != nil && !s3store.IsNotFound(err) {
93 return Metadata{}, fmt.Errorf("read site version: %w", err)
94 }
95 if err == nil {
96 metadata.SiteVersion = strings.TrimSpace(string(siteVersionObject.Data))
97 }
86 return metadata, nil 98 return metadata, nil
87 } 99 }
88 100
Modifiedinternal/publish/publish.go +2−0
@@ -36,6 +36,7 @@ type Result struct {
36 PackObjects uint32 36 PackObjects uint32
37 Head string 37 Head string
38 Branches []string 38 Branches []string
39 SiteVersion string
39 } 40 }
40 41
41 type Publisher struct { 42 type Publisher struct {
@@ -188,6 +189,7 @@ func (p *Publisher) Push(ctx context.Context, updates []Update, expected map[str
188 } 189 }
189 result.Head = metadata.Head 190 result.Head = metadata.Head
190 result.Branches = metadata.BranchNames() 191 result.Branches = metadata.BranchNames()
192 result.SiteVersion = metadata.SiteVersion
191 if err := lock.RefreshIfOlderThan(ctx, 30*time.Minute); err != nil { 193 if err := lock.RefreshIfOlderThan(ctx, 30*time.Minute); err != nil {
192 return Result{}, err 194 return Result{}, err
193 } 195 }
Modifiedinternal/site/site.go +30−7
@@ -4,6 +4,7 @@ import (
4 "bytes" 4 "bytes"
5 "compress/gzip" 5 "compress/gzip"
6 "context" 6 "context"
7 "crypto/sha256"
7 "fmt" 8 "fmt"
8 "html/template" 9 "html/template"
9 "mime" 10 "mime"
@@ -52,6 +53,7 @@ type Generator struct {
52 CloneURL string 53 CloneURL string
53 Branches []string 54 Branches []string
54 DefaultBranch string 55 DefaultBranch string
56 AssetsCurrent bool
55 Progress func(format string, args ...any) 57 Progress func(format string, args ...any)
56 templates *template.Template 58 templates *template.Template
57 submoduleMu sync.Mutex 59 submoduleMu sync.Mutex
@@ -149,6 +151,19 @@ func New(git gitutil.Git, baseURL, repoName, owner string) (*Generator, error) {
149 }, nil 151 }, nil
150 } 152 }
151 153
154 func (g *Generator) AssetVersion() string {
155 hash := sha256.New()
156 _, _ = hash.Write([]byte(siteCSS))
157 _, _ = hash.Write([]byte(g.chromaCSS()))
158 _, _ = hash.Write(gabrielMarkSVG)
159 _, _ = hash.Write(gabrielMarkPNG)
160 return fmt.Sprintf("%x", hash.Sum(nil))
161 }
162
163 func (g *Generator) SetAssetsCurrent(current bool) {
164 g.AssetsCurrent = current
165 }
166
152 // BuildBranch generates a full branch browser when oldOID is empty. For later 167 // BuildBranch generates a full branch browser when oldOID is empty. For later
153 // pushes it only regenerates changed blobs and the directory pages that contain 168 // pushes it only regenerates changed blobs and the directory pages that contain
154 // them. The repository landing page is refreshed when isHead is true. 169 // them. The repository landing page is refreshed when isHead is true.
@@ -159,13 +174,21 @@ func (g *Generator) BuildBranch(ctx context.Context, branch, oldOID, newOID stri
159 return Build{}, err 174 return Build{}, err
160 } 175 }
161 result := Build{} 176 result := Build{}
162 result.Pages = append(result.Pages, Page{ 177 if !g.AssetsCurrent {
163 Route: ".dumbforge/assets/site.css", 178 result.Pages = append(result.Pages, Page{
164 Body: []byte(siteCSS + g.chromaCSS()), 179 Route: ".dumbforge/assets/site.css",
165 ContentType: "text/css; charset=utf-8", 180 Body: []byte(siteCSS + g.chromaCSS()),
166 Cache: siteCache, 181 ContentType: "text/css; charset=utf-8",
167 }) 182 Cache: siteCache,
168 result.Pages = append(result.Pages, brandPages()...) 183 })
184 result.Pages = append(result.Pages, brandPages()...)
185 result.Pages = append(result.Pages, Page{
186 Route: ".dumbforge/site-version",
187 Body: []byte(g.AssetVersion() + "\n"),
188 ContentType: "text/plain; charset=utf-8",
189 Cache: siteCache,
190 })
191 }
169 commitsPage, err := g.commitsPage(branch, summary) 192 commitsPage, err := g.commitsPage(branch, summary)
170 if err != nil { 193 if err != nil {
171 return Build{}, err 194 return Build{}, err
Modifiedinternal/site/site_test.go +9−2
@@ -63,6 +63,7 @@ func TestFullAndIncrementalBuild(t *testing.T) {
63 assertPageDoesNotContain(t, full, "blob/main/vector.svg", `<div class="image-preview">`) 63 assertPageDoesNotContain(t, full, "blob/main/vector.svg", `<div class="image-preview">`)
64 assertPage(t, full, ".dumbforge/assets/gabriel-mark.svg", "<svg") 64 assertPage(t, full, ".dumbforge/assets/gabriel-mark.svg", "<svg")
65 assertPage(t, full, ".dumbforge/assets/gabriel-mark.png", "PNG") 65 assertPage(t, full, ".dumbforge/assets/gabriel-mark.png", "PNG")
66 assertPage(t, full, ".dumbforge/site-version", generator.AssetVersion())
66 assertPage(t, full, ".dumbforge/assets/site.css", `@media (prefers-color-scheme: dark)`) 67 assertPage(t, full, ".dumbforge/assets/site.css", `@media (prefers-color-scheme: dark)`)
67 assertPage(t, full, ".dumbforge/assets/site.css", `--bg: #0d1117`) 68 assertPage(t, full, ".dumbforge/assets/site.css", `--bg: #0d1117`)
68 assertPage(t, full, "tree/main/src", "main.go") 69 assertPage(t, full, "tree/main/src", "main.go")
@@ -77,6 +78,7 @@ func TestFullAndIncrementalBuild(t *testing.T) {
77 assertPageDoesNotContain(t, full, "", "Search this repository") 78 assertPageDoesNotContain(t, full, "", "Search this repository")
78 assertPageDoesNotContain(t, full, "", ">Docs<") 79 assertPageDoesNotContain(t, full, "", ">Docs<")
79 assertPageDoesNotContain(t, full, "", "Forged from a static bucket") 80 assertPageDoesNotContain(t, full, "", "Forged from a static bucket")
81 generator.SetAssetsCurrent(true)
80 82
81 write(t, dir, "src/main.go", "package main\n\nfunc main() { println(\"changed\") }\n") 83 write(t, dir, "src/main.go", "package main\n\nfunc main() { println(\"changed\") }\n")
82 runGit(t, dir, "add", "src/main.go") 84 runGit(t, dir, "add", "src/main.go")
@@ -86,8 +88,13 @@ func TestFullAndIncrementalBuild(t *testing.T) {
86 if err != nil { 88 if err != nil {
87 t.Fatal(err) 89 t.Fatal(err)
88 } 90 }
89 if len(incremental.Pages) != 8 { 91 if len(incremental.Pages) != 5 {
90 t.Fatalf("incremental build generated %d pages, want 8", len(incremental.Pages)) 92 t.Fatalf("incremental build generated %d pages, want 5", len(incremental.Pages))
93 }
94 for _, page := range incremental.Pages {
95 if strings.HasPrefix(page.Route, ".dumbforge/assets/") || page.Route == ".dumbforge/site-version" {
96 t.Fatalf("incremental build unnecessarily regenerated %q", page.Route)
97 }
91 } 98 }
92 assertPage(t, incremental, "blob/main/src/main.go", "changed") 99 assertPage(t, incremental, "blob/main/src/main.go", "changed")
93 assertPage(t, incremental, "tree/main/src", "change one file") 100 assertPage(t, incremental, "tree/main/src", "change one file")