lfs

package
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: AGPL-3.0 Imports: 28 Imported by: 0

Documentation

Overview

Package lfs implements the Git LFS Batch API protocol, including HTTP handlers, HMAC-signed URL generation, and file locking.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrLockExists   = errors.New("lock already exists")
	ErrLockNotFound = errors.New("lock not found")
	ErrLockNotOwner = errors.New("not the lock owner")
)

Sentinel errors for lock operations.

Functions

This section is empty.

Types

type Action

type Action struct {
	Href      string            `json:"href"`
	Header    map[string]string `json:"header,omitempty"`
	ExpiresIn int64             `json:"expires_in,omitempty"`
	ExpiresAt string            `json:"expires_at,omitempty"`
}

Action describes an LFS transfer action (download, upload, verify).

type BatchRequest

type BatchRequest struct {
	Operation string   `json:"operation"`
	Transfers []string `json:"transfers,omitempty"`
	Ref       *Ref     `json:"ref,omitempty"`
	Objects   []Object `json:"objects"`
	HashAlgo  string   `json:"hash_algo,omitempty"`
}

BatchRequest is the incoming request to POST /objects/batch.

type BatchResponse

type BatchResponse struct {
	Transfer string           `json:"transfer,omitempty"`
	Objects  []ObjectResponse `json:"objects"`
	HashAlgo string           `json:"hash_algo,omitempty"`
}

BatchResponse is the response from POST /objects/batch.

type CreateLockRequest

type CreateLockRequest struct {
	Path string `json:"path"`
	Ref  *Ref   `json:"ref,omitempty"`
}

CreateLockRequest is the incoming request to POST /locks.

type CreateLockResponse

type CreateLockResponse struct {
	Lock             Lock   `json:"lock"`
	Message          string `json:"message,omitempty"`
	RequestID        string `json:"request_id,omitempty"`
	DocumentationURL string `json:"documentation_url,omitempty"`
}

CreateLockResponse is the response from POST /locks.

type ErrorResponse

type ErrorResponse struct {
	Message          string `json:"message"`
	RequestID        string `json:"request_id,omitempty"`
	DocumentationURL string `json:"documentation_url,omitempty"`
}

ErrorResponse is a top-level error response.

type FileLockStore

type FileLockStore struct {
	// contains filtered or unexported fields
}

FileLockStore persists locks as JSON files on the local filesystem. An in-memory cache avoids repeated disk reads; it is validated against the on-disk file's modification time and size on every access, so external modifications (manual edits, restores) are detected automatically.

Write operations (Create, Unlock) load and rewrite the entire lock list, making this implementation O(n) per mutation. This is well-suited for typical LFS deployments with moderate lock counts (hundreds to low thousands per endpoint). Deployments expecting very high lock volumes should consider a database-backed LockStore implementation.

func NewFileLockStore

func NewFileLockStore(basePath string) *FileLockStore

NewFileLockStore creates a FileLockStore rooted at basePath.

func (*FileLockStore) Create

func (s *FileLockStore) Create(ctx context.Context, endpoint, path, ownerName string) (Lock, error)

Create acquires a lock on the given path for the named owner.

func (*FileLockStore) List

func (s *FileLockStore) List(ctx context.Context, endpoint string, opts ListLocksOptions) ([]Lock, string, error)

List returns locks matching the given options with cursor-based pagination.

func (*FileLockStore) Unlock

func (s *FileLockStore) Unlock(ctx context.Context, endpoint, id, requester string, force bool) (Lock, error)

Unlock releases a lock by ID, checking ownership unless force is true.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler serves Git LFS API requests for a single endpoint.

func NewHandler

func NewHandler(cfg HandlerConfig) *Handler

NewHandler creates a Handler for the given configuration.

func (*Handler) BatchHandler

func (h *Handler) BatchHandler(w http.ResponseWriter, r *http.Request)

BatchHandler handles POST /objects/batch requests.

