1package git
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "log/slog"
8 "net/http"
9 "os"
10 "path/filepath"
11 "strings"
12
13 "git.gabrielgio.me/cerrado/pkg/ext"
14 "git.gabrielgio.me/cerrado/pkg/service"
15 "git.gabrielgio.me/cerrado/templates"
16 "github.com/alecthomas/chroma/v2"
17 "github.com/alecthomas/chroma/v2/formatters/html"
18 "github.com/alecthomas/chroma/v2/lexers"
19 "github.com/alecthomas/chroma/v2/styles"
20 "github.com/go-git/go-git/v5/plumbing"
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 gitService
30 readmePath string
31 }
32
33 gitService interface {
34 ListRepositories() ([]*service.Repository, error)
35 ListCommits(name string, ref string, count int) ([]*object.Commit, error)
36 GetHead(name string) (*plumbing.Reference, error)
37 GetTree(name, ref, path string) (*object.Tree, error)
38 GetFileContent(name, ref, path string) (string, error)
39 GetAbout(name string) (string, error)
40 ListTags(name string) ([]*plumbing.Reference, error)
41 ListBranches(name string) ([]*plumbing.Reference, error)
42 WriteTarGZip(w io.Writer, name, ref, filename string) error
43 }
44
45 configurationRepository interface {
46 GetRootReadme() string
47 }
48)
49
50func NewGitHandler(gitService gitService, confRepo configurationRepository) *GitHandler {
51 return &GitHandler{
52 gitService: gitService,
53 readmePath: confRepo.GetRootReadme(),
54 }
55}
56
57func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error {
58 repos, err := g.gitService.ListRepositories()
59 if err != nil {
60 return err
61 }
62
63 f, err := os.Open(g.readmePath)
64 if err != nil {
65 return err
66 }
67
68 bs, err := io.ReadAll(f)
69 if err != nil {
70 return err
71 }
72
73 extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
74 p := parser.NewWithExtensions(extensions)
75 doc := p.Parse(bs)
76
77 htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
78 opts := markdownhtml.RendererOptions{Flags: htmlFlag}
79 renderer := markdownhtml.NewRenderer(opts)
80
81 bs = markdown.Render(doc, renderer)
82
83 gitList := &templates.GitListPage{
84 Respositories: repos,
85 About: bs,
86 }
87 templates.WritePageTemplate(w, gitList)
88 return nil
89}
90
91func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
92 ext.SetGZip(w)
93 name := r.PathValue("name")
94 refs := r.PathValue("refs")
95 ref := strings.TrimSuffix(refs, ".tar.gz")
96
97 // TODO: remove it once we can support more than gzip
98 if !strings.HasSuffix(refs, ".tar.gz") {
99 ext.NotFound(w)
100 return nil
101 }
102
103 filenameWithExt := fmt.Sprintf("%s-%s.tar.gz", name, ref)
104 ext.SetFileName(w, filenameWithExt)
105 filename := fmt.Sprintf("%s-%s", name, ref)
106
107 // writing to a buffer so we can run all the process before writing error
108 var buf bytes.Buffer
109 err := g.gitService.WriteTarGZip(&buf, name, ref, filename)
110 if err != nil {
111 return err
112 }
113
114 // since that has write to w it cannot return a error.
115 _, err = io.Copy(w, &buf)
116 if err != nil {
117 slog.Error("Error copying buffer", "error", err)
118 }
119
120 return nil
121}
122
123func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
124 ext.SetHTML(w)
125 name := r.PathValue("name")
126 ref, err := g.gitService.GetHead(name)
127 if err != nil {
128 return err
129 }
130
131 tags, err := g.gitService.ListTags(name)
132 if err != nil {
133 return err
134 }
135
136 branches, err := g.gitService.ListBranches(name)
137 if err != nil {
138 return err
139 }
140
141 commits, err := g.gitService.ListCommits(name, "", 10)
142 if err != nil {
143 return err
144 }
145
146 gitList := &templates.GitItemPage{
147 Name: name,
148 Ref: ref.Name().Short(),
149 GitItemBase: &templates.GitItemSummaryPage{
150 Tags: tags,
151 Branches: branches,
152 Commits: commits,
153 },
154 }
155 templates.WritePageTemplate(w, gitList)
156 return nil
157}
158
159func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
160 ext.SetHTML(w)
161 name := r.PathValue("name")
162 ref, err := g.gitService.GetHead(name)
163 if err != nil {
164 return err
165 }
166
167 file, err := g.gitService.GetAbout(name)
168 if err != nil {
169 return err
170 }
171
172 extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
173 p := parser.NewWithExtensions(extensions)
174 doc := p.Parse([]byte(file))
175
176 htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
177 opts := markdownhtml.RendererOptions{Flags: htmlFlag}
178 renderer := markdownhtml.NewRenderer(opts)
179
180 bs := markdown.Render(doc, renderer)
181
182 gitList := &templates.GitItemPage{
183 Name: name,
184 Ref: ref.Name().Short(),
185 GitItemBase: &templates.GitItemAboutPage{
186 About: bs,
187 },
188 }
189 templates.WritePageTemplate(w, gitList)
190 return nil
191}
192
193func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
194 ext.SetHTML(w)
195 name := r.PathValue("name")
196
197 tags, err := g.gitService.ListTags(name)
198 if err != nil {
199 return err
200 }
201
202 branches, err := g.gitService.ListBranches(name)
203 if err != nil {
204 return err
205 }
206
207 ref, err := g.gitService.GetHead(name)
208 if err != nil {
209 return err
210 }
211
212 gitList := &templates.GitItemPage{
213 Name: name,
214 Ref: ref.Name().Short(),
215 GitItemBase: &templates.GitItemRefsPage{
216 Tags: tags,
217 Branches: branches,
218 },
219 }
220 templates.WritePageTemplate(w, gitList)
221 return nil
222}
223
224func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
225 ext.SetHTML(w)
226 name := r.PathValue("name")
227 ref := r.PathValue("ref")
228 rest := r.PathValue("rest")
229
230 tree, err := g.gitService.GetTree(name, ref, rest)
231 if err != nil {
232 return err
233 }
234
235 gitList := &templates.GitItemPage{
236 Name: name,
237 Ref: ref,
238 GitItemBase: &templates.GitItemTreePage{
239 CurrentPath: rest,
240 Tree: tree,
241 },
242 }
243 templates.WritePageTemplate(w, gitList)
244 return nil
245}
246
247func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
248 ext.SetHTML(w)
249 name := r.PathValue("name")
250 ref := r.PathValue("ref")
251 rest := r.PathValue("rest")
252
253 file, err := g.gitService.GetFileContent(name, ref, rest)
254 if err != nil {
255 return err
256 }
257
258 filename := filepath.Base(rest)
259 lexer := GetLexers(filename)
260 style := styles.Get("xcode")
261 formatter := html.New(
262 html.WithLineNumbers(true),
263 )
264 iterator, err := lexer.Tokenise(nil, file)
265 if err != nil {
266 return err
267 }
268
269 var code bytes.Buffer
270 err = formatter.Format(&code, style, iterator)
271 if err != nil {
272 return err
273 }
274
275 gitList := &templates.GitItemPage{
276 Name: name,
277 Ref: ref,
278 GitItemBase: &templates.GitItemBlobPage{
279 File: rest,
280 Content: code.Bytes(),
281 },
282 }
283 templates.WritePageTemplate(w, gitList)
284 return nil
285}
286
287func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
288 ext.SetHTML(w)
289 name := r.PathValue("name")
290 ref := r.PathValue("ref")
291
292 commits, err := g.gitService.ListCommits(name, ref, 1000)
293 if err != nil {
294 return err
295 }
296
297 gitList := &templates.GitItemPage{
298 Name: name,
299 Ref: ref,
300 GitItemBase: &templates.GitItemLogPage{
301 Commits: commits,
302 },
303 }
304 templates.WritePageTemplate(w, gitList)
305 return nil
306}
307
308func GetLexers(filename string) chroma.Lexer {
309 if filename == "APKBUILD" {
310 return lexers.Get("sh")
311 }
312
313 lexer := lexers.Get(filename)
314
315 if lexer == nil {
316 lexer = lexers.Get("txt")
317 }
318 return lexer
319}