cerrado @ 4ea63e98cc999ab05d1ac98b64875d7413e86972

feat: Add initial log
diff --git a/pkg/git/git.go b/pkg/git/git.go
index b9ab235f7226301c7f57ca245c5b1660fa6230b9..80c0e4613fdd261329634bf48bd58069ff827f6b 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -2,6 +2,8 @@ package git
 
 import (
 	"errors"
+	"fmt"
+	"io"
 
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing/object"
@@ -46,3 +48,36 @@ 		return nil, err
 	}
 	return c, nil
 }
+
+func (g *GitRepository) Commits() ([]*object.Commit, error) {
+	repo, err := git.PlainOpen(g.path)
+	if err != nil {
+		return nil, err
+	}
+	ref, err := repo.Head()
+	if err != nil {
+		return nil, errors.Join(MissingHeadErr, err)
+	}
+
+	ci, err := repo.Log(&git.LogOptions{From: ref.Hash()})
+	if err != nil {
+		return nil, fmt.Errorf("commits from ref: %w", err)
+	}
+
+	commits := []*object.Commit{}
+	// TODO: for now only load first 1000
+	for x := 0; x < 1000; x++ {
+		c, err := ci.Next()
+		if err != nil && errors.Is(err, io.EOF) {
+			break
+		} else if err != nil {
+			return nil, err
+		}
+		commits = append(commits, c)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	return commits, nil
+}
diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
index 45cd865ea42c7929931a0472529d7916644bd8ce..ebfb37f298597e7aee5afa1ca9026e44fe9b364e 100644
--- a/pkg/handler/git/handler.go
+++ b/pkg/handler/git/handler.go
@@ -6,6 +6,7 @@ 	"net/http"
 
 	"git.gabrielgio.me/cerrado/pkg/service"
 	"git.gabrielgio.me/cerrado/templates"
+	"github.com/go-git/go-git/v5/plumbing/object"
 	"github.com/gorilla/mux"
 )
 
@@ -16,6 +17,7 @@ 	}
 
 	gitService interface {
 		ListRepositories() ([]*service.Repository, error)
+		ListCommits(string) ([]*object.Commit, error)
 	}
 )
 
@@ -74,9 +76,18 @@ }
 
 func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) {
 	name := mux.Vars(r)["name"]
+
+	commits, err := g.gitService.ListCommits(name)
+	if err != nil {
+		slog.Error("Error loading commits", "error", err)
+		return
+	}
+
 	gitList := &templates.GitItemPage{
-		Name:        name,
-		GitItemBase: &templates.GitItemLogPage{},
+		Name: name,
+		GitItemBase: &templates.GitItemLogPage{
+			Commits: commits,
+		},
 	}
 	templates.WritePageTemplate(w, gitList)
 }
diff --git a/pkg/service/git.go b/pkg/service/git.go
index 2b1fe252f8e7785238896329ceca3305c71b817c..614770fa55884c145409031ec3e31fee1c9f6a07 100644
--- a/pkg/service/git.go
+++ b/pkg/service/git.go
@@ -5,6 +5,7 @@ 	"path"
 
 	"git.gabrielgio.me/cerrado/pkg/config"
 	"git.gabrielgio.me/cerrado/pkg/git"
+	"github.com/go-git/go-git/v5/plumbing/object"
 )
 
 type (
@@ -21,6 +22,7 @@ 	}
 
 	configurationRepository interface {
 		List() []*config.GitRepositoryConfiguration
+		GetByName(name string) *config.GitRepositoryConfiguration
 	}
 )
 
@@ -55,3 +57,11 @@ 	}
 
 	return repos, nil
 }
+
+func (g *GitService) ListCommits(name string) ([]*object.Commit, error) {
+	// TODO: handle nil
+	r := g.configRepo.GetByName(name)
+
+	repo := git.NewGitRepository(r.Path)
+	return repo.Commits()
+}
diff --git a/scss/main.scss b/scss/main.scss
index deea23b381c4ce957482bdf81c2d71ecdb0972dc..9f17dadab978cf2bef7863c4ff6a292d87e107a8 100644
--- a/scss/main.scss
+++ b/scss/main.scss
@@ -26,10 +26,6 @@     font-size: $base-font-size;
     margin: 0;
 }
 
