Skip to content

Commit

Permalink
Add commands to Add and list local repositories (#47)
Browse files Browse the repository at this point in the history
# Changes

## Primary change

Add `RegisterLocalRepositoryCommand` and `ListLocalRepositories` query.

## Supporting changes

- Organize step definitions by the kind of repository.

## Future work

- Add business logic for edge cases to commands, for faulty paths, paths
that aren't Git repositories, etc...
- Re-visit testing.

## Version

Same.
  • Loading branch information
kkrull authored Aug 6, 2024
1 parent 61e8332 commit a90bb8a
Show file tree
Hide file tree
Showing 25 changed files with 559 additions and 152 deletions.
1 change: 1 addition & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ version: "0.2"
words:
- cmdremote
- cmdroot
- corerepository
- fswatch
- kkrull
- Krull
Expand Down
4 changes: 4 additions & 0 deletions src/go/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Artifacts
_marmot
marmot
marmot.exe

# Testing
cukefeature/localDir
24 changes: 24 additions & 0 deletions src/go/corerepository/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@ package corerepository

import "net/url"

// Construct a container containing the given repositories.
func SomeRepositories(repositories []Repository) Repositories {
return &RepositoriesArray{Repositories: repositories}
}

// Construct a container containing no repositories of any kind.
func NoRepositories() Repositories {
return &RepositoriesArray{
Repositories: make([]Repository, 0),
}
}

// Any number of Git repositories.
type Repositories interface {
// How many repositories are in this collection
Count() int

// Paths to each repository on the local file system
LocalPaths() []string

// String versions of each remote's URL
RemoteHrefs() []string

Expand All @@ -23,6 +38,15 @@ func (array RepositoriesArray) Count() int {
return len(array.Repositories)
}

func (array RepositoriesArray) LocalPaths() []string {
localPaths := make([]string, len(array.Repositories))
for i, repository := range array.Repositories {
localPaths[i] = repository.LocalPath
}

return localPaths
}

func (array RepositoriesArray) RemoteHrefs() []string {
remoteHrefs := make([]string, len(array.Repositories))
for i, repository := range array.Repositories {
Expand Down
19 changes: 17 additions & 2 deletions src/go/corerepository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,36 @@ package corerepository

import "net/url"

// Construct a repository with the given path.
func LocalRepository(localPath string) Repository {
return Repository{
LocalPath: localPath,
RemoteUrl: nil,
}
}

// Construct a repository with a URL to its remote host.
func RemoteRepository(remoteUrl *url.URL) Repository {
return Repository{RemoteUrl: remoteUrl}
return Repository{
LocalPath: "",
RemoteUrl: remoteUrl,
}
}

// Construct a repository with something recognizable as a URL to its remote host.
func RemoteRepositoryS(remoteUrl string) (Repository, error) {
if parsedUrl, parseErr := url.Parse(remoteUrl); parseErr != nil {
return Repository{}, parseErr
} else {
return Repository{RemoteUrl: parsedUrl}, nil
return Repository{
LocalPath: "",
RemoteUrl: parsedUrl,
}, nil
}
}

// One Git repository.
type Repository struct {
LocalPath string
RemoteUrl *url.URL
}
2 changes: 2 additions & 0 deletions src/go/corerepository/repository_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import "net/url"

// A source of Git repositories that a meta repo might care about.
type RepositorySource interface {
AddLocal(localPath string) error
AddRemote(hostUrl *url.URL) error
ListLocal() (Repositories, error)
ListRemote() (Repositories, error)
}
52 changes: 42 additions & 10 deletions src/go/corerepositorymock/repository_source_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,55 @@ import (

func NewRepositorySource() *RepositorySource {
return &RepositorySource{
AddRemoteCalls: make([]*url.URL, 0),
AddRemoteErrors: make(map[string]error),
addLocalCalls: make([]string, 0),
addLocalErrors: make(map[string]error),
addRemoteCalls: make([]*url.URL, 0),
addRemoteErrors: make(map[string]error),
ListLocalPaths: make([]string, 0),
ListRemoteUrls: make([]*url.URL, 0),
}
}

// Mock implementation for testing with RepositorySource.
type RepositorySource struct {
AddRemoteCalls []*url.URL
AddRemoteErrors map[string]error
addLocalCalls []string
addLocalErrors map[string]error
addRemoteCalls []*url.URL
addRemoteErrors map[string]error
ListLocalPaths []string
ListRemoteUrls []*url.URL
}

/* Local repositories */

func (source *RepositorySource) AddLocal(localPath string) error {
source.addLocalCalls = append(source.addLocalCalls, localPath)
return source.addLocalErrors[localPath]
}

func (source *RepositorySource) AddLocalExpected(expectedPaths ...string) {
ginkgo.GinkgoHelper()
Expect(source.addLocalCalls).To(ConsistOf(expectedPaths))
}

func (source *RepositorySource) AddLocalFails(path string, err error) {
source.addLocalErrors[path] = err
}

func (source *RepositorySource) ListLocal() (core.Repositories, error) {
repositories := make([]core.Repository, len(source.ListLocalPaths))
for i, localPath := range source.ListLocalPaths {
repositories[i] = core.LocalRepository(localPath)
}

return &core.RepositoriesArray{Repositories: repositories}, nil
}

/* Remote repositories */

func (source *RepositorySource) AddRemote(hostUrl *url.URL) error {
source.AddRemoteCalls = append(source.AddRemoteCalls, hostUrl)
return source.AddRemoteErrors[hostUrl.String()]
source.addRemoteCalls = append(source.addRemoteCalls, hostUrl)
return source.addRemoteErrors[hostUrl.String()]
}

func (source *RepositorySource) AddRemoteExpected(expectedHref string) {
Expand All @@ -36,8 +69,7 @@ func (source *RepositorySource) AddRemoteExpected(expectedHref string) {
}

func (source *RepositorySource) AddRemoteFails(faultyHref string, errorMsg string) {
ginkgo.GinkgoHelper()
source.AddRemoteErrors[faultyHref] = errors.New(errorMsg)
source.addRemoteErrors[faultyHref] = errors.New(errorMsg)
}

func (source *RepositorySource) AddRemoteNotExpected(unexpectedHref string) {
Expand All @@ -47,8 +79,8 @@ func (source *RepositorySource) AddRemoteNotExpected(unexpectedHref string) {
}

func (source *RepositorySource) addRemoteHrefs() []string {
actualHrefs := make([]string, len(source.AddRemoteCalls))
for i, call := range source.AddRemoteCalls {
actualHrefs := make([]string, len(source.addRemoteCalls))
for i, call := range source.addRemoteCalls {
actualHrefs[i] = call.String()
}

Expand Down
2 changes: 2 additions & 0 deletions src/go/cukefeature/cukefeature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ func TestFeatures(t *testing.T) {
}

func InitializeScenario(ctx *godog.ScenarioContext) {
step.AddLocalRepositorySteps(ctx)
step.AddMetaRepoSteps(ctx)
step.AddRemoteRepositorySteps(ctx)
step.AddRepositorySteps(ctx)
support.AddTo(ctx)
}
10 changes: 9 additions & 1 deletion src/go/cukefeature/repository.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ Feature: Repository
@LocalDir
Scenario: Meta Repos have no repositories when initialized
Given I have initialized a new meta repo
When I list local repositories in that meta repo
Then that repository listing should be empty
When I list remote repositories in that meta repo
Then that repository listing should be empty

@LocalDir
Scenario: A Meta Repo remembers local repositories
Given Git repositories on the local filesystem
And I have initialized a new meta repo
And I have registered those local repositories with a meta repo
When I list local repositories in that meta repo
Then that repository listing should be empty
Then that repository listing should include those local repositories

@LocalDir
Scenario: A Meta Repo remembers remote repositories
Expand Down
58 changes: 58 additions & 0 deletions src/go/cukestep/local_repo_steps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cukestep

import (
"fmt"
"path/filepath"

"github.com/cucumber/godog"
support "github.com/kkrull/marmot/cukesupport"
)

/* State */

var thoseLocalRepositories *support.LocalRepositories = support.NoLocalRepositories()

/* Configuration */

// Add step definitions related to repositories on the local filesystem.
func AddLocalRepositorySteps(ctx *godog.ScenarioContext) {
ctx.Given(`^Git repositories on the local filesystem$`, func() error {
if testDir, pathErr := support.TestDir(); pathErr != nil {
return pathErr
} else {
return createLocalRepo(filepath.Join(testDir, "empty-dir"))
}
})

ctx.Given(`^I have registered those local repositories with a meta repo$`, registerThoseLocals)

ctx.Then(`^that repository listing should include those local repositories$`, func() error {
if repoDir, pathErr := support.TestSubDir("empty-dir"); pathErr != nil {
return pathErr
} else {
thatListingShouldHaveLocals(repoDir)
return nil
}
})
}

/* Steps */

func createLocalRepo(repoDir string) error {
if repo, repoErr := support.InitLocalRepository(repoDir); repoErr != nil {
return repoErr
} else {
thoseLocalRepositories = support.SomeLocalRepositories(repo)
return nil
}
}

func registerThoseLocals() error {
if factory, factoryErr := support.ThatCommandFactory(); factoryErr != nil {
return fmt.Errorf("repository_steps: failed to configure; %w", factoryErr)
} else if registerCmd, factoryErr := factory.NewRegisterLocalRepositories(); factoryErr != nil {
return fmt.Errorf("repository_steps: failed to initialize; %w", factoryErr)
} else {
return registerCmd.Run(thoseLocalRepositories.LocalPaths()...)
}
}
4 changes: 1 addition & 3 deletions src/go/cukestep/meta_repo_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (

"github.com/cucumber/godog"
support "github.com/kkrull/marmot/cukesupport"
"github.com/kkrull/marmot/svcfs"
"github.com/kkrull/marmot/use"
)

// Add step definitions to manage the life cycle of a meta repo.
Expand All @@ -18,7 +16,7 @@ func AddMetaRepoSteps(ctx *godog.ScenarioContext) {
/* Steps */

func initNewMetaRepo(ctx *godog.ScenarioContext) error {
factory := use.NewCommandFactory().WithMetaDataAdmin(svcfs.NewJsonMetaRepoAdmin("42"))
factory := support.ThatCommandFactoryS("42")
if initCmd, factoryErr := factory.NewInitMetaRepo(); factoryErr != nil {
return fmt.Errorf("meta_repo_steps: failed to initialize; %w", factoryErr)
} else if thatMetaRepo, initErr := support.InitThatMetaRepo(ctx); initErr != nil {
Expand Down
38 changes: 38 additions & 0 deletions src/go/cukestep/remote_repo_steps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cukestep

import (
"fmt"
"net/url"

"github.com/cucumber/godog"
support "github.com/kkrull/marmot/cukesupport"
)

/* Configuration */

// Add step definitions related to remote repositories.
func AddRemoteRepositorySteps(ctx *godog.ScenarioContext) {
ctx.Given(`^I have registered remote repositories$`, func() error {
return registerRemote("https://github.com/actions/checkout")
})

ctx.Then(`^that repository listing should include those remote repositories$`, func() {
thatListingShouldHaveRemotes("https://github.com/actions/checkout")
})
}

/* Steps */

func registerRemote(remoteHref string) error {
if remoteUrl, parseErr := url.Parse(remoteHref); parseErr != nil {
return parseErr
} else if factory, factoryErr := support.ThatCommandFactory(); factoryErr != nil {
return fmt.Errorf("repository_steps: failed to configure; %w", factoryErr)
} else if registerCmd, factoryErr := factory.NewRegisterRemoteRepositories(); factoryErr != nil {
return fmt.Errorf("repository_steps: failed to initialize; %w", factoryErr)
} else if runErr := registerCmd.Run([]*url.URL{remoteUrl}); runErr != nil {
return fmt.Errorf("repository_steps: failed to register repositories; %w", runErr)
} else {
return nil
}
}
Loading

0 comments on commit a90bb8a

Please sign in to comment.