aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--templates/gititemrefs.qtpl2
-rw-r--r--templates/gititemrefs.qtpl.go90
-rw-r--r--templates/gititemsummary.qtpl1
-rw-r--r--templates/gititemsummary.qtpl.go105
9 files changed, 304 insertions, 85 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 {
diff --git a/templates/gititemrefs.qtpl b/templates/gititemrefs.qtpl
index ff1561b..6244082 100644
--- a/templates/gititemrefs.qtpl
+++ b/templates/gititemrefs.qtpl
@@ -21,6 +21,7 @@ type GitItemRefsPage struct {
</div>
<div class="col-8">
<div class="float-end">
+ <a href="/{%s name %}/archive/{%s t.Name().Short() %}.tar.gz">tar.gz</a>
<a href="/{%s name %}/tree/{%s t.Name().Short() %}/">tree</a>
<a href="/{%s name %}/log/{%s t.Name().Short() %}/">log</a>
</div>
@@ -41,6 +42,7 @@ type GitItemRefsPage struct {
</div>
<div class="col-8">
<div class="float-end">
+ <a href="/{%s name %}/archive/{%s b.Name().Short() %}.tar.gz">tar.gz</a>
<a href="/{%s name %}/tree/{%s b.Name().Short() %}/">tree</a>
<a href="/{%s name %}/log/{%s b.Name().Short() %}/">log</a>
</div>
diff --git a/templates/gititemrefs.qtpl.go b/templates/gititemrefs.qtpl.go
index b00736e..da9bfe7 100644
--- a/templates/gititemrefs.qtpl.go
+++ b/templates/gititemrefs.qtpl.go
@@ -90,113 +90,131 @@ func (g *GitItemRefsPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref
//line gititemrefs.qtpl:24
qw422016.E().S(name)
//line gititemrefs.qtpl:24
- qw422016.N().S(`/tree/`)
+ qw422016.N().S(`/archive/`)
//line gititemrefs.qtpl:24
qw422016.E().S(t.Name().Short())
//line gititemrefs.qtpl:24
- qw422016.N().S(`/">tree</a>
+ qw422016.N().S(`.tar.gz">tar.gz</a>
<a href="/`)
//line gititemrefs.qtpl:25
qw422016.E().S(name)
//line gititemrefs.qtpl:25
- qw422016.N().S(`/log/`)
+ qw422016.N().S(`/tree/`)
//line gititemrefs.qtpl:25
qw422016.E().S(t.Name().Short())
//line gititemrefs.qtpl:25
+ qw422016.N().S(`/">tree</a>
+ <a href="/`)
+//line gititemrefs.qtpl:26
+ qw422016.E().S(name)
+//line gititemrefs.qtpl:26
+ qw422016.N().S(`/log/`)
+//line gititemrefs.qtpl:26
+ qw422016.E().S(t.Name().Short())
+//line gititemrefs.qtpl:26
qw422016.N().S(`/">log</a>
</div>
</div>
</div>
`)
-//line gititemrefs.qtpl:29
+//line gititemrefs.qtpl:30
}
-//line gititemrefs.qtpl:29
+//line gititemrefs.qtpl:30
qw422016.N().S(`
</div>
`)
-//line gititemrefs.qtpl:31
+//line gititemrefs.qtpl:32
} else {
-//line gititemrefs.qtpl:31
+//line gititemrefs.qtpl:32
qw422016.N().S(`
<p> No tags </p>
`)
-//line gititemrefs.qtpl:33
+//line gititemrefs.qtpl:34
}
-//line gititemrefs.qtpl:33
+//line gititemrefs.qtpl:34
qw422016.N().S(`
</div>
<div class="col-md-4">
<div class="event-list">
`)
-//line gititemrefs.qtpl:37
+//line gititemrefs.qtpl:38
for _, b := range g.Branches {
-//line gititemrefs.qtpl:37
+//line gititemrefs.qtpl:38
qw422016.N().S(`
<div class="row event">
<div class="col-4">
`)
-//line gititemrefs.qtpl:40
+//line gititemrefs.qtpl:41
qw422016.E().S(b.Name().Short())
-//line gititemrefs.qtpl:40
+//line gititemrefs.qtpl:41
qw422016.N().S(`
</div>
<div class="col-8">
<div class="float-end">
<a href="/`)
-//line gititemrefs.qtpl:44
+//line gititemrefs.qtpl:45
+ qw422016.E().S(name)
+//line gititemrefs.qtpl:45
+ qw422016.N().S(`/archive/`)
+//line gititemrefs.qtpl:45
+ qw422016.E().S(b.Name().Short())
+//line gititemrefs.qtpl:45
+ qw422016.N().S(`.tar.gz">tar.gz</a>
+ <a href="/`)
+//line gititemrefs.qtpl:46
qw422016.E().S(name)
-//line gititemrefs.qtpl:44
+//line gititemrefs.qtpl:46
qw422016.N().S(`/tree/`)
-//line gititemrefs.qtpl:44
+//line gititemrefs.qtpl:46
qw422016.E().S(b.Name().Short())
-//line gititemrefs.qtpl:44
+//line gititemrefs.qtpl:46
qw422016.N().S(`/">tree</a>
<a href="/`)
-//line gititemrefs.qtpl:45
+//line gititemrefs.qtpl:47
qw422016.E().S(name)
-//line gititemrefs.qtpl:45
+//line gititemrefs.qtpl:47
qw422016.N().S(`/log/`)
-//line gititemrefs.qtpl:45
+//line gititemrefs.qtpl:47
qw422016.E().S(b.Name().Short())
-//line gititemrefs.qtpl:45
+//line gititemrefs.qtpl:47
qw422016.N().S(`/">log</a>
</div>
</div>
</div>
`)
-//line gititemrefs.qtpl:49
+//line gititemrefs.qtpl:51
}
-//line gititemrefs.qtpl:49
+//line gititemrefs.qtpl:51
qw422016.N().S(`
</div>
</div>
</div>
`)
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
}
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
func (g *GitItemRefsPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
qw422016 := qt422016.AcquireWriter(qq422016)
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
g.StreamGitContent(qw422016, name, ref)
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
qt422016.ReleaseWriter(qw422016)
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
}
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
func (g *GitItemRefsPage) GitContent(name, ref string) string {
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
qb422016 := qt422016.AcquireByteBuffer()
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
g.WriteGitContent(qb422016, name, ref)
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
qs422016 := string(qb422016.B)
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
qt422016.ReleaseByteBuffer(qb422016)
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
return qs422016
-//line gititemrefs.qtpl:53
+//line gititemrefs.qtpl:55
}
diff --git a/templates/gititemsummary.qtpl b/templates/gititemsummary.qtpl
index e3d3a08..06a785a 100644
--- a/templates/gititemsummary.qtpl
+++ b/templates/gititemsummary.qtpl
@@ -23,6 +23,7 @@ type GitItemSummaryPage struct {
</div>
<div class="col-8">
<div class="float-end">
+ <a href="/{%s name %}/archive/{%s t.Name().Short() %}.tar.gz">tar.gz</a>
<a href="/{%s name %}/tree/{%s t.Name().Short() %}/">tree</a>
<a href="/{%s name %}/log/{%s t.Name().Short() %}/">log</a>
</div>
diff --git a/templates/gititemsummary.qtpl.go b/templates/gititemsummary.qtpl.go
index ad025f7..4e48efd 100644
--- a/templates/gititemsummary.qtpl.go
+++ b/templates/gititemsummary.qtpl.go
@@ -94,83 +94,92 @@ func (g *GitItemSummaryPage) StreamGitContent(qw422016 *qt422016.Writer, name, r
//line gititemsummary.qtpl:26
qw422016.E().S(name)
//line gititemsummary.qtpl:26
- qw422016.N().S(`/tree/`)
+ qw422016.N().S(`/archive/`)
//line gititemsummary.qtpl:26
qw422016.E().S(t.Name().Short())
//line gititemsummary.qtpl:26
- qw422016.N().S(`/">tree</a>
+ qw422016.N().S(`.tar.gz">tar.gz</a>
<a href="/`)
//line gititemsummary.qtpl:27
qw422016.E().S(name)
//line gititemsummary.qtpl:27
- qw422016.N().S(`/log/`)
+ qw422016.N().S(`/tree/`)
//line gititemsummary.qtpl:27
qw422016.E().S(t.Name().Short())
//line gititemsummary.qtpl:27
+ qw422016.N().S(`/">tree</a>
+ <a href="/`)
+//line gititemsummary.qtpl:28
+ qw422016.E().S(name)
+//line gititemsummary.qtpl:28
+ qw422016.N().S(`/log/`)
+//line gititemsummary.qtpl:28
+ qw422016.E().S(t.Name().Short())
+//line gititemsummary.qtpl:28
qw422016.N().S(`/">log</a>
</div>
</div>
</div>
`)
-//line gititemsummary.qtpl:31
+//line gititemsummary.qtpl:32
}
-//line gititemsummary.qtpl:31
+//line gititemsummary.qtpl:32
qw422016.N().S(`
</div>
`)
-//line gititemsummary.qtpl:33
+//line gititemsummary.qtpl:34
} else {
-//line gititemsummary.qtpl:33
+//line gititemsummary.qtpl:34
qw422016.N().S(`
<p> No tags </p>
`)
-//line gititemsummary.qtpl:35
+//line gititemsummary.qtpl:36
}
-//line gititemsummary.qtpl:35
+//line gititemsummary.qtpl:36
qw422016.N().S(`
</div>
<div class="col-md-4">
<div class="event-list">
`)
-//line gititemsummary.qtpl:39
+//line gititemsummary.qtpl:40
for _, b := range g.Branches {
-//line gititemsummary.qtpl:39
+//line gititemsummary.qtpl:40
qw422016.N().S(`
<div class="row event">
<div class="col-4">
`)
-//line gititemsummary.qtpl:42
+//line gititemsummary.qtpl:43
qw422016.E().S(b.Name().Short())
-//line gititemsummary.qtpl:42
+//line gititemsummary.qtpl:43
qw422016.N().S(`
</div>
<div class="col-8">
<div class="float-end">
<a href="/`)
-//line gititemsummary.qtpl:46
+//line gititemsummary.qtpl:47
qw422016.E().S(name)
-//line gititemsummary.qtpl:46
+//line gititemsummary.qtpl:47
qw422016.N().S(`/tree/`)
-//line gititemsummary.qtpl:46
+//line gititemsummary.qtpl:47
qw422016.E().S(b.Name().Short())
-//line gititemsummary.qtpl:46
+//line gititemsummary.qtpl:47
qw422016.N().S(`/">tree</a>
<a href="/`)
-//line gititemsummary.qtpl:47
+//line gititemsummary.qtpl:48
qw422016.E().S(name)
-//line gititemsummary.qtpl:47
+//line gititemsummary.qtpl:48
qw422016.N().S(`/log/`)
-//line gititemsummary.qtpl:47
+//line gititemsummary.qtpl:48
qw422016.E().S(b.Name().Short())
-//line gititemsummary.qtpl:47
+//line gititemsummary.qtpl:48
qw422016.N().S(`/">log</a>
</div>
</div>
</div>
`)
-//line gititemsummary.qtpl:51
+//line gititemsummary.qtpl:52
}
-//line gititemsummary.qtpl:51
+//line gititemsummary.qtpl:52
qw422016.N().S(`
</div>
</div>
@@ -178,70 +187,70 @@ func (g *GitItemSummaryPage) StreamGitContent(qw422016 *qt422016.Writer, name, r
<div class="row">
<div class="event-list">
`)
-//line gititemsummary.qtpl:57
+//line gititemsummary.qtpl:58
for _, c := range g.Commits {
-//line gititemsummary.qtpl:57
+//line gititemsummary.qtpl:58
qw422016.N().S(`
<div class="row event">
<div class="col-xxl-2">
`)
-//line gititemsummary.qtpl:60
+//line gititemsummary.qtpl:61
qw422016.E().S(TimeFormat(c.Committer.When))
-//line gititemsummary.qtpl:60
+//line gititemsummary.qtpl:61
qw422016.N().S(`
</div>
<div class="col-xxl-7 code-view">
<pre>`)
-//line gititemsummary.qtpl:63
+//line gititemsummary.qtpl:64
qw422016.E().S(c.Message)
-//line gititemsummary.qtpl:63
+//line gititemsummary.qtpl:64
qw422016.N().S(`</pre>
</div>
<div class="col-xxl-3">
<small>`)
-//line gititemsummary.qtpl:66
+//line gititemsummary.qtpl:67
qw422016.E().S(c.Committer.Name)
-//line gititemsummary.qtpl:66
+//line gititemsummary.qtpl:67
qw422016.N().S(` &lt;`)
-//line gititemsummary.qtpl:66
+//line gititemsummary.qtpl:67
qw422016.E().S(c.Committer.Email)
-//line gititemsummary.qtpl:66
+//line gititemsummary.qtpl:67
qw422016.N().S(`&gt;</small>
</div>
</div>
`)
-//line gititemsummary.qtpl:69
+//line gititemsummary.qtpl:70
}
-//line gititemsummary.qtpl:69
+//line gititemsummary.qtpl:70
qw422016.N().S(`
</div>
</div>
`)
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
}
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
func (g *GitItemSummaryPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
qw422016 := qt422016.AcquireWriter(qq422016)
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
g.StreamGitContent(qw422016, name, ref)
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
qt422016.ReleaseWriter(qw422016)
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
}
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
func (g *GitItemSummaryPage) GitContent(name, ref string) string {
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
qb422016 := qt422016.AcquireByteBuffer()
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
g.WriteGitContent(qb422016, name, ref)
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
qs422016 := string(qb422016.B)
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
qt422016.ReleaseByteBuffer(qb422016)
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
return qs422016
-//line gititemsummary.qtpl:72
+//line gititemsummary.qtpl:73
}