aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorGabriel A. Giovanini <mail@gabrielgio.me>2024-06-22 16:30:47 +0200
committerGabriel A. Giovanini <mail@gabrielgio.me>2024-06-22 16:30:47 +0200
commite1664fcbc4685906d3dabc66bf947a17bce7efc0 (patch)
treec9ec73616b841e72397fca975e46f691d031e621 /pkg
parent19839337ce0c74b67c5480b71e98d97a112aa104 (diff)
downloadcerrado-e1664fcbc4685906d3dabc66bf947a17bce7efc0.tar.gz
cerrado-e1664fcbc4685906d3dabc66bf947a17bce7efc0.tar.bz2
cerrado-e1664fcbc4685906d3dabc66bf947a17bce7efc0.zip
feat: Add archive capability
Diffstat (limited to 'pkg')
-rw-r--r--pkg/ext/mime.go11
-rw-r--r--pkg/git/git.go118
-rw-r--r--pkg/handler/git/handler.go36
-rw-r--r--pkg/handler/router.go1
-rw-r--r--pkg/service/git.go25
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 {