Documentation
¶
Overview ¶
Package packfile provides efficient parsing of Git packfiles stored in object storage. It extracts commit OIDs using range requests to minimize data transfer costs.
Index ¶
- Variables
- func EmptyOIDIterator() iter.Seq[OID]
- type BasePackfile
- type BasePackfileManager
- type BlobStorePackfileParser
- func (p *BlobStorePackfileParser) ExtractCommitOIDs(ctx context.Context, packfileKey, indexKey string) ([]OID, error)
- func (p *BlobStorePackfileParser) WithMaxClusterSize(size int64) *BlobStorePackfileParser
- func (p *BlobStorePackfileParser) WithMaxConcurrentFetches(n int) *BlobStorePackfileParser
- func (p *BlobStorePackfileParser) WithMaxGap(gap int64) *BlobStorePackfileParser
- func (p *BlobStorePackfileParser) WithMaxIterations(maxIter int) *BlobStorePackfileParser
- func (p *BlobStorePackfileParser) WithMinClusterSize(size int64) *BlobStorePackfileParser
- type ExtractionCallback
- type InMemoryBasePackfileManager
- type OID
- type ObjectType
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidOIDLength is returned when an OID has an invalid length. ErrInvalidOIDLength = errors.New("invalid OID length: expected 20 (SHA-1) or 32 (SHA-256) bytes") // ErrInvalidHexString is returned when a hex string cannot be decoded. ErrInvalidHexString = errors.New("invalid hex string") )
var ( // ErrMaxIterationsExceeded is returned when delta resolution exceeds the maximum number of iterations. ErrMaxIterationsExceeded = errors.New("exceeded maximum iterations while resolving delta chains") // ErrEmptyData is returned when attempting to parse an empty data buffer. ErrEmptyData = errors.New("empty data") // ErrTruncatedOFSDelta is returned when an OFS_DELTA object is truncated. ErrTruncatedOFSDelta = errors.New("truncated OFS_DELTA") // ErrTruncatedREFDelta is returned when a REF_DELTA object is truncated. ErrTruncatedREFDelta = errors.New("truncated REF_DELTA") // ErrUnknownObjectType is returned when an unknown object type is encountered. ErrUnknownObjectType = errors.New("unknown object type") // ErrOffsetTooLarge is returned when an offset exceeds int64 maximum. ErrOffsetTooLarge = errors.New("offset too large") )
var ( // ErrPackfileNotFound is returned when a packfile doesn't exist for the given repository. ErrPackfileNotFound = errors.New("base packfile not found") )
Functions ¶
func EmptyOIDIterator ¶ added in v1.24.1
EmptyOIDIterator returns an iterator that yields no OIDs.
Types ¶
type BasePackfile ¶
type BasePackfile struct {
// Repository is the repository path (e.g., "gitlab-org/gitlab").
Repository string `json:"repository"`
// PackfileURI is the full URI to the .pack file in object storage.
// Example: "s3://packhorse-packfiles/gitlab-org/gitlab/base-20250126.pack"
PackfileURI string `json:"packfile_uri"`
// IndexURI is the full URI to the .idx file in object storage.
// Derived from PackfileURI by replacing .pack with .idx.
IndexURI string `json:"index_uri"`
// HashAlgorithm is the hash algorithm used ("sha1" or "sha256").
HashAlgorithm string `json:"hash_algorithm"`
// PackfileHash is the hash of the packfile content (binary format).
PackfileHash OID `json:"packfile_hash"`
// CreatedAt is when the packfile became available (extraction completed).
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is when the packfile metadata was last updated.
UpdatedAt time.Time `json:"updated_at"`
// SizeBytes is the size of the .pack file in bytes.
SizeBytes int64 `json:"size_bytes,omitempty"`
}
BasePackfile tracks an available base packfile and its metadata. Base packfiles are generated externally and registered with Packhorse via API.
BasePackfile instances only exist after successful commit ID extraction. The lifecycle is atomic from the client's perspective: either a packfile exists with all its commit OIDs, or it doesn't exist at all.
BasePackfile contains only the metadata that would be stored in a distributed store like Redis. Commit OIDs are returned alongside the metadata by the manager's Get() method as a separate iterator, not stored in this struct.
BasePackfile instances are immutable after creation. Callers must not modify any fields.
type BasePackfileManager ¶
type BasePackfileManager interface {
// Register starts asynchronous commit ID extraction for a base packfile.
// The packfile won't be visible via Get() until extraction completes successfully.
// Returns immediately (non-blocking).
//
// Parameters:
// - repository: Repository path (e.g., "gitlab-org/gitlab")
// - packfileURI: URI to .pack file in object storage
// - hashAlgorithm: "sha1" or "sha256"
// - packfileHash: Hash of the packfile (hex-encoded string)
//
// If extraction fails, the packfile never becomes visible. The error is logged
// but not exposed to clients.
Register(ctx context.Context, repository, packfileURI, hashAlgorithm, packfileHash string) error
// Get returns the base packfile metadata and a commit OID iterator for a repository.
// Both are captured from the same snapshot, guaranteeing consistency.
// Returns (nil, empty iterator, nil) if the packfile hasn't been extracted yet or doesn't exist.
// The returned BasePackfile is immutable and must not be modified.
//
// Usage:
// bp, commitOIDs, err := manager.Get(ctx, "gitlab-org/gitlab")
// if bp != nil {
// for oid := range commitOIDs {
// // use oid
// }
// }
Get(ctx context.Context, repository string) (*BasePackfile, iter.Seq[OID], error)
// Delete removes a base packfile.
// Returns an error if the packfile doesn't exist.
Delete(ctx context.Context, repository string) error
}
BasePackfileManager tracks base packfiles and their metadata. Base packfiles are generated externally and registered via API.
The lifecycle is atomic from the client's perspective: either a packfile exists with all its commit OIDs, or it doesn't exist at all. Clients never see intermediate extraction states or failures.
Phase 1 uses an in-memory implementation with sync.RWMutex. Phase 2 will use Redis for multi-instance coordination.
type BlobStorePackfileParser ¶
type BlobStorePackfileParser struct {
// contains filtered or unexported fields
}
BlobStorePackfileParser efficiently extracts commit OIDs from packfiles stored in object storage by using range requests to fetch only necessary data.
func NewBlobStorePackfileParser ¶
func NewBlobStorePackfileParser(bucket *blob.Bucket) *BlobStorePackfileParser
NewBlobStorePackfileParser creates a new parser that reads from the given bucket. The parser will use default values for clustering and iteration limits.
func (*BlobStorePackfileParser) ExtractCommitOIDs ¶
func (p *BlobStorePackfileParser) ExtractCommitOIDs(ctx context.Context, packfileKey, indexKey string) ([]OID, error)
ExtractCommitOIDs extracts all commit OIDs from a packfile by: 1. Parsing the index file to get all [OID, offset] pairs 2. Iteratively resolving object types with delta resolution:
- Cluster work items into efficient byte ranges
- Fetch each range and resolve objects within it
- Follow delta chains recursively within ranges
- Add unresolved items (bases outside range) back to workqueue
3. Repeat until all objects are resolved
This algorithm efficiently handles OFS_DELTA and REF_DELTA objects by batching range requests and resolving delta chains within each range.
Returns all commit OIDs found in the packfile.
func (*BlobStorePackfileParser) WithMaxClusterSize ¶
func (p *BlobStorePackfileParser) WithMaxClusterSize(size int64) *BlobStorePackfileParser
WithMaxClusterSize sets the maximum size of a single range cluster. Larger values reduce the number of range requests but may fetch more unnecessary data.
func (*BlobStorePackfileParser) WithMaxConcurrentFetches ¶
func (p *BlobStorePackfileParser) WithMaxConcurrentFetches(n int) *BlobStorePackfileParser
WithMaxConcurrentFetches sets the maximum number of concurrent range fetch operations. Higher values increase parallelism but also increase memory usage and connection count.
func (*BlobStorePackfileParser) WithMaxGap ¶
func (p *BlobStorePackfileParser) WithMaxGap(gap int64) *BlobStorePackfileParser
WithMaxGap sets the maximum gap between objects before creating a new cluster. Smaller values create more separate ranges, larger values may fetch more unused data.
func (*BlobStorePackfileParser) WithMaxIterations ¶
func (p *BlobStorePackfileParser) WithMaxIterations(maxIter int) *BlobStorePackfileParser
WithMaxIterations sets the maximum number of resolution iterations. This is useful for testing or adjusting security constraints. A lower value provides more DoS protection but may fail on legitimate packfiles with deep or scattered delta chains. A higher value allows more complex packfiles but increases vulnerability to adversarial inputs.
func (*BlobStorePackfileParser) WithMinClusterSize ¶
func (p *BlobStorePackfileParser) WithMinClusterSize(size int64) *BlobStorePackfileParser
WithMinClusterSize sets the minimum size to read for each object. This ensures enough data is read to handle objects with delta information.
type ExtractionCallback ¶
ExtractionCallback performs synchronous commit ID extraction from a packfile. It receives the repository name, packfile URI, and index URI, and returns the extracted commit OIDs or an error.
The manager calls this in a goroutine to perform async extraction. On success, the packfile becomes available. On error, the packfile never appears to clients.
type InMemoryBasePackfileManager ¶
type InMemoryBasePackfileManager struct {
// contains filtered or unexported fields
}
InMemoryBasePackfileManager is a thread-safe in-memory implementation of BasePackfileManager. This is the Phase 1 implementation suitable for single-instance deployments. Phase 2 will introduce a Redis-backed implementation for multi-instance coordination.
The implementation only stores packfiles after successful extraction. Failed extractions never create entries, so no cleanup is needed.
func NewInMemoryBasePackfileManager ¶
func NewInMemoryBasePackfileManager(callback ExtractionCallback) *InMemoryBasePackfileManager
NewInMemoryBasePackfileManager creates a new in-memory base packfile manager. The extraction callback performs synchronous extraction and is called in a goroutine.
func (*InMemoryBasePackfileManager) Delete ¶
func (m *InMemoryBasePackfileManager) Delete(ctx context.Context, repository string) error
Delete removes a base packfile registration.
func (*InMemoryBasePackfileManager) Get ¶
func (m *InMemoryBasePackfileManager) Get(ctx context.Context, repository string) (*BasePackfile, iter.Seq[OID], error)
Get returns the base packfile metadata and a commit OID iterator for a repository. Both are captured from the same snapshot under a single lock acquisition, guaranteeing consistency between metadata and OIDs. Returns (nil, empty iterator, nil) if no packfile exists.
func (*InMemoryBasePackfileManager) Register ¶
func (m *InMemoryBasePackfileManager) Register(ctx context.Context, repository, packfileURI, hashAlgorithm, packfileHash string) error
Register starts asynchronous commit ID extraction for a base packfile. The packfile won't be visible until extraction completes successfully.
type OID ¶
type OID struct {
// contains filtered or unexported fields
}
OID represents an immutable Git object identifier in binary form. For SHA-1: 20 bytes, for SHA-256: 32 bytes. The internal data slice is unexported to enforce immutability. This reduces memory usage by 50% compared to hex-encoded strings.
func OIDFromHex ¶
OIDFromHex creates an OID from a hex-encoded string.
func OIDMustParse ¶
OIDMustParse creates an OID from a hex-encoded string, panicking on error. This is primarily useful in tests and initialization code where the input is known to be valid.
func (OID) Bytes ¶
Bytes returns a copy of the OID bytes. The copy ensures the OID remains immutable even if the caller modifies the result.
func (OID) MarshalJSON ¶
MarshalJSON implements json.Marshaler, encoding OID as a hex string.
func (*OID) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler, decoding from a hex string.
type ObjectType ¶
type ObjectType int
ObjectType represents the type of a Git object in a packfile.
const ( ObjectCommit ObjectType = 1 ObjectTree ObjectType = 2 ObjectBlob ObjectType = 3 ObjectTag ObjectType = 4 ObjectOfsDelta ObjectType = 6 ObjectRefDelta ObjectType = 7 )