all repos

mugit @ 0aadda9e4803f69c68a0130cd9942741445f046e

🐮 git server that your cow will love
8 files changed, 148 insertions(+), 9 deletions(-)
feat: add rss feeds
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-13 18:49:23 +0200
Change ID: okxtnpqzpvnpsvyzmvwsqtzuqmwpznlu
Parent: 56a787b
M flake.nix

@@ -23,7 +23,7 @@ mugit = pkgs.buildGoModule {

pname = "mugit"; version = version; src = ./.; - vendorHash = "sha256-VcNnosr9Co+MFEA36s4BIDmg/bx+/mTIHdgOaGJKhbc="; + vendorHash = "sha256-rY/O5padrE0cwwnvLIR3lM9xdpwloy0OFbp6/ge5gAc="; ldflags = [ "-s" "-w"
M go.mod

@@ -7,6 +7,7 @@ github.com/bluekeyes/go-gitdiff v0.8.1

github.com/cyphar/filepath-securejoin v0.4.1 github.com/gliderlabs/ssh v0.3.8 github.com/go-git/go-git/v5 v5.16.4 + github.com/gorilla/feeds v1.2.0 github.com/urfave/cli/v3 v3.6.2 github.com/yuin/goldmark v1.7.16 golang.org/x/crypto v0.47.0
M go.sum

@@ -36,6 +36,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=

github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= +github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
M internal/git/repo.go

@@ -167,7 +167,10 @@ return "Not displaying binary file", nil

} } -type Branch struct{ Name string } +type Branch struct { + Name string + LastUpdate time.Time +} func (g *Repo) Branches() ([]*Branch, error) { bi, err := g.r.Branches()

@@ -177,8 +180,14 @@ }

var branches []*Branch err = bi.ForEach(func(r *plumbing.Reference) error { + cmt, cerr := g.r.CommitObject(r.Hash()) + if cerr != nil { + return cerr + } + branches = append(branches, &Branch{ - Name: r.Name().Short(), + Name: r.Name().Short(), + LastUpdate: cmt.Committer.When, }) return nil })
M internal/git/tags.go

@@ -19,8 +19,9 @@ // TagReference represents both lightweight and annotated tags.

// Lightweight tags contain only a reference. // Annotated tags contain both a reference and tag metadata. type TagReference struct { - ref *plumbing.Reference - tag *object.Tag + ref *plumbing.Reference + tag *object.Tag + when time.Time } func (t *TagReference) Name() string {

@@ -34,6 +35,10 @@ }

return "" } +func (t *TagReference) When() time.Time { + return t.when +} + func (g *Repo) Tags() ([]*TagReference, error) { iter, err := g.r.Tags() if err != nil {

@@ -46,17 +51,27 @@ obj, err := g.r.TagObject(ref.Hash())

switch err { case nil: tags = append(tags, &TagReference{ - ref: ref, - tag: obj, + ref: ref, + tag: obj, + when: obj.Tagger.When, }) + return nil + case plumbing.ErrObjectNotFound: + commit, cerr := g.r.CommitObject(ref.Hash()) + if cerr != nil { + return cerr + } + tags = append(tags, &TagReference{ - ref: ref, + ref: ref, + when: commit.Committer.When, }) + return nil + default: return err } - return nil }); err != nil { return nil, err }
A internal/handlers/feed.go

@@ -0,0 +1,105 @@

+package handlers + +import ( + "net/http" + + "github.com/gorilla/feeds" +) + +func (h *handlers) repoFeedHandler(w http.ResponseWriter, r *http.Request) { + name := getNormalizedName(r.PathValue("name")) + repo, err := h.openPublicRepo(name, "") + if err != nil { + h.write404(w, err) + return + } + + desc, err := repo.Description() + if err != nil { + h.write500(w, err) + return + } + + repoName := repo.Name() + feed := &feeds.Feed{ + Title: repoName, + Link: &feeds.Link{Href: h.c.Meta.Host + "/" + repoName}, + Description: desc, + } + + // branches + branches, err := repo.Branches() + if err != nil { + h.write500(w, err) + return + } + + for _, branch := range branches { + feed.Items = append(feed.Items, &feeds.Item{ + Id: "b:" + branch.Name, + Title: "branch: " + branch.Name, + Link: &feeds.Link{Href: h.c.Meta.Host + "/tree/" + branch.Name}, + Updated: branch.LastUpdate, + }) + } + + // tags + tags, err := repo.Tags() + if err != nil { + h.write500(w, err) + } + + for _, tag := range tags { + feed.Items = append(feed.Items, &feeds.Item{ + Id: "t:" + tag.Name(), + Title: "tag: " + tag.Name(), + Link: &feeds.Link{Href: h.c.Meta.Host + "/tree/" + tag.Name()}, + Description: desc, + Updated: tag.When(), + Content: tag.Message(), + }) + } + + rss, err := feed.ToRss() + if err != nil { + h.write500(w, err) + return + } + + w.Header().Set("Content-Type", "application/rss+xml") + w.Write([]byte(rss)) +} + +func (h *handlers) indexFeedHandler(w http.ResponseWriter, r *http.Request) { + repos, err := h.listPublicRepos() + if err != nil { + h.write500(w, err) + return + } + + feed := &feeds.Feed{ + Title: h.c.Meta.Host, + Link: &feeds.Link{Href: h.c.Meta.Host}, + Description: h.c.Meta.Description, + } + + for _, repo := range repos { + feed.Items = append(feed.Items, &feeds.Item{ + Title: repo.Name, + Link: &feeds.Link{Href: h.c.Meta.Host + "/" + repo.Name}, + Description: repo.Desc, + Id: repo.Name, + Updated: repo.LastCommit, + Content: repo.Desc, + }) + } + + rss, err := feed.ToRss() + if err != nil { + h.write500(w, err) + return + } + + w.Header().Set("Content-Type", "application/rss+xml") + w.Write([]byte(rss)) +}
M internal/handlers/handlers.go

@@ -25,11 +25,13 @@ h := handlers{cfg, tmpls}

mux := http.NewServeMux() mux.HandleFunc("GET /", h.indexHandler) + mux.HandleFunc("GET /index.xml", h.indexFeedHandler) mux.HandleFunc("GET /static/{file}", h.serveStatic) mux.HandleFunc("GET /{name}", h.multiplex) mux.HandleFunc("POST /{name}", h.multiplex) mux.HandleFunc("GET /{name}/{rest...}", h.multiplex) mux.HandleFunc("POST /{name}/{rest...}", h.multiplex) + mux.HandleFunc("GET /{name}/feed/{$}", h.repoFeedHandler) mux.HandleFunc("GET /{name}/tree/{ref}/{rest...}", h.repoTreeHandler) mux.HandleFunc("GET /{name}/blob/{ref}/{rest...}", h.fileContentsHandler) mux.HandleFunc("GET /{name}/log/{ref}", h.logHandler)
M web/templates/_head.html

@@ -6,4 +6,9 @@ <link rel="icon" href="/static/favicon.svg">

{{ if and .servername .gomod }} <meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}"> {{ end }} + {{ if and .servername .name }} + <link rel="alternate" type="application/rss" href="{{ .servername }}/{{ .name }}/feed"> + {{ else }} + <link rel="alternate" type="application/rss" href="{{ .servername }}/index.xml"> + {{ end }} {{ end }}