cerrado @ e1664fcbc4685906d3dabc66bf947a17bce7efc0

feat: Add archive capability
  1diff --git a/pkg/ext/mime.go b/pkg/ext/mime.go
  2index 6da66e3db03709439e1a8731116081dd5fe738bb..c42d4de563775a21af2511f6b2dc18562f62626a 100644
  3--- a/pkg/ext/mime.go
  4+++ b/pkg/ext/mime.go
  5@@ -5,7 +5,8 @@
  6 type ContentType = string
  7 
  8 const (
  9-	TextHTML ContentType = "text/html"
 10+	TextHTML        ContentType = "text/html"
 11+	ApplicationGZip ContentType = "application/gzip"
 12 )
 13 
 14 func Html(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
 15@@ -14,9 +15,17 @@ 		next(w, r)
 16 	}
 17 }
 18 
 19+func SetFileName(w http.ResponseWriter, name string) {
 20+	h := "inline; filename=\"" + name + "\""
 21+	w.Header().Add("Content-Disposition", h)
 22+}
 23+
 24 func SetHTML(w http.ResponseWriter) {
 25 	SetMIME(w, TextHTML)
 26+}
 27 
 28+func SetGZip(w http.ResponseWriter) {
 29+	SetMIME(w, ApplicationGZip)
 30 }
 31 
 32 func SetMIME(w http.ResponseWriter, mime ContentType) {
 33diff --git a/pkg/git/git.go b/pkg/git/git.go
 34index b725cd875a2615e043e4b15cad5b6bfc98860a85..591fafb069b141c9e7089f6b6982349b396feef4 100644
 35--- a/pkg/git/git.go
 36+++ b/pkg/git/git.go
 37@@ -1,9 +1,13 @@
 38 package git
 39 
 40 import (
 41+	"archive/tar"
 42 	"errors"
 43 	"fmt"
 44 	"io"
 45+	"io/fs"
 46+	"path"
 47+	"time"
 48 
 49 	"github.com/go-git/go-git/v5"
 50 	"github.com/go-git/go-git/v5/plumbing"
 51@@ -25,6 +29,13 @@
 52 		ref plumbing.Hash
 53 		// this is setRef when ref is setRef
 54 		setRef bool
 55+	}
 56+	infoWrapper struct {
 57+		name    string
 58+		size    int64
 59+		mode    fs.FileMode
 60+		modTime time.Time
 61+		isDir   bool
 62 	}
 63 )
 64 
 65@@ -213,3 +224,110 @@ 	} else {
 66 		return "Binary file", nil
 67 	}
 68 }
 69+
 70+func (g *GitRepository) WriteTar(w io.Writer, prefix string) error {
 71+	tw := tar.NewWriter(w)
 72+	defer tw.Close()
 73+
 74+	tree, err := g.Tree("")
 75+	if err != nil {
 76+		return err
 77+	}
 78+
 79+	walker := object.NewTreeWalker(tree, true, nil)
 80+	defer walker.Close()
 81+
 82+	name, entry, err := walker.Next()
 83+	for ; err == nil; name, entry, err = walker.Next() {
 84+		info, err := newInfoWrapper(name, prefix, &entry, tree)
 85+		if err != nil {
 86+			return err
 87+		}
 88+
 89+		header, err := tar.FileInfoHeader(info, "")
 90+		if err != nil {
 91+			return err
 92+		}
 93+
 94+		err = tw.WriteHeader(header)
 95+		if err != nil {
 96+			return err
 97+		}
 98+
 99+		if !info.IsDir() {
100+			c, err := g.FileContent(name)
101+			if err != nil {
102+				return err
103+			}
104+
105+			_, err = tw.Write([]byte(c))
106+			if err != nil {
107+				return err
108+			}
109+		}
110+	}
111+
112+	return nil
113+}
114+
115+func newInfoWrapper(
116+	filename string,
117+	prefix string,
118+	entry *object.TreeEntry,
119+	tree *object.Tree,
120+) (*infoWrapper, error) {
121+	var (
122+		size  int64
123+		mode  fs.FileMode
124+		isDir bool
125+	)
126+
127+	if entry.Mode.IsFile() {
128+		file, err := tree.TreeEntryFile(entry)
129+		if err != nil {
130+			return nil, err
131+		}
132+		mode = fs.FileMode(file.Mode)
133+
134+		size, err = tree.Size(filename)
135+		if err != nil {
136+			return nil, err
137+		}
138+	} else {
139+		isDir = true
140+		mode = fs.ModeDir | fs.ModePerm
141+	}
142+
143+	fullname := path.Join(prefix, filename)
144+	return &infoWrapper{
145+		name:    fullname,
146+		size:    size,
147+		mode:    mode,
148+		modTime: time.Unix(0, 0),
149+		isDir:   isDir,
150+	}, nil
151+}
152+
153+func (i *infoWrapper) Name() string {
154+	return i.name
155+}
156+
157+func (i *infoWrapper) Size() int64 {
158+	return i.size
159+}
160+
161+func (i *infoWrapper) Mode() fs.FileMode {
162+	return i.mode
163+}
164+
165+func (i *infoWrapper) ModTime() time.Time {
166+	return i.modTime
167+}
168+
169+func (i *infoWrapper) IsDir() bool {
170+	return i.isDir
171+}
172+
173+func (i *infoWrapper) Sys() any {
174+	return nil
175+}
176diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
177index 25505ba4c8ebfc520f69f9e21b87eef41752cd66..aed991747bb2e3d5054d2a0921321de6388f48d9 100644
178--- a/pkg/handler/git/handler.go
179+++ b/pkg/handler/git/handler.go
180@@ -2,10 +2,13 @@ package git
181 
182 import (
183 	"bytes"
184+	"fmt"
185 	"io"
186+	"log/slog"
187 	"net/http"
188 	"os"
189 	"path/filepath"
190+	"strings"
191 
192 	"git.gabrielgio.me/cerrado/pkg/ext"
193 	"git.gabrielgio.me/cerrado/pkg/service"
194@@ -36,6 +39,7 @@ 		GetFileContent(name, ref, path string) (string, error)
195 		GetAbout(name string) (string, error)
196 		ListTags(name string) ([]*plumbing.Reference, error)
197 		ListBranches(name string) ([]*plumbing.Reference, error)
198+		WriteTarGZip(w io.Writer, name, ref, filename string) error
199 	}
200 
201 	configurationRepository interface {
202@@ -81,6 +85,38 @@ 		Respositories: repos,
203 		About:         bs,
204 	}
205 	templates.WritePageTemplate(w, gitList)
206+	return nil
207+}
208+
209+func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
210+	ext.SetGZip(w)
211+	name := r.PathValue("name")
212+	refs := r.PathValue("refs")
213+	ref := strings.TrimSuffix(refs, ".tar.gz")
214+
215+	// TODO: remove it once we can support more than gzip
216+	if !strings.HasSuffix(refs, ".tar.gz") {
217+		ext.NotFound(w)
218+		return nil
219+	}
220+
221+	filenameWithExt := fmt.Sprintf("%s-%s.tar.gz", name, ref)
222+	ext.SetFileName(w, filenameWithExt)
223+	filename := fmt.Sprintf("%s-%s", name, ref)
224+
225+	// writing to a buffer so we can run all the process before writing error
226+	var buf bytes.Buffer
227+	err := g.gitService.WriteTarGZip(&buf, name, ref, filename)
228+	if err != nil {
229+		return err
230+	}
231+
232+	// since that has write to w it cannot return a error.
233+	_, err = io.Copy(w, &buf)
234+	if err != nil {
235+		slog.Error("Error copying buffer", "error", err)
236+	}
237+
238 	return nil
239 }
240 
241diff --git a/pkg/handler/router.go b/pkg/handler/router.go
242index c8f89840effc9263911f7afc73b02722436b36f9..2293ab662f2e92a23bc3d3c953413ad00489cd58 100644
243--- a/pkg/handler/router.go
244+++ b/pkg/handler/router.go
245@@ -41,6 +41,7 @@ 	mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs)
246 	mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree)
247 	mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob)
248 	mux.HandleFunc("/{name}/log/{ref}/", gitHandler.Log)
249+	mux.HandleFunc("/{name}/archive/{refs...}", gitHandler.Archive)
250 	mux.HandleFunc("/config", configHandler)
251 	mux.HandleFunc("/about", aboutHandler.About)
252 	mux.HandleFunc("/", gitHandler.List)
253diff --git a/pkg/service/git.go b/pkg/service/git.go
254index 6bb6e9ebb7739ae025c124766ac402d19c429aad..cbee90a375721e07a0efea8b2653aa9a711d87ed 100644
255--- a/pkg/service/git.go
256+++ b/pkg/service/git.go
257@@ -1,7 +1,9 @@
258 package service
259 
260 import (
261+	"compress/gzip"
262 	"errors"
263+	"io"
264 	"log/slog"
265 
266 	"git.gabrielgio.me/cerrado/pkg/config"
267@@ -90,6 +92,29 @@ 	if err != nil {
268 		return nil, err
269 	}
270 	return repo.Commits(count)
271+}
272+
273+func (g *GitService) WriteTarGZip(w io.Writer, name, ref string, filename string) error {
274+	r := g.configRepo.GetByName(name)
275+	if r == nil {
276+		return RepositoryNotFoundErr
277+	}
278+
279+	repo, err := git.OpenRepository(r.Path)
280+	if err != nil {
281+		return err
282+	}
283+
284+	err = repo.SetRef(ref)
285+	if err != nil {
286+		return err
287+	}
288+
289+	gw := gzip.NewWriter(w)
290+	defer gw.Close()
291+
292+	return repo.WriteTar(gw, filename)
293+
294 }
295 
296 func (g *GitService) GetTree(name, ref, path string) (*object.Tree, error) {
297diff --git a/templates/gititemrefs.qtpl b/templates/gititemrefs.qtpl
298index ff1561b7626970689ef318510c4610dbf20d5623..624408297c9680822ebf314fd7fb955e823ab606 100644
299--- a/templates/gititemrefs.qtpl
300+++ b/templates/gititemrefs.qtpl
301@@ -21,6 +21,7 @@            {%s t.Name().Short() %}
302           </div>
303           <div class="col-8">
304             <div class="float-end">
305+              <a href="/{%s name %}/archive/{%s t.Name().Short() %}.tar.gz">tar.gz</a>
306               <a href="/{%s name %}/tree/{%s t.Name().Short() %}/">tree</a>
307               <a href="/{%s name %}/log/{%s t.Name().Short() %}/">log</a>
308             </div>
309@@ -41,6 +42,7 @@            {%s b.Name().Short() %}
310           </div>
311           <div class="col-8">
312             <div class="float-end">
313+              <a href="/{%s name %}/archive/{%s b.Name().Short() %}.tar.gz">tar.gz</a>
314               <a href="/{%s name %}/tree/{%s b.Name().Short() %}/">tree</a>
315               <a href="/{%s name %}/log/{%s b.Name().Short() %}/">log</a>
316             </div>
317diff --git a/templates/gititemrefs.qtpl.go b/templates/gititemrefs.qtpl.go
318index b00736ecb57ca6d0fca34676cb9e455591c92d7a..da9bfe757f4d6bc91fb3d53c65dc2cd0faa36074 100644
319--- a/templates/gititemrefs.qtpl.go
320+++ b/templates/gititemrefs.qtpl.go
321@@ -90,113 +90,131 @@               <a href="/`)
322 //line gititemrefs.qtpl:24
323 			qw422016.E().S(name)
324 //line gititemrefs.qtpl:24
325-			qw422016.N().S(`/tree/`)
326+			qw422016.N().S(`/archive/`)
327 //line gititemrefs.qtpl:24
328 			qw422016.E().S(t.Name().Short())
329 //line gititemrefs.qtpl:24
330-			qw422016.N().S(`/">tree</a>
331+			qw422016.N().S(`.tar.gz">tar.gz</a>
332               <a href="/`)
333 //line gititemrefs.qtpl:25
334 			qw422016.E().S(name)
335 //line gititemrefs.qtpl:25
336-			qw422016.N().S(`/log/`)
337+			qw422016.N().S(`/tree/`)
338 //line gititemrefs.qtpl:25
339 			qw422016.E().S(t.Name().Short())
340 //line gititemrefs.qtpl:25
341+			qw422016.N().S(`/">tree</a>
342+              <a href="/`)
343+//line gititemrefs.qtpl:26
344+			qw422016.E().S(name)
345+//line gititemrefs.qtpl:26
346+			qw422016.N().S(`/log/`)
347+//line gititemrefs.qtpl:26
348+			qw422016.E().S(t.Name().Short())
349+//line gititemrefs.qtpl:26
350 			qw422016.N().S(`/">log</a>
351             </div>
352           </div>
353       </div>
354       `)
355-//line gititemrefs.qtpl:29
356+//line gititemrefs.qtpl:30
357 		}
358-//line gititemrefs.qtpl:29
359+//line gititemrefs.qtpl:30
360 		qw422016.N().S(`
361     </div>
362     `)
363-//line gititemrefs.qtpl:31
364+//line gititemrefs.qtpl:32
365 	} else {
366-//line gititemrefs.qtpl:31
367+//line gititemrefs.qtpl:32
368 		qw422016.N().S(`
369         <p> No tags </p>
370     `)
371-//line gititemrefs.qtpl:33
372+//line gititemrefs.qtpl:34
373 	}
374-//line gititemrefs.qtpl:33
375+//line gititemrefs.qtpl:34
376 	qw422016.N().S(`
377   </div>
378   <div class="col-md-4">
379     <div class="event-list">
380       `)
381-//line gititemrefs.qtpl:37
382+//line gititemrefs.qtpl:38
383 	for _, b := range g.Branches {
384-//line gititemrefs.qtpl:37
385+//line gititemrefs.qtpl:38
386 		qw422016.N().S(`
387       <div class="row event">
388           <div class="col-4">
389            `)
390-//line gititemrefs.qtpl:40
391+//line gititemrefs.qtpl:41
392 		qw422016.E().S(b.Name().Short())
393-//line gititemrefs.qtpl:40
394+//line gititemrefs.qtpl:41
395 		qw422016.N().S(`
396           </div>
397           <div class="col-8">
398             <div class="float-end">
399               <a href="/`)
400-//line gititemrefs.qtpl:44
401+//line gititemrefs.qtpl:45
402 		qw422016.E().S(name)
403-//line gititemrefs.qtpl:44
404+//line gititemrefs.qtpl:45
405+		qw422016.N().S(`/archive/`)
406+//line gititemrefs.qtpl:45
407+		qw422016.E().S(b.Name().Short())
408+//line gititemrefs.qtpl:45
409+		qw422016.N().S(`.tar.gz">tar.gz</a>
410+              <a href="/`)
411+//line gititemrefs.qtpl:46
412+		qw422016.E().S(name)
413+//line gititemrefs.qtpl:46
414 		qw422016.N().S(`/tree/`)
415-//line gititemrefs.qtpl:44
416+//line gititemrefs.qtpl:46
417 		qw422016.E().S(b.Name().Short())
418-//line gititemrefs.qtpl:44
419+//line gititemrefs.qtpl:46
420 		qw422016.N().S(`/">tree</a>
421               <a href="/`)
422-//line gititemrefs.qtpl:45
423+//line gititemrefs.qtpl:47
424 		qw422016.E().S(name)
425-//line gititemrefs.qtpl:45
426+//line gititemrefs.qtpl:47
427 		qw422016.N().S(`/log/`)
428-//line gititemrefs.qtpl:45
429+//line gititemrefs.qtpl:47
430 		qw422016.E().S(b.Name().Short())
431-//line gititemrefs.qtpl:45
432+//line gititemrefs.qtpl:47
433 		qw422016.N().S(`/">log</a>
434             </div>
435           </div>
436       </div>
437       `)
438-//line gititemrefs.qtpl:49
439+//line gititemrefs.qtpl:51
440 	}
441-//line gititemrefs.qtpl:49
442+//line gititemrefs.qtpl:51
443 	qw422016.N().S(`
444     </div>
445   </div>
446 </div>
447 `)
448-//line gititemrefs.qtpl:53
449+//line gititemrefs.qtpl:55
450 }
451 
452-//line gititemrefs.qtpl:53
453+//line gititemrefs.qtpl:55
454 func (g *GitItemRefsPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
455-//line gititemrefs.qtpl:53
456+//line gititemrefs.qtpl:55
457 	qw422016 := qt422016.AcquireWriter(qq422016)
458-//line gititemrefs.qtpl:53
459+//line gititemrefs.qtpl:55
460 	g.StreamGitContent(qw422016, name, ref)
461-//line gititemrefs.qtpl:53
462+//line gititemrefs.qtpl:55
463 	qt422016.ReleaseWriter(qw422016)
464-//line gititemrefs.qtpl:53
465+//line gititemrefs.qtpl:55
466 }
467 
468-//line gititemrefs.qtpl:53
469+//line gititemrefs.qtpl:55
470 func (g *GitItemRefsPage) GitContent(name, ref string) string {
471-//line gititemrefs.qtpl:53
472+//line gititemrefs.qtpl:55
473 	qb422016 := qt422016.AcquireByteBuffer()
474-//line gititemrefs.qtpl:53
475+//line gititemrefs.qtpl:55
476 	g.WriteGitContent(qb422016, name, ref)
477-//line gititemrefs.qtpl:53
478+//line gititemrefs.qtpl:55
479 	qs422016 := string(qb422016.B)
480-//line gititemrefs.qtpl:53
481+//line gititemrefs.qtpl:55
482 	qt422016.ReleaseByteBuffer(qb422016)
483-//line gititemrefs.qtpl:53
484+//line gititemrefs.qtpl:55
485 	return qs422016
486-//line gititemrefs.qtpl:53
487+//line gititemrefs.qtpl:55
488 }
489diff --git a/templates/gititemsummary.qtpl b/templates/gititemsummary.qtpl
490index e3d3a0828874c51b78a70cc172d34a7fada73a1e..06a785ad74913ce89927a32daff49b18529e9159 100644
491--- a/templates/gititemsummary.qtpl
492+++ b/templates/gititemsummary.qtpl
493@@ -23,6 +23,7 @@            {%s t.Name().Short() %}
494           </div>
495           <div class="col-8">
496             <div class="float-end">
497+              <a href="/{%s name %}/archive/{%s t.Name().Short() %}.tar.gz">tar.gz</a>
498               <a href="/{%s name %}/tree/{%s t.Name().Short() %}/">tree</a>
499               <a href="/{%s name %}/log/{%s t.Name().Short() %}/">log</a>
500             </div>
501diff --git a/templates/gititemsummary.qtpl.go b/templates/gititemsummary.qtpl.go
502index ad025f7e8a8343850615d1c5e27c1f2cbf89d68c..4e48efd0da70a6301114cfd65ef9929f96804953 100644
503--- a/templates/gititemsummary.qtpl.go
504+++ b/templates/gititemsummary.qtpl.go
505@@ -94,83 +94,92 @@               <a href="/`)
506 //line gititemsummary.qtpl:26
507 			qw422016.E().S(name)
508 //line gititemsummary.qtpl:26
509-			qw422016.N().S(`/tree/`)
510+			qw422016.N().S(`/archive/`)
511 //line gititemsummary.qtpl:26
512 			qw422016.E().S(t.Name().Short())
513 //line gititemsummary.qtpl:26
514-			qw422016.N().S(`/">tree</a>
515+			qw422016.N().S(`.tar.gz">tar.gz</a>
516               <a href="/`)
517 //line gititemsummary.qtpl:27
518 			qw422016.E().S(name)
519 //line gititemsummary.qtpl:27
520-			qw422016.N().S(`/log/`)
521+			qw422016.N().S(`/tree/`)
522 //line gititemsummary.qtpl:27
523 			qw422016.E().S(t.Name().Short())
524 //line gititemsummary.qtpl:27
525+			qw422016.N().S(`/">tree</a>
526+              <a href="/`)
527+//line gititemsummary.qtpl:28
528+			qw422016.E().S(name)
529+//line gititemsummary.qtpl:28
530+			qw422016.N().S(`/log/`)
531+//line gititemsummary.qtpl:28
532+			qw422016.E().S(t.Name().Short())
533+//line gititemsummary.qtpl:28
534 			qw422016.N().S(`/">log</a>
535             </div>
536           </div>
537       </div>
538       `)
539-//line gititemsummary.qtpl:31
540+//line gititemsummary.qtpl:32
541 		}
542-//line gititemsummary.qtpl:31
543+//line gititemsummary.qtpl:32
544 		qw422016.N().S(`
545     </div>
546     `)
547-//line gititemsummary.qtpl:33
548+//line gititemsummary.qtpl:34
549 	} else {
550-//line gititemsummary.qtpl:33
551+//line gititemsummary.qtpl:34
552 		qw422016.N().S(`
553         <p> No tags </p>
554     `)
555-//line gititemsummary.qtpl:35
556+//line gititemsummary.qtpl:36
557 	}
558-//line gititemsummary.qtpl:35
559+//line gititemsummary.qtpl:36
560 	qw422016.N().S(`
561   </div>
562   <div class="col-md-4">
563     <div class="event-list">
564       `)
565-//line gititemsummary.qtpl:39
566+//line gititemsummary.qtpl:40
567 	for _, b := range g.Branches {
568-//line gititemsummary.qtpl:39
569+//line gititemsummary.qtpl:40
570 		qw422016.N().S(`
571       <div class="row event">
572           <div class="col-4">
573            `)
574-//line gititemsummary.qtpl:42
575+//line gititemsummary.qtpl:43
576 		qw422016.E().S(b.Name().Short())
577-//line gititemsummary.qtpl:42
578+//line gititemsummary.qtpl:43
579 		qw422016.N().S(`
580           </div>
581           <div class="col-8">
582             <div class="float-end">
583               <a href="/`)
584-//line gititemsummary.qtpl:46
585+//line gititemsummary.qtpl:47
586 		qw422016.E().S(name)
587-//line gititemsummary.qtpl:46
588+//line gititemsummary.qtpl:47
589 		qw422016.N().S(`/tree/`)
590-//line gititemsummary.qtpl:46
591+//line gititemsummary.qtpl:47
592 		qw422016.E().S(b.Name().Short())
593-//line gititemsummary.qtpl:46
594+//line gititemsummary.qtpl:47
595 		qw422016.N().S(`/">tree</a>
596               <a href="/`)
597-//line gititemsummary.qtpl:47
598+//line gititemsummary.qtpl:48
599 		qw422016.E().S(name)
600-//line gititemsummary.qtpl:47
601+//line gititemsummary.qtpl:48
602 		qw422016.N().S(`/log/`)
603-//line gititemsummary.qtpl:47
604+//line gititemsummary.qtpl:48
605 		qw422016.E().S(b.Name().Short())
606-//line gititemsummary.qtpl:47
607+//line gititemsummary.qtpl:48
608 		qw422016.N().S(`/">log</a>
609             </div>
610           </div>
611       </div>
612       `)
613-//line gititemsummary.qtpl:51
614+//line gititemsummary.qtpl:52
615 	}
616-//line gititemsummary.qtpl:51
617+//line gititemsummary.qtpl:52
618 	qw422016.N().S(`
619     </div>
620   </div>
621@@ -178,70 +187,70 @@ </div>
622 <div class="row">
623   <div class="event-list">
624     `)
625-//line gititemsummary.qtpl:57
626+//line gititemsummary.qtpl:58
627 	for _, c := range g.Commits {
628-//line gititemsummary.qtpl:57
629+//line gititemsummary.qtpl:58
630 		qw422016.N().S(`
631     <div class="row event">
632         <div class="col-xxl-2">
633          `)
634-//line gititemsummary.qtpl:60
635+//line gititemsummary.qtpl:61
636 		qw422016.E().S(TimeFormat(c.Committer.When))
637-//line gititemsummary.qtpl:60
638+//line gititemsummary.qtpl:61
639 		qw422016.N().S(`
640         </div>
641         <div class="col-xxl-7 code-view">
642          <pre>`)
643-//line gititemsummary.qtpl:63
644+//line gititemsummary.qtpl:64
645 		qw422016.E().S(c.Message)
646-//line gititemsummary.qtpl:63
647+//line gititemsummary.qtpl:64
648 		qw422016.N().S(`</pre>
649         </div>
650         <div class="col-xxl-3">
651          <small>`)
652-//line gititemsummary.qtpl:66
653+//line gititemsummary.qtpl:67
654 		qw422016.E().S(c.Committer.Name)
655-//line gititemsummary.qtpl:66
656+//line gititemsummary.qtpl:67
657 		qw422016.N().S(` &lt;`)
658-//line gititemsummary.qtpl:66
659+//line gititemsummary.qtpl:67
660 		qw422016.E().S(c.Committer.Email)
661-//line gititemsummary.qtpl:66
662+//line gititemsummary.qtpl:67
663 		qw422016.N().S(`&gt;</small>
664         </div>
665     </div>
666     `)
667-//line gititemsummary.qtpl:69
668+//line gititemsummary.qtpl:70
669 	}
670-//line gititemsummary.qtpl:69
671+//line gititemsummary.qtpl:70
672 	qw422016.N().S(`
673   </div>
674 </div>
675 `)
676-//line gititemsummary.qtpl:72
677+//line gititemsummary.qtpl:73
678 }
679 
680-//line gititemsummary.qtpl:72
681+//line gititemsummary.qtpl:73
682 func (g *GitItemSummaryPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
683-//line gititemsummary.qtpl:72
684+//line gititemsummary.qtpl:73
685 	qw422016 := qt422016.AcquireWriter(qq422016)
686-//line gititemsummary.qtpl:72
687+//line gititemsummary.qtpl:73
688 	g.StreamGitContent(qw422016, name, ref)
689-//line gititemsummary.qtpl:72
690+//line gititemsummary.qtpl:73
691 	qt422016.ReleaseWriter(qw422016)
692-//line gititemsummary.qtpl:72
693+//line gititemsummary.qtpl:73
694 }
695 
696-//line gititemsummary.qtpl:72
697+//line gititemsummary.qtpl:73
698 func (g *GitItemSummaryPage) GitContent(name, ref string) string {
699-//line gititemsummary.qtpl:72
700+//line gititemsummary.qtpl:73
701 	qb422016 := qt422016.AcquireByteBuffer()
702-//line gititemsummary.qtpl:72
703+//line gititemsummary.qtpl:73
704 	g.WriteGitContent(qb422016, name, ref)
705-//line gititemsummary.qtpl:72
706+//line gititemsummary.qtpl:73
707 	qs422016 := string(qb422016.B)
708-//line gititemsummary.qtpl:72
709+//line gititemsummary.qtpl:73
710 	qt422016.ReleaseByteBuffer(qb422016)
711-//line gititemsummary.qtpl:72
712+//line gititemsummary.qtpl:73
713 	return qs422016
714-//line gititemsummary.qtpl:72
715+//line gititemsummary.qtpl:73
716 }