Documentation
¶
Overview ¶
Package interfaces defines the core interfaces for Hitch's dependency injection and testability architecture.
Overview ¶
This package contains interface definitions that enable:
- Dependency injection throughout the codebase
- Easy mocking for unit tests
- Decoupling between packages
- Flexibility to swap implementations
By programming to interfaces rather than concrete types, Hitch achieves:
- Better testability (can mock any dependency)
- Cleaner separation of concerns
- Easier refactoring and maintenance
- Support for alternative implementations
Core Interfaces ¶
The package defines interfaces for all major Hitch components:
Repository Interface:
type Repository interface {
CurrentBranch() (string, error)
CurrentCommitSHA() (string, error)
Checkout(ref string) error
BranchExists(name string) bool
CreateBranch(name string, fromRef string) error
DeleteBranch(name string, force bool) error
Merge(branch string, message string) error
// ... and more
}
This interface abstracts Git operations, allowing tests to use mock repositories instead of real Git repositories.
Interface Categories ¶
Interfaces are organized by functionality:
Git Operations:
- Repository: Core Git repository operations
- RepositoryValidator: Repository validation and health checks
Safety and Testing:
- TempBranchTester: Safe merge testing in temporary branches
Metadata Management:
- MetadataReader: Reading hitch.json and metadata
- MetadataWriter: Writing and updating metadata
- LockManager: Environment locking and concurrency control
Hook System:
- HookExecutor: Executing pre/post operation hooks
Usage Patterns ¶
Using interfaces for dependency injection:
// Function accepts interface, not concrete type
func PromoteBranch(repo interfaces.Repository, meta interfaces.MetadataWriter) error {
// Implementation works with any type that satisfies the interface
currentBranch, err := repo.CurrentBranch()
if err != nil {
return err
}
// ... rest of implementation
}
// Production code uses real implementation
repo, _ := git.OpenRepo(".")
writer := metadata.NewWriter(repo)
err := PromoteBranch(repo, writer)
// Test code uses mocks
mockRepo := &MockRepository{...}
mockWriter := &MockMetadataWriter{...}
err := PromoteBranch(mockRepo, mockWriter)
Repository Interface ¶
The Repository interface defines all Git operations needed by Hitch:
Branch operations:
CurrentBranch() (string, error) BranchExists(name string) bool CreateBranch(name string, fromRef string) error DeleteBranch(name string, force bool) error
Checkout and navigation:
Checkout(ref string) error CurrentCommitSHA() (string, error)
Merge operations:
Merge(branch string, message string) error HasUncommittedChanges(branch string) (bool, error)
User information:
UserName() (string, error) UserEmail() (string, error)
Repository state:
WorkingTree() string IsGitRepository() bool
TempBranchTester Interface ¶
Defines the interface for safe merge testing:
type TempBranchTester interface {
TestMerge(ctx context.Context, config safety.TestConfig) safety.TestResult
}
This is the core safety mechanism - all operations are tested in temporary branches before being applied to actual environment branches.
LockManager Interface ¶
Manages environment locks for concurrency control:
type LockManager interface {
StartSession() error
EndSession()
AcquireLock(req metadata.LockRequest) (metadata.LockResult, error)
ReleaseLock(environment, user string) error
IsLocked(environment string) bool
RecoverLocks(user string, force bool) (metadata.RecoveryReport, error)
}
Prevents race conditions when multiple users operate on the same environment.
MetadataReader and MetadataWriter ¶
Separate interfaces for reading and writing metadata:
type MetadataReader interface {
Read() (*metadata.Metadata, error)
Exists() bool
}
type MetadataWriter interface {
Write(meta *metadata.Metadata) error
CreateOrphanBranch() error
}
This separation follows the Interface Segregation Principle - code that only reads metadata doesn't get write capabilities.
HookExecutor Interface ¶
Defines hook execution capabilities:
type HookExecutor interface {
ExecuteHooks(ctx context.Context, hookType hooks.HookType,
hookCtx hooks.HookContext) []hooks.HookResult
}
Allows custom hook execution logic while maintaining a consistent interface.
Testing Benefits ¶
Interfaces enable comprehensive testing without real Git operations:
type MockRepository struct {
CurrentBranchFunc func() (string, error)
CheckoutFunc func(ref string) error
// ... other functions
}
func (m *MockRepository) CurrentBranch() (string, error) {
if m.CurrentBranchFunc != nil {
return m.CurrentBranchFunc()
}
return "main", nil
}
// In tests
mockRepo := &MockRepository{
CurrentBranchFunc: func() (string, error) {
return "feature/test", nil
},
}
Implementation Requirements ¶
Types implementing these interfaces must:
- Handle all documented error cases
- Be thread-safe where indicated
- Follow the documented contracts
- Return meaningful error messages
Interface Evolution ¶
When adding methods to interfaces:
- Consider backwards compatibility
- Add new methods to end of interface
- Update all implementations
- Update documentation
- Consider creating new interface if major changes needed
Real Implementations ¶
Concrete implementations of these interfaces:
- Repository: git.Repo (internal/git/repo.go)
- RepositoryValidator: git.RepositoryValidator (internal/git/validator.go)
- TempBranchTester: safety.TempBranchTester (internal/safety/temp_branch.go)
- MetadataReader: metadata.Reader (internal/metadata/reader.go)
- MetadataWriter: metadata.Writer (internal/metadata/writer.go)
- LockManager: metadata.LockManager (internal/metadata/lock_manager.go)
- HookExecutor: hooks.HookExecutor (internal/hooks/types.go)
Design Principles ¶
The interfaces follow these design principles:
- Single Responsibility: Each interface has one clear purpose
- Interface Segregation: Clients depend only on methods they use
- Dependency Inversion: Depend on abstractions, not concretions
- Minimal Surface Area: Interfaces expose only necessary methods
Example: Full Dependency Injection ¶
type PromoteService struct {
repo interfaces.Repository
metaReader interfaces.MetadataReader
metaWriter interfaces.MetadataWriter
tempTester interfaces.TempBranchTester
lockManager interfaces.LockManager
hookExecutor interfaces.HookExecutor
}
func NewPromoteService(
repo interfaces.Repository,
metaReader interfaces.MetadataReader,
metaWriter interfaces.MetadataWriter,
tempTester interfaces.TempBranchTester,
lockManager interfaces.LockManager,
hookExecutor interfaces.HookExecutor,
) *PromoteService {
return &PromoteService{
repo: repo,
metaReader: metaReader,
metaWriter: metaWriter,
tempTester: tempTester,
lockManager: lockManager,
hookExecutor: hookExecutor,
}
}
func (s *PromoteService) Promote(branch, environment string) error {
// Use all dependencies through their interfaces
// Easy to test with mocks
// Easy to swap implementations
}
Best Practices ¶
When working with interfaces:
- Accept interfaces, return structs (in most cases)
- Keep interfaces small and focused
- Define interfaces in the package that uses them
- Don't create interfaces until you need them (YAGNI)
- Use embedding for interface composition when appropriate
Dependencies ¶
This package depends on:
- internal/git (for git.ValidationResult)
- internal/metadata (for metadata types)
- internal/safety (for safety types)
- internal/hooks (for hook types)
However, it only uses types, not implementations, avoiding circular dependencies.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type HookExecutor ¶
type HookExecutor interface {
ExecuteHooks(ctx context.Context, hookType hooks.HookType, hookCtx hooks.HookContext) []hooks.HookResult
}
HookExecutor defines the interface for executing hooks
type LockManager ¶
type LockManager interface {
StartSession() error
EndSession()
AcquireLock(req metadata.LockRequest) (metadata.LockResult, error)
ReleaseLock(environment, user string) error
IsLocked(environment string) bool
RecoverLocks(user string, force bool) (metadata.RecoveryReport, error)
}
LockManager defines the interface for managing environment locks
type MetadataReader ¶
MetadataReader defines the interface for reading metadata
type MetadataWriter ¶
MetadataWriter defines the interface for writing metadata
type Repository ¶
type Repository interface {
// Basic repository operations
CurrentBranch() (string, error)
CurrentCommitSHA() (string, error)
Checkout(ref string) error
BranchExists(name string) bool
CreateBranch(name string, fromRef string) error
DeleteBranch(name string, force bool) error
// Branch operations
GetBranches() ([]string, error)
GetBranchesMatching(pattern string) ([]string, error)
// Git operations
Merge(branch string, message string) error
UserName() (string, error)
UserEmail() (string, error)
HasUncommittedChanges(branch string) (bool, error)
WorkingTree() string
IsGitRepository() bool
}
Repository defines the interface for Git repository operations This interface allows for better testability and flexibility
type RepositoryValidator ¶
type RepositoryValidator interface {
ValidateRepository() git.ValidationResult
}
RepositoryValidator defines the interface for repository validation
type TempBranchTester ¶
type TempBranchTester interface {
TestMerge(ctx context.Context, config safety.TestConfig) safety.TestResult
}
TempBranchTester defines the interface for testing merge operations safely