cerrado @ 2fb43cc1271572d26298da70f823814cae87a3a0

  1package git
  2
  3import (
  4	"bytes"
  5	"errors"
  6	"fmt"
  7	"io"
  8	"log/slog"
  9	"net/http"
 10	"os"
 11	"path/filepath"
 12	"strings"
 13
 14	"git.gabrielgio.me/cerrado/pkg/ext"
 15	"git.gabrielgio.me/cerrado/pkg/service"
 16	"git.gabrielgio.me/cerrado/templates"
 17	"github.com/alecthomas/chroma/v2"
 18	"github.com/alecthomas/chroma/v2/formatters/html"
 19	"github.com/alecthomas/chroma/v2/lexers"
 20	"github.com/alecthomas/chroma/v2/styles"
 21	"github.com/go-git/go-git/v5/plumbing/object"
 22	"github.com/gomarkdown/markdown"
 23	markdownhtml "github.com/gomarkdown/markdown/html"
 24	"github.com/gomarkdown/markdown/parser"
 25)
 26
 27type (
 28	GitHandler struct {
 29		gitService *service.GitService
 30		readmePath string
 31	}
 32
 33	configurationRepository interface {
 34		GetRootReadme() string
 35	}
 36)
 37
 38func NewGitHandler(gitService *service.GitService, confRepo configurationRepository) *GitHandler {
 39	return &GitHandler{
 40		gitService: gitService,
 41		readmePath: confRepo.GetRootReadme(),
 42	}
 43}
 44
 45func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error {
 46	repos, err := g.gitService.ListRepositories()
 47	if err != nil {
 48		return err
 49	}
 50
 51	f, err := os.Open(g.readmePath)
 52	if err != nil {
 53		return err
 54	}
 55
 56	bs, err := io.ReadAll(f)
 57	if err != nil {
 58		return err
 59	}
 60
 61	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
 62	p := parser.NewWithExtensions(extensions)
 63	doc := p.Parse(bs)
 64
 65	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
 66	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
 67	renderer := markdownhtml.NewRenderer(opts)
 68
 69	bs = markdown.Render(doc, renderer)
 70
 71	gitList := &templates.GitListPage{
 72		Respositories: repos,
 73		About:         bs,
 74	}
 75	templates.WritePageTemplate(w, gitList)
 76	return nil
 77}
 78
 79func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
 80	ext.SetGZip(w)
 81	name := r.PathValue("name")
 82	file := r.PathValue("file")
 83	ref := strings.TrimSuffix(file, ".tar.gz")
 84
 85	// TODO: remove it once we can support more than gzip
 86	if !strings.HasSuffix(file, ".tar.gz") {
 87		ext.NotFound(w)
 88		return nil
 89	}
 90
 91	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
 92	ext.SetFileName(w, filename)
 93
 94	prefix := fmt.Sprintf("%s-%s", name, ref)
 95	err := g.gitService.WriteTarGZip(w, name, ref, prefix)
 96	if err != nil {
 97		// once we start writing to the body we can't report error anymore
 98		// so we are only left with printing the error.
 99		slog.Error("Error generating tar gzip file", "error", err)
100	}
101
102	return nil
103}
104
105func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
106	ext.SetHTML(w)
107	name := r.PathValue("name")
108	ref, err := g.gitService.GetHead(name)
109	if err != nil {
110		return err
111	}
112
113	tags, err := g.gitService.ListTags(name)
114	if err != nil {
115		return err
116	}
117
118	branches, err := g.gitService.ListBranches(name)
119	if err != nil {
120		return err
121	}
122
123	commits, err := g.gitService.ListCommits(name, "", 10)
124	if err != nil {
125		return err
126	}
127
128	gitList := &templates.GitItemPage{
129		Name: name,
130		Ref:  ref.Name().Short(),
131		GitItemBase: &templates.GitItemSummaryPage{
132			Tags:     tags,
133			Branches: branches,
134			Commits:  commits,
135		},
136	}
137	templates.WritePageTemplate(w, gitList)
138	return nil
139}
140
141func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
142	ext.SetHTML(w)
143	name := r.PathValue("name")
144	ref, err := g.gitService.GetHead(name)
145	if err != nil {
146		return err
147	}
148
149	file, err := g.gitService.GetAbout(name)
150	if errors.Is(err, object.ErrFileNotFound) {
151		templates.WritePageTemplate(w, &templates.GitItemPage{
152			Name: name,
153			Ref:  ref.Name().Short(),
154			GitItemBase: &templates.GitItemAboutPage{
155				About: []byte("About file not configured properly"),
156			},
157		})
158		return nil
159	}
160	if err != nil {
161		return err
162	}
163
164	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
165	p := parser.NewWithExtensions(extensions)
166	doc := p.Parse(file)
167
168	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
169	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
170	renderer := markdownhtml.NewRenderer(opts)
171
172	bs := markdown.Render(doc, renderer)
173
174	gitList := &templates.GitItemPage{
175		Name: name,
176		Ref:  ref.Name().Short(),
177		GitItemBase: &templates.GitItemAboutPage{
178			About: bs,
179		},
180	}
181	templates.WritePageTemplate(w, gitList)
182	return nil
183}
184
185func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
186	ext.SetHTML(w)
187	name := r.PathValue("name")
188
189	tags, err := g.gitService.ListTags(name)
190	if err != nil {
191		return err
192	}
193
194	branches, err := g.gitService.ListBranches(name)
195	if err != nil {
196		return err
197	}
198
199	ref, err := g.gitService.GetHead(name)
200	if err != nil {
201		return err
202	}
203
204	gitList := &templates.GitItemPage{
205		Name: name,
206		Ref:  ref.Name().Short(),
207		GitItemBase: &templates.GitItemRefsPage{
208			Tags:     tags,
209			Branches: branches,
210		},
211	}
212	templates.WritePageTemplate(w, gitList)
213	return nil
214}
215
216func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
217	ext.SetHTML(w)
218	name := r.PathValue("name")
219	ref := r.PathValue("ref")
220	rest := r.PathValue("rest")
221	paths := []string{}
222
223	// this is avoid Split from generating a len 1 array with empty string
224	if rest != "" {
225		paths = strings.Split(rest, "/")
226	}
227
228	tree, err := g.gitService.GetTree(name, ref, rest)
229	if err != nil {
230		return err
231	}
232
233	gitList := &templates.GitItemPage{
234		Name: name,
235		Ref:  ref,
236		GitItemBase: &templates.GitItemTreePage{
237			Path: paths,
238			Tree: tree,
239		},
240	}
241	templates.WritePageTemplate(w, gitList)
242	return nil
243}
244
245func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
246	ext.SetHTML(w)
247	name := r.PathValue("name")
248	ref := r.PathValue("ref")
249	rest := r.PathValue("rest")
250	paths := []string{}
251
252	// this is avoid Split from generating a len 1 array with empty string
253	if rest != "" {
254		paths = strings.Split(rest, "/")
255	}
256
257	isBin, err := g.gitService.IsBinary(name, ref, rest)
258	if err != nil {
259		return err
260	}
261
262	// if it is binary no need to over all the chroma process
263	if isBin {
264		gitList := &templates.GitItemPage{
265			Name: name,
266			Ref:  ref,
267			GitItemBase: &templates.GitItemBlobPage{
268				Path:    paths,
269				Content: []byte("Binary file"),
270			},
271		}
272		templates.WritePageTemplate(w, gitList)
273		return nil
274	}
275
276	file, err := g.gitService.GetFileContent(name, ref, rest)
277	if err != nil {
278		return err
279	}
280
281	filename := filepath.Base(rest)
282	lexer := GetLexers(filename)
283	style := styles.Get("xcode")
284	formatter := html.New(
285		html.WithLineNumbers(true),
286		html.WithLinkableLineNumbers(true, "L"),
287	)
288
289	iterator, err := lexer.Tokenise(nil, string(file))
290	if err != nil {
291		return err
292	}
293
294	var code bytes.Buffer
295	err = formatter.Format(&code, style, iterator)
296	if err != nil {
297		return err
298	}
299
300	gitList := &templates.GitItemPage{
301		Name: name,
302		Ref:  ref,
303		GitItemBase: &templates.GitItemBlobPage{
304			Path:    paths,
305			Content: code.Bytes(),
306		},
307	}
308	templates.WritePageTemplate(w, gitList)
309	return nil
310}
311
312func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
313	ext.SetHTML(w)
314	name := r.PathValue("name")
315	ref := r.PathValue("ref")
316
317	commits, err := g.gitService.ListCommits(name, ref, 1000)
318	if err != nil {
319		return err
320	}
321
322	gitList := &templates.GitItemPage{
323		Name: name,
324		Ref:  ref,
325		GitItemBase: &templates.GitItemLogPage{
326			Commits: commits,
327		},
328	}
329	templates.WritePageTemplate(w, gitList)
330	return nil
331}
332
333func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error {
334	ext.SetHTML(w)
335	name := r.PathValue("name")
336	ref := r.PathValue("ref")
337
338	commit, err := g.gitService.LastCommit(name, ref)
339	if err != nil {
340		return err
341	}
342
343	diff, err := g.gitService.Diff(name, ref)
344	if err != nil {
345		return err
346	}
347
348	gitList := &templates.GitItemPage{
349		Name: name,
350		Ref:  ref,
351		GitItemBase: &templates.GitItemCommitPage{
352			Commit: commit,
353			Diff:   diff,
354		},
355	}
356	templates.WritePageTemplate(w, gitList)
357	return nil
358}
359
360func GetLexers(filename string) chroma.Lexer {
361	if filename == "APKBUILD" {
362		return lexers.Get("sh")
363	}
364
365	lexer := lexers.Get(filename)
366
367	if lexer == nil {
368		lexer = lexers.Get("txt")
369	}
370	return lexer
371}