From bf8e37d522a51eb330bb419bca2f72d32ee1b625 Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 23 Feb 2026 05:06:48 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20modernise=20to=20Go=201.26=20=E2=80=94?= =?UTF-8?q?=20iterators=20and=20slices=20stdlib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add All(), Dirty(), Ahead() iter.Seq[RepoStatus] iterators - Reimplement DirtyRepos/AheadRepos via slices.Collect - Use slices.Contains for git status character matching - Refresh go.sum Co-Authored-By: Gemini Co-Authored-By: Virgil --- git.go | 5 +++-- go.sum | 4 ++-- service.go | 49 +++++++++++++++++++++++++++++++++++-------------- service_test.go | 47 +++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 79 insertions(+), 26 deletions(-) diff --git a/git.go b/git.go index 67665cc..77df035 100644 --- a/git.go +++ b/git.go @@ -7,6 +7,7 @@ import ( "io" "os" "os/exec" + "slices" "strconv" "strings" "sync" @@ -105,12 +106,12 @@ func getStatus(ctx context.Context, path, name string) RepoStatus { } // Staged (index has changes) - if x == 'A' || x == 'D' || x == 'R' || x == 'M' { + if slices.Contains([]byte{'A', 'D', 'R', 'M'}, x) { status.Staged++ } // Modified in working tree - if y == 'M' || y == 'D' { + if slices.Contains([]byte{'M', 'D'}, y) { status.Modified++ } } diff --git a/go.sum b/go.sum index 90a7a07..300f238 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -forge.lthn.ai/core/go v0.0.1 h1:6DFABiGUccu3iQz2avpYbh0X24xccIsve6TSipziKT4= -forge.lthn.ai/core/go v0.0.1/go.mod h1:vr4W9GMcyKbOJWmo22zQ9KmzLbdr2s17Q6LkVjpOeFU= +forge.lthn.ai/core/go v0.0.1 h1:ubk4nmkA3treOUNgPS28wKd1jB6cUlEQUV7jDdGa3zM= +forge.lthn.ai/core/go v0.0.1/go.mod h1:59YsnuMaAGQUxIhX68oK2/HnhQJEPWL1iEZhDTrNCbY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/service.go b/service.go index 892d6fc..9254642 100644 --- a/service.go +++ b/service.go @@ -2,6 +2,8 @@ package git import ( "context" + "iter" + "slices" "forge.lthn.ai/core/go/pkg/framework" ) @@ -103,24 +105,43 @@ func (s *Service) handleTask(c *framework.Core, t framework.Task) (any, bool, er // Status returns last status result. func (s *Service) Status() []RepoStatus { return s.lastStatus } -// DirtyRepos returns repos with uncommitted changes. -func (s *Service) DirtyRepos() []RepoStatus { - var dirty []RepoStatus - for _, st := range s.lastStatus { - if st.Error == nil && st.IsDirty() { - dirty = append(dirty, st) +// All returns an iterator over all last known statuses. +func (s *Service) All() iter.Seq[RepoStatus] { + return slices.Values(s.lastStatus) +} + +// Dirty returns an iterator over repos with uncommitted changes. +func (s *Service) Dirty() iter.Seq[RepoStatus] { + return func(yield func(RepoStatus) bool) { + for _, st := range s.lastStatus { + if st.Error == nil && st.IsDirty() { + if !yield(st) { + return + } + } } } - return dirty +} + +// Ahead returns an iterator over repos with unpushed commits. +func (s *Service) Ahead() iter.Seq[RepoStatus] { + return func(yield func(RepoStatus) bool) { + for _, st := range s.lastStatus { + if st.Error == nil && st.HasUnpushed() { + if !yield(st) { + return + } + } + } + } +} + +// DirtyRepos returns repos with uncommitted changes. +func (s *Service) DirtyRepos() []RepoStatus { + return slices.Collect(s.Dirty()) } // AheadRepos returns repos with unpushed commits. func (s *Service) AheadRepos() []RepoStatus { - var ahead []RepoStatus - for _, st := range s.lastStatus { - if st.Error == nil && st.HasUnpushed() { - ahead = append(ahead, st) - } - } - return ahead + return slices.Collect(s.Ahead()) } diff --git a/service_test.go b/service_test.go index e3ae7f6..b199126 100644 --- a/service_test.go +++ b/service_test.go @@ -1,6 +1,7 @@ package git import ( + "slices" "testing" "github.com/stretchr/testify/assert" @@ -23,10 +24,13 @@ func TestService_DirtyRepos_Good(t *testing.T) { dirty := s.DirtyRepos() assert.Len(t, dirty, 3) - names := make([]string, len(dirty)) - for i, d := range dirty { - names[i] = d.Name - } + names := slices.Collect(func(yield func(string) bool) { + for _, d := range dirty { + if !yield(d.Name) { + return + } + } + }) assert.Contains(t, names, "dirty-modified") assert.Contains(t, names, "dirty-untracked") assert.Contains(t, names, "dirty-staged") @@ -64,10 +68,13 @@ func TestService_AheadRepos_Good(t *testing.T) { ahead := s.AheadRepos() assert.Len(t, ahead, 2) - names := make([]string, len(ahead)) - for i, a := range ahead { - names[i] = a.Name - } + names := slices.Collect(func(yield func(string) bool) { + for _, a := range ahead { + if !yield(a.Name) { + return + } + } + }) assert.Contains(t, names, "ahead-by-one") assert.Contains(t, names, "ahead-by-five") } @@ -90,6 +97,30 @@ func TestService_AheadRepos_Good_EmptyStatus(t *testing.T) { assert.Empty(t, ahead) } +func TestService_Iterators_Good(t *testing.T) { + s := &Service{ + lastStatus: []RepoStatus{ + {Name: "clean"}, + {Name: "dirty", Modified: 1}, + {Name: "ahead", Ahead: 2}, + }, + } + + // Test All() + all := slices.Collect(s.All()) + assert.Len(t, all, 3) + + // Test Dirty() + dirty := slices.Collect(s.Dirty()) + assert.Len(t, dirty, 1) + assert.Equal(t, "dirty", dirty[0].Name) + + // Test Ahead() + ahead := slices.Collect(s.Ahead()) + assert.Len(t, ahead, 1) + assert.Equal(t, "ahead", ahead[0].Name) +} + func TestService_Status_Good(t *testing.T) { expected := []RepoStatus{ {Name: "repo1", Branch: "main"},