all repos — legit @ hide

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		DisplayName, Name, Desc, Idle string
 37		d                             time.Time
 38	}
 39
 40	infos := []info{}
 41
 42	for _, dir := range dirs {
 43		name := dir.Name()
 44		if !dir.IsDir() || d.isIgnored(name) || d.isUnlisted(name) {
 45			continue
 46		}
 47
 48		path := filepath.Join(d.c.Repo.ScanPath, name)
 49		gr, err := git.Open(path, "")
 50		if err != nil {
 51			log.Println(err)
 52			continue
 53		}
 54
 55		c, err := gr.LastCommit()
 56		if err != nil {
 57			d.Write500(w)
 58			log.Println(err)
 59			return
 60		}
 61
 62		infos = append(infos, info{
 63			DisplayName: getDisplayName(name),
 64			Name:        name,
 65			Desc:        getDescription(path),
 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["displayname"] = getDisplayName(name)
153	data["ref"] = mainBranch
154	data["readme"] = readmeContent
155	data["commits"] = commits
156	data["desc"] = getDescription(path)
157	data["servername"] = d.c.Server.Name
158	data["meta"] = d.c.Meta
159	data["gomod"] = isGoModule(gr)
160
161	if err := t.ExecuteTemplate(w, "repo", data); err != nil {
162		log.Println(err)
163		return
164	}
165
166	return
167}
168
169func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
170	name := r.PathValue("name")
171	if d.isIgnored(name) {
172		d.Write404(w)
173		return
174	}
175	treePath := r.PathValue("rest")
176	ref := r.PathValue("ref")
177
178	name = filepath.Clean(name)
179	path := filepath.Join(d.c.Repo.ScanPath, name)
180	gr, err := git.Open(path, ref)
181	if err != nil {
182		d.Write404(w)
183		return
184	}
185
186	files, err := gr.FileTree(treePath)
187	if err != nil {
188		d.Write500(w)
189		log.Println(err)
190		return
191	}
192
193	data := make(map[string]any)
194	data["name"] = name
195	data["displayname"] = getDisplayName(name)
196	data["ref"] = ref
197	data["parent"] = treePath
198	data["desc"] = getDescription(path)
199	data["dotdot"] = filepath.Dir(treePath)
200
201	d.listFiles(files, data, w)
202	return
203}
204
205func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
206	var raw bool
207	if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
208		raw = rawParam
209	}
210
211	name := r.PathValue("name")
212	if d.isIgnored(name) {
213		d.Write404(w)
214		return
215	}
216	treePath := r.PathValue("rest")
217	ref := r.PathValue("ref")
218
219	name = filepath.Clean(name)
220	path := filepath.Join(d.c.Repo.ScanPath, name)
221	gr, err := git.Open(path, ref)
222	if err != nil {
223		d.Write404(w)
224		return
225	}
226
227	contents, err := gr.FileContent(treePath)
228	data := make(map[string]any)
229	data["name"] = name
230	data["displayname"] = getDisplayName(name)
231	data["ref"] = ref
232	data["desc"] = getDescription(path)
233	data["path"] = treePath
234
235	if raw {
236		d.showRaw(contents, w)
237	} else {
238		d.showFile(contents, data, w)
239	}
240	return
241}
242
243func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
244	name := r.PathValue("name")
245	if d.isIgnored(name) {
246		d.Write404(w)
247		return
248	}
249
250	file := r.PathValue("file")
251
252	// TODO: extend this to add more files compression (e.g.: xz)
253	if !strings.HasSuffix(file, ".tar.gz") {
254		d.Write404(w)
255		return
256	}
257
258	ref := strings.TrimSuffix(file, ".tar.gz")
259
260	// This allows the browser to use a proper name for the file when
261	// downloading
262	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
263	setContentDisposition(w, filename)
264	setGZipMIME(w)
265
266	path := filepath.Join(d.c.Repo.ScanPath, name)
267	gr, err := git.Open(path, ref)
268	if err != nil {
269		d.Write404(w)
270		return
271	}
272
273	gw := gzip.NewWriter(w)
274	defer gw.Close()
275
276	prefix := fmt.Sprintf("%s-%s", name, ref)
277	err = gr.WriteTar(gw, prefix)
278	if err != nil {
279		// once we start writing to the body we can't report error anymore
280		// so we are only left with printing the error.
281		log.Println(err)
282		return
283	}
284
285	err = gw.Flush()
286	if err != nil {
287		// once we start writing to the body we can't report error anymore
288		// so we are only left with printing the error.
289		log.Println(err)
290		return
291	}
292}
293
294func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
295	name := r.PathValue("name")
296	if d.isIgnored(name) {
297		d.Write404(w)
298		return
299	}
300	ref := r.PathValue("ref")
301
302	path := filepath.Join(d.c.Repo.ScanPath, name)
303	gr, err := git.Open(path, ref)
304	if err != nil {
305		d.Write404(w)
306		return
307	}
308
309	commits, err := gr.Commits()
310	if err != nil {
311		d.Write500(w)
312		log.Println(err)
313		return
314	}
315
316	tpath := filepath.Join(d.c.Dirs.Templates, "*")
317	t := template.Must(template.ParseGlob(tpath))
318
319	data := make(map[string]interface{})
320	data["commits"] = commits
321	data["meta"] = d.c.Meta
322	data["name"] = name
323	data["displayname"] = getDisplayName(name)
324	data["ref"] = ref
325	data["desc"] = getDescription(path)
326	data["log"] = true
327
328	if err := t.ExecuteTemplate(w, "log", data); err != nil {
329		log.Println(err)
330		return
331	}
332}
333
334func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
335	name := r.PathValue("name")
336	if d.isIgnored(name) {
337		d.Write404(w)
338		return
339	}
340	ref := r.PathValue("ref")
341
342	path := filepath.Join(d.c.Repo.ScanPath, name)
343	gr, err := git.Open(path, ref)
344	if err != nil {
345		d.Write404(w)
346		return
347	}
348
349	diff, err := gr.Diff()
350	if err != nil {
351		d.Write500(w)
352		log.Println(err)
353		return
354	}
355
356	tpath := filepath.Join(d.c.Dirs.Templates, "*")
357	t := template.Must(template.ParseGlob(tpath))
358
359	data := make(map[string]interface{})
360
361	data["commit"] = diff.Commit
362	data["stat"] = diff.Stat
363	data["diff"] = diff.Diff
364	data["meta"] = d.c.Meta
365	data["name"] = name
366	data["displayname"] = getDisplayName(name)
367	data["ref"] = ref
368	data["desc"] = getDescription(path)
369
370	if err := t.ExecuteTemplate(w, "commit", data); err != nil {
371		log.Println(err)
372		return
373	}
374}
375
376func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
377	name := r.PathValue("name")
378	if d.isIgnored(name) {
379		d.Write404(w)
380		return
381	}
382
383	path := filepath.Join(d.c.Repo.ScanPath, name)
384	gr, err := git.Open(path, "")
385	if err != nil {
386		d.Write404(w)
387		return
388	}
389
390	tags, err := gr.Tags()
391	if err != nil {
392		// Non-fatal, we *should* have at least one branch to show.
393		log.Println(err)
394	}
395
396	branches, err := gr.Branches()
397	if err != nil {
398		log.Println(err)
399		d.Write500(w)
400		return
401	}
402
403	tpath := filepath.Join(d.c.Dirs.Templates, "*")
404	t := template.Must(template.ParseGlob(tpath))
405
406	data := make(map[string]interface{})
407
408	data["meta"] = d.c.Meta
409	data["name"] = name
410	data["displayname"] = getDisplayName(name)
411	data["branches"] = branches
412	data["tags"] = tags
413	data["desc"] = getDescription(path)
414
415	if err := t.ExecuteTemplate(w, "refs", data); err != nil {
416		log.Println(err)
417		return
418	}
419}
420
421func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
422	f := r.PathValue("file")
423	f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
424
425	http.ServeFile(w, r, f)
426}