1diff --git a/README.md b/README.md
2index e49e6bc371427c1555d6339562a74b0b66f0bf80..bd5e69eb48b984a8f2648b4e5474352d62a015fa 100644
3--- a/README.md
4+++ b/README.md
5@@ -23,8 +23,6 @@ To run the project you just need to do a make run.
6
7 ### TODO
8
9-- Add path to tree view
10- - Fix href with extra slash
11 - Add message to tags
12 - Add link to tar browser from commit page
13 - Add patch to the commit page
14diff --git a/pkg/git/git.go b/pkg/git/git.go
15index 6b58d35cd3208ba2d12937ad1b31baca0c8ad78b..7341c1bc3f04e6d91f17ac877bb523afd5ee19e2 100644
16--- a/pkg/git/git.go
17+++ b/pkg/git/git.go
18@@ -38,6 +38,11 @@ mode fs.FileMode
19 modTime time.Time
20 isDir bool
21 }
22+
23+ TagReference struct {
24+ ref *plumbing.Reference
25+ tag *object.Tag
26+ }
27 )
28
29 func OpenRepository(dir string) (*GitRepository, error) {
30@@ -119,18 +124,31 @@ func (g *GitRepository) Head() (*plumbing.Reference, error) {
31 return g.repository.Head()
32 }
33
34-func (g *GitRepository) Tags() ([]*plumbing.Reference, error) {
35- ti, err := g.repository.Tags()
36+func (g *GitRepository) Tags() ([]*TagReference, error) {
37+ iter, err := g.repository.Tags()
38 if err != nil {
39 return nil, err
40 }
41
42- tags := []*plumbing.Reference{}
43- err = ti.ForEach(func(t *plumbing.Reference) error {
44- tags = append(tags, t)
45+ tags := make([]*TagReference, 0)
46+
47+ if err := iter.ForEach(func(ref *plumbing.Reference) error {
48+ obj, err := g.repository.TagObject(ref.Hash())
49+ switch err {
50+ case nil:
51+ tags = append(tags, &TagReference{
52+ ref: ref,
53+ tag: obj,
54+ })
55+ case plumbing.ErrObjectNotFound:
56+ tags = append(tags, &TagReference{
57+ ref: ref,
58+ })
59+ default:
60+ return err
61+ }
62 return nil
63- })
64- if err != nil {
65+ }); err != nil {
66 return nil, err
67 }
68
69@@ -361,3 +379,19 @@
70 func (i *infoWrapper) Sys() any {
71 return nil
72 }
73+
74+func (t *TagReference) HashString() string {
75+ return t.ref.Hash().String()
76+}
77+
78+func (t *TagReference) ShortName() string {
79+ return t.ref.Name().Short()
80+}
81+
82+func (t *TagReference) Message() string {
83+ if t.tag != nil {
84+ return t.tag.Message
85+ }
86+ return ""
87+
88+}
89diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
90index 5e5012263de12db42cb7457a21a2fad99ee50b89..9549f0e414d81baa03f6c1e385ea5c872c0e31f5 100644
91--- a/pkg/handler/git/handler.go
92+++ b/pkg/handler/git/handler.go
93@@ -18,7 +18,6 @@ "github.com/alecthomas/chroma/v2"
94 "github.com/alecthomas/chroma/v2/formatters/html"
95 "github.com/alecthomas/chroma/v2/lexers"
96 "github.com/alecthomas/chroma/v2/styles"
97- "github.com/go-git/go-git/v5/plumbing"
98 "github.com/go-git/go-git/v5/plumbing/object"
99 "github.com/gomarkdown/markdown"
100 markdownhtml "github.com/gomarkdown/markdown/html"
101@@ -27,30 +26,16 @@ )
102
103 type (
104 GitHandler struct {
105- gitService gitService
106+ gitService *service.GitService
107 readmePath string
108 }
109
110- gitService interface {
111- ListRepositories() ([]*service.Repository, error)
112- ListCommits(name string, ref string, count int) ([]*object.Commit, error)
113- LastCommit(name string, ref string) (*object.Commit, error)
114- GetHead(name string) (*plumbing.Reference, error)
115- GetTree(name, ref, path string) (*object.Tree, error)
116- IsBinary(name, ref, path string) (bool, error)
117- GetFileContent(name, ref, path string) ([]byte, error)
118- GetAbout(name string) ([]byte, error)
119- ListTags(name string) ([]*plumbing.Reference, error)
120- ListBranches(name string) ([]*plumbing.Reference, error)
121- WriteTarGZip(w io.Writer, name, ref, prefix string) error
122- }
123-
124 configurationRepository interface {
125 GetRootReadme() string
126 }
127 )
128
129-func NewGitHandler(gitService gitService, confRepo configurationRepository) *GitHandler {
130+func NewGitHandler(gitService *service.GitService, confRepo configurationRepository) *GitHandler {
131 return &GitHandler{
132 gitService: gitService,
133 readmePath: confRepo.GetRootReadme(),
134diff --git a/pkg/service/git.go b/pkg/service/git.go
135index df4e3aa36917ce74895247c02a4f2a4f39f868b8..b368f0cc321b64d593bd2399e86c5de8d43c77f7 100644
136--- a/pkg/service/git.go
137+++ b/pkg/service/git.go
138@@ -217,7 +217,7 @@
139 return file, nil
140 }
141
142-func (g *GitService) ListTags(name string) ([]*plumbing.Reference, error) {
143+func (g *GitService) ListTags(name string) ([]*git.TagReference, error) {
144 r := g.configRepo.GetByName(name)
145 if r == nil {
146 return nil, ErrRepositoryNotFound
147diff --git a/pkg/u/file.go b/pkg/u/file.go
148index fafe0fb00f4b30262594af5ab6b32873515301bb..5010b3eef2a8ea0c37b6bbeb23b7ab721197a93c 100644
149--- a/pkg/u/file.go
150+++ b/pkg/u/file.go
151@@ -4,7 +4,7 @@ import (
152 "errors"
153 "log/slog"
154 "os"
155- "path/filepath"
156+ "strings"
157 )
158
159 func FileExist(filename string) bool {
160@@ -22,21 +22,43 @@ }
161 }
162
163 // This is just a slin wraper to make easier to compose path in the template
164-type Pathing string
165+type Pathing struct {
166+ sb strings.Builder
167+}
168
169-func Root() Pathing {
170- return "/"
171+func NewPathing() *Pathing {
172+ return &Pathing{}
173 }
174
175-func (s Pathing) AddPath(p string) Pathing {
176- return Pathing(filepath.Join(string(s), p))
177+func (s *Pathing) AddPath(p string) *Pathing {
178+ if len(p) == 0 {
179+ return s
180+ }
181+
182+ // if it has trailing / remove it
183+ if p[len(p)-1] == '/' {
184+ p = p[:len(p)-1]
185+ return s.AddPath(p)
186+ }
187+
188+ // if it does not have it so add
189+ if p[0] == '/' {
190+ s.sb.WriteString(p)
191+ } else {
192+ s.sb.WriteString("/" + p)
193+ }
194+
195+ return s
196 }
197
198-func (s Pathing) AddPaths(p []string) Pathing {
199- f := filepath.Join(p...)
200- return Pathing(filepath.Join(string(s), f))
201+func (s *Pathing) AddPaths(p []string) *Pathing {
202+ for _, v := range p {
203+ s.AddPath(v)
204+ }
205+
206+ return s
207 }
208
209-func (s Pathing) Done() string {
210- return string(s)
211+func (s *Pathing) Done() string {
212+ return s.sb.String()
213 }
214diff --git a/pkg/u/file_test.go b/pkg/u/file_test.go
215new file mode 100644
216index 0000000000000000000000000000000000000000..b7d69752f1a0d9d7e6b0e7c828a417e1f8162b15
217--- /dev/null
218+++ b/pkg/u/file_test.go
219@@ -0,0 +1,59 @@
220+// go:build unit
221+package u
222+
223+import "testing"
224+
225+func TestPathing(t *testing.T) {
226+ testCases := []struct {
227+ name string
228+ in []any
229+ out string
230+ }{
231+ {
232+ name: "root",
233+ in: []any{},
234+ out: "",
235+ },
236+ {
237+ name: "empty",
238+ in: []any{
239+ "/",
240+ []string{"/", "/"},
241+ "/",
242+ []string{"/"},
243+ },
244+ out: "",
245+ },
246+ {
247+ name: "empty",
248+ in: []any{
249+ "usr",
250+ []string{"/share/", "lib"},
251+ "/demo",
252+ []string{"/out//"},
253+ },
254+ out: "/usr/share/lib/demo/out",
255+ },
256+ }
257+
258+ for _, tc := range testCases {
259+ t.Run(tc.name, func(t *testing.T) {
260+ r := NewPathing()
261+
262+ for _, v := range tc.in {
263+ switch s := v.(type) {
264+ case string:
265+ r = r.AddPath(s)
266+ case []string:
267+ r = r.AddPaths(s)
268+ }
269+ }
270+
271+ path := r.Done()
272+ if tc.out != path {
273+ t.Errorf("String mismatch: wanted %s got %s", tc.out, path)
274+ }
275+
276+ })
277+ }
278+}
279diff --git a/templates/gititemrefs.qtpl b/templates/gititemrefs.qtpl
280index 624408297c9680822ebf314fd7fb955e823ab606..9f4f74b4c0e7b99f398e79238b3f049b8847b62f 100644
281--- a/templates/gititemrefs.qtpl
282+++ b/templates/gititemrefs.qtpl
283@@ -1,8 +1,9 @@
284 {% import "github.com/go-git/go-git/v5/plumbing" %}
285+{% import "git.gabrielgio.me/cerrado/pkg/git" %}
286
287 {% code
288 type GitItemRefsPage struct {
289- Tags []*plumbing.Reference
290+ Tags []*git.TagReference
291 Branches []*plumbing.Reference
292 }
293 %}
294@@ -12,26 +13,7 @@
295 {% func (g *GitItemRefsPage) GitContent(name, ref string) %}
296 <div class="row">
297 <div class="col-md-8">
298- {% if len(g.Tags) > 0 %}
299- <div class="event-list">
300- {% for _, t := range g.Tags %}
301- <div class="row event me-md-2">
302- <div class="col-4">
303- {%s t.Name().Short() %}
304- </div>
305- <div class="col-8">
306- <div class="float-end">
307- <a href="/{%s name %}/archive/{%s t.Name().Short() %}.tar.gz">tar.gz</a>
308- <a href="/{%s name %}/tree/{%s t.Name().Short() %}/">tree</a>
309- <a href="/{%s name %}/log/{%s t.Name().Short() %}/">log</a>
310- </div>
311- </div>
312- </div>
313- {% endfor %}
314- </div>
315- {% else %}
316- <p> No tags </p>
317- {% endif %}
318+ {%= ListTags(name, g.Tags) %}
319 </div>
320 <div class="col-md-4">
321 <div class="event-list">
322diff --git a/templates/gititemrefs.qtpl.go b/templates/gititemrefs.qtpl.go
323index da9bfe757f4d6bc91fb3d53c65dc2cd0faa36074..d54301de94e72454f4fc6070f0f5fa906febf1c9 100644
324--- a/templates/gititemrefs.qtpl.go
325+++ b/templates/gititemrefs.qtpl.go
326@@ -7,214 +7,154 @@
327 //line gititemrefs.qtpl:1
328 import "github.com/go-git/go-git/v5/plumbing"
329
330-//line gititemrefs.qtpl:3
331+//line gititemrefs.qtpl:2
332+import "git.gabrielgio.me/cerrado/pkg/git"
333+
334+//line gititemrefs.qtpl:4
335 import (
336 qtio422016 "io"
337
338 qt422016 "github.com/valyala/quicktemplate"
339 )
340
341-//line gititemrefs.qtpl:3
342+//line gititemrefs.qtpl:4
343 var (
344 _ = qtio422016.Copy
345 _ = qt422016.AcquireByteBuffer
346 )
347
348-//line gititemrefs.qtpl:4
349+//line gititemrefs.qtpl:5
350 type GitItemRefsPage struct {
351- Tags []*plumbing.Reference
352+ Tags []*git.TagReference
353 Branches []*plumbing.Reference
354 }
355
356-//line gititemrefs.qtpl:10
357+//line gititemrefs.qtpl:11
358 func (g *GitItemRefsPage) StreamNav(qw422016 *qt422016.Writer, name, ref string) {
359-//line gititemrefs.qtpl:10
360+//line gititemrefs.qtpl:11
361 StreamGitItemNav(qw422016, name, ref, Refs)
362-//line gititemrefs.qtpl:10
363+//line gititemrefs.qtpl:11
364 }
365
366-//line gititemrefs.qtpl:10
367+//line gititemrefs.qtpl:11
368 func (g *GitItemRefsPage) WriteNav(qq422016 qtio422016.Writer, name, ref string) {
369-//line gititemrefs.qtpl:10
370+//line gititemrefs.qtpl:11
371 qw422016 := qt422016.AcquireWriter(qq422016)
372-//line gititemrefs.qtpl:10
373+//line gititemrefs.qtpl:11
374 g.StreamNav(qw422016, name, ref)
375-//line gititemrefs.qtpl:10
376+//line gititemrefs.qtpl:11
377 qt422016.ReleaseWriter(qw422016)
378-//line gititemrefs.qtpl:10
379+//line gititemrefs.qtpl:11
380 }
381
382-//line gititemrefs.qtpl:10
383+//line gititemrefs.qtpl:11
384 func (g *GitItemRefsPage) Nav(name, ref string) string {
385-//line gititemrefs.qtpl:10
386+//line gititemrefs.qtpl:11
387 qb422016 := qt422016.AcquireByteBuffer()
388-//line gititemrefs.qtpl:10
389+//line gititemrefs.qtpl:11
390 g.WriteNav(qb422016, name, ref)
391-//line gititemrefs.qtpl:10
392+//line gititemrefs.qtpl:11
393 qs422016 := string(qb422016.B)
394-//line gititemrefs.qtpl:10
395+//line gititemrefs.qtpl:11
396 qt422016.ReleaseByteBuffer(qb422016)
397-//line gititemrefs.qtpl:10
398+//line gititemrefs.qtpl:11
399 return qs422016
400-//line gititemrefs.qtpl:10
401+//line gititemrefs.qtpl:11
402 }
403
404-//line gititemrefs.qtpl:12
405+//line gititemrefs.qtpl:13
406 func (g *GitItemRefsPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref string) {
407-//line gititemrefs.qtpl:12
408+//line gititemrefs.qtpl:13
409 qw422016.N().S(`
410 <div class="row">
411 <div class="col-md-8">
412 `)
413-//line gititemrefs.qtpl:15
414- if len(g.Tags) > 0 {
415-//line gititemrefs.qtpl:15
416- qw422016.N().S(`
417- <div class="event-list">
418- `)
419-//line gititemrefs.qtpl:17
420- for _, t := range g.Tags {
421-//line gititemrefs.qtpl:17
422- qw422016.N().S(`
423- <div class="row event me-md-2">
424- <div class="col-4">
425- `)
426-//line gititemrefs.qtpl:20
427- qw422016.E().S(t.Name().Short())
428-//line gititemrefs.qtpl:20
429- qw422016.N().S(`
430- </div>
431- <div class="col-8">
432- <div class="float-end">
433- <a href="/`)
434-//line gititemrefs.qtpl:24
435- qw422016.E().S(name)
436-//line gititemrefs.qtpl:24
437- qw422016.N().S(`/archive/`)
438-//line gititemrefs.qtpl:24
439- qw422016.E().S(t.Name().Short())
440-//line gititemrefs.qtpl:24
441- qw422016.N().S(`.tar.gz">tar.gz</a>
442- <a href="/`)
443-//line gititemrefs.qtpl:25
444- qw422016.E().S(name)
445-//line gititemrefs.qtpl:25
446- qw422016.N().S(`/tree/`)
447-//line gititemrefs.qtpl:25
448- qw422016.E().S(t.Name().Short())
449-//line gititemrefs.qtpl:25
450- qw422016.N().S(`/">tree</a>
451- <a href="/`)
452-//line gititemrefs.qtpl:26
453- qw422016.E().S(name)
454-//line gititemrefs.qtpl:26
455- qw422016.N().S(`/log/`)
456-//line gititemrefs.qtpl:26
457- qw422016.E().S(t.Name().Short())
458-//line gititemrefs.qtpl:26
459- qw422016.N().S(`/">log</a>
460- </div>
461- </div>
462- </div>
463- `)
464-//line gititemrefs.qtpl:30
465- }
466-//line gititemrefs.qtpl:30
467- qw422016.N().S(`
468- </div>
469- `)
470-//line gititemrefs.qtpl:32
471- } else {
472-//line gititemrefs.qtpl:32
473- qw422016.N().S(`
474- <p> No tags </p>
475- `)
476-//line gititemrefs.qtpl:34
477- }
478-//line gititemrefs.qtpl:34
479+//line gititemrefs.qtpl:16
480+ StreamListTags(qw422016, name, g.Tags)
481+//line gititemrefs.qtpl:16
482 qw422016.N().S(`
483 </div>
484 <div class="col-md-4">
485 <div class="event-list">
486 `)
487-//line gititemrefs.qtpl:38
488+//line gititemrefs.qtpl:20
489 for _, b := range g.Branches {
490-//line gititemrefs.qtpl:38
491+//line gititemrefs.qtpl:20
492 qw422016.N().S(`
493 <div class="row event">
494 <div class="col-4">
495 `)
496-//line gititemrefs.qtpl:41
497+//line gititemrefs.qtpl:23
498 qw422016.E().S(b.Name().Short())
499-//line gititemrefs.qtpl:41
500+//line gititemrefs.qtpl:23
501 qw422016.N().S(`
502 </div>
503 <div class="col-8">
504 <div class="float-end">
505 <a href="/`)
506-//line gititemrefs.qtpl:45
507+//line gititemrefs.qtpl:27
508 qw422016.E().S(name)
509-//line gititemrefs.qtpl:45
510+//line gititemrefs.qtpl:27
511 qw422016.N().S(`/archive/`)
512-//line gititemrefs.qtpl:45
513+//line gititemrefs.qtpl:27
514 qw422016.E().S(b.Name().Short())
515-//line gititemrefs.qtpl:45
516+//line gititemrefs.qtpl:27
517 qw422016.N().S(`.tar.gz">tar.gz</a>
518 <a href="/`)
519-//line gititemrefs.qtpl:46
520+//line gititemrefs.qtpl:28
521 qw422016.E().S(name)
522-//line gititemrefs.qtpl:46
523+//line gititemrefs.qtpl:28
524 qw422016.N().S(`/tree/`)
525-//line gititemrefs.qtpl:46
526+//line gititemrefs.qtpl:28
527 qw422016.E().S(b.Name().Short())
528-//line gititemrefs.qtpl:46
529+//line gititemrefs.qtpl:28
530 qw422016.N().S(`/">tree</a>
531 <a href="/`)
532-//line gititemrefs.qtpl:47
533+//line gititemrefs.qtpl:29
534 qw422016.E().S(name)
535-//line gititemrefs.qtpl:47
536+//line gititemrefs.qtpl:29
537 qw422016.N().S(`/log/`)
538-//line gititemrefs.qtpl:47
539+//line gititemrefs.qtpl:29
540 qw422016.E().S(b.Name().Short())
541-//line gititemrefs.qtpl:47
542+//line gititemrefs.qtpl:29
543 qw422016.N().S(`/">log</a>
544 </div>
545 </div>
546 </div>
547 `)
548-//line gititemrefs.qtpl:51
549+//line gititemrefs.qtpl:33
550 }
551-//line gititemrefs.qtpl:51
552+//line gititemrefs.qtpl:33
553 qw422016.N().S(`
554 </div>
555 </div>
556 </div>
557 `)
558-//line gititemrefs.qtpl:55
559+//line gititemrefs.qtpl:37
560 }
561
562-//line gititemrefs.qtpl:55
563+//line gititemrefs.qtpl:37
564 func (g *GitItemRefsPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
565-//line gititemrefs.qtpl:55
566+//line gititemrefs.qtpl:37
567 qw422016 := qt422016.AcquireWriter(qq422016)
568-//line gititemrefs.qtpl:55
569+//line gititemrefs.qtpl:37
570 g.StreamGitContent(qw422016, name, ref)
571-//line gititemrefs.qtpl:55
572+//line gititemrefs.qtpl:37
573 qt422016.ReleaseWriter(qw422016)
574-//line gititemrefs.qtpl:55
575+//line gititemrefs.qtpl:37
576 }
577
578-//line gititemrefs.qtpl:55
579+//line gititemrefs.qtpl:37
580 func (g *GitItemRefsPage) GitContent(name, ref string) string {
581-//line gititemrefs.qtpl:55
582+//line gititemrefs.qtpl:37
583 qb422016 := qt422016.AcquireByteBuffer()
584-//line gititemrefs.qtpl:55
585+//line gititemrefs.qtpl:37
586 g.WriteGitContent(qb422016, name, ref)
587-//line gititemrefs.qtpl:55
588+//line gititemrefs.qtpl:37
589 qs422016 := string(qb422016.B)
590-//line gititemrefs.qtpl:55
591+//line gititemrefs.qtpl:37
592 qt422016.ReleaseByteBuffer(qb422016)
593-//line gititemrefs.qtpl:55
594+//line gititemrefs.qtpl:37
595 return qs422016
596-//line gititemrefs.qtpl:55
597+//line gititemrefs.qtpl:37
598 }
599diff --git a/templates/gititemsummary.qtpl b/templates/gititemsummary.qtpl
600index ef2c534df5b1d5ab85760c7de436b83d5f041e7c..44e160474b48574d3c93773fa3780a17529aa480 100644
601--- a/templates/gititemsummary.qtpl
602+++ b/templates/gititemsummary.qtpl
603@@ -1,9 +1,10 @@
604 {% import "github.com/go-git/go-git/v5/plumbing" %}
605 {% import "github.com/go-git/go-git/v5/plumbing/object" %}
606+{% import "git.gabrielgio.me/cerrado/pkg/git" %}
607
608 {% code
609 type GitItemSummaryPage struct {
610- Tags []*plumbing.Reference
611+ Tags []*git.TagReference
612 Branches []*plumbing.Reference
613 Commits []*object.Commit
614 }
615@@ -14,26 +15,7 @@
616 {% func (g *GitItemSummaryPage) GitContent(name, ref string) %}
617 <div class="row">
618 <div class="col-md-8">
619- {% if len(g.Tags) > 0 %}
620- <div class="event-list">
621- {% for _, t := range g.Tags %}
622- <div class="row event me-md-2">
623- <div class="col-4">
624- {%s t.Name().Short() %}
625- </div>
626- <div class="col-8">
627- <div class="float-end">
628- <a href="/{%s name %}/archive/{%s t.Name().Short() %}.tar.gz">tar.gz</a>
629- <a href="/{%s name %}/tree/{%s t.Name().Short() %}/">tree</a>
630- <a href="/{%s name %}/log/{%s t.Name().Short() %}/">log</a>
631- </div>
632- </div>
633- </div>
634- {% endfor %}
635- </div>
636- {% else %}
637- <p> No tags </p>
638- {% endif %}
639+ {%= ListTags(name, g.Tags) %}
640 </div>
641 <div class="col-md-4">
642 <div class="event-list">
643diff --git a/templates/gititemsummary.qtpl.go b/templates/gititemsummary.qtpl.go
644index 570a95501376383761ed6eeaf38d4e7d25c765ad..24fed9df74108f464aa3b1685e1fa597559620fc 100644
645--- a/templates/gititemsummary.qtpl.go
646+++ b/templates/gititemsummary.qtpl.go
647@@ -10,185 +10,125 @@
648 //line gititemsummary.qtpl:2
649 import "github.com/go-git/go-git/v5/plumbing/object"
650
651-//line gititemsummary.qtpl:4
652+//line gititemsummary.qtpl:3
653+import "git.gabrielgio.me/cerrado/pkg/git"
654+
655+//line gititemsummary.qtpl:5
656 import (
657 qtio422016 "io"
658
659 qt422016 "github.com/valyala/quicktemplate"
660 )
661
662-//line gititemsummary.qtpl:4
663+//line gititemsummary.qtpl:5
664 var (
665 _ = qtio422016.Copy
666 _ = qt422016.AcquireByteBuffer
667 )
668
669-//line gititemsummary.qtpl:5
670+//line gititemsummary.qtpl:6
671 type GitItemSummaryPage struct {
672- Tags []*plumbing.Reference
673+ Tags []*git.TagReference
674 Branches []*plumbing.Reference
675 Commits []*object.Commit
676 }
677
678-//line gititemsummary.qtpl:12
679+//line gititemsummary.qtpl:13
680 func (g *GitItemSummaryPage) StreamNav(qw422016 *qt422016.Writer, name, ref string) {
681-//line gititemsummary.qtpl:12
682+//line gititemsummary.qtpl:13
683 StreamGitItemNav(qw422016, name, ref, Summary)
684-//line gititemsummary.qtpl:12
685+//line gititemsummary.qtpl:13
686 }
687
688-//line gititemsummary.qtpl:12
689+//line gititemsummary.qtpl:13
690 func (g *GitItemSummaryPage) WriteNav(qq422016 qtio422016.Writer, name, ref string) {
691-//line gititemsummary.qtpl:12
692+//line gititemsummary.qtpl:13
693 qw422016 := qt422016.AcquireWriter(qq422016)
694-//line gititemsummary.qtpl:12
695+//line gititemsummary.qtpl:13
696 g.StreamNav(qw422016, name, ref)
697-//line gititemsummary.qtpl:12
698+//line gititemsummary.qtpl:13
699 qt422016.ReleaseWriter(qw422016)
700-//line gititemsummary.qtpl:12
701+//line gititemsummary.qtpl:13
702 }
703
704-//line gititemsummary.qtpl:12
705+//line gititemsummary.qtpl:13
706 func (g *GitItemSummaryPage) Nav(name, ref string) string {
707-//line gititemsummary.qtpl:12
708+//line gititemsummary.qtpl:13
709 qb422016 := qt422016.AcquireByteBuffer()
710-//line gititemsummary.qtpl:12
711+//line gititemsummary.qtpl:13
712 g.WriteNav(qb422016, name, ref)
713-//line gititemsummary.qtpl:12
714+//line gititemsummary.qtpl:13
715 qs422016 := string(qb422016.B)
716-//line gititemsummary.qtpl:12
717+//line gititemsummary.qtpl:13
718 qt422016.ReleaseByteBuffer(qb422016)
719-//line gititemsummary.qtpl:12
720+//line gititemsummary.qtpl:13
721 return qs422016
722-//line gititemsummary.qtpl:12
723+//line gititemsummary.qtpl:13
724 }
725
726-//line gititemsummary.qtpl:14
727+//line gititemsummary.qtpl:15
728 func (g *GitItemSummaryPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref string) {
729-//line gititemsummary.qtpl:14
730+//line gititemsummary.qtpl:15
731 qw422016.N().S(`
732 <div class="row">
733 <div class="col-md-8">
734 `)
735-//line gititemsummary.qtpl:17
736- if len(g.Tags) > 0 {
737-//line gititemsummary.qtpl:17
738- qw422016.N().S(`
739- <div class="event-list">
740- `)
741-//line gititemsummary.qtpl:19
742- for _, t := range g.Tags {
743-//line gititemsummary.qtpl:19
744- qw422016.N().S(`
745- <div class="row event me-md-2">
746- <div class="col-4">
747- `)
748-//line gititemsummary.qtpl:22
749- qw422016.E().S(t.Name().Short())
750-//line gititemsummary.qtpl:22
751- qw422016.N().S(`
752- </div>
753- <div class="col-8">
754- <div class="float-end">
755- <a href="/`)
756-//line gititemsummary.qtpl:26
757- qw422016.E().S(name)
758-//line gititemsummary.qtpl:26
759- qw422016.N().S(`/archive/`)
760-//line gititemsummary.qtpl:26
761- qw422016.E().S(t.Name().Short())
762-//line gititemsummary.qtpl:26
763- qw422016.N().S(`.tar.gz">tar.gz</a>
764- <a href="/`)
765-//line gititemsummary.qtpl:27
766- qw422016.E().S(name)
767-//line gititemsummary.qtpl:27
768- qw422016.N().S(`/tree/`)
769-//line gititemsummary.qtpl:27
770- qw422016.E().S(t.Name().Short())
771-//line gititemsummary.qtpl:27
772- qw422016.N().S(`/">tree</a>
773- <a href="/`)
774-//line gititemsummary.qtpl:28
775- qw422016.E().S(name)
776-//line gititemsummary.qtpl:28
777- qw422016.N().S(`/log/`)
778-//line gititemsummary.qtpl:28
779- qw422016.E().S(t.Name().Short())
780-//line gititemsummary.qtpl:28
781- qw422016.N().S(`/">log</a>
782- </div>
783- </div>
784- </div>
785- `)
786-//line gititemsummary.qtpl:32
787- }
788-//line gititemsummary.qtpl:32
789- qw422016.N().S(`
790- </div>
791- `)
792-//line gititemsummary.qtpl:34
793- } else {
794-//line gititemsummary.qtpl:34
795- qw422016.N().S(`
796- <p> No tags </p>
797- `)
798-//line gititemsummary.qtpl:36
799- }
800-//line gititemsummary.qtpl:36
801+//line gititemsummary.qtpl:18
802+ StreamListTags(qw422016, name, g.Tags)
803+//line gititemsummary.qtpl:18
804 qw422016.N().S(`
805 </div>
806 <div class="col-md-4">
807 <div class="event-list">
808 `)
809-//line gititemsummary.qtpl:40
810+//line gititemsummary.qtpl:22
811 for _, b := range g.Branches {
812-//line gititemsummary.qtpl:40
813+//line gititemsummary.qtpl:22
814 qw422016.N().S(`
815 <div class="row event">
816 <div class="col-4">
817 `)
818-//line gititemsummary.qtpl:43
819+//line gititemsummary.qtpl:25
820 qw422016.E().S(b.Name().Short())
821-//line gititemsummary.qtpl:43
822+//line gititemsummary.qtpl:25
823 qw422016.N().S(`
824 </div>
825 <div class="col-8">
826 <div class="float-end">
827 <a href="/`)
828-//line gititemsummary.qtpl:47
829+//line gititemsummary.qtpl:29
830 qw422016.E().S(name)
831-//line gititemsummary.qtpl:47
832+//line gititemsummary.qtpl:29
833 qw422016.N().S(`/archive/`)
834-//line gititemsummary.qtpl:47
835+//line gititemsummary.qtpl:29
836 qw422016.E().S(b.Name().Short())
837-//line gititemsummary.qtpl:47
838+//line gititemsummary.qtpl:29
839 qw422016.N().S(`.tar.gz">tar.gz</a>
840 <a href="/`)
841-//line gititemsummary.qtpl:48
842+//line gititemsummary.qtpl:30
843 qw422016.E().S(name)
844-//line gititemsummary.qtpl:48
845+//line gititemsummary.qtpl:30
846 qw422016.N().S(`/tree/`)
847-//line gititemsummary.qtpl:48
848+//line gititemsummary.qtpl:30
849 qw422016.E().S(b.Name().Short())
850-//line gititemsummary.qtpl:48
851+//line gititemsummary.qtpl:30
852 qw422016.N().S(`/">tree</a>
853 <a href="/`)
854-//line gititemsummary.qtpl:49
855+//line gititemsummary.qtpl:31
856 qw422016.E().S(name)
857-//line gititemsummary.qtpl:49
858+//line gititemsummary.qtpl:31
859 qw422016.N().S(`/log/`)
860-//line gititemsummary.qtpl:49
861+//line gititemsummary.qtpl:31
862 qw422016.E().S(b.Name().Short())
863-//line gititemsummary.qtpl:49
864+//line gititemsummary.qtpl:31
865 qw422016.N().S(`/">log</a>
866 </div>
867 </div>
868 </div>
869 `)
870-//line gititemsummary.qtpl:53
871+//line gititemsummary.qtpl:35
872 }
873-//line gititemsummary.qtpl:53
874+//line gititemsummary.qtpl:35
875 qw422016.N().S(`
876 </div>
877 </div>
878@@ -196,48 +136,48 @@ </div>
879 <div class="row">
880 <div class="event-list">
881 `)
882-//line gititemsummary.qtpl:59
883+//line gititemsummary.qtpl:41
884 for _, c := range g.Commits {
885-//line gititemsummary.qtpl:59
886+//line gititemsummary.qtpl:41
887 qw422016.N().S(`
888 `)
889-//line gititemsummary.qtpl:60
890+//line gititemsummary.qtpl:42
891 StreamCommit(qw422016, name, c)
892-//line gititemsummary.qtpl:60
893+//line gititemsummary.qtpl:42
894 qw422016.N().S(`
895 `)
896-//line gititemsummary.qtpl:61
897+//line gititemsummary.qtpl:43
898 }
899-//line gititemsummary.qtpl:61
900+//line gititemsummary.qtpl:43
901 qw422016.N().S(`
902 </div>
903 </div>
904 `)
905-//line gititemsummary.qtpl:64
906+//line gititemsummary.qtpl:46
907 }
908
909-//line gititemsummary.qtpl:64
910+//line gititemsummary.qtpl:46
911 func (g *GitItemSummaryPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
912-//line gititemsummary.qtpl:64
913+//line gititemsummary.qtpl:46
914 qw422016 := qt422016.AcquireWriter(qq422016)
915-//line gititemsummary.qtpl:64
916+//line gititemsummary.qtpl:46
917 g.StreamGitContent(qw422016, name, ref)
918-//line gititemsummary.qtpl:64
919+//line gititemsummary.qtpl:46
920 qt422016.ReleaseWriter(qw422016)
921-//line gititemsummary.qtpl:64
922+//line gititemsummary.qtpl:46
923 }
924
925-//line gititemsummary.qtpl:64
926+//line gititemsummary.qtpl:46
927 func (g *GitItemSummaryPage) GitContent(name, ref string) string {
928-//line gititemsummary.qtpl:64
929+//line gititemsummary.qtpl:46
930 qb422016 := qt422016.AcquireByteBuffer()
931-//line gititemsummary.qtpl:64
932+//line gititemsummary.qtpl:46
933 g.WriteGitContent(qb422016, name, ref)
934-//line gititemsummary.qtpl:64
935+//line gititemsummary.qtpl:46
936 qs422016 := string(qb422016.B)
937-//line gititemsummary.qtpl:64
938+//line gititemsummary.qtpl:46
939 qt422016.ReleaseByteBuffer(qb422016)
940-//line gititemsummary.qtpl:64
941+//line gititemsummary.qtpl:46
942 return qs422016
943-//line gititemsummary.qtpl:64
944+//line gititemsummary.qtpl:46
945 }
946diff --git a/templates/gititemtree.qtpl b/templates/gititemtree.qtpl
947index 86fb29cbac5c8aa52d98b729ca13e839ca839db8..5898506af0e41201754aec08dcb72171efd2a675 100644
948--- a/templates/gititemtree.qtpl
949+++ b/templates/gititemtree.qtpl
950@@ -15,7 +15,7 @@ )
951 %}
952
953 {% code func url(name, mode, ref, filename string, path []string) string {
954- return u.Root().
955+ return u.NewPathing().
956 AddPath(name).
957 AddPath(mode).
958 AddPath(ref).
959diff --git a/templates/gititemtree.qtpl.go b/templates/gititemtree.qtpl.go
960index c0fc3a7787bc21e5a1755a73a878af40c1531c8c..f8d1fd2880caeda1c09bed34c6cdf9e8193bbec0 100644
961--- a/templates/gititemtree.qtpl.go
962+++ b/templates/gititemtree.qtpl.go
963@@ -38,7 +38,7 @@ )
964
965 //line gititemtree.qtpl:17
966 func url(name, mode, ref, filename string, path []string) string {
967- return u.Root().
968+ return u.NewPathing().
969 AddPath(name).
970 AddPath(mode).
971 AddPath(ref).
972diff --git a/templates/tags.qtpl b/templates/tags.qtpl
973new file mode 100644
974index 0000000000000000000000000000000000000000..5cd617fc26b94a2513c0b1fc86d89c9d6e8b8132
975--- /dev/null
976+++ b/templates/tags.qtpl
977@@ -0,0 +1,31 @@
978+{% import "git.gabrielgio.me/cerrado/pkg/git" %}
979+
980+{% func ListTags(name string, tags []*git.TagReference) %}
981+{% if len(tags) > 0 %}
982+<div class="event-list">
983+ {% for _, t := range tags %}
984+ <div class="event me-md-2">
985+ <div class="row ">
986+ <div class="col-4">
987+ <a title="{%s t.HashString() %}" href="/{%s name %}/commit/{%s t.HashString() %}">{%s t.ShortName() %}</a>
988+ </div>
989+ <div class="col-8">
990+ <div class="float-end">
991+ <a href="/{%s name %}/archive/{%s t.ShortName() %}.tar.gz">tar.gz</a>
992+ <a href="/{%s name %}/tree/{%s t.ShortName() %}/">tree</a>
993+ <a href="/{%s name %}/log/{%s t.ShortName() %}/">log</a>
994+ </div>
995+ </div>
996+ </div>
997+ {% if t.Message() != "" %}
998+ <div class="code-view">
999+ <pre>{%s t.Message() %}</pre>
1000+ </div>
1001+ {% endif %}
1002+ </div>
1003+ {% endfor %}
1004+</div>
1005+{% else %}
1006+ <p> No tags </p>
1007+{% endif %}
1008+{% endfunc %}
1009diff --git a/templates/tags.qtpl.go b/templates/tags.qtpl.go
1010new file mode 100644
1011index 0000000000000000000000000000000000000000..7d8eca8f5a309f79029bdef9a1ec902ff0a40375
1012--- /dev/null
1013+++ b/templates/tags.qtpl.go
1014@@ -0,0 +1,154 @@
1015+// Code generated by qtc from "tags.qtpl". DO NOT EDIT.
1016+// See https://github.com/valyala/quicktemplate for details.
1017+
1018+//line tags.qtpl:1
1019+package templates
1020+
1021+//line tags.qtpl:1
1022+import "git.gabrielgio.me/cerrado/pkg/git"
1023+
1024+//line tags.qtpl:3
1025+import (
1026+ qtio422016 "io"
1027+
1028+ qt422016 "github.com/valyala/quicktemplate"
1029+)
1030+
1031+//line tags.qtpl:3
1032+var (
1033+ _ = qtio422016.Copy
1034+ _ = qt422016.AcquireByteBuffer
1035+)
1036+
1037+//line tags.qtpl:3
1038+func StreamListTags(qw422016 *qt422016.Writer, name string, tags []*git.TagReference) {
1039+//line tags.qtpl:3
1040+ qw422016.N().S(`
1041+`)
1042+//line tags.qtpl:4
1043+ if len(tags) > 0 {
1044+//line tags.qtpl:4
1045+ qw422016.N().S(`
1046+<div class="event-list">
1047+ `)
1048+//line tags.qtpl:6
1049+ for _, t := range tags {
1050+//line tags.qtpl:6
1051+ qw422016.N().S(`
1052+ <div class="event me-md-2">
1053+ <div class="row ">
1054+ <div class="col-4">
1055+ <a title="`)
1056+//line tags.qtpl:10
1057+ qw422016.E().S(t.HashString())
1058+//line tags.qtpl:10
1059+ qw422016.N().S(`" href="/`)
1060+//line tags.qtpl:10
1061+ qw422016.E().S(name)
1062+//line tags.qtpl:10
1063+ qw422016.N().S(`/commit/`)
1064+//line tags.qtpl:10
1065+ qw422016.E().S(t.HashString())
1066+//line tags.qtpl:10
1067+ qw422016.N().S(`">`)
1068+//line tags.qtpl:10
1069+ qw422016.E().S(t.ShortName())
1070+//line tags.qtpl:10
1071+ qw422016.N().S(`</a>
1072+ </div>
1073+ <div class="col-8">
1074+ <div class="float-end">
1075+ <a href="/`)
1076+//line tags.qtpl:14
1077+ qw422016.E().S(name)
1078+//line tags.qtpl:14
1079+ qw422016.N().S(`/archive/`)
1080+//line tags.qtpl:14
1081+ qw422016.E().S(t.ShortName())
1082+//line tags.qtpl:14
1083+ qw422016.N().S(`.tar.gz">tar.gz</a>
1084+ <a href="/`)
1085+//line tags.qtpl:15
1086+ qw422016.E().S(name)
1087+//line tags.qtpl:15
1088+ qw422016.N().S(`/tree/`)
1089+//line tags.qtpl:15
1090+ qw422016.E().S(t.ShortName())
1091+//line tags.qtpl:15
1092+ qw422016.N().S(`/">tree</a>
1093+ <a href="/`)
1094+//line tags.qtpl:16
1095+ qw422016.E().S(name)
1096+//line tags.qtpl:16
1097+ qw422016.N().S(`/log/`)
1098+//line tags.qtpl:16
1099+ qw422016.E().S(t.ShortName())
1100+//line tags.qtpl:16
1101+ qw422016.N().S(`/">log</a>
1102+ </div>
1103+ </div>
1104+ </div>
1105+ `)
1106+//line tags.qtpl:20
1107+ if t.Message() != "" {
1108+//line tags.qtpl:20
1109+ qw422016.N().S(`
1110+ <div class="code-view">
1111+ <pre>`)
1112+//line tags.qtpl:22
1113+ qw422016.E().S(t.Message())
1114+//line tags.qtpl:22
1115+ qw422016.N().S(`</pre>
1116+ </div>
1117+ `)
1118+//line tags.qtpl:24
1119+ }
1120+//line tags.qtpl:24
1121+ qw422016.N().S(`
1122+ </div>
1123+ `)
1124+//line tags.qtpl:26
1125+ }
1126+//line tags.qtpl:26
1127+ qw422016.N().S(`
1128+</div>
1129+`)
1130+//line tags.qtpl:28
1131+ } else {
1132+//line tags.qtpl:28
1133+ qw422016.N().S(`
1134+ <p> No tags </p>
1135+`)
1136+//line tags.qtpl:30
1137+ }
1138+//line tags.qtpl:30
1139+ qw422016.N().S(`
1140+`)
1141+//line tags.qtpl:31
1142+}
1143+
1144+//line tags.qtpl:31
1145+func WriteListTags(qq422016 qtio422016.Writer, name string, tags []*git.TagReference) {
1146+//line tags.qtpl:31
1147+ qw422016 := qt422016.AcquireWriter(qq422016)
1148+//line tags.qtpl:31
1149+ StreamListTags(qw422016, name, tags)
1150+//line tags.qtpl:31
1151+ qt422016.ReleaseWriter(qw422016)
1152+//line tags.qtpl:31
1153+}
1154+
1155+//line tags.qtpl:31
1156+func ListTags(name string, tags []*git.TagReference) string {
1157+//line tags.qtpl:31
1158+ qb422016 := qt422016.AcquireByteBuffer()
1159+//line tags.qtpl:31
1160+ WriteListTags(qb422016, name, tags)
1161+//line tags.qtpl:31
1162+ qs422016 := string(qb422016.B)
1163+//line tags.qtpl:31
1164+ qt422016.ReleaseByteBuffer(qb422016)
1165+//line tags.qtpl:31
1166+ return qs422016
1167+//line tags.qtpl:31
1168+}