all repos

mugit @ 0aadda9

馃惍 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
Authored at: 2026-02-13 17:13:14 +0200
Change ID: okxtnpqzpvnpsvyzmvwsqtzuqmwpznlu
Parent: 56a787b
M flake.nix
路路路
        23
        23
                     pname = "mugit";

      
        24
        24
                     version = version;

      
        25
        25
                     src = ./.;

      
        26
        
        -            vendorHash = "sha256-VcNnosr9Co+MFEA36s4BIDmg/bx+/mTIHdgOaGJKhbc=";

      
        
        26
        +            vendorHash = "sha256-rY/O5padrE0cwwnvLIR3lM9xdpwloy0OFbp6/ge5gAc=";

      
        27
        27
                     ldflags = [

      
        28
        28
                       "-s"

      
        29
        29
                       "-w"

      
M go.mod
路路路
        7
        7
         	github.com/cyphar/filepath-securejoin v0.4.1

      
        8
        8
         	github.com/gliderlabs/ssh v0.3.8

      
        9
        9
         	github.com/go-git/go-git/v5 v5.16.4

      
        
        10
        +	github.com/gorilla/feeds v1.2.0

      
        10
        11
         	github.com/urfave/cli/v3 v3.6.2

      
        11
        12
         	github.com/yuin/goldmark v1.7.16

      
        12
        13
         	golang.org/x/crypto v0.47.0

      
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
         	})

      
M internal/git/tags.go
路路路
        19
        19
         // Lightweight tags contain only a reference.

      
        20
        20
         // Annotated tags contain both a reference and tag metadata.

      
        21
        21
         type TagReference struct {

      
        22
        
        -	ref *plumbing.Reference

      
        23
        
        -	tag *object.Tag

      
        
        22
        +	ref  *plumbing.Reference

      
        
        23
        +	tag  *object.Tag

      
        
        24
        +	when time.Time

      
        24
        25
         }

      
        25
        26
         

      
        26
        27
         func (t *TagReference) Name() string {

      路路路
        34
        35
         	return ""

      
        35
        36
         }

      
        36
        37
         

      
        
        38
        +func (t *TagReference) When() time.Time {

      
        
        39
        +	return t.when

      
        
        40
        +}

      
        
        41
        +

      
        37
        42
         func (g *Repo) Tags() ([]*TagReference, error) {

      
        38
        43
         	iter, err := g.r.Tags()

      
        39
        44
         	if err != nil {

      路路路
        46
        51
         		switch err {

      
        47
        52
         		case nil:

      
        48
        53
         			tags = append(tags, &TagReference{

      
        49
        
        -				ref: ref,

      
        50
        
        -				tag: obj,

      
        
        54
        +				ref:  ref,

      
        
        55
        +				tag:  obj,

      
        
        56
        +				when: obj.Tagger.When,

      
        51
        57
         			})

      
        
        58
        +			return nil

      
        
        59
        +

      
        52
        60
         		case plumbing.ErrObjectNotFound:

      
        
        61
        +			commit, cerr := g.r.CommitObject(ref.Hash())

      
        
        62
        +			if cerr != nil {

      
        
        63
        +				return cerr

      
        
        64
        +			}

      
        
        65
        +

      
        53
        66
         			tags = append(tags, &TagReference{

      
        54
        
        -				ref: ref,

      
        
        67
        +				ref:  ref,

      
        
        68
        +				when: commit.Committer.When,

      
        55
        69
         			})

      
        
        70
        +			return nil

      
        
        71
        +

      
        56
        72
         		default:

      
        57
        73
         			return err

      
        58
        74
         		}

      
        59
        
        -		return nil

      
        60
        75
         	}); err != nil {

      
        61
        76
         		return nil, err

      
        62
        77
         	}

      
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 }}