all repos

mugit @ 831d5c6

馃惍 git server that your cow will love
6 files changed, 68 insertions(+), 57 deletions(-)
git: resolve name and paths
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-13 18:49:23 +0200
Authored at: 2026-02-13 17:35:23 +0200
Change ID: nmqwpqklxkxxpytsrxvoxwsloswuktqs
Parent: dc62a68
A internal/git/paths.go
路路路
        
        1
        +package git

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"fmt"

      
        
        5
        +	"strings"

      
        
        6
        +

      
        
        7
        +	securejoin "github.com/cyphar/filepath-securejoin"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +func ResolveName(name string) string {

      
        
        11
        +	return strings.TrimSuffix(name, ".git") + ".git"

      
        
        12
        +}

      
        
        13
        +

      
        
        14
        +func ResolvePath(baseDir, repoName string) (string, error) {

      
        
        15
        +	path, err := securejoin.SecureJoin(baseDir, repoName)

      
        
        16
        +	if err != nil {

      
        
        17
        +		return "", fmt.Errorf("failed to secure join paths: %w", err)

      
        
        18
        +	}

      
        
        19
        +	return path, err

      
        
        20
        +}

      
M internal/git/repo.go
路路路
        20
        20
         var (

      
        21
        21
         	ErrEmptyRepo    = errors.New("repository has no commits")

      
        22
        22
         	ErrFileNotFound = errors.New("file not found")

      
        
        23
        +	ErrPrivate      = errors.New("repository is private")

      
        23
        24
         )

      
        24
        25
         

      
        25
        26
         type Repo struct {

      路路路
        29
        30
         }

      
        30
        31
         

      
        31
        32
         // Open opens a git repository at path. If ref is empty, HEAD is used.

      
        32
        
        -func Open(path string, ref string) (*Repo, error) {

      
        
        33
        +func Open(path, ref string) (*Repo, error) {

      
        33
        34
         	var err error

      
        34
        35
         	g := Repo{}

      
        35
        36
         	g.path = path

      路路路
        55
        56
         		g.h = *hash

      
        56
        57
         	}

      
        57
        58
         	return &g, nil

      
        
        59
        +}

      
        
        60
        +

      
        
        61
        +// OpenPublic opens a repository, returns [ErrPrivate] if it's private.

      
        
        62
        +func OpenPublic(path, ref string) (*Repo, error) {

      
        
        63
        +	r, err := Open(path, ref)

      
        
        64
        +	if err != nil {

      
        
        65
        +		return nil, err

      
        
        66
        +	}

      
        
        67
        +

      
        
        68
        +	isPrivate, err := r.IsPrivate()

      
        
        69
        +	if err != nil {

      
        
        70
        +		return nil, err

      
        
        71
        +	}

      
        
        72
        +

      
        
        73
        +	if isPrivate {

      
        
        74
        +		return nil, ErrPrivate

      
        
        75
        +	}

      
        
        76
        +

      
        
        77
        +	return r, nil

      
        58
        78
         }

      
        59
        79
         

      
        60
        80
         func (g *Repo) IsEmpty() bool {

      
M internal/handlers/feed.go
路路路
        7
        7
         )

      
        8
        8
         

      
        9
        9
         func (h *handlers) repoFeedHandler(w http.ResponseWriter, r *http.Request) {

      
        10
        
        -	name := getNormalizedName(r.PathValue("name"))

      
        11
        
        -	repo, err := h.openPublicRepo(name, "")

      
        
        10
        +	repo, err := h.openPublicRepo(r.PathValue("name"), "")

      
        12
        11
         	if err != nil {

      
        13
        12
         		h.write404(w, err)

      
        14
        13
         		return

      
M internal/handlers/git.go
路路路
        6
        6
         	"log/slog"

      
        7
        7
         	"net/http"

      
        8
        8
         

      
        9
        
        -	securejoin "github.com/cyphar/filepath-securejoin"

      
        
        9
        +	"olexsmir.xyz/mugit/internal/git"

      
        10
        10
         	"olexsmir.xyz/mugit/internal/git/gitx"

      
        11
        11
         )

      
        12
        12
         

      路路路
        83
        83
         }

      
        84
        84
         

      
        85
        85
         func (h *handlers) checkRepoPublicityAndGetPath(name string) (string, error) {

      
        86
        
        -	repoPath := repoNameToPath(name)

      
        87
        
        -	_, err := h.openPublicRepo(name, "")

      
        
        86
        +	name = git.ResolveName(name)

      
        
        87
        +	path, err := git.ResolvePath(h.c.Repo.Dir, name)

      
        88
        88
         	if err != nil {

      
        89
        89
         		return "", err

      
        90
        90
         	}

      
        91
        91
         

      
        92
        
        -	path, err := securejoin.SecureJoin(h.c.Repo.Dir, repoPath)

      
        93
        
        -	if err != nil {

      
        
        92
        +	if _, err := git.OpenPublic(path, ""); err != nil {

      
        94
        93
         		return "", err

      
        95
        94
         	}

      
        96
        95
         

      
        97
        
        -	return path, nil

      
        
        96
        +	return path, err

      
        
        97
        +}

      
        
        98
        +

      
        
        99
        +func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) {

      
        
        100
        +	name = git.ResolveName(name)

      
        
        101
        +	path, err := git.ResolvePath(h.c.Repo.Dir, name)

      
        
        102
        +	if err != nil {

      
        
        103
        +		return nil, err

      
        
        104
        +	}

      
        
        105
        +	return git.OpenPublic(path, ref)

      
        98
        106
         }

      
        99
        107
         

      
        100
        108
         type flushWriter struct {

      
M internal/handlers/handlers.go
路路路
        26
        26
         	mux := http.NewServeMux()

      
        27
        27
         	mux.HandleFunc("GET /", h.indexHandler)

      
        28
        28
         	mux.HandleFunc("GET /index.xml", h.indexFeedHandler)

      
        29
        
        -	mux.HandleFunc("GET /static/{file}", h.serveStatic)

      
        
        29
        +	mux.HandleFunc("GET /static/{file}", h.serveStaticHandler)

      
        30
        30
         	mux.HandleFunc("GET /{name}", h.multiplex)

      
        31
        31
         	mux.HandleFunc("POST /{name}", h.multiplex)

      
        32
        32
         	mux.HandleFunc("GET /{name}/{rest...}", h.multiplex)

      路路路
        43
        43
         	return h.loggingMiddleware(handler)

      
        44
        44
         }

      
        45
        45
         

      
        46
        
        -func (h *handlers) serveStatic(w http.ResponseWriter, r *http.Request) {

      
        
        46
        +func (h *handlers) serveStaticHandler(w http.ResponseWriter, r *http.Request) {

      
        47
        47
         	f := filepath.Clean(r.PathValue("file"))

      
        48
        48
         	http.ServeFileFS(w, r, web.StaticFS, f)

      
        49
        
        -}

      
        50
        
        -

      
        51
        
        -func repoNameToPath(name string) string { return name + ".git" }

      
        52
        
        -func getNormalizedName(name string) string {

      
        53
        
        -	return strings.TrimSuffix(name, ".git")

      
        54
        49
         }

      
        55
        50
         

      
        56
        51
         var templateFuncs = template.FuncMap{

      
M internal/handlers/repo.go
路路路
        15
        15
         	"strings"

      
        16
        16
         	"time"

      
        17
        17
         

      
        18
        
        -	securejoin "github.com/cyphar/filepath-securejoin"

      
        19
        18
         	"github.com/yuin/goldmark"

      
        20
        19
         	"github.com/yuin/goldmark/extension"

      
        21
        20
         	"github.com/yuin/goldmark/renderer/html"

      路路路
        43
        42
         	))

      
        44
        43
         

      
        45
        44
         func (h *handlers) repoIndex(w http.ResponseWriter, r *http.Request) {

      
        46
        
        -	name := getNormalizedName(r.PathValue("name"))

      
        47
        
        -	repo, err := h.openPublicRepo(name, "")

      
        
        45
        +	repo, err := h.openPublicRepo(r.PathValue("name"), "")

      
        48
        46
         	if err != nil {

      
        49
        47
         		h.write404(w, err)

      
        50
        48
         		return

      路路路
        57
        55
         	}

      
        58
        56
         

      
        59
        57
         	data := make(map[string]any)

      
        60
        
        -	data["name"] = name

      
        
        58
        +	data["name"] = repo.Name()

      
        61
        59
         	data["desc"] = desc

      
        62
        60
         	data["servername"] = h.c.Meta.Host

      
        63
        61
         	data["meta"] = h.c.Meta

      路路路
        123
        121
         }

      
        124
        122
         

      
        125
        123
         func (h *handlers) repoTreeHandler(w http.ResponseWriter, r *http.Request) {

      
        126
        
        -	name := getNormalizedName(r.PathValue("name"))

      
        
        124
        +	name := r.PathValue("name")

      
        127
        125
         	ref := r.PathValue("ref")

      
        128
        126
         	treePath := r.PathValue("rest")

      
        129
        127
         

      路路路
        158
        156
         }

      
        159
        157
         

      
        160
        158
         func (h *handlers) fileContentsHandler(w http.ResponseWriter, r *http.Request) {

      
        161
        
        -	name := getNormalizedName(r.PathValue("name"))

      
        
        159
        +	name := r.PathValue("name")

      
        162
        160
         	ref := r.PathValue("ref")

      
        163
        161
         	treePath := r.PathValue("rest")

      
        164
        162
         

      路路路
        222
        220
         }

      
        223
        221
         

      
        224
        222
         func (h *handlers) logHandler(w http.ResponseWriter, r *http.Request) {

      
        225
        
        -	name := getNormalizedName(r.PathValue("name"))

      
        
        223
        +	name := r.PathValue("name")

      
        226
        224
         	ref := r.PathValue("ref")

      
        227
        225
         

      
        228
        226
         	repo, err := h.openPublicRepo(name, ref)

      路路路
        254
        252
         }

      
        255
        253
         

      
        256
        254
         func (h *handlers) commitHandler(w http.ResponseWriter, r *http.Request) {

      
        257
        
        -	name := getNormalizedName(r.PathValue("name"))

      
        
        255
        +	name := r.PathValue("name")

      
        258
        256
         	ref := r.PathValue("ref")

      
        259
        257
         	repo, err := h.openPublicRepo(name, ref)

      
        260
        258
         	if err != nil {

      路路路
        286
        284
         }

      
        287
        285
         

      
        288
        286
         func (h *handlers) refsHandler(w http.ResponseWriter, r *http.Request) {

      
        289
        
        -	name := getNormalizedName(r.PathValue("name"))

      
        290
        
        -	repo, err := h.openPublicRepo(name, "")

      
        
        287
        +	repo, err := h.openPublicRepo(r.PathValue("name"), "")

      
        291
        288
         	if err != nil {

      
        292
        289
         		h.write404(w, err)

      
        293
        290
         		return

      路路路
        319
        316
         

      
        320
        317
         	data := make(map[string]any)

      
        321
        318
         	data["meta"] = h.c.Meta

      
        322
        
        -	data["name"] = name

      
        
        319
        +	data["name"] = repo.Name()

      
        323
        320
         	data["desc"] = desc

      
        324
        321
         	data["ref"] = masterBranch

      
        325
        322
         	data["branches"] = branches

      路路路
        353
        350
         	}

      
        354
        351
         }

      
        355
        352
         

      
        356
        
        -var errPrivateRepo = errors.New("private err")

      
        357
        
        -

      
        358
        
        -func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) {

      
        359
        
        -	// Convert normalized name back to filesystem path with .git suffix

      
        360
        
        -	name = repoNameToPath(name)

      
        361
        
        -

      
        362
        
        -	path, err := securejoin.SecureJoin(h.c.Repo.Dir, name)

      
        363
        
        -	if err != nil {

      
        364
        
        -		return nil, err

      
        365
        
        -	}

      
        366
        
        -

      
        367
        
        -	repo, err := git.Open(path, ref)

      
        368
        
        -	if err != nil {

      
        369
        
        -		return nil, err

      
        370
        
        -	}

      
        371
        
        -

      
        372
        
        -	isPrivate, err := repo.IsPrivate()

      
        373
        
        -	if err != nil {

      
        374
        
        -		return nil, err

      
        375
        
        -	}

      
        376
        
        -	if isPrivate {

      
        377
        
        -		return nil, errPrivateRepo

      
        378
        
        -	}

      
        379
        
        -

      
        380
        
        -	return repo, nil

      
        381
        
        -}

      
        382
        
        -

      
        383
        353
         type repoList struct {

      
        384
        354
         	Name       string

      
        385
        355
         	Desc       string

      路路路
        400
        370
         		}

      
        401
        371
         

      
        402
        372
         		name := dir.Name()

      
        403
        
        -		normalizedName := getNormalizedName(name)

      
        404
        
        -		repo, err := h.openPublicRepo(normalizedName, "")

      
        
        373
        +		repo, err := h.openPublicRepo(name, "")

      
        405
        374
         		if err != nil {

      
        406
        375
         			// if it's not git repo, just ignore it

      
        407
        376
         			continue

      路路路
        420
        389
         		}

      
        421
        390
         

      
        422
        391
         		repos = append(repos, repoList{

      
        423
        
        -			Name:       normalizedName,

      
        
        392
        +			Name:       repo.Name(),

      
        424
        393
         			Desc:       desc,

      
        425
        394
         			LastCommit: lastCommit.Committed,

      
        426
        395
         		})