-.card-body {
-  padding: 5px;
-}
-
 .navbar-nav {
   margin-top: 0px
 }
@@ -65,3 +61,34 @@ .code-view {
     display: grid;
     overflow-x: auto;
 }
+
+.logs {
+  >div:nth-child(odd) {
+    background: #f8f9fa;
+  }
+
+  >div {
+    padding: 10px;
+  }
+
+  pre {
+    white-space: break-spaces;
+    margin: 0;
+  }
+}
+
+.logs pre::first-line {
+  font-weight: bold;
+}
+
+@include media-breakpoint-down(sm) {
+  // add extra spacing then list is seen on vertical
+  .logs>div>div:first-child {
+    margin-bottom: 15px;
+  }
+  .logs>div>div:last-child {
+    margin-top: 15px;
+  }
+}
+
+
diff --git a/templates/base.qtpl b/templates/base.qtpl
index ba32aad77d3aa3bf33796127b6af71409d673e1c..16b878014575bb4d5d6dc275b17774b1d63079c3 100644
--- a/templates/base.qtpl
+++ b/templates/base.qtpl
@@ -1,6 +1,7 @@
 This is a base page template. All the other template pages implement this interface.
 
 {% import "strconv" %}
+{% import "time" %}
 
 {% code 
  var Slug = ""
@@ -21,6 +22,11 @@         if u != nil {
             return strconv.FormatUint(uint64(*u), 10)
         }
         return ""
+    }
+%}
+
+{% code func TimeFormat(t time.Time) string {
+        return t.Format("2006-01-02")
     }
 %}
 
diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
index 2f419c68086d58c248e274382152c0ab9a3fc6fb..6d4d0a082e97a6f3bba19dc6a343ba313eabcc21 100644
--- a/templates/base.qtpl.go
+++ b/templates/base.qtpl.go
@@ -10,52 +10,55 @@
 //line base.qtpl:3
 import "strconv"
 
-//line base.qtpl:5
+//line base.qtpl:4
+import "time"
+
+//line base.qtpl:6
 import (
 	qtio422016 "io"
 
 	qt422016 "github.com/valyala/quicktemplate"
 )
 
-//line base.qtpl:5
+//line base.qtpl:6
 var (
 	_ = qtio422016.Copy
 	_ = qt422016.AcquireByteBuffer
 )
 
-//line base.qtpl:6
+//line base.qtpl:7
 var Slug = ""
 
