all repos — legit @ acac8d47d0dd4bab02274f750d22937044bee988

web frontend for git

routes/routes.go (view raw)

  1package routes
  2
  3import (
  4	"compress/gzip"
  5	"fmt"
  6	"html/template"
  7	"log"
  8	"net/http"
  9	"os"
 10	"path/filepath"
 11	"sort"
 12	"strconv"
 13	"strings"
 14	"time"
 15
 16	"git.icyphox.sh/legit/config"
 17	"git.icyphox.sh/legit/git"
 18	"github.com/dustin/go-humanize"
 19	"github.com/microcosm-cc/bluemonday"
 20	"github.com/russross/blackfriday/v2"
 21)
 22
 23type deps struct {
 24	c *config.Config
 25}
 26
 27func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
 28	dirs, err := os.ReadDir(d.c.Repo.ScanPath)
 29	if err != nil {
 30		d.Write500(w)
 31		log.Printf("reading scan path: %s", err)
 32		return
 33	}
 34
 35	type info struct {
 36		Name, Desc, Idle string
 37		d                time.Time
 38	}
 39
 40	infos := []info{}
 41
 42	for _, dir := range dirs {
 43		if d.isIgnored(dir.Name()) {
 44			continue
 45		}
 46
 47		path := filepath.Join(d.c.Repo.ScanPath, dir.Name())
 48		gr, err := git.Open(path, "")
 49		if err != nil {
 50			log.Println(err)
 51			continue
 52		}
 53
 54		c, err := gr.LastCommit()
 55		if err != nil {
 56			d.Write500(w)
 57			log.Println(err)
 58			return
 59		}
 60
 61		desc := getDescription(path)
 62
 63		infos = append(infos, info{
 64			Name: dir.Name(),
 65			Desc: desc,
 66			Idle: humanize.Time(c.Author.When),
 67			d:    c.Author.When,
 68		})
 69	}
 70
 71	sort.Slice(infos, func(i, j int) bool {
 72		return infos[j].d.Before(infos[i].d)
 73	})
 74
 75	tpath := filepath.Join(d.c.Dirs.Templates, "*")
 76	t := template.Must(template.ParseGlob(tpath))
 77
 78	data := make(map[string]interface{})
 79	data["meta"] = d.c.Meta
 80	data["info"] = infos
 81
 82	if err := t.ExecuteTemplate(w, "index", data); err != nil {
 83		log.Println(err)
 84		return
 85	}
 86}
 87
 88func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
 89	name := r.PathValue("name")
 90	if d.isIgnored(name) {
 91		d.Write404(w)
 92		return
 93	}
 94	name = filepath.Clean(name)
 95	path := filepath.Join(d.c.Repo.ScanPath, name)
 96
 97	gr, err := git.Open(path, "")
 98	if err != nil {
 99		d.Write404(w)
100		return
101	}
102
103	commits, err := gr.Commits()
104	if err != nil {
105		d.Write500(w)
106		log.Println(err)
107		return
108	}
109
110	var readmeContent template.HTML
111	for _, readme := range d.c.Repo.Readme {
112		ext := filepath.Ext(readme)
113		content, _ := gr.FileContent(readme)
114		if len(content) > 0 {
115			switch ext {
116			case ".md", ".mkd", ".markdown":
117				unsafe := blackfriday.Run(
118					[]byte(content),
119					blackfriday.WithExtensions(blackfriday.CommonExtensions),
120				)
121				html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
122				readmeContent = template.HTML(html)
123			default:
124				readmeContent = template.HTML(
125					fmt.Sprintf(`<pre>%s</pre>`, content),
126				)
127			}
128			break
129		}
130	}
131
132	if readmeContent == "" {
133		log.Printf("no readme found for %s", name)
134	}
135
136	mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
137	if err != nil {
138		d.Write500(w)
139		log.Println(err)
140		return
141	}
142
143	tpath := filepath.Join(d.c.Dirs.Templates, "*")
144	t := template.Must(template.ParseGlob(tpath))
145
146	if len(commits) >= 3 {
147		commits = commits[:3]
148	}
149
150	data := make(map[string]any)
151	data["name"] = name
152	data["ref"] = mainBranch
153	data["readme"] = readmeContent
154	data["commits"] = commits
155	data["desc"] = getDescription(path)
156	data["servername"] = d.c.Server.Name
157	data["meta"] = d.c.Meta
158	data["gomod"] = isGoModule(gr)
159
160	if err := t.ExecuteTemplate(w, "repo", data); err != nil {
161		log.Println(err)
162		return
163	}
164
165	return
166}
167
168func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
169	name := r.PathValue("name")
170	if d.isIgnored(name) {
171		d.Write404(w)
172		return
173	}
174	treePath := r.PathValue("rest")
175	ref := r.PathValue("ref")
176
177	name = filepath.Clean(name)
178	path := filepath.Join(d.c.Repo.ScanPath, name)
179	gr, err := git.Open(path, ref)
180	if err != nil {
181		d.Write404(w)
182		return
183	}
184
185	files, err := gr.FileTree(treePath)
186	if err != nil {
187		d.Write500(w)
188		log.Println(err)
189		return
190	}
191
192	data := make(map[string]any)
193	data["name"] = name
194	data["ref"] = ref
195	data["parent"] = treePath
196	data["desc"] = getDescription(path)
197	data["dotdot"] = filepath.Dir(treePath)
198
199	d.listFiles(files, data, w)
200	return
201}
202
203func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
204	var raw bool
205	if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
206		raw = rawParam
207	}
208
209	name := r.PathValue("name")
210	if d.isIgnored(name) {
211		d.Write404(w)
212		return
213	}
214	treePath := r.PathValue("rest")
215	ref := r.PathValue("ref")
216
217	name = filepath.Clean(name)
218	path := filepath.Join(d.c.Repo.ScanPath, name)
219	gr, err := git.Open(path, ref)
220	if err != nil {
221		d.Write404(w)
222		return
223	}
224
225	contents, err := gr.FileContent(treePath)
226	data := make(map[string]any)
227	data["name"] = name
228	data["ref"] = ref
229	data["desc"] = getDescription(path)
230	data["path"] = treePath
231
232	if raw {
233		d.showRaw(contents, w)
234	} else {
235		d.showFile(contents, data, w)
236	}
237	return
238}
239
240func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
241	name := r.PathValue("name")
242	if d.isIgnored(name) {
243		d.Write404(w)
244		return
245	}
246
247	file := r.PathValue("file")
248
249	// TODO: extend this to add more files compression (e.g.: xz)
250	if !strings.HasSuffix(file, ".tar.gz") {
251		d.Write404(w)
252		return
253	}
254
255	ref := strings.TrimSuffix(file, ".tar.gz")
256
257	// This allows the browser to use a proper name for the file when
258	// downloading
259	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
260	setContentDisposition(w, filename)
261	setGZipMIME(w)
262
263	path := filepath.Join(d.c.Repo.ScanPath, name)
264	gr, err := git.Open(path, ref)
265	if err != nil {
266		d.Write404(w)
267		return
268	}
269
270	gw := gzip.NewWriter(w)
271	defer gw.Close()
272
273	prefix := fmt.Sprintf("%s-%s", name, ref)
274	err = gr.WriteTar(gw, prefix)
275	if err != nil {
276		// once we start writing to the body we can't report error anymore
277		// so we are only left with printing the error.
278		log.Println(err)
279		return
280	}
281
282	err = gw.Flush()
283	if err != nil {
284		// once we start writing to the body we can't report error anymore
285		// so we are only left with printing the error.
286		log.Println(err)
287		return
288	}
289}
290
291func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
292	name := r.PathValue("name")
293	if d.isIgnored(name) {
294		d.Write404(w)
295		return
296	}
297	ref := r.PathValue("ref")
298
299	path := filepath.Join(d.c.Repo.ScanPath, name)
300	gr, err := git.Open(path, ref)
301	if err != nil {
302		d.Write404(w)
303		return
304	}
305
306	commits, err := gr.Commits()
307	if err != nil {
308		d.Write500(w)
309		log.Println(err)
310		return
311	}
312
313	tpath := filepath.Join(d.c.Dirs.Templates, "*")
314	t := template.Must(template.ParseGlob(tpath))
315
316	data := make(map[string]interface{})
317	data["commits"] = commits
318	data["meta"] = d.c.Meta
319	data["name"] = name
320	data["ref"] = ref
321	data["desc"] = getDescription(path)
322	data["log"] = true
323
324	if err := t.ExecuteTemplate(w, "log", data); err != nil {
325		log.Println(err)
326		return
327	}
328}
329
330func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
331	name := r.PathValue("name")
332	if d.isIgnored(name) {
333		d.Write404(w)
334		return
335	}
336	ref := r.PathValue("ref")
337
338	path := filepath.Join(d.c.Repo.ScanPath, name)
339	gr, err := git.Open(path, ref)
340	if err != nil {
341		d.Write404(w)
342		return
343	}
344
345	diff, err := gr.Diff()
346	if err != nil {
347		d.Write500(w)
348		log.Println(err)
349		return
350	}
351
352	tpath := filepath.Join(d.c.Dirs.Templates, "*")
353	t := template.Must(template.ParseGlob(tpath))
354
355	data := make(map[string]interface{})
356
357	data["commit"] = diff.Commit
358	data["stat"] = diff.Stat
359	data["diff"] = diff.Diff
360	data["meta"] = d.c.Meta
361	data["name"] = name
362	data["ref"] = ref
363	data["desc"] = getDescription(path)
364
365	if err := t.ExecuteTemplate(w, "commit", data); err != nil {
366		log.Println(err)
367		return
368	}
369}
370
371func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
372	name := r.PathValue("name")
373	if d.isIgnored(name) {
374		d.Write404(w)
375		return
376	}
377
378	path := filepath.Join(d.c.Repo.ScanPath, name)
379	gr, err := git.Open(path, "")
380	if err != nil {
381		d.Write404(w)
382		return
383	}
384
385	tags, err := gr.Tags()
386	if err != nil {
387		// Non-fatal, we *should* have at least one branch to show.
388		log.Println(err)
389	}
390
391	branches, err := gr.Branches()
392	if err != nil {
393		log.Println(err)
394		d.Write500(w)
395		return
396	}
397
398	tpath := filepath.Join(d.c.Dirs.Templates, "*")
399	t := template.Must(template.ParseGlob(tpath))
400
401	data := make(map[string]interface{})
402
403	data["meta"] = d.c.Meta
404	data["name"] = name
405	data["branches"] = branches
406	data["tags"] = tags
407	data["desc"] = getDescription(path)
408
409	if err := t.ExecuteTemplate(w, "refs", data); err != nil {
410		log.Println(err)
411		return
412	}
413}
414
415func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
416	f := r.PathValue("file")
417	f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
418
419	http.ServeFile(w, r, f)
420}