all repos — legit @ 03124550c9f86fbbdb31feb139640f55c3bb6818

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