all repos — legit @ e108c4e924105ef88e73f0fe45069454b66ccd12

web frontend for git, written in go

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	if err != nil {
229		d.Write500(w)
230		return
231	}
232	data := make(map[string]any)
233	data["name"] = name
234	data["displayname"] = getDisplayName(name)
235	data["ref"] = ref
236	data["desc"] = getDescription(path)
237	data["path"] = treePath
238
239	if raw {
240		d.showRaw(contents, w)
241	} else {
242		if d.c.Meta.SyntaxHighlight == "" {
243			d.showFile(contents, data, w)
244		} else {
245			d.showFileWithHighlight(treePath, contents, data, w)
246		}
247	}
248}
249
250func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
251	name := r.PathValue("name")
252	if d.isIgnored(name) {
253		d.Write404(w)
254		return
255	}
256
257	file := r.PathValue("file")
258
259	// TODO: extend this to add more files compression (e.g.: xz)
260	if !strings.HasSuffix(file, ".tar.gz") {
261		d.Write404(w)
262		return
263	}
264
265	ref := strings.TrimSuffix(file, ".tar.gz")
266
267	// This allows the browser to use a proper name for the file when
268	// downloading
269	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
270	setContentDisposition(w, filename)
271	setGZipMIME(w)
272
273	path := filepath.Join(d.c.Repo.ScanPath, name)
274	gr, err := git.Open(path, ref)
275	if err != nil {
276		d.Write404(w)
277		return
278	}
279
280	gw := gzip.NewWriter(w)
281	defer gw.Close()
282
283	prefix := fmt.Sprintf("%s-%s", name, ref)
284	err = gr.WriteTar(gw, prefix)
285	if err != nil {
286		// once we start writing to the body we can't report error anymore
287		// so we are only left with printing the error.
288		log.Println(err)
289		return
290	}
291
292	err = gw.Flush()
293	if err != nil {
294		// once we start writing to the body we can't report error anymore
295		// so we are only left with printing the error.
296		log.Println(err)
297		return
298	}
299}
300
301func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
302	name := r.PathValue("name")
303	if d.isIgnored(name) {
304		d.Write404(w)
305		return
306	}
307	ref := r.PathValue("ref")
308
309	path := filepath.Join(d.c.Repo.ScanPath, name)
310	gr, err := git.Open(path, ref)
311	if err != nil {
312		d.Write404(w)
313		return
314	}
315
316	commits, err := gr.Commits()
317	if err != nil {
318		d.Write500(w)
319		log.Println(err)
320		return
321	}
322
323	tpath := filepath.Join(d.c.Dirs.Templates, "*")
324	t := template.Must(template.ParseGlob(tpath))
325
326	data := make(map[string]interface{})
327	data["commits"] = commits
328	data["meta"] = d.c.Meta
329	data["name"] = name
330	data["displayname"] = getDisplayName(name)
331	data["ref"] = ref
332	data["desc"] = getDescription(path)
333	data["log"] = true
334
335	if err := t.ExecuteTemplate(w, "log", data); err != nil {
336		log.Println(err)
337		return
338	}
339}
340
341func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
342	name := r.PathValue("name")
343	if d.isIgnored(name) {
344		d.Write404(w)
345		return
346	}
347	ref := r.PathValue("ref")
348
349	path := filepath.Join(d.c.Repo.ScanPath, name)
350	gr, err := git.Open(path, ref)
351	if err != nil {
352		d.Write404(w)
353		return
354	}
355
356	diff, err := gr.Diff()
357	if err != nil {
358		d.Write500(w)
359		log.Println(err)
360		return
361	}
362
363	tpath := filepath.Join(d.c.Dirs.Templates, "*")
364	t := template.Must(template.ParseGlob(tpath))
365
366	data := make(map[string]interface{})
367
368	data["commit"] = diff.Commit
369	data["stat"] = diff.Stat
370	data["diff"] = diff.Diff
371	data["meta"] = d.c.Meta
372	data["name"] = name
373	data["displayname"] = getDisplayName(name)
374	data["ref"] = ref
375	data["desc"] = getDescription(path)
376
377	if err := t.ExecuteTemplate(w, "commit", data); err != nil {
378		log.Println(err)
379		return
380	}
381}
382
383func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
384	name := r.PathValue("name")
385	if d.isIgnored(name) {
386		d.Write404(w)
387		return
388	}
389
390	path := filepath.Join(d.c.Repo.ScanPath, name)
391	gr, err := git.Open(path, "")
392	if err != nil {
393		d.Write404(w)
394		return
395	}
396
397	tags, err := gr.Tags()
398	if err != nil {
399		// Non-fatal, we *should* have at least one branch to show.
400		log.Println(err)
401	}
402
403	branches, err := gr.Branches()
404	if err != nil {
405		log.Println(err)
406		d.Write500(w)
407		return
408	}
409
410	tpath := filepath.Join(d.c.Dirs.Templates, "*")
411	t := template.Must(template.ParseGlob(tpath))
412
413	data := make(map[string]interface{})
414
415	data["meta"] = d.c.Meta
416	data["name"] = name
417	data["displayname"] = getDisplayName(name)
418	data["branches"] = branches
419	data["tags"] = tags
420	data["desc"] = getDescription(path)
421
422	if err := t.ExecuteTemplate(w, "refs", data); err != nil {
423		log.Println(err)
424		return
425	}
426}
427
428func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
429	f := r.PathValue("file")
430	f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
431
432	http.ServeFile(w, r, f)
433}