func (*Handler) CreateLockHandler

func (h *Handler) CreateLockHandler(w http.ResponseWriter, r *http.Request)

CreateLockHandler handles POST /{endpoint}/locks requests.

func (*Handler) DownloadHandler

func (h *Handler) DownloadHandler(w http.ResponseWriter, r *http.Request)

DownloadHandler handles GET /objects/{oid} requests.

The HMAC-signed URL already proves the batch handler authenticated and authorized this request. The batch response sets "authenticated": true, which tells the Git LFS client not to resend credentials on transfer requests.

func (*Handler) ListLocksHandler

func (h *Handler) ListLocksHandler(w http.ResponseWriter, r *http.Request)

ListLocksHandler handles GET /{endpoint}/locks requests. Only Accept is validated (not Content-Type) because GET requests have no body.

func (*Handler) UnlockHandler

func (h *Handler) UnlockHandler(w http.ResponseWriter, r *http.Request)

UnlockHandler handles POST /{endpoint}/locks/{id}/unlock requests.

func (*Handler) UploadHandler

func (h *Handler) UploadHandler(w http.ResponseWriter, r *http.Request)

UploadHandler handles PUT /objects/{oid} requests.

The HMAC-signed URL already proves the batch handler authenticated and authorized this request. The batch response sets "authenticated": true, which tells the Git LFS client not to resend credentials on transfer requests.

func (*Handler) VerifyHandler

func (h *Handler) VerifyHandler(w http.ResponseWriter, r *http.Request)

VerifyHandler handles POST /objects/verify requests.

func (*Handler) VerifyLocksHandler

func (h *Handler) VerifyLocksHandler(w http.ResponseWriter, r *http.Request)

VerifyLocksHandler handles POST /{endpoint}/locks/verify requests.

type HandlerConfig

type HandlerConfig struct {
	Endpoint      *config.Endpoint
	Store         storage.Adapter
	Auth          auth.Authenticator
	BaseURL       string
	MaxUploadSize int64
	Signer        *URLSigner
	LockStore     LockStore
}

HandlerConfig holds the dependencies for creating a Handler.

type ListLocksOptions

type ListLocksOptions struct {
	Path    string
	ID      string
	Cursor  string
	Limit   int
	Refspec string // Accepted per spec; not used for filtering (auth is repo-level).
}

ListLocksOptions configures lock listing queries.

type ListLocksResponse

type ListLocksResponse struct {
	Locks      []Lock `json:"locks"`
	NextCursor string `json:"next_cursor,omitempty"`
}

ListLocksResponse is the response from GET /locks.

type Lock

type Lock struct {
	ID       string    `json:"id"`
	Path     string    `json:"path"`
	LockedAt string    `json:"locked_at"`
	Owner    LockOwner `json:"owner"`
}

Lock represents a file lock.

type LockOwner

type LockOwner struct {
	Name string `json:"name"`
}

LockOwner identifies who holds a lock.

type LockStore

type LockStore interface {
	// Create acquires a lock on the given path. Returns ErrLockExists if the
	// path is already locked (the existing lock is returned alongside the error).
	Create(ctx context.Context, endpoint, path, ownerName string) (Lock, error)

	// List returns locks matching the given options with cursor-based pagination.
	// The returned string is the next cursor (empty if no more results).
	List(ctx context.Context, endpoint string, opts ListLocksOptions) ([]Lock, string, error)

	// Unlock releases a lock. Returns ErrLockNotFound if the lock does not
	// exist, or ErrLockNotOwner if the requester is not the owner and force
	// is false.
	Unlock(ctx context.Context, endpoint, id, requester string, force bool) (Lock, error)
}

LockStore manages file locks for LFS endpoints.

type Object

type Object struct {
	OID  string `json:"oid"`
	Size int64  `json:"size"`
}

Object represents a single LFS object in a batch request.

type ObjectError

type ObjectError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

