diff options
| author | Gabriel A. Giovanini <mail@gabrielgio.me> | 2024-06-22 16:30:47 +0200 | 
|---|---|---|
| committer | Gabriel A. Giovanini <mail@gabrielgio.me> | 2024-06-22 16:30:47 +0200 | 
| commit | e1664fcbc4685906d3dabc66bf947a17bce7efc0 (patch) | |
| tree | c9ec73616b841e72397fca975e46f691d031e621 /pkg | |
| parent | 19839337ce0c74b67c5480b71e98d97a112aa104 (diff) | |
| download | cerrado-e1664fcbc4685906d3dabc66bf947a17bce7efc0.tar.gz cerrado-e1664fcbc4685906d3dabc66bf947a17bce7efc0.tar.bz2 cerrado-e1664fcbc4685906d3dabc66bf947a17bce7efc0.zip | |
feat: Add archive capability
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/ext/mime.go | 11 | ||||
| -rw-r--r-- | pkg/git/git.go | 118 | ||||
| -rw-r--r-- | pkg/handler/git/handler.go | 36 | ||||
| -rw-r--r-- | pkg/handler/router.go | 1 | ||||
| -rw-r--r-- | pkg/service/git.go | 25 | 
5 files changed, 190 insertions, 1 deletions
| diff --git a/pkg/ext/mime.go b/pkg/ext/mime.go index 6da66e3..c42d4de 100644 --- a/pkg/ext/mime.go +++ b/pkg/ext/mime.go @@ -5,7 +5,8 @@ import "net/http"  type ContentType = string  const ( -	TextHTML ContentType = "text/html" +	TextHTML        ContentType = "text/html" +	ApplicationGZip ContentType = "application/gzip"  )  func Html(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { @@ -14,9 +15,17 @@ func Html(next func(w http.ResponseWriter, r *http.Request)) func(w http.Respons  	}  } +func SetFileName(w http.ResponseWriter, name string) { +	h := "inline; filename=\"" + name + "\"" +	w.Header().Add("Content-Disposition", h) +} +  func SetHTML(w http.ResponseWriter) {  	SetMIME(w, TextHTML) +} +func SetGZip(w http.ResponseWriter) { +	SetMIME(w, ApplicationGZip)  }  func SetMIME(w http.ResponseWriter, mime ContentType) { diff --git a/pkg/git/git.go b/pkg/git/git.go index b725cd8..591fafb 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1,9 +1,13 @@  package git  import ( +	"archive/tar"  	"errors"  	"fmt"  	"io" +	"io/fs" +	"path" +	"time"  	"github.com/go-git/go-git/v5"  	"github.com/go-git/go-git/v5/plumbing" @@ -26,6 +30,13 @@ type (  		// this is setRef when ref is setRef  		setRef bool  	} +	infoWrapper struct { +		name    string +		size    int64 +		mode    fs.FileMode +		modTime time.Time +		isDir   bool +	}  )  func OpenRepository(dir string) (*GitRepository, error) { @@ -213,3 +224,110 @@ func (g *GitRepository) FileContent(path string) (string, error) {  		return "Binary file", nil  	}  } + +func (g *GitRepository) WriteTar(w io.Writer, prefix string) error { +	tw := tar.NewWriter(w) +	defer tw.Close() + +	tree, err := g.Tree("") +	if err != nil { +		return err +	} + +	walker := object.NewTreeWalker(tree, true, nil) +	defer walker.Close() + +	name, entry, err := walker.Next() +	for ; err == nil; name, entry, err = walker.Next() { +		info, err := newInfoWrapper(name, prefix, &entry, tree) +		if err != nil { +			return err +		} + +		header, err := tar.FileInfoHeader(info, "") +		if err != nil { +			return err +		} + +		err = tw.WriteHeader(header) +		if err != nil { +			return err +		} + +		if !info.IsDir() { +			c, err := g.FileContent(name) +			if err != nil { +				return err +			} + +			_, err = tw.Write([]byte(c)) +			if err != nil { +				return err +			} +		} +	} + +	return nil +} + +func newInfoWrapper( +	filename string, +	prefix string, +	entry *object.TreeEntry, +	tree *object.Tree, +) (*infoWrapper, error) { +	var ( +		size  int64 +		mode  fs.FileMode +		isDir bool +	) + +	if entry.Mode.IsFile() { +		file, err := tree.TreeEntryFile(entry) +		if err != nil { +			return nil, err +		} +		mode = fs.FileMode(file.Mode) + +		size, err = tree.Size(filename) +		if err != nil { +			return nil, err +		} +	} else { +		isDir = true +		mode = fs.ModeDir | fs.ModePerm +	} + +	fullname := path.Join(prefix, filename) +	return &infoWrapper{ +		name:    fullname, +		size:    size, +		mode:    mode, +		modTime: time.Unix(0, 0), +		isDir:   isDir, +	}, nil +} + +func (i *infoWrapper) Name() string { +	return i.name +} + +func (i *infoWrapper) Size() int64 { +	return i.size +} + +func (i *infoWrapper) Mode() fs.FileMode { +	return i.mode +} + +func (i *infoWrapper) ModTime() time.Time { +	return i.modTime +} + +func (i *infoWrapper) IsDir() bool { +	return i.isDir +} + +func (i *infoWrapper) Sys() any { +	return nil +} diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go index 25505ba..aed9917 100644 --- a/pkg/handler/git/handler.go +++ b/pkg/handler/git/handler.go @@ -2,10 +2,13 @@ package git  import (  	"bytes" +	"fmt"  	"io" +	"log/slog"  	"net/http"  	"os"  	"path/filepath" +	"strings"  	"git.gabrielgio.me/cerrado/pkg/ext"  	"git.gabrielgio.me/cerrado/pkg/service" @@ -36,6 +39,7 @@ type (  		GetAbout(name string) (string, error)  		ListTags(name string) ([]*plumbing.Reference, error)  		ListBranches(name string) ([]*plumbing.Reference, error) +		WriteTarGZip(w io.Writer, name, ref, filename string) error  	}  	configurationRepository interface { @@ -84,6 +88,38 @@ func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error {  	return nil  } +func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error { +	ext.SetGZip(w) +	name := r.PathValue("name") +	refs := r.PathValue("refs") +	ref := strings.TrimSuffix(refs, ".tar.gz") + +	// TODO: remove it once we can support more than gzip +	if !strings.HasSuffix(refs, ".tar.gz") { +		ext.NotFound(w) +		return nil +	} + +	filenameWithExt := fmt.Sprintf("%s-%s.tar.gz", name, ref) +	ext.SetFileName(w, filenameWithExt) +	filename := fmt.Sprintf("%s-%s", name, ref) + +	// writing to a buffer so we can run all the process before writing error +	var buf bytes.Buffer +	err := g.gitService.WriteTarGZip(&buf, name, ref, filename) +	if err != nil { +		return err +	} + +	// since that has write to w it cannot return a error. +	_, err = io.Copy(w, &buf) +	if err != nil { +		slog.Error("Error copying buffer", "error", err) +	} + +	return nil +} +  func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {  	ext.SetHTML(w)  	name := r.PathValue("name") diff --git a/pkg/handler/router.go b/pkg/handler/router.go index c8f8984..2293ab6 100644 --- a/pkg/handler/router.go +++ b/pkg/handler/router.go @@ -41,6 +41,7 @@ func MountHandler(  	mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree)  	mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob)  	mux.HandleFunc("/{name}/log/{ref}/", gitHandler.Log) +	mux.HandleFunc("/{name}/archive/{refs...}", gitHandler.Archive)  	mux.HandleFunc("/config", configHandler)  	mux.HandleFunc("/about", aboutHandler.About)  	mux.HandleFunc("/", gitHandler.List) diff --git a/pkg/service/git.go b/pkg/service/git.go index 6bb6e9e..cbee90a 100644 --- a/pkg/service/git.go +++ b/pkg/service/git.go @@ -1,7 +1,9 @@  package service  import ( +	"compress/gzip"  	"errors" +	"io"  	"log/slog"  	"git.gabrielgio.me/cerrado/pkg/config" @@ -92,6 +94,29 @@ func (g *GitService) ListCommits(name, ref string, count int) ([]*object.Commit,  	return repo.Commits(count)  } +func (g *GitService) WriteTarGZip(w io.Writer, name, ref string, filename string) error { +	r := g.configRepo.GetByName(name) +	if r == nil { +		return RepositoryNotFoundErr +	} + +	repo, err := git.OpenRepository(r.Path) +	if err != nil { +		return err +	} + +	err = repo.SetRef(ref) +	if err != nil { +		return err +	} + +	gw := gzip.NewWriter(w) +	defer gw.Close() + +	return repo.WriteTar(gw, filename) + +} +  func (g *GitService) GetTree(name, ref, path string) (*object.Tree, error) {  	r := g.configRepo.GetByName(name)  	if r == nil { | 
