From 4534dffb865eb1a50bfbc291a5c3798183081caf Mon Sep 17 00:00:00 2001 From: "Gabriel A. Giovanini" Date: Sun, 26 May 2024 20:33:37 +0200 Subject: feat: Add actual git listing implementation --- pkg/git/git.go | 82 +++++++++++++++++++++++++++++++++++++++++++ pkg/handler/git.go | 9 ++++- pkg/handler/status.go | 2 +- pkg/service/git.go | 51 +++++++++++++++++++-------- pkg/u/file.go | 21 +++++++++++ pkg/u/list.go | 17 +++++++++ pkg/u/list_test.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++ pkg/u/util.go | 17 --------- pkg/u/util_test.go | 96 --------------------------------------------------- 9 files changed, 262 insertions(+), 129 deletions(-) create mode 100644 pkg/git/git.go create mode 100644 pkg/u/file.go create mode 100644 pkg/u/list.go create mode 100644 pkg/u/list_test.go delete mode 100644 pkg/u/util.go delete mode 100644 pkg/u/util_test.go (limited to 'pkg') diff --git a/pkg/git/git.go b/pkg/git/git.go new file mode 100644 index 0000000..85a3b95 --- /dev/null +++ b/pkg/git/git.go @@ -0,0 +1,82 @@ +package git + +import ( + "errors" + "os" + "path" + + "git.gabrielgio.me/cerrado/pkg/u" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" +) + +var ( + ScanPathErr = errors.New("Scan path does not exist") + RepoPathErr = errors.New("Repository path does not exist") + missingHeadErr = errors.New("Head not found") +) + +type ( + GitServerRepository struct { + scanPath string + } + + GitRepository struct { + path string + } +) + +func NewGitServerRepository(scanPath string) *GitServerRepository { + return &GitServerRepository{scanPath} +} + +func NewGitRepository(dir string) *GitRepository { + return &GitRepository{ + path: dir, + } +} + +func (g *GitServerRepository) List() ([]*GitRepository, error) { + if !u.FileExist(g.scanPath) { + return nil, ScanPathErr + } + + entries, err := os.ReadDir(g.scanPath) + if err != nil { + return nil, err + } + + repos := make([]*GitRepository, 0) + for _, e := range entries { + if !e.IsDir() { + continue + } + + fullPath := path.Join(g.scanPath, e.Name()) + repos = append(repos, NewGitRepository(fullPath)) + } + + return repos, nil +} + +func (g *GitRepository) Path() string { + return g.path +} + +func (g *GitRepository) LastCommit() (*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) + } + + c, err := repo.CommitObject(ref.Hash()) + if err != nil { + return nil, err + } + return c, nil +} diff --git a/pkg/handler/git.go b/pkg/handler/git.go index 5b09121..1ed2c49 100644 --- a/pkg/handler/git.go +++ b/pkg/handler/git.go @@ -1,6 +1,7 @@ package handler import ( + "log/slog" "net/http" "git.gabrielgio.me/cerrado/pkg/service" @@ -16,6 +17,12 @@ func NewGitHandler(gitService *service.GitService) *GitHandler { } func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) { - gitList := &templates.GitListPage{g.gitService.ListRepositories()} + repos, err := g.gitService.ListRepositories() + if err != nil { + slog.Error("Error listing repo", "error", err) + return + } + + gitList := &templates.GitListPage{repos} templates.WritePageTemplate(w, gitList) } diff --git a/pkg/handler/status.go b/pkg/handler/status.go index 2a84a7e..1ca7f70 100644 --- a/pkg/handler/status.go +++ b/pkg/handler/status.go @@ -19,7 +19,7 @@ func ConfigFile(configPath string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, _ *http.Request) { f, err := os.Open(configPath) if err != nil { - slog.Error("Error openning config file json", "error", err, "path", configPath) + slog.Error("Error openning config file", "error", err, "path", configPath) return } diff --git a/pkg/service/git.go b/pkg/service/git.go index 0415cee..94ca75e 100644 --- a/pkg/service/git.go +++ b/pkg/service/git.go @@ -1,30 +1,53 @@ package service -import "fmt" +import ( + "path" + + "git.gabrielgio.me/cerrado/pkg/git" +) type ( - GitService struct{} + GitService struct { + server *git.GitServerRepository + } Repository struct { - Name string - Title string - Description string + Name string + Title string + LastCommitMessage string + LastCommitDate string } ) -func NewGitService() *GitService { - return &GitService{} +// TODO: make it configurable +const timeFormat = "2006.01.02 15:04:05" + +func NewGitService(server *git.GitServerRepository) *GitService { + return &GitService{ + server: server, + } } -func (g *GitService) ListRepositories() []*Repository { - repos := make([]*Repository, 10) +func (g *GitService) ListRepositories() ([]*Repository, error) { + rs, err := g.server.List() + if err != nil { + return nil, err + } + + repos := make([]*Repository, len(rs)) + for i, r := range rs { + obj, err := r.LastCommit() + if err != nil { + return nil, err + } - for i := range 10 { + baseName := path.Base(r.Path()) repos[i] = &Repository{ - Name: fmt.Sprintf("repository-%d", i), - Title: fmt.Sprintf("Repository %d", i), - Description: fmt.Sprintf("This is a description for repository %d", i), + Name: baseName, + Title: baseName, + LastCommitMessage: obj.Message, + LastCommitDate: obj.Author.When.Format(timeFormat), } } - return repos + return repos, nil } diff --git a/pkg/u/file.go b/pkg/u/file.go new file mode 100644 index 0000000..cf86c75 --- /dev/null +++ b/pkg/u/file.go @@ -0,0 +1,21 @@ +package u + +import ( + "errors" + "log/slog" + "os" +) + +func FileExist(filename string) bool { + if _, err := os.Stat(filename); err == nil { + return true + + } else if errors.Is(err, os.ErrNotExist) { + return false + } else { + slog.Warn("Schrödinger's file: it may or may not exist", "file", filename) + // Schrodinger: file may or may not exist. To be extra safe it will + // report the file doest not exist + return false + } +} diff --git a/pkg/u/list.go b/pkg/u/list.go new file mode 100644 index 0000000..34eafd1 --- /dev/null +++ b/pkg/u/list.go @@ -0,0 +1,17 @@ +package u + +func First[T any](v []T) (T, bool) { + if len(v) == 0 { + var zero T + return zero, false + } + return v[0], true +} + +func ChunkBy[T any](items []T, chunkSize int) [][]T { + var chunks = make([][]T, 0, (len(items)/chunkSize)+1) + for chunkSize < len(items) { + items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize]) + } + return append(chunks, items) +} diff --git a/pkg/u/list_test.go b/pkg/u/list_test.go new file mode 100644 index 0000000..a6d84c7 --- /dev/null +++ b/pkg/u/list_test.go @@ -0,0 +1,96 @@ +// go:build unit + +package u + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestFirst(t *testing.T) { + testCases := []struct { + name string + slice []int + first int + exist bool + }{ + { + name: "multiple items slice", + slice: []int{1, 2, 3}, + first: 1, + exist: true, + }, + { + name: "single item slice", + slice: []int{1}, + first: 1, + exist: true, + }, + { + name: "empty slice", + slice: []int{}, + first: 0, + exist: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + first, empty := First(tc.slice) + + if first != tc.first { + t.Errorf("Error first, want %d got %d", tc.first, first) + } + + if empty != tc.exist { + t.Errorf("Error empty, want %t got %t", tc.exist, empty) + } + + }) + } +} + +func TestSubList(t *testing.T) { + testCases := []struct { + name string + slice []int + size int + want [][]int + }{ + { + name: "sigle size sub list", + slice: []int{1, 2, 3}, + size: 1, + want: [][]int{{1}, {2}, {3}}, + }, + { + name: "multiple size sub list", + slice: []int{1, 2, 3, 4}, + size: 2, + want: [][]int{{1, 2}, {3, 4}}, + }, + { + name: "uneven multiple size sub list", + slice: []int{1, 2, 3, 4, 5}, + size: 2, + want: [][]int{{1, 2}, {3, 4}, {5}}, + }, + { + name: "empty sub list", + slice: []int{}, + size: 2, + want: [][]int{{}}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + subList := ChunkBy(tc.slice, tc.size) + + if diff := cmp.Diff(tc.want, subList); diff != "" { + t.Errorf("Wrong result given - wanted + got\n %s", diff) + } + }) + } +} diff --git a/pkg/u/util.go b/pkg/u/util.go deleted file mode 100644 index 34eafd1..0000000 --- a/pkg/u/util.go +++ /dev/null @@ -1,17 +0,0 @@ -package u - -func First[T any](v []T) (T, bool) { - if len(v) == 0 { - var zero T - return zero, false - } - return v[0], true -} - -func ChunkBy[T any](items []T, chunkSize int) [][]T { - var chunks = make([][]T, 0, (len(items)/chunkSize)+1) - for chunkSize < len(items) { - items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize]) - } - return append(chunks, items) -} diff --git a/pkg/u/util_test.go b/pkg/u/util_test.go deleted file mode 100644 index a6d84c7..0000000 --- a/pkg/u/util_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// go:build unit - -package u - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -func TestFirst(t *testing.T) { - testCases := []struct { - name string - slice []int - first int - exist bool - }{ - { - name: "multiple items slice", - slice: []int{1, 2, 3}, - first: 1, - exist: true, - }, - { - name: "single item slice", - slice: []int{1}, - first: 1, - exist: true, - }, - { - name: "empty slice", - slice: []int{}, - first: 0, - exist: false, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - - first, empty := First(tc.slice) - - if first != tc.first { - t.Errorf("Error first, want %d got %d", tc.first, first) - } - - if empty != tc.exist { - t.Errorf("Error empty, want %t got %t", tc.exist, empty) - } - - }) - } -} - -func TestSubList(t *testing.T) { - testCases := []struct { - name string - slice []int - size int - want [][]int - }{ - { - name: "sigle size sub list", - slice: []int{1, 2, 3}, - size: 1, - want: [][]int{{1}, {2}, {3}}, - }, - { - name: "multiple size sub list", - slice: []int{1, 2, 3, 4}, - size: 2, - want: [][]int{{1, 2}, {3, 4}}, - }, - { - name: "uneven multiple size sub list", - slice: []int{1, 2, 3, 4, 5}, - size: 2, - want: [][]int{{1, 2}, {3, 4}, {5}}, - }, - { - name: "empty sub list", - slice: []int{}, - size: 2, - want: [][]int{{}}, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - - subList := ChunkBy(tc.slice, tc.size) - - if diff := cmp.Diff(tc.want, subList); diff != "" { - t.Errorf("Wrong result given - wanted + got\n %s", diff) - } - }) - } -} -- cgit v1.2.3