web frontend for git repositories, written in Go git.pocka.jp/legit.git
3

Configure Feed

Select the types of activity you want to include in your feed.

Embed default templates into the binary

https://tangled.org/pocka.jp/legit/issues/3
https://github.com/icyphox/legit/issues/10

Deployment & distribution convenience.

Shota FUJI (Jul 13, 2026, 5:58 PM +0900) eac513f5 0e496be9

+43 -39
+5 -1
config.yaml
··· 23 23 24 24 # Runtime directories. 25 25 dirs: 26 - # Path to a directory containing HTML templates. 26 + # [optional] 27 + # Path to a directory containing custom HTML templates. 27 28 # Templates are written in Go's html/template syntax. 28 29 # <https://pkg.go.dev/html/template> 30 + # 31 + # If you omit this, legit will use its default templates directory 32 + # ("templates" directory next to this file) embedded in the binary. 29 33 templates: ./templates 30 34 31 35 # [optional]
+6 -2
config/config.go
··· 55 55 if c.Repo.ScanPath, err = resolvePath(c.Repo.ScanPath, f); err != nil { 56 56 return nil, err 57 57 } 58 - if c.Dirs.Templates, err = resolvePath(c.Dirs.Templates, f); err != nil { 59 - return nil, err 58 + 59 + // Override templates dir 60 + if c.Dirs.Templates != "" { 61 + if c.Dirs.Templates, err = resolvePath(c.Dirs.Templates, f); err != nil { 62 + return nil, err 63 + } 60 64 } 61 65 62 66 // Override static dir
-3
demo/config.yaml
··· 28 28 - main 29 29 - trunk 30 30 31 - dirs: 32 - templates: ../templates 33 - 34 31 meta: 35 32 title: legit Demo 36 33 description: Test legit features with real repositories.
+2 -4
flake.nix
··· 29 29 legit = self.packages.${system}.legit; 30 30 files = pkgs.lib.fileset.toSource { 31 31 root = ./.; 32 - fileset = pkgs.lib.fileset.unions [ 33 - ./config.yaml 34 - ./templates 35 - ]; 32 + fileset = pkgs.lib.fileset.unions [ ./config.yaml ]; 36 33 }; 37 34 in 38 35 { ··· 47 44 ./go.mod 48 45 ./go.sum 49 46 ./static 47 + ./templates 50 48 (fileFilter (file: file.hasExt "go") ./.) 51 49 ]; 52 50 };
+14 -1
main.go
··· 17 17 //go:embed static/* 18 18 var defaultStaticDir embed.FS 19 19 20 + //go:embed templates/* 21 + var defaultTemplatesDir embed.FS 22 + 20 23 func main() { 21 24 var cfg string 22 25 var host string ··· 68 71 } 69 72 } 70 73 71 - mux := routes.Handlers(c, staticDir) 74 + var templatesDir fs.FS 75 + if c.Dirs.Templates != "" { 76 + templatesDir = os.DirFS(c.Dirs.Templates) 77 + } else { 78 + templatesDir, err = fs.Sub(defaultTemplatesDir, "templates") 79 + if err != nil { 80 + log.Fatalf("Unable to open default templates dir: %s", err) 81 + } 82 + } 83 + 84 + mux := routes.Handlers(c, staticDir, templatesDir) 72 85 addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) 73 86 log.Println("starting server on", addr) 74 87 log.Fatal(http.ListenAndServe(addr, mux))
+5 -4
routes/handler.go
··· 30 30 } 31 31 } 32 32 33 - func Handlers(c *config.Config, staticDir fs.FS) *http.ServeMux { 33 + func Handlers(c *config.Config, staticDir fs.FS, templatesDir fs.FS) *http.ServeMux { 34 34 mux := http.NewServeMux() 35 35 d := deps{ 36 - c: c, 37 - staticDir: staticDir, 38 - ugcPolicy: bluemonday.UGCPolicy(), 36 + c: c, 37 + staticDir: staticDir, 38 + templatesDir: templatesDir, 39 + ugcPolicy: bluemonday.UGCPolicy(), 39 40 } 40 41 41 42 mux.HandleFunc("GET /", d.Index)
+9 -14
routes/routes.go
··· 30 30 // returned by "os.Root.FS". 31 31 staticDir fs.FS 32 32 33 + templatesDir fs.FS 34 + 33 35 // ugcPolicy is a bluemonday policy for user generated content. 34 36 ugcPolicy *bluemonday.Policy 35 37 } ··· 83 85 return summaries[j].LastCommit.Committer.When.Before(summaries[i].LastCommit.Committer.When) 84 86 }) 85 87 86 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 87 - t := template.Must(template.ParseGlob(tpath)) 88 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 88 89 89 90 data := repoListData{ 90 91 Config: d.c, ··· 200 201 } 201 202 } 202 203 203 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 204 - t := template.Must(template.ParseGlob(tpath)) 204 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 205 205 206 206 if len(commits) >= 3 { 207 207 commits = commits[:3] ··· 273 273 Files: files, 274 274 } 275 275 276 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 277 - t := template.Must(template.ParseGlob(tpath)) 276 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 278 277 279 278 if err := t.ExecuteTemplate(w, "repo-tree-ref", data); err != nil { 280 279 log.Println(err) ··· 336 335 relpath = strings.Split(treePath, "/") 337 336 } 338 337 339 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 340 - t := template.Must(template.ParseGlob(tpath)) 338 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 341 339 342 340 if r.URL.Query().Has("preview") { 343 341 previewType := r.URL.Query().Get("preview") ··· 510 508 return 511 509 } 512 510 513 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 514 - t := template.Must(template.ParseGlob(tpath)) 511 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 515 512 516 513 data := repoLogRefData{ 517 514 Config: d.c, ··· 557 554 return 558 555 } 559 556 560 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 561 - t := template.Must(template.ParseGlob(tpath)) 557 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 562 558 563 559 data := repoCommitData{ 564 560 Config: d.c, ··· 619 615 return 620 616 } 621 617 622 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 623 - t := template.Must(template.ParseGlob(tpath)) 618 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 624 619 625 620 data := repoRefsData{ 626 621 Config: d.c,
+2 -5
routes/template.go
··· 4 4 "html/template" 5 5 "log" 6 6 "net/http" 7 - "path/filepath" 8 7 ) 9 8 10 9 func (d *deps) Write404(w http.ResponseWriter) { 11 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 12 - t := template.Must(template.ParseGlob(tpath)) 10 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 13 11 14 12 data := error404Data{ 15 13 Config: d.c, ··· 22 20 } 23 21 24 22 func (d *deps) Write500(w http.ResponseWriter) { 25 - tpath := filepath.Join(d.c.Dirs.Templates, "*") 26 - t := template.Must(template.ParseGlob(tpath)) 23 + t := template.Must(template.ParseFS(d.templatesDir, "*")) 27 24 28 25 data := error500Data{ 29 26 Config: d.c,
-3
tests/k6/config.yaml
··· 8 8 mainBranch: 9 9 - trunk 10 10 11 - dirs: 12 - templates: ../../templates 13 - 14 11 meta: 15 12 title: Legit Load Testing 16 13 syntaxHighlight: true
-2
tests/k6/repos.nix
··· 15 15 fileset = lib.fileset.unions [ 16 16 ./create_repos.bash 17 17 ./config.yaml 18 - ../../templates 19 18 ]; 20 19 }; 21 20 ··· 24 23 cd $out/tests/k6/repos 25 24 bash $src/tests/k6/create_repos.bash 26 25 ln -s $src/tests/k6/config.yaml $out/tests/k6/config.yaml 27 - ln -s $src/templates $out/templates 28 26 ''; 29 27 30 28 nativeBuildInputs = [