Documentation
¶
Overview ¶
Package filter implements the Git process boundary of FXVCS: the long-running clean/smudge filter (Git filter protocol version 2), the pointer-aware diff and merge drivers, the pre-push publication barrier, and launcher installation into repository-local Git config.
Normative behaviour is documented in docs/spec/filter-process.md, docs/spec/diff-merge-drivers.md, docs/spec/hook-integration.md, docs/spec/launcher-installation.md and docs/spec/failure-semantics.md.
Index ¶
- Constants
- Variables
- func DefaultLauncherPath() (string, error)
- func DefaultLegacyLauncherPath() (string, bool, error)
- func FormatUpgradeRequired(e *format.UpgradeRequiredError) string
- func HookScript(launcher string) string
- func Install(ctx context.Context, git *gitclient.Client, launcher string) error
- func LauncherPath(goos string, getenv func(string) string, home string) (string, error)
- func LegacyLauncherPath(goos string, getenv func(string) string, home string) (string, bool, error)
- func PrePush(ctx context.Context, co *Checkout, remoteName string, refs []PushRef, ...) error
- func PrePushRefusalMessage(res HookInstallResult, launcher string) string
- func QuoteCommandPath(p, goos string) string
- func RenderDiff(w io.Writer, path string, oldSide, newSide Side) error
- func RunDiffDriver(args []string, installed format.Version, stdout io.Writer) error
- func RunOneShot(ctx context.Context, opts ProcessOptions, command, pathname string, ...) error
- func RunPrePush(ctx context.Context, opts ProcessOptions, remoteName string, stdin io.Reader, ...) error
- func RunProcess(ctx context.Context, opts ProcessOptions, stdin io.Reader, ...) error
- func Serve(ctx context.Context, r io.Reader, w io.Writer, h Handler, log io.Writer) error
- func Uninstall(ctx context.Context, git *gitclient.Client) error
- func WholeManifest(domainID, oid string, size int64) domain.ObjectManifest
- func WholePointer(domainID, oid string, size int64) (pointer.Pointer, error)
- type Backend
- type Checkout
- type ConfigEntry
- type Engine
- type Handler
- type HookInstallResult
- type MergeOutcome
- type MergeResult
- type PendingObject
- type ProcessOptions
- type PublicationBarrierError
- type PublishFailure
- type PushRef
- type Request
- type Side
Constants ¶
const ( ConflictOurs = "<<<<<<< ours\n" ConflictSep = "=======\n" ConflictTheirs = ">>>>>>> theirs\n" )
Conflict layout markers.
const HookMarker = "# fxvcs-managed pre-push hook"
HookMarker identifies a hook file written by fxvcs.
const (
// MaxPacketPayload is the largest payload one packet may carry.
MaxPacketPayload = 65516
)
pkt-line framing as used by Git's long-running filter protocol: each packet is a 4-hex-digit length (including the 4 length bytes) followed by payload. "0000" is the flush packet that terminates a list.
const (
ProtocolVersion = format.FilterProtocolVersion
)
Protocol constants (git/Documentation/gitattributes: "Long Running Filter Process").
Variables ¶
var ( // ErrUnsupportedPointer wraps pointer.ErrUnsupportedVersion with the // installed/required detail required at the byte boundary. ErrUnsupportedPointer = errors.New("filter: unsupported pointer format version") // ErrMalformedPointer is a blob that starts like a pointer but is invalid. ErrMalformedPointer = errors.New("filter: malformed pointer") // ErrObjectMismatch is a cached object whose bytes/size disagree with the // pointer; the working file is never written from it. ErrObjectMismatch = errors.New("filter: cached object does not match pointer") )
Errors surfaced by the engine.
var ErrHookExists = errors.New("filter: a pre-push hook that is not managed by fxvcs already exists")
ErrHookExists is returned when a user hook already occupies pre-push.
var ErrNoPublicationLedger = errors.New("filter: publication ledger unavailable; cannot verify that pushed objects are published")
ErrNoPublicationLedger is returned when the hook runs without an engine Backend: publication cannot be judged, so the push is refused rather than waved through.
var ErrNotPointerInput = errors.New("filter: input is not an fxvcs pointer")
ErrNotPointerInput is returned by drivers when they need pointer bytes and receive something else.
ErrObjectUnavailable is returned by smudge for a path recorded as hydrated whose object is neither cached nor obtainable from a configured remote.
var ErrProtocol = errors.New("filter: protocol error")
ErrProtocol is wrapped by every handshake/framing failure.
Functions ¶
func DefaultLauncherPath ¶
DefaultLauncherPath returns the stable per-user launcher path for this OS.
func DefaultLegacyLauncherPath ¶
DefaultLegacyLauncherPath is LegacyLauncherPath for the running OS.
func FormatUpgradeRequired ¶
func FormatUpgradeRequired(e *format.UpgradeRequiredError) string
FormatUpgradeRequired renders the multi-line stderr block every Git entry point prints when the repository requires a newer client.
func HookScript ¶
HookScript renders the pre-push hook script that chains to the launcher.
func Install ¶
Install writes GitConfigEntries(launcher) into the repository-local config of the checkout that git operates on. Values are set with `git config --local`, which is atomic per key; the set is idempotent.
func LauncherPath ¶
LauncherPath computes the launcher path for goos using getenv and home. It is separated from DefaultLauncherPath so all three shapes can be tested on one machine.
func LegacyLauncherPath ¶
LegacyLauncherPath returns the exact former default launcher path when this OS has one. It is used only to recognize fxvcs-owned defaults during migration; arbitrary recorded paths remain custom and are never moved.
func PrePush ¶
func PrePush(ctx context.Context, co *Checkout, remoteName string, refs []PushRef, log io.Writer) error
PrePush implements the publication barrier for one push. remoteName is the first hook argument (used to select commits not yet on that remote when the remote ref is new). It scans the added/modified paths of every commit being pushed, keeps those whose filter attribute is fxvcs, decodes each pointer, publishes every object that is not yet recorded on a requiredForPublication remote (Backend.Publish uploads to bound remotes and records the result), and fails only when an object still lacks a publication record afterwards — because a required remote is unbound or unreachable, or the object is no longer available locally. Git refs therefore never become remotely reachable before their assets are durable, and the common path needs no separate `fxvcs objects publish`.
func PrePushRefusalMessage ¶
func PrePushRefusalMessage(res HookInstallResult, launcher string) string
PrePushRefusalMessage is the stderr text printed when a hook must be chained manually.
func QuoteCommandPath ¶
QuoteCommandPath quotes an executable path for use inside a git config command value. Git runs these values through `sh -c` (Git for Windows ships its own sh), so:
- on Windows backslashes become forward slashes, which every Git for Windows sh and the Windows loader accept, avoiding sh escaping rules;
- the path is wrapped in double quotes when it contains a space or any shell-special character, and `"`, `$`, backtick, and backslash are escaped inside the quotes.
func RenderDiff ¶
RenderDiff writes the side-by-side pointer metadata view and a one-line summary. Output is deterministic and contains no host paths.
func RunDiffDriver ¶
RunDiffDriver is the `fxvcs diff-driver` entry point (GIT_EXTERNAL_DIFF argument convention: path oldSide-file oldSide-hex oldSide-mode newSide-file newSide-hex newSide-mode). With a single argument (unmerged path) it prints a note.
func RunOneShot ¶
func RunOneShot(ctx context.Context, opts ProcessOptions, command, pathname string, stdin io.Reader, stdout, stderr io.Writer) error
RunOneShot is the `fxvcs clean-filter <path>` / `smudge-filter <path>` entry point for debugging and `git cat-file --filters`-style use: it reads the entire content from stdin and writes the result to stdout.
func RunPrePush ¶
func RunPrePush(ctx context.Context, opts ProcessOptions, remoteName string, stdin io.Reader, stderr io.Writer) error
RunPrePush is the `fxvcs pre-push <remote> <url>` entry point.
func RunProcess ¶
func RunProcess(ctx context.Context, opts ProcessOptions, stdin io.Reader, stdout, stderr io.Writer) error
RunProcess is the `fxvcs filter-process` entry point. It preflights the checkout, then serves the long-running filter protocol on stdin/stdout. Diagnostics go to stderr, which Git forwards to the user. It returns *format.UpgradeRequiredError (unwrapped) when the repository requires a newer client so the CLI can choose the upgrade-required exit code.
func Serve ¶
Serve speaks the long-running filter protocol on r/w until Git closes the stream. It returns nil on a clean EOF and a wrapped ErrProtocol on framing or handshake violations. Handler errors are reported to Git per command and do not stop the server; they are also written to log (may be nil).
func WholeManifest ¶
func WholeManifest(domainID, oid string, size int64) domain.ObjectManifest
WholeManifest builds the whole/1, uncompressed manifest for an object.
Types ¶
type Backend ¶
type Backend interface {
// Ingest records content as pending publication and returns the
// deterministic pointer (identical bytes → identical pointer).
Ingest(ctx context.Context, content io.Reader, path string) (pointer.Pointer, error)
// HydrationIntent reports the intent recorded for path. known is false
// when no row (or no state database) exists: the unhydrated default.
HydrationIntent(ctx context.Context, path string) (hydrated, known bool, err error)
// RecordHydrated notes, best effort, that the working file at path holds
// the real bytes of digest (the clean filter just ingested them from it).
RecordHydrated(ctx context.Context, path, digest string, size int64) error
// FetchObject brings digest into the local cache from a configured
// remote, verifying while streaming. objectstore.ErrNotFound when none
// has it.
FetchObject(ctx context.Context, digest string) error
// ContentObjects returns every object digest the pointer depends on: the
// file itself for whole/1, the manifest and its chunks for a chunked
// encoding. It may need the manifest, so it can touch a remote.
ContentObjects(ctx context.Context, ptr pointer.Pointer) ([]string, error)
// OpenContent returns the whole file behind a pointer, fetching whatever
// is missing and verifying every byte against the manifest and the
// pointer. Any error means "no valid content"; the caller must not write
// a partial result.
OpenContent(ctx context.Context, ptr pointer.Pointer) (io.ReadCloser, error)
// MissingRemotes returns the requiredForPublication remotes that hold no
// publication record for the pointer's object.
MissingRemotes(ctx context.Context, ptr pointer.Pointer) ([]string, error)
// MissingRemotesMany is MissingRemotes for many pointers at once, keyed by
// pointer oid. The barrier uses it instead of a loop: against a network
// object store, one query per asset is one sequential round trip per
// asset, paid before any upload starts.
MissingRemotesMany(ctx context.Context, ptrs []pointer.Pointer) (map[string][]string, error)
// RequiredRemotes lists the requiredForPublication remote names.
RequiredRemotes() []string
// Publish uploads the given pending objects (by digest) to every
// requiredForPublication remote bound on this machine and records the
// result in the ledger. It returns per-object failures instead of
// stopping at the first, and never pushes Git refs. log receives
// human-readable progress (Git shows hook stderr to the user).
Publish(ctx context.Context, digests []string, log io.Writer) []PublishFailure
// Close releases the backend (databases, locks).
Close() error
}
Backend is the engine the Git process boundary delegates to for everything that must agree with the SDK operations: ingestion (cache pin + publication ledger row through the single ingestion path), hydration intent recorded in state.db, remote fetches, and the publication ledger consulted by the pre-push barrier. It is implemented over app.Repo by internal/filter/wire; this package never imports the engine so the dependency stays one-way.
Every method is bounded local work except FetchObject and MissingRemotes, which may talk to configured remotes; the caller holds no lock around them.
type Checkout ¶
type Checkout struct {
Root string // working-tree top level
Repo *domain.Repository
Git *gitclient.Client
Cache *cache.Cache
Domain string
// Backend is the engine (nil in cache-only mode, see ProcessOptions).
Backend Backend
// ConfigSource says where repository.yaml came from: "worktree",
// "index" or "HEAD".
ConfigSource string
// contains filtered or unexported fields
}
Checkout is the resolved Git/FXVCS context of a filter or driver process.
func OpenCheckout ¶
func OpenCheckout(ctx context.Context, opts ProcessOptions) (*Checkout, error)
OpenCheckout resolves the working tree through Git, loads .fxvcs/repository.yaml (working tree first, then the index, then HEAD so a clone whose checkout is still in progress can be served), and runs the mandatory preflight. On *format.UpgradeRequiredError the caller must stop before answering any Git command.
type ConfigEntry ¶
ConfigEntry is one repository-local git config key/value.
func GitConfigEntries ¶
func GitConfigEntries(launcher string) []ConfigEntry
GitConfigEntries returns the exact repository-local configuration that routes Git's filter, diff, and merge drivers to launcher. The launcher path is quoted for Git's shell so spaces and (on Windows) backslashes survive.
type Engine ¶
type Engine struct {
Cache *cache.Cache
// Domain is the storageDomainID new objects are ingested under (clean).
Domain string
// Installed is the running binary version, for diagnostics.
Installed format.Version
// Backend is optional (see type comment).
Backend Backend
}
Engine performs clean and smudge. With a Backend it ingests through the engine's single ingestion path (cache pin + publication ledger row), consults the recorded hydration intent, and may fetch from configured remotes; without one (unit tests, degraded clone-in-progress mode) it works over the local object cache alone and never records anything.
func (*Engine) Clean ¶
Clean converts working-tree bytes into a pointer, ingesting the object into the cache and pinning it as pending publication. Content that already is a valid pointer passes through unchanged (idempotent). It streams: memory is bounded by pointer.MaxSize plus copy buffers regardless of file size.
func (*Engine) Smudge ¶
Smudge converts a pointer into working-tree bytes according to the recorded hydration intent of the path:
- no Backend, no state row, or intent unhydrated: emit the pointer bytes unchanged (the unhydrated default; a fresh clone holds pointers even when the host cache happens to contain the object);
- intent hydrated: reconstruct from the cache, else fetch from a configured remote into the cache, else fail the Git operation (ErrObjectUnavailable) — never silently substitute a pointer for a path the user hydrated.
Non-pointer input passes through unchanged. It never emits partial or unverified content as success.
type Handler ¶
type Handler interface {
Clean(ctx context.Context, req Request, content io.Reader, out io.Writer) error
Smudge(ctx context.Context, req Request, content io.Reader, out io.Writer) error
}
Handler transforms content for one command. It reads the whole input from content and writes the result to out. Writing anything to out commits the response to status=success unless the handler then returns an error, in which case the protocol reports status=error after the partial content and Git discards the result. Returning an error before writing reports status=error immediately.
type HookInstallResult ¶
type HookInstallResult struct {
Path string
HooksPath string // directory (honors core.hooksPath)
Updated bool // an fxvcs-managed hook was refreshed
}
HookInstallResult reports where the hook went.
func InstallPrePushHook ¶
func InstallPrePushHook(ctx context.Context, git *gitclient.Client, launcher string) (HookInstallResult, error)
InstallPrePushHook writes the managed hook into the hooks directory Git reports (which honors core.hooksPath and linked worktrees). It never overwrites a hook it did not write: ErrHookExists is returned instead, and the caller should instruct the user to chain `fxvcs pre-push "$@"`.
type MergeOutcome ¶
type MergeOutcome int
MergeOutcome classifies a merge.
const ( // MergeClean means result holds the merged pointer (exit 0). MergeClean MergeOutcome = iota // MergeConflict means result holds the conflict layout (exit 1). MergeConflict )
func Merge ¶
Merge performs the pointer-aware three-way merge. Rules:
- ours == theirs (bytes) -> ours, clean
- ours or theirs is not a valid pointer -> ErrNotPointerInput
- base absent or not a pointer -> conflict (both sides added)
- ours == base -> theirs, clean
- theirs == base -> ours, clean
- otherwise -> conflict layout, both pointers kept
It never picks by timestamp or size.
type MergeResult ¶
type MergeResult struct {
Outcome MergeOutcome
Path string
}
MergeResult is what RunMergeDriver did.
func RunMergeDriver ¶
func RunMergeDriver(basePath, oursPath, theirsPath, pathname string, installed format.Version) (MergeResult, error)
RunMergeDriver is the `fxvcs merge-driver %O %A %B %P` entry point. It rewrites the %A file with the result (or the conflict layout) and returns the outcome. On ErrNotPointerInput (or any error) %A is left untouched and the caller must exit non-zero so Git records the path as unmerged.
type PendingObject ¶
type PendingObject struct {
Digest string
Domain string
Path string // first path where it was seen
Commit string
Missing []string // required remotes without a publication record
Reason string // why automatic publication did not succeed, when known
// Pointer is the index pointer the object belongs to; a chunked encoding
// expands to a manifest object plus its chunks, all of which must be
// published before the push may proceed.
Pointer pointer.Pointer
}
PendingObject is a pointer reachable from a pushed commit whose object is not published to every required remote.
type ProcessOptions ¶
type ProcessOptions struct {
// Installed is the running binary version (required).
Installed format.Version
// Cwd is the directory Git started the process in ("" = os.Getwd).
Cwd string
// Git overrides the git client (tests). nil = gitclient.New(Cwd).
Git *gitclient.Client
// CacheDir overrides the object cache root ("" = cache.DefaultDir()).
CacheDir string
// Connect opens the engine Backend for the resolved working tree
// (internal/filter/wire supplies it). nil runs the cache-only engine:
// clean pins in the cache but records no ledger row or intent, smudge
// always emits pointers, pre-push cannot judge publication.
Connect func(ctx context.Context, root string) (Backend, error)
// Log receives diagnostics that are not errors (nil = discard).
Log io.Writer
}
ProcessOptions configures the filter process and one-shot filters.
type PublicationBarrierError ¶
type PublicationBarrierError struct {
Pending []PendingObject
}
PublicationBarrierError is returned when a push must be refused.
func (*PublicationBarrierError) Error ¶
func (e *PublicationBarrierError) Error() string
type PublishFailure ¶
PublishFailure reports one object×remote upload that did not succeed.
type PushRef ¶
type PushRef struct {
LocalRef, LocalOID, RemoteRef, RemoteOID string
}
PushRef is one line of the pre-push stdin protocol:
<local ref> SP <local oid> SP <remote ref> SP <remote oid> LF
type Request ¶
type Request struct {
Command string // clean | smudge
Pathname string
Ref string // optional (smudge)
Treeish string // optional (smudge)
Blob string // optional (smudge)
CanDelay bool // Git offered delay; this implementation never delays
}
Request is one filter command from Git.
type Side ¶
type Side struct {
Absent bool
Pointer *pointer.Pointer
// For raw sides: computed identity so pointer and hydrated file compare.
OID string
Size int64
Mode string
}
Side describes one side of a diff: absent, a pointer, or raw bytes (a hydrated working file or a historical blob).
func ReadSide ¶
ReadSide classifies a file handed to the diff driver. path=="", "/dev/null", "nul", or mode "." marks an absent side. The hex is not used for that decision because Git reports an all-zero hex for a working-tree file that does exist. Raw sides are hashed while streaming so memory stays bounded.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package wire connects the Git process boundary (internal/filter) to the engine (internal/app) so that the clean filter ingests through the same path as every SDK operation, smudge honours the hydration intent recorded in state.db, and the pre-push barrier consults the publication ledger.
|
Package wire connects the Git process boundary (internal/filter) to the engine (internal/app) so that the clean filter ingests through the same path as every SDK operation, smudge honours the hydration intent recorded in state.db, and the pre-push barrier consults the publication ledger. |