all repos

mugit @ e1d1a9eda5d5e5aacfe4a2f899f9f65ba5b772d5

馃惍 git server that your cow will love
4 files changed, 70 insertions(+), 0 deletions(-)
git: archive
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-13 01:58:03 +0200
Authored at: 2026-02-13 01:24:10 +0200
Change ID: lzunywnrunzvkwotpklqpwzsstwqnvnm
Parent: d0b4683
A internal/git/gitx/archive.go
路路路
        
        1
        +package gitx

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"context"

      
        
        5
        +	"fmt"

      
        
        6
        +	"io"

      
        
        7
        +	"os/exec"

      
        
        8
        +	"regexp"

      
        
        9
        +	"strings"

      
        
        10
        +)

      
        
        11
        +

      
        
        12
        +// ArchiveTar generates a tarball of a git ref.

      
        
        13
        +func ArchiveTar(ctx context.Context, repoDir, ref string, out io.Writer) error {

      
        
        14
        +	if !isValidRef(ref) {

      
        
        15
        +		return fmt.Errorf("invalid ref: %s", ref)

      
        
        16
        +	}

      
        
        17
        +

      
        
        18
        +	cmd := exec.CommandContext(ctx, "git", "archive", "--format=tar.gz", ref)

      
        
        19
        +	cmd.Dir = repoDir

      
        
        20
        +	cmd.Env = gitEnv

      
        
        21
        +	cmd.Stdout = out

      
        
        22
        +	cmd.Stderr = io.Discard

      
        
        23
        +

      
        
        24
        +	if err := cmd.Run(); err != nil {

      
        
        25
        +		return fmt.Errorf("git archive %s: %w", ref, err)

      
        
        26
        +	}

      
        
        27
        +

      
        
        28
        +	return nil

      
        
        29
        +}

      
        
        30
        +

      
        
        31
        +func isValidRef(ref string) bool {

      
        
        32
        +	if ref == "" || strings.Contains(ref, "..") {

      
        
        33
        +		return false

      
        
        34
        +	}

      
        
        35
        +	matched, _ := regexp.MatchString(`^[a-zA-Z0-9._/-]+$`, ref)

      
        
        36
        +	return matched

      
        
        37
        +}

      
M internal/handlers/git.go
路路路
        2
        2
         

      
        3
        3
         import (

      
        4
        4
         	"compress/gzip"

      
        
        5
        +	"fmt"

      
        5
        6
         	"io"

      
        6
        7
         	"log/slog"

      
        7
        8
         	"net/http"

      路路路
        90
        91
         	if err := gitx.UploadPack(r.Context(), path, true, reader, newFlushWriter(w)); err != nil {

      
        91
        92
         		// Don't call w.WriteHeader here - connection already started!

      
        92
        93
         		slog.Error("git: upload-pack", "err", err)

      
        
        94
        +		return

      
        
        95
        +	}

      
        
        96
        +}

      
        
        97
        +

      
        
        98
        +func (h *handlers) archiveHandler(w http.ResponseWriter, r *http.Request) {

      
        
        99
        +	name := getNormalizedName(r.PathValue("name"))

      
        
        100
        +	ref := r.PathValue("ref")

      
        
        101
        +

      
        
        102
        +	path, err := securejoin.SecureJoin(h.c.Repo.Dir, repoNameToPath(name))

      
        
        103
        +	if err != nil {

      
        
        104
        +		http.Error(w, "invalid path", http.StatusBadRequest)

      
        
        105
        +		slog.Error("git: upload-pack path", "err", err)

      
        
        106
        +		return

      
        
        107
        +	}

      
        
        108
        +

      
        
        109
        +	_, err = h.openPublicRepo(name, ref)

      
        
        110
        +	if err != nil {

      
        
        111
        +		h.write404(w, err)

      
        
        112
        +		return

      
        
        113
        +	}

      
        
        114
        +

      
        
        115
        +	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)

      
        
        116
        +	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))

      
        
        117
        +	w.Header().Set("Content-Type", "application/gzip")

      
        
        118
        +	w.WriteHeader(http.StatusOK)

      
        
        119
        +

      
        
        120
        +	if err := gitx.ArchiveTar(r.Context(), path, ref, w); err != nil {

      
        
        121
        +		slog.Error("git: archive", "ref", ref, "err", err)

      
        93
        122
         		return

      
        94
        123
         	}

      
        95
        124
         }

      
M internal/handlers/handlers.go
路路路
        35
        35
         	mux.HandleFunc("GET /{name}/log/{ref}", h.logHandler)

      
        36
        36
         	mux.HandleFunc("GET /{name}/commit/{ref}", h.commitHandler)

      
        37
        37
         	mux.HandleFunc("GET /{name}/refs/{$}", h.refsHandler)

      
        
        38
        +	mux.HandleFunc("GET /{name}/archive/{ref}", h.archiveHandler)

      
        
        39
        +

      
        38
        40
         

      
        39
        41
         	handler := h.recoverMiddleware(mux)

      
        40
        42
         	return h.loggingMiddleware(handler)

      
M web/templates/repo_refs.html
路路路
        15
        15
                 <strong>{{ .Name }}</strong>

      
        16
        16
                 <a href="/{{ $name }}/tree/{{ .Name }}/">browse</a>

      
        17
        17
                 <a href="/{{ $name }}/log/{{ .Name }}">log</a>

      
        
        18
        +        <a href="/{{ $name }}/archive/{{ .Name }}">tar.gz</a>

      
        18
        19
                 </div>

      
        19
        20
               {{ end }}

      
        20
        21
               </div>

      路路路
        26
        27
               <strong>{{ .Name }}</strong>

      
        27
        28
               <a href="/{{ $name }}/tree/{{ .Name }}/">browse</a>

      
        28
        29
               <a href="/{{ $name }}/log/{{ .Name }}">log</a>

      
        
        30
        +      <a href="/{{ $name }}/archive/{{ .Name }}">tar.gz</a>

      
        29
        31
               {{ if .Message }}

      
        30
        32
               <pre>{{ .Message }}</pre>

      
        31
        33
               </div>