ObjectError is an error attached to a single object in a batch response.

type ObjectResponse

type ObjectResponse struct {
	OID           string            `json:"oid"`
	Size          int64             `json:"size"`
	Authenticated *bool             `json:"authenticated,omitempty"`
	Actions       map[string]Action `json:"actions,omitempty"`
	Error         *ObjectError      `json:"error,omitempty"`
}

ObjectResponse is the per-object response within a batch response.

Authenticated is a pointer so the field is omitted from JSON when nil (object errors) but serialised as true/false when set (successful actions). The LFS spec uses this to tell the client whether the server already authenticated the action URLs, avoiding redundant credential prompts.

type Ref

type Ref struct {
	Name string `json:"name"`
}

Ref identifies a Git ref associated with a batch request. The spec allows servers to use this for ref-aware authorization (e.g., per-branch permissions). Anchor LFS authorises at the repository level via GitHub permissions, so the ref is accepted for protocol compliance but not used for access decisions.

type SignedURL

type SignedURL struct {
	Href      string
	ExpiresIn int64
	ExpiresAt string
}

SignedURL holds a signed URL together with its expiration metadata, all derived from a single timestamp to avoid clock skew between fields.

type URLSigner

type URLSigner struct {
	// contains filtered or unexported fields
}

URLSigner produces and validates HMAC-signed URLs with expiration.

func NewURLSigner

func NewURLSigner(key []byte, expiry time.Duration) (*URLSigner, error)

NewURLSigner creates a signer. If key is nil or empty, a random 32-byte key is generated in memory (callers should prefer persisting the key to disk).

func (*URLSigner) Close

func (s *URLSigner) Close() error

Close zeros the signing key in memory. It should be called during graceful shutdown to reduce the window in which key material is recoverable from a process memory dump.

func (*URLSigner) Expiry

func (s *URLSigner) Expiry() time.Duration

Expiry returns the configured URL expiry duration.

func (*URLSigner) Sign

func (s *URLSigner) Sign(baseURL, path string) SignedURL

Sign returns a signed URL with expiration query parameters appended. The baseURL is prepended to the signed path for the final href; the MAC is computed over the path and expiry only (not the scheme/host). The returned SignedURL includes expiration metadata derived from the same timestamp used for signing, ensuring consistency across all fields.

func (*URLSigner) Verify

func (s *URLSigner) Verify(requestURI string) error

Verify checks the signature and expiration of a request URI (path + query). Returns nil if valid.

The MAC is computed over "path?exp=<value>" only — the sig parameter and any other query parameters are excluded from the signed message. This means additional query parameters will not invalidate the signature, but they also will not be covered by it. Do not rely on unsigned query parameters for security-sensitive decisions.

type UnlockRequest

type UnlockRequest struct {
	Force bool `json:"force,omitempty"`
	Ref   *Ref `json:"ref,omitempty"`
}

UnlockRequest is the incoming request to POST /locks/:id/unlock.

type UnlockResponse

type UnlockResponse struct {
	Lock Lock `json:"lock"`
}

UnlockResponse is the response from POST /locks/:id/unlock.

type VerifyLocksRequest

type VerifyLocksRequest struct {
	Ref    *Ref   `json:"ref,omitempty"`
	Cursor string `json:"cursor,omitempty"`
	Limit  int    `json:"limit,omitempty"`
}

VerifyLocksRequest is the incoming request to POST /locks/verify.

type VerifyLocksResponse

type VerifyLocksResponse struct {
	Ours       []Lock `json:"ours"`
	Theirs     []Lock `json:"theirs"`
	NextCursor string `json:"next_cursor,omitempty"`
}

VerifyLocksResponse is the response from POST /locks/verify.

type VerifyRequest

type VerifyRequest struct {
	OID  string `json:"oid"`
	Size int64  `json:"size"`
}

VerifyRequest is the incoming request to POST /objects/verify.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL