Documentation
¶
Overview ¶
Package cache provides efficient caching of Git repositories with two-tier storage.
Overview ¶
The repository cache avoids repeated network operations by maintaining:
- Bare repositories (Tier 1): Single source of truth for Git objects
- Working tree checkouts (Tier 2): Multiple isolated checkouts per repository
- Metadata index: Tracks checkout lifecycle with TTL-based expiration
Architecture ¶
The cache uses a two-tier structure with git alternates for efficient storage:
~/.cache/git/
├── index.json # Metadata index
├── bare/ # Tier 1: Bare repositories (object storage)
│ └── github.com/
│ └── my/
│ └── repo.git/ # All git objects stored here
└── checkouts/ # Tier 2: Working trees
└── github.com/my/repo/
├── main/
│ ├── team-docs/ # Persistent checkout
│ └── build-abc123/ # Ephemeral checkout
└── v1.0.0/
└── prod-ref/
Checkouts use git alternates (.git/objects/info/alternates) to reference the bare repository's object database, avoiding duplication of git objects. This provides ~90% disk savings compared to copying objects to each checkout.
Usage ¶
Create a cache and get a checkout:
cache, err := cache.NewRepositoryCache("~/.cache/git")
if err != nil {
return err
}
// Persistent checkout with stable key
path, err := cache.GetCheckout(ctx, "https://github.com/my/repo", "team-docs",
cache.WithRef("main"))
// Ephemeral checkout with unique key and TTL
cacheKey := uuid.New().String()
path, err = cache.GetCheckout(ctx, "https://github.com/my/repo", cacheKey,
cache.WithRef("main"),
cache.WithTTL(1*time.Hour))
Start background garbage collection:
stop := cache.StartGC(5*time.Minute, cache.PruneExpired()) defer stop()
Cache Keys ¶
Checkouts are identified by a composite key: (URL + ref + cacheKey)
- Same composite key = reuses existing checkout
- Different cache key = creates isolated checkout
- Different ref = creates separate checkout for that ref
Use stable keys (e.g., "team-docs") for persistent checkouts that should be reused across calls. Use unique keys (e.g., UUID) for ephemeral checkouts that should be isolated and cleaned up after use.
Index ¶
- type CacheOption
- type CacheStats
- type CheckoutMetadata
- type PruneStrategy
- type RepositoryCache
- func (c *RepositoryCache) Clear(url string) error
- func (c *RepositoryCache) ClearAll() error
- func (c *RepositoryCache) GetCheckout(ctx context.Context, url, cacheKey string, opts ...CacheOption) (string, error)
- func (c *RepositoryCache) Prune(strategies ...PruneStrategy) error
- func (c *RepositoryCache) RemoveCheckout(url, cacheKey string) error
- func (c *RepositoryCache) StartGC(interval time.Duration, strategies ...PruneStrategy) (stop func())
- func (c *RepositoryCache) Stats() (*CacheStats, error)
- type RepositoryCacheOption
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CacheOption ¶
type CacheOption func(*cacheOptions)
CacheOption configures cache operations.
func WithAuth ¶
func WithAuth(auth git.Auth) CacheOption
WithAuth provides authentication for network operations.
Example:
auth, _ := git.SSHKeyFile("git", "~/.ssh/id_rsa")
path, _ := cache.GetCheckout(ctx, url, key, WithAuth(auth))
func WithDepth ¶
func WithDepth(depth int) CacheOption
WithDepth sets shallow clone depth (0 = full clone).
Example:
path, _ := cache.GetCheckout(ctx, url, key, WithDepth(1))
func WithRef ¶
func WithRef(ref string) CacheOption
WithRef specifies which Git reference to checkout (branch, tag, or commit). If not specified, uses the repository's default branch. The ref becomes part of the composite cache key.
Example:
path, _ := cache.GetCheckout(ctx, url, key, WithRef("v1.0.0"))
func WithTTL ¶
func WithTTL(ttl time.Duration) CacheOption
WithTTL sets time-to-live for automatic cleanup via Prune(). Checkouts with expired TTL are removed during pruning. Use for ephemeral checkouts (e.g., CI/CD builds).
Example:
// Auto-cleanup after 1 hour
path, _ := cache.GetCheckout(ctx, url, uuid.New().String(),
WithTTL(1*time.Hour))
func WithUpdate ¶
func WithUpdate() CacheOption
WithUpdate checks the remote and updates the cache if needed. Without this option, cached data is returned as-is (may be stale).
Example:
path, _ := cache.GetCheckout(ctx, url, key, WithUpdate())
type CacheStats ¶
type CacheStats struct {
BareRepos int // Number of bare repositories
Checkouts int // Number of checkouts
TotalSize int64 // Total disk usage in bytes
BareSize int64 // Disk usage of bare repositories
CheckoutsSize int64 // Disk usage of checkouts
OldestCheckout *time.Time
NewestCheckout *time.Time
}
CacheStats provides statistics about the cache.
type CheckoutMetadata ¶
type CheckoutMetadata struct {
URL string // Original repository URL
Ref string // Git reference (branch/tag/commit)
CacheKey string // User-provided cache key
CreatedAt time.Time // When checkout was created
LastAccess time.Time // Last time checkout was accessed
TTL *time.Duration // Time-to-live (nil = persistent)
ExpiresAt *time.Time // Computed expiration time
}
CheckoutMetadata tracks metadata for a single checkout.
type PruneStrategy ¶
type PruneStrategy interface {
ShouldPrune(metadata *CheckoutMetadata) bool
}
PruneStrategy determines which checkouts should be removed during pruning.
func PruneExpired ¶
func PruneExpired() PruneStrategy
PruneExpired removes checkouts with expired TTL. This is the default strategy if no strategies are provided.
func PruneOlderThan ¶
func PruneOlderThan(maxAge time.Duration) PruneStrategy
PruneOlderThan removes checkouts not accessed within the specified duration.
Example:
cache.Prune(PruneOlderThan(7*24*time.Hour)) // Remove if unused for 7 days
func PruneToSize ¶
func PruneToSize(maxBytes int64) PruneStrategy
PruneToSize removes oldest checkouts until total cache size is under limit. Removes least-recently-accessed checkouts first. Never removes checkouts without TTL (persistent checkouts).
Example:
cache.Prune(PruneToSize(10*1024*1024*1024)) // Keep under 10GB
type RepositoryCache ¶
type RepositoryCache struct {
// contains filtered or unexported fields
}
RepositoryCache manages a two-tier cache of Git repositories.
Tier 1 (Bare Cache): Stores bare repositories (Git database only) for efficient storage and network operations.
Tier 2 (Checkout Cache): Stores working trees for actual file access. Each checkout is identified by a composite key (URL + ref + cacheKey).
The cache maintains a metadata index for lifecycle management, supporting TTL-based expiration and automatic garbage collection.
func NewRepositoryCache ¶
func NewRepositoryCache(basePath string, opts ...RepositoryCacheOption) (*RepositoryCache, error)
NewRepositoryCache creates a new repository cache at the specified base path. The cache manages bare repositories and checkouts with metadata tracking.
The basePath typically points to ~/.cache/git or a similar cache directory. The cache creates two subdirectories (bare/, checkouts/) and an index.json file.
By default, NewRepositoryCache uses the local filesystem (osfs). A custom filesystem can be provided via WithFilesystem for testing purposes.
Example:
cache, err := cache.NewRepositoryCache("~/.cache/git")
// With custom filesystem (for testing)
cache, err := cache.NewRepositoryCache("/cache/path",
cache.WithFilesystem(memfs.New()))
func (*RepositoryCache) Clear ¶
func (c *RepositoryCache) Clear(url string) error
Clear removes all cached data for a specific URL. This includes the bare cache, all checkouts for that URL, and index entries.
Example:
cache.Clear("https://github.com/my/repo")
func (*RepositoryCache) ClearAll ¶
func (c *RepositoryCache) ClearAll() error
ClearAll removes all cached repositories and resets the cache. This removes all bare repositories, checkouts, and the index.
func (*RepositoryCache) GetCheckout ¶
func (c *RepositoryCache) GetCheckout(ctx context.Context, url, cacheKey string, opts ...CacheOption) (string, error)
GetCheckout returns a path to a working tree suitable for symlinking or temporary use.
The checkout is identified by a composite key: (url + ref + cacheKey). - Same composite key = reuses existing checkout (if it exists) - Different cache key = creates isolated checkout - Different ref = creates separate checkout for that ref
If the repository doesn't exist in cache, it's cloned. If WithUpdate() is specified, the cache is refreshed from the remote before returning.
The cacheKey controls checkout lifecycle: - Stable key (e.g., "team-docs") = persistent, reused across calls - Unique key (e.g., UUID) = ephemeral, intended for single use
Ephemeral checkouts should specify WithTTL() to enable automatic cleanup.
The returned path remains valid across calls and can be safely symlinked. Symlinks are preserved during updates via in-place refresh.
Examples:
// Persistent checkout with stable key
path, _ := cache.GetCheckout(ctx, "https://github.com/my/docs", "team-docs",
WithRef("main"))
os.Symlink(filepath.Join(path, "docs"), ".sow/refs/team-docs")
// Ephemeral checkout with unique key and TTL
cacheKey := uuid.New().String()
path, _ := cache.GetCheckout(ctx, "https://github.com/my/repo", cacheKey,
WithRef("main"),
WithTTL(1*time.Hour))
defer cache.RemoveCheckout(url, cacheKey)
// Force fresh update
path, _ := cache.GetCheckout(ctx, url, "build",
WithUpdate(),
WithAuth(auth))
func (*RepositoryCache) Prune ¶
func (c *RepositoryCache) Prune(strategies ...PruneStrategy) error
Prune removes checkouts based on the provided strategies. Common strategies include expired TTL, last access time, and cache size limits.
If no strategies are provided, defaults to removing expired checkouts.
Examples:
// Remove expired checkouts (TTL-based) cache.Prune() // Remove checkouts not accessed in 7 days cache.Prune(PruneOlderThan(7*24*time.Hour)) // Multiple strategies (OR logic) cache.Prune(PruneExpired(), PruneOlderThan(30*24*time.Hour))
func (*RepositoryCache) RemoveCheckout ¶
func (c *RepositoryCache) RemoveCheckout(url, cacheKey string) error
RemoveCheckout removes a specific checkout by URL and cache key. This removes the checkout directory, updates the index, and removes it from the in-memory cache.
Example:
cache.RemoveCheckout("https://github.com/my/repo", "build-abc123")
func (*RepositoryCache) StartGC ¶
func (c *RepositoryCache) StartGC(interval time.Duration, strategies ...PruneStrategy) (stop func())
StartGC starts a background garbage collector that periodically prunes the cache. The GC runs at the specified interval using the provided pruning strategies.
Returns a function to stop the garbage collector. This function is safe to call multiple times and will block until the GC goroutine has fully stopped.
The stop function should be called on shutdown or deferred to ensure clean shutdown.
Examples:
// Start GC that runs every 5 minutes, removing expired checkouts
stop := cache.StartGC(5*time.Minute, PruneExpired())
defer stop()
// Worker service with multiple strategies
stop := cache.StartGC(10*time.Minute,
PruneExpired(),
PruneOlderThan(24*time.Hour))
defer stop()
func (*RepositoryCache) Stats ¶
func (c *RepositoryCache) Stats() (*CacheStats, error)
Stats returns statistics about the cache (entries, disk usage, etc.).
type RepositoryCacheOption ¶
type RepositoryCacheOption func(*repositoryCacheOptions)
RepositoryCacheOption configures RepositoryCache creation.
func WithFilesystem ¶
func WithFilesystem(fs billy.Filesystem) RepositoryCacheOption
WithFilesystem sets the billy filesystem to use for cache operations. If not provided, defaults to osfs.New(basePath) rooted at the cache base path.
This option is primarily useful for testing, allowing use of memfs or other virtual filesystems.
Example:
cache, err := cache.NewRepositoryCache("/cache/path",
cache.WithFilesystem(memfs.New()))