-//line base.qtpl:10
+//line base.qtpl:11
 type Page interface {
-//line base.qtpl:10
+//line base.qtpl:11
 	Title() string
-//line base.qtpl:10
+//line base.qtpl:11
 	StreamTitle(qw422016 *qt422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 	WriteTitle(qq422016 qtio422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 	Content() string
-//line base.qtpl:10
+//line base.qtpl:11
 	StreamContent(qw422016 *qt422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 	WriteContent(qq422016 qtio422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 	Script() string
-//line base.qtpl:10
+//line base.qtpl:11
 	StreamScript(qw422016 *qt422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 	WriteScript(qq422016 qtio422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 	Navbar() string
-//line base.qtpl:10
+//line base.qtpl:11
 	StreamNavbar(qw422016 *qt422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 	WriteNavbar(qq422016 qtio422016.Writer)
-//line base.qtpl:10
+//line base.qtpl:11
 }
 
-//line base.qtpl:19
+//line base.qtpl:20
 func FromUInttoString(u *uint) string {
 	if u != nil {
 		return strconv.FormatUint(uint64(*u), 10)
@@ -63,174 +66,179 @@ 	}
 	return ""
 }
 
+//line base.qtpl:28
+func TimeFormat(t time.Time) string {
+	return t.Format("2006-01-02")
+}
+
 // Page prints a page implementing Page interface.
 
-//line base.qtpl:28
+//line base.qtpl:34
 func StreamPageTemplate(qw422016 *qt422016.Writer, p Page) {
-//line base.qtpl:28
+//line base.qtpl:34
 	qw422016.N().S(`
 <html lang="en">
     <head>
         <meta charset="utf-8">
         <link rel="icon" href="data:,">
         <title>cerrado | `)
-//line base.qtpl:33
+//line base.qtpl:39
 	p.StreamTitle(qw422016)
-//line base.qtpl:33
+//line base.qtpl:39
 	qw422016.N().S(`</title> 
         <link rel="stylesheet" href="/static/main`)
-//line base.qtpl:34
+//line base.qtpl:40
 	qw422016.E().S(Slug)
-//line base.qtpl:34
+//line base.qtpl:40
 	qw422016.N().S(`.css">
         <meta name="viewport" content="width=device-width, initial-scale=1" />
     </head>
     <body>
         `)
-//line base.qtpl:38
+//line base.qtpl:44
 	p.StreamNavbar(qw422016)
-//line base.qtpl:38
+//line base.qtpl:44
 	qw422016.N().S(`
         <div class="container">
             `)
-//line base.qtpl:40
+//line base.qtpl:46
 	p.StreamContent(qw422016)
-//line base.qtpl:40
+//line base.qtpl:46
 	qw422016.N().S(`
         </div>
     </body>
     `)
-//line base.qtpl:43
+//line base.qtpl:49
 	p.StreamScript(qw422016)
-//line base.qtpl:43
+//line base.qtpl:49
 	qw422016.N().S(`
 </html>
 `)
-//line base.qtpl:45
+//line base.qtpl:51
 }
 
-//line base.qtpl:45
+//line base.qtpl:51
 func WritePageTemplate(qq422016 qtio422016.Writer, p Page) {
-//line base.qtpl:45
+//line base.qtpl:51
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:45
+//line base.qtpl:51
 	StreamPageTemplate(qw422016, p)
-//line base.qtpl:45
+//line base.qtpl:51
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:45
+//line base.qtpl:51
 }
 
-//line base.qtpl:45
+//line base.qtpl:51
 func PageTemplate(p Page) string {
-//line base.qtpl:45
+//line base.qtpl:51
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:45
+//line base.qtpl:51
 	WritePageTemplate(qb422016, p)
-//line base.qtpl:45
+//line base.qtpl:51
 	qs422016 := string(qb422016.B)
-//line base.qtpl:45
+//line base.qtpl:51
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:45
+//line base.qtpl:51
 	return qs422016
-//line base.qtpl:45
+//line base.qtpl:51
 }
 
-//line base.qtpl:47
+//line base.qtpl:53
 type BasePage struct{}
 
-//line base.qtpl:48
+//line base.qtpl:54
 func (p *BasePage) StreamTitle(qw422016 *qt422016.Writer) {
-//line base.qtpl:48
+//line base.qtpl:54
 	qw422016.N().S(`Empty`)
-//line base.qtpl:48
+//line base.qtpl:54
 }
 
-//line base.qtpl:48
+//line base.qtpl:54
 func (p *BasePage) WriteTitle(qq422016 qtio422016.Writer) {
-//line base.qtpl:48
+//line base.qtpl:54
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:48
+//line base.qtpl:54
 	p.StreamTitle(qw422016)
-//line base.qtpl:48
+//line base.qtpl:54
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:48
+//line base.qtpl:54
 }
 
-//line base.qtpl:48
+//line base.qtpl:54
 func (p *BasePage) Title() string {
-//line base.qtpl:48
+//line base.qtpl:54
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:48
+//line base.qtpl:54
 	p.WriteTitle(qb422016)
-//line base.qtpl:48
+//line base.qtpl:54
 	qs422016 := string(qb422016.B)
-//line base.qtpl:48
+//line base.qtpl:54
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:48
+//line base.qtpl:54
 	return qs422016
-//line base.qtpl:48
+//line base.qtpl:54
 }
 
-//line base.qtpl:49
+//line base.qtpl:55
 func (p *BasePage) StreamBody(qw422016 *qt422016.Writer) {
-//line base.qtpl:49
+//line base.qtpl:55
 	qw422016.N().S(`HelloWorld`)
-//line base.qtpl:49
+//line base.qtpl:55
 }
 
-//line base.qtpl:49
+//line base.qtpl:55
 func (p *BasePage) WriteBody(qq422016 qtio422016.Writer) {
-//line base.qtpl:49
+//line base.qtpl:55
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:49
+//line base.qtpl:55
 	p.StreamBody(qw422016)
-//line base.qtpl:49
+//line base.qtpl:55
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:49
+//line base.qtpl:55
 }
 
-//line base.qtpl:49
+//line base.qtpl:55
 func (p *BasePage) Body() string {
-//line base.qtpl:49
+//line base.qtpl:55
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:49
+//line base.qtpl:55
 	p.WriteBody(qb422016)
-//line base.qtpl:49
+//line base.qtpl:55
 	qs422016 := string(qb422016.B)
-//line base.qtpl:49
+//line base.qtpl:55
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:49
+//line base.qtpl:55
 	return qs422016
-//line base.qtpl:49
+//line base.qtpl:55
 }
 
-//line base.qtpl:50
+//line base.qtpl:56
 func (p *BasePage) StreamScript(qw422016 *qt422016.Writer) {
-//line base.qtpl:50
+//line base.qtpl:56
 }
 
-//line base.qtpl:50
+//line base.qtpl:56
 func (p *BasePage) WriteScript(qq422016 qtio422016.Writer) {
-//line base.qtpl:50
+//line base.qtpl:56
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:50
+//line base.qtpl:56
 	p.StreamScript(qw422016)
-//line base.qtpl:50
+//line base.qtpl:56
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:50
+//line base.qtpl:56
 }
 
-//line base.qtpl:50
+//line base.qtpl:56
 func (p *BasePage) Script() string {
-//line base.qtpl:50
+//line base.qtpl:56
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:50
+//line base.qtpl:56
 	p.WriteScript(qb422016)
-//line base.qtpl:50
+//line base.qtpl:56
 	qs422016 := string(qb422016.B)
-//line base.qtpl:50
+//line base.qtpl:56
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:50
+//line base.qtpl:56
 	return qs422016
-//line base.qtpl:50
+//line base.qtpl:56
 }
diff --git a/templates/gititem.qtpl b/templates/gititem.qtpl
index 4a6c49a5709efe777228786bb881715991e01268..d2fcea7fcbac20ca753c668f71b7937a7c61e7c5 100644
--- a/templates/gititem.qtpl
+++ b/templates/gititem.qtpl
@@ -23,7 +23,7 @@ </div>
 <div class="row">
 {%= p.Nav(p.Name) %}
 </div>
-<div class="row">
+<div class="container">
 {%= p.GitContent() %}
 </div>
 {% endfunc %}
diff --git a/templates/gititem.qtpl.go b/templates/gititem.qtpl.go
index f978c4d3d66423b73ba92eecd30ca5c359004237..9709a43ec697fd27687af9e2ac9b424ff8e981a1 100644
--- a/templates/gititem.qtpl.go
+++ b/templates/gititem.qtpl.go
@@ -124,7 +124,7 @@ 	p.StreamNav(qw422016, p.Name)
 //line gititem.qtpl:24
 	qw422016.N().S(`
 </div>
-<div class="row">
+<div class="container">
 `)
 //line gititem.qtpl:27
 	p.StreamGitContent(qw422016)
diff --git a/templates/gititemlog.qtpl b/templates/gititemlog.qtpl
index ae88a5205a998eec94dc046b0a7e2f2e71d3ede4..a39fb777497d50df7bec71fa6d2465b3a1b56570 100644
--- a/templates/gititemlog.qtpl
+++ b/templates/gititemlog.qtpl
@@ -1,10 +1,27 @@
+{% import "github.com/go-git/go-git/v5/plumbing/object" %}
+
 {% code
 type GitItemLogPage struct {
+    Commits []*object.Commit
 }
 %}
 
 {% func (g *GitItemLogPage) Nav(name string) %}{%= GitItemNav(name, Log) %}{% endfunc %}
 
 {% func (g *GitItemLogPage) GitContent() %}
-<h4>Log</h4>
+<div class="logs">
+  {% for _, c := range g.Commits %}
+  <div class="row">
+      <div class="col-sm-2">
+       {%s TimeFormat(c.Committer.When) %}
+      </div>
+      <div class="col-sm-7">
+       <pre>{%s c.Message %}</pre>
+      </div>
+      <div class="col-sm-3">
+       {%s c.Committer.Name %}
+      </div>
+  </div>
+  {% endfor %}
+</div>
 {% endfunc %}
diff --git a/templates/gititemlog.qtpl.go b/templates/gititemlog.qtpl.go
index 2d559d745fa7d15387f20e382b4e8f18bc8b29b6..cc5652d56de5724d82b1ec87ed045e9722c6dfc5 100644
--- a/templates/gititemlog.qtpl.go
+++ b/templates/gititemlog.qtpl.go
@@ -5,86 +5,124 @@ //line gititemlog.qtpl:1
 package templates
 
 //line gititemlog.qtpl:1
+import "github.com/go-git/go-git/v5/plumbing/object"
+
+//line gititemlog.qtpl:3
 import (
 	qtio422016 "io"
 
 	qt422016 "github.com/valyala/quicktemplate"
 )
 
-//line gititemlog.qtpl:1
+//line gititemlog.qtpl:3
 var (
 	_ = qtio422016.Copy
 	_ = qt422016.AcquireByteBuffer
 )
 
-//line gititemlog.qtpl:2
+//line gititemlog.qtpl:4
 type GitItemLogPage struct {
+	Commits []*object.Commit
 }
 
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 func (g *GitItemLogPage) StreamNav(qw422016 *qt422016.Writer, name string) {
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	StreamGitItemNav(qw422016, name, Log)
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 }
 
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 func (g *GitItemLogPage) WriteNav(qq422016 qtio422016.Writer, name string) {
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	g.StreamNav(qw422016, name)
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	qt422016.ReleaseWriter(qw422016)
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 }
 
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 func (g *GitItemLogPage) Nav(name string) string {
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	qb422016 := qt422016.AcquireByteBuffer()
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	g.WriteNav(qb422016, name)
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	qs422016 := string(qb422016.B)
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	qt422016.ReleaseByteBuffer(qb422016)
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 	return qs422016
-//line gititemlog.qtpl:6
+//line gititemlog.qtpl:9
 }
 
-//line gititemlog.qtpl:8
+//line gititemlog.qtpl:11
 func (g *GitItemLogPage) StreamGitContent(qw422016 *qt422016.Writer) {
-//line gititemlog.qtpl:8
+//line gititemlog.qtpl:11
 	qw422016.N().S(`
-<h4>Log</h4>
+<div class="logs">
+  `)
+//line gititemlog.qtpl:13
+	for _, c := range g.Commits {
+//line gititemlog.qtpl:13
+		qw422016.N().S(`
+  <div class="row">
+      <div class="col-sm-2">
+       `)
+//line gititemlog.qtpl:16
+		qw422016.E().S(TimeFormat(c.Committer.When))
+//line gititemlog.qtpl:16
+		qw422016.N().S(`
+      </div>
+      <div class="col-sm-7">
+       <pre>`)
+//line gititemlog.qtpl:19
+		qw422016.E().S(c.Message)
+//line gititemlog.qtpl:19
+		qw422016.N().S(`</pre>
+      </div>
+      <div class="col-sm-3">
+       `)
+//line gititemlog.qtpl:22
+		qw422016.E().S(c.Committer.Name)
+//line gititemlog.qtpl:22
+		qw422016.N().S(`
+      </div>
+  </div>
+  `)
+//line gititemlog.qtpl:25
+	}
+//line gititemlog.qtpl:25
+	qw422016.N().S(`
+</div>
 `)
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 }
 
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 func (g *GitItemLogPage) WriteGitContent(qq422016 qtio422016.Writer) {
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	g.StreamGitContent(qw422016)
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	qt422016.ReleaseWriter(qw422016)
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 }
 
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 func (g *GitItemLogPage) GitContent() string {
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	qb422016 := qt422016.AcquireByteBuffer()
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	g.WriteGitContent(qb422016)
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	qs422016 := string(qb422016.B)
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	qt422016.ReleaseByteBuffer(qb422016)
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 	return qs422016
-//line gititemlog.qtpl:10
+//line gititemlog.qtpl:27
 }