diff options
| -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 | ||||
| -rw-r--r-- | templates/gititemrefs.qtpl | 2 | ||||
| -rw-r--r-- | templates/gititemrefs.qtpl.go | 90 | ||||
| -rw-r--r-- | templates/gititemsummary.qtpl | 1 | ||||
| -rw-r--r-- | templates/gititemsummary.qtpl.go | 105 | 
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(` <`) -//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(`></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  } | 
