Skip to content

Commit ec61352

Browse files
authored
Merge pull request #1 from dAppCore/dev
[sync] go-git: 2 commits from Forge
2 parents 92d6683 + 876119c commit ec61352

7 files changed

Lines changed: 266 additions & 20 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Two files:
3232

3333
## Test Conventions
3434

35-
- `_Good`, `_Bad` suffix pattern for success/failure cases.
35+
- `_Good` / `_Bad` suffix pattern for success / failure cases.
3636
- Tests use real git repos created by `initTestRepo()` in temp directories.
3737
- Service helper tests (in `service_test.go`) construct `Service` structs directly without the framework.
3838
- Framework integration tests (in `service_extra_test.go`) use `core.New()` and test handler dispatch.

git.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import (
55
"bytes"
66
"context"
77
"fmt"
8-
"io"
8+
goio "io"
99
"os"
1010
"os/exec"
1111
"path/filepath"
1212
"slices"
1313
"strconv"
1414
"strings"
1515
"sync"
16+
17+
coreerr "forge.lthn.ai/core/go-log"
1618
)
1719

1820
// RepoStatus represents the git status of a single repository.
@@ -81,7 +83,7 @@ func getStatus(ctx context.Context, path, name string) RepoStatus {
8183

8284
// Validate path to prevent directory traversal
8385
if !filepath.IsAbs(path) {
84-
status.Error = fmt.Errorf("path must be absolute: %s", path)
86+
status.Error = coreerr.E("git.getStatus", "path must be absolute: "+path, nil)
8587
return status
8688
}
8789

@@ -200,7 +202,7 @@ func gitInteractive(ctx context.Context, dir string, args ...string) error {
200202

201203
// Capture stderr for error reporting while also showing it
202204
var stderr bytes.Buffer
203-
cmd.Stderr = io.MultiWriter(os.Stderr, &stderr)
205+
cmd.Stderr = goio.MultiWriter(os.Stderr, &stderr)
204206

205207
if err := cmd.Run(); err != nil {
206208
return &GitError{
@@ -272,6 +274,9 @@ func gitCommand(ctx context.Context, dir string, args ...string) (string, error)
272274
return stdout.String(), nil
273275
}
274276

277+
// Compile-time interface checks.
278+
var _ error = (*GitError)(nil)
279+
275280
// GitError wraps a git command error with stderr output and command context.
276281
type GitError struct {
277282
Args []string
@@ -285,9 +290,12 @@ func (e *GitError) Error() string {
285290
stderr := strings.TrimSpace(e.Stderr)
286291

287292
if stderr != "" {
288-
return fmt.Errorf("git command %q failed: %s", cmd, stderr).Error()
293+
return fmt.Sprintf("git command %q failed: %s", cmd, stderr)
294+
}
295+
if e.Err != nil {
296+
return fmt.Sprintf("git command %q failed: %v", cmd, e.Err)
289297
}
290-
return fmt.Errorf("git command %q failed: %w", cmd, e.Err).Error()
298+
return fmt.Sprintf("git command %q failed", cmd)
291299
}
292300

293301
// Unwrap returns the underlying error for error chain inspection.

git_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,13 @@ func TestGetStatus_Bad_InvalidPath(t *testing.T) {
372372
assert.Equal(t, "bad-repo", status.Name)
373373
}
374374

375+
func TestGetStatus_Bad_RelativePath(t *testing.T) {
376+
status := getStatus(context.Background(), "relative/path", "rel-repo")
377+
assert.Error(t, status.Error)
378+
assert.Contains(t, status.Error.Error(), "path must be absolute")
379+
assert.Equal(t, "rel-repo", status.Name)
380+
}
381+
375382
// --- Status (parallel multi-repo) tests ---
376383

377384
func TestStatus_Good_MultipleRepos(t *testing.T) {

go.mod

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ module forge.lthn.ai/core/go-git
33
go 1.26.0
44

55
require (
6-
forge.lthn.ai/core/go v0.3.0
6+
forge.lthn.ai/core/go v0.3.1
7+
forge.lthn.ai/core/go-log v0.0.4
78
github.com/stretchr/testify v1.11.1
89
)
910

1011
require (
1112
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
12-
github.com/kr/pretty v0.3.1 // indirect
13+
github.com/kr/text v0.2.0 // indirect
1314
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
14-
github.com/rogpeppe/go-internal v1.14.1 // indirect
15-
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
1615
gopkg.in/yaml.v3 v3.0.1 // indirect
1716
)

go.sum

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
forge.lthn.ai/core/go v0.3.0 h1:mOG97ApMprwx9Ked62FdWVwXTGSF6JO6m0DrVpoH2Q4=
2-
forge.lthn.ai/core/go v0.3.0/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc=
1+
forge.lthn.ai/core/go v0.3.1 h1:5FMTsUhLcxSr07F9q3uG0Goy4zq4eLivoqi8shSY4UM=
2+
forge.lthn.ai/core/go v0.3.1/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc=
3+
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
4+
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
35
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
46
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
57
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6-
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
78
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
89
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
9-
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
10-
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1110
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
1211
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
13-
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
1412
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
1513
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
16-
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
1714
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
1815
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
1916
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=

service.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ package git
22

33
import (
44
"context"
5-
"fmt"
65
"iter"
76
"path/filepath"
87
"slices"
98
"strings"
109
"sync"
1110

1211
"forge.lthn.ai/core/go/pkg/core"
12+
coreerr "forge.lthn.ai/core/go-log"
1313
)
1414

1515
// Queries for git service
@@ -51,6 +51,9 @@ type ServiceOptions struct {
5151
WorkDir string
5252
}
5353

54+
// Compile-time interface checks.
55+
var _ core.Startable = (*Service)(nil)
56+
5457
// Service provides git operations as a Core service.
5558
type Service struct {
5659
*core.ServiceRuntime[ServiceOptions]
@@ -137,14 +140,14 @@ func (s *Service) handleTask(c *core.Core, t core.Task) (any, bool, error) {
137140

138141
func (s *Service) validatePath(path string) error {
139142
if !filepath.IsAbs(path) {
140-
return fmt.Errorf("path must be absolute: %s", path)
143+
return coreerr.E("git.validatePath", "path must be absolute: "+path, nil)
141144
}
142145

143146
workDir := s.opts.WorkDir
144147
if workDir != "" {
145148
rel, err := filepath.Rel(workDir, path)
146149
if err != nil || strings.HasPrefix(rel, "..") {
147-
return fmt.Errorf("path %s is outside of allowed WorkDir %s", path, workDir)
150+
return coreerr.E("git.validatePath", "path "+path+" is outside of allowed WorkDir "+workDir, nil)
148151
}
149152
}
150153
return nil

0 commit comments

Comments
 (0)