8 files changed,
148 insertions(+),
9 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-02-13 18:49:23 +0200
Authored at:
2026-02-13 17:13:14 +0200
Change ID:
okxtnpqzpvnpsvyzmvwsqtzuqmwpznlu
Parent:
56a787b
M
go.sum
路路路 36 36 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 37 37 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 38 38 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 39 +github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= 40 +github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= 39 41 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 40 42 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 41 43 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
M
internal/git/repo.go
路路路 167 167 } 168 168 } 169 169 170 -type Branch struct{ Name string } 170 +type Branch struct { 171 + Name string 172 + LastUpdate time.Time 173 +} 171 174 172 175 func (g *Repo) Branches() ([]*Branch, error) { 173 176 bi, err := g.r.Branches() 路路路 177 180 178 181 var branches []*Branch 179 182 err = bi.ForEach(func(r *plumbing.Reference) error { 183 + cmt, cerr := g.r.CommitObject(r.Hash()) 184 + if cerr != nil { 185 + return cerr 186 + } 187 + 180 188 branches = append(branches, &Branch{ 181 - Name: r.Name().Short(), 189 + Name: r.Name().Short(), 190 + LastUpdate: cmt.Committer.When, 182 191 }) 183 192 return nil 184 193 })
A
internal/handlers/feed.go
路路路 1 +package handlers 2 + 3 +import ( 4 + "net/http" 5 + 6 + "github.com/gorilla/feeds" 7 +) 8 + 9 +func (h *handlers) repoFeedHandler(w http.ResponseWriter, r *http.Request) { 10 + name := getNormalizedName(r.PathValue("name")) 11 + repo, err := h.openPublicRepo(name, "") 12 + if err != nil { 13 + h.write404(w, err) 14 + return 15 + } 16 + 17 + desc, err := repo.Description() 18 + if err != nil { 19 + h.write500(w, err) 20 + return 21 + } 22 + 23 + repoName := repo.Name() 24 + feed := &feeds.Feed{ 25 + Title: repoName, 26 + Link: &feeds.Link{Href: h.c.Meta.Host + "/" + repoName}, 27 + Description: desc, 28 + } 29 + 30 + // branches 31 + branches, err := repo.Branches() 32 + if err != nil { 33 + h.write500(w, err) 34 + return 35 + } 36 + 37 + for _, branch := range branches { 38 + feed.Items = append(feed.Items, &feeds.Item{ 39 + Id: "b:" + branch.Name, 40 + Title: "branch: " + branch.Name, 41 + Link: &feeds.Link{Href: h.c.Meta.Host + "/tree/" + branch.Name}, 42 + Updated: branch.LastUpdate, 43 + }) 44 + } 45 + 46 + // tags 47 + tags, err := repo.Tags() 48 + if err != nil { 49 + h.write500(w, err) 50 + } 51 + 52 + for _, tag := range tags { 53 + feed.Items = append(feed.Items, &feeds.Item{ 54 + Id: "t:" + tag.Name(), 55 + Title: "tag: " + tag.Name(), 56 + Link: &feeds.Link{Href: h.c.Meta.Host + "/tree/" + tag.Name()}, 57 + Description: desc, 58 + Updated: tag.When(), 59 + Content: tag.Message(), 60 + }) 61 + } 62 + 63 + rss, err := feed.ToRss() 64 + if err != nil { 65 + h.write500(w, err) 66 + return 67 + } 68 + 69 + w.Header().Set("Content-Type", "application/rss+xml") 70 + w.Write([]byte(rss)) 71 +} 72 + 73 +func (h *handlers) indexFeedHandler(w http.ResponseWriter, r *http.Request) { 74 + repos, err := h.listPublicRepos() 75 + if err != nil { 76 + h.write500(w, err) 77 + return 78 + } 79 + 80 + feed := &feeds.Feed{ 81 + Title: h.c.Meta.Host, 82 + Link: &feeds.Link{Href: h.c.Meta.Host}, 83 + Description: h.c.Meta.Description, 84 + } 85 + 86 + for _, repo := range repos { 87 + feed.Items = append(feed.Items, &feeds.Item{ 88 + Title: repo.Name, 89 + Link: &feeds.Link{Href: h.c.Meta.Host + "/" + repo.Name}, 90 + Description: repo.Desc, 91 + Id: repo.Name, 92 + Updated: repo.LastCommit, 93 + Content: repo.Desc, 94 + }) 95 + } 96 + 97 + rss, err := feed.ToRss() 98 + if err != nil { 99 + h.write500(w, err) 100 + return 101 + } 102 + 103 + w.Header().Set("Content-Type", "application/rss+xml") 104 + w.Write([]byte(rss)) 105 +}
M
internal/handlers/handlers.go
路路路 25 25 26 26 mux := http.NewServeMux() 27 27 mux.HandleFunc("GET /", h.indexHandler) 28 + mux.HandleFunc("GET /index.xml", h.indexFeedHandler) 28 29 mux.HandleFunc("GET /static/{file}", h.serveStatic) 29 30 mux.HandleFunc("GET /{name}", h.multiplex) 30 31 mux.HandleFunc("POST /{name}", h.multiplex) 31 32 mux.HandleFunc("GET /{name}/{rest...}", h.multiplex) 32 33 mux.HandleFunc("POST /{name}/{rest...}", h.multiplex) 34 + mux.HandleFunc("GET /{name}/feed/{$}", h.repoFeedHandler) 33 35 mux.HandleFunc("GET /{name}/tree/{ref}/{rest...}", h.repoTreeHandler) 34 36 mux.HandleFunc("GET /{name}/blob/{ref}/{rest...}", h.fileContentsHandler) 35 37 mux.HandleFunc("GET /{name}/log/{ref}", h.logHandler)
M
web/templates/_head.html
路路路 6 6 {{ if and .servername .gomod }} 7 7 <meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}"> 8 8 {{ end }} 9 + {{ if and .servername .name }} 10 + <link rel="alternate" type="application/rss" href="{{ .servername }}/{{ .name }}/feed"> 11 + {{ else }} 12 + <link rel="alternate" type="application/rss" href="{{ .servername }}/index.xml"> 13 + {{ end }} 9 14 {{ end }}