Documentation
¶
Index ¶
- Constants
- Variables
- func AcquireWorkspaceLock(cwd string) error
- func AddTag(ctx context.Context, store storage.Storer, cwd string, name string, ...) error
- func ComputeFileHash(filePath string) (core.Hash, error)
- func ComputeRestoreChanges(ctx context.Context, workDir string, cfg *core.CoreConfig, ...) (added []core.FileEntry, modified []core.FileEntry, deleted []string, err error)
- func CountFileLines(ctx context.Context, store storage.Storer, entry core.FileEntry) int
- func CountFileLinesWithError(ctx context.Context, store storage.Storer, entry core.FileEntry) (int, error)
- func CountSnapshotChanges(ctx context.Context, store storage.Storer, summary *core.SnapshotSummary) (added, modified, deleted int)
- func CountUnreachableSnapshots(ctx context.Context, store storage.Storer, workDir string) (int, error)
- func CreateBranch(ctx context.Context, store storage.Storer, cwd, name string) (core.SnapshotID, error)
- func CreateSnapshot(ctx context.Context, store storage.Storer, workDir string, message string, ...) (*core.Snapshot, error)
- func DeleteBranch(ctx context.Context, store storage.Storer, cwd, name string) error
- func DeleteTag(ctx context.Context, store storage.Storer, cwd string, name string) error
- func DetectFileTypeLabel(ctx context.Context, store storage.Storer, entry *core.FileEntry) string
- func ImportFileFromBranch(ctx context.Context, store storage.Storer, ...) (*core.FileEntry, error)
- func InitProject(path string) error
- func InitProjectWithFactory(path string, factory StoreFactory) error
- func ListBranches(ctx context.Context, store storage.Storer) ([]*core.Reference, string, error)
- func LsRemote(ctx context.Context, workDir, remoteName string) ([]*core.Reference, error)
- func OpenProject(path string) (storage.Storer, *core.Config, error)
- func OpenProjectWithFactory(path string, factory StoreFactory) (storage.Storer, *core.Config, error)
- func PullDryRun(ctx context.Context, store storage.Storer, workDir, remoteName, branch string, ...) (*remote.SyncStats, error)
- func PushDryRun(ctx context.Context, store storage.Storer, workDir, remoteName, branch string, ...) (*remote.SyncStats, error)
- func RebuildIndexFromSnapshot(ctx context.Context, store storage.Storer, snapID core.SnapshotID) error
- func ReleaseWorkspaceLock(cwd string)
- func RemoveStalePidFile(cwd string, pid int)
- func RenameBranch(ctx context.Context, store storage.Storer, cwd, oldName, newName string) error
- func RenameTag(ctx context.Context, store storage.Storer, cwd string, oldName, newName string) error
- func ResolveBranchTips(ctx context.Context, store storage.Storer) (map[string][]string, error)
- func ResolveCurrentBranchName(ctx context.Context, store storage.Storer) string
- func ResolveHeadSnapshot(ctx context.Context, store storage.Storer) *core.Snapshot
- func ResolveSnapshotRef(ctx context.Context, store storage.Storer, id string) (*core.Snapshot, error)
- func ResolveTagTips(ctx context.Context, store storage.Storer) (map[string][]string, error)
- func RestoreSnapshot(ctx context.Context, store storage.Storer, workDir string, ...) (backupID string, err error)
- func RunDaemonLoop(ctx context.Context, store storage.Storer, cwd string, interval int, keep int, ...)
- func SetConfigValue(ctx context.Context, store storage.Storer, cfg *core.Config, key, value string) error
- func SnapshotFileDiff(ctx context.Context, store storage.Storer, snapshot *core.Snapshot) (added []core.FileEntry, modified []core.FileEntry, deleted []string, err error)
- func SortSnapshotSummariesNewestFirst(snaps []*core.SnapshotSummary)
- func StartDaemon(ctx context.Context, cwd string, interval int, keep int) (int, error)
- func StopDaemon(ctx context.Context, cwd string) (int, int, error)
- func SwitchBranch(ctx context.Context, store storage.Storer, workDir string, name string, ...) (string, string, int, error)
- func TouchWorkspaceLock(workDir string) error
- func UndoLastSave(ctx context.Context, store storage.Storer, workDir string, ...) error
- func WalkSnapshotChain(ctx context.Context, store storage.Storer, startHash core.Hash) ([]*core.SnapshotSummary, error)
- type ChangeSummary
- type ChunkRef
- type ChunkStatus
- type CloneOptions
- type CloneResult
- type ContentDiffResult
- type ExportResult
- type FileDiffResult
- type FileStat
- type FileViewResult
- type GCReport
- type IntegrityReport
- type PullResult
- type PushResult
- type StoreFactory
- type TagInfo
- type WatchState
Constants ¶
const AutoSavePrefix = "auto -"
AutoSavePrefix is the message prefix that identifies auto-saved snapshots (created by 'drift watch'). Commands that filter or label auto-saves (e.g. 'log' hiding them by default, 'gc --keep-auto' preserving them) share this constant so the prefix has a single source of truth.
Variables ¶
var ( // ErrNotARepo is returned when an operation requires a drift // repository but the target directory does not contain a .drift/ // directory. The CLI maps this to a dedicated exit code so scripts // can branch on it without parsing the message. ErrNotARepo = errors.New("not a drift repository") // ErrNothingToSave is returned when a snapshot is requested but the // workspace has no changes since the last snapshot. ErrNothingToSave = errors.New("nothing to save") // ErrBranchNotFound is returned when a referenced branch does not exist. ErrBranchNotFound = errors.New("branch not found") // ErrBranchAlreadyExists is returned when creating a branch that already // exists. ErrBranchAlreadyExists = errors.New("branch already exists") // ErrSnapshotNotFound is returned when a referenced snapshot does not // exist. ErrSnapshotNotFound = errors.New("snapshot not found") // ErrAmbiguousID is returned when an id:<prefix> reference matches more // than one snapshot. The wrapped error message lists the matching // snapshots so callers can surface them to the user. ErrAmbiguousID = errors.New("ambiguous snapshot ID prefix") // ErrTagAlreadyExists is returned when creating a tag that already exists. ErrTagAlreadyExists = errors.New("tag already exists") // ErrTagNotFound is returned when a referenced tag does not exist. ErrTagNotFound = errors.New("tag not found") // ErrCannotUndo is returned when UndoLastSave is called but HEAD is // already at the initial snapshot (no previous snapshot to revert to). ErrCannotUndo = errors.New("cannot undo: already at initial snapshot") // ErrUncommittedChanges is returned when an operation that would lose // workspace changes is attempted (e.g. undo with a dirty workspace, or // switch --no-autosave with a dirty workspace). ErrUncommittedChanges = errors.New("uncommitted changes would be lost") // ErrCannotDeleteCurrentBranch is returned when attempting to delete the // currently checked-out branch. ErrCannotDeleteCurrentBranch = errors.New("cannot delete the current branch") // ErrCannotDeleteMain is returned when attempting to delete the 'main' // branch, which is protected. ErrCannotDeleteMain = errors.New("cannot delete 'main'") // ErrCannotRenameMain is returned when attempting to rename the 'main' // branch, which is protected. ErrCannotRenameMain = errors.New("cannot rename 'main'") // ErrFileNotFound is returned when a file path is not found in a // snapshot (e.g. 'drift cat <snapshot> <path>' where path is absent). ErrFileNotFound = errors.New("file not found in snapshot") // ErrLocked is returned by AcquireWorkspaceLock when the workspace // lock is held by another live operation. Callers may test for it // with errors.Is. ErrLocked = errors.New("workspace is locked by another operation") // ErrLockLost is returned by TouchWorkspaceLock when the lock has // been stolen by another process during a long-running operation. // The caller MUST abort immediately: continuing to modify the store // while another process holds the lock will cause corruption. ErrLockLost = errors.New("workspace lock lost to another process") )
Sentinel errors for the porcelain (business) layer. Callers use errors.Is to test for specific failure modes; lower layers wrap these with fmt.Errorf("...: %w", ...) to attach context while preserving the sentinel identity.
var ErrInvalidPath = errors.New("invalid file path")
Functions ¶
func AcquireWorkspaceLock ¶
AcquireWorkspaceLock creates a workspace lock file at .drift/workspace.lock. It coordinates access between workspace-modifying commands (switch, restore) and the watch daemon so the daemon does not observe an inconsistent state (files rewritten but index not yet rebuilt) during a transition.
This lock is NOT reentrant. Calling AcquireWorkspaceLock from within a function that already holds the lock will return ErrLocked. Use the NoLock variants for internal calls.
Acquisition is race-free across processes:
- Fast path: O_CREATE|O_EXCL atomic create. Two processes can never both create the lock when none exists.
- Stale-replacement path: the would-be acquirer writes a PID-embedded claim file (workspace.lock.claim.<pid>.<rand>) using O_CREATE|O_EXCL, re-checks that the lock is still stale, then atomically renames the claim file onto the lock path. os.Rename is atomic on the same filesystem, so exactly one of the contending processes wins the rename; the loser's rename either fails (Windows) or silently overwrites with its own content (POSIX), but the loser then observes its PID is not the recorded owner and aborts. This closes the cross-process TOCTOU window that the previous WriteFileAtomic-over-stale approach left open, where two processes could both pass isLockStale and both believe they held the lock after their respective temp-file+rename landed.
A lock that cannot be parsed (e.g. an empty file being written concurrently) is treated as held rather than stale, so a writer is never clobbered mid-write.
func AddTag ¶
func AddTag(ctx context.Context, store storage.Storer, cwd string, name string, snapID core.SnapshotID) error
AddTag creates a tag pointing to the given snapshot. It rejects empty names, invalid names, zero-hash targets, unknown snapshots, and names that already exist. The existence check and the ref write are guarded by the workspace lock so that two concurrent AddTag calls for the same name cannot both pass the check (TOCTOU).
func ComputeFileHash ¶
ComputeFileHash returns the BLAKE3 file hash for filePath by chunking it with the detected engine and hashing the concatenation of chunk hashes. The hash is independent of chunk data layout and matches the hash CreateSnapshot would produce for the same file.
func ComputeRestoreChanges ¶
func ComputeRestoreChanges(ctx context.Context, workDir string, cfg *core.CoreConfig, snapshot *core.Snapshot) (added []core.FileEntry, modified []core.FileEntry, deleted []string, err error)
ComputeRestoreChanges compares the current workspace files against the target snapshot and returns what would change if the snapshot were restored: files in the snapshot but not in the workspace (added), files in both but with different content (modified), and files in the workspace but not in the snapshot (deleted). Modification is detected by comparing content hashes (BLAKE3) rather than modtime, since tools like "cp -p" preserve modtime while changing content.
func CountFileLines ¶
CountFileLines returns the total number of lines in the file represented by entry, by counting newline bytes across all of its chunks.
Error handling: a missing chunk or a cancelled context causes the function to return 0. This silent fallback is intentional for the current callers (log -v / log --json), which treat line counting as best-effort decoration and prefer a missing count over aborting the whole log listing. Callers that need explicit error handling should use CountFileLinesWithError.
func CountFileLinesWithError ¶
func CountFileLinesWithError(ctx context.Context, store storage.Storer, entry core.FileEntry) (int, error)
CountFileLinesWithError is like CountFileLines but returns an error when a chunk cannot be read or the context is cancelled, so callers that need to distinguish "zero lines" from "failed to count" can do so.
func CountSnapshotChanges ¶
func CountSnapshotChanges(ctx context.Context, store storage.Storer, summary *core.SnapshotSummary) (added, modified, deleted int)
CountSnapshotChanges loads the snapshot referenced by summary and returns the added/modified/deleted file counts relative to its parent. Errors are logged and zero counts are returned so that a single failure does not abort the whole log listing.
func CountUnreachableSnapshots ¶
func CountUnreachableSnapshots(ctx context.Context, store storage.Storer, workDir string) (int, error)
CountUnreachableSnapshots returns the number of snapshots that are not reachable from any branch or tag reference.
Like CollectGarbage, it acquires the workspace lock so it does not observe a half-applied save/switch/restore that would produce a misleading count.
func CreateBranch ¶
func CreateBranch(ctx context.Context, store storage.Storer, cwd, name string) (core.SnapshotID, error)
CreateBranch creates a new branch pointing at the current HEAD snapshot. Returns the tip snapshot ID (zero if HEAD has no commits yet).
func CreateSnapshot ¶
func CreateSnapshot(ctx context.Context, store storage.Storer, workDir string, message string, author string, cfg *core.CoreConfig) (*core.Snapshot, error)
CreateSnapshot scans workDir, chunks new or modified files, stores them, and writes a new snapshot on the current branch (HEAD's symbolic target, defaulting to heads/main). The workspace lock is acquired for the duration of the save. message must be non-empty; author defaults to "drift" when empty; cfg may be nil (core.DefaultConfig is used). Returns ErrNothingToSave when the workspace matches the index.
Tags are NOT written into the snapshot. Tags live exclusively as tags/<name> refs (created by cmd/save.go via AddTag after the snapshot exists), which keeps them mutable — 'tag delete' and 'tag rename' actually take effect on the log view, instead of being frozen into the immutable snapshot. Old snapshots with embedded Tags fields are still readable; ResolveTagTips + mergeTags in the log layer merges both sources so historical data is preserved.
func DeleteBranch ¶
DeleteBranch removes a branch reference. It refuses to delete:
- "main" (the default, protected branch)
- the current branch (user must switch away first)
Only the reference is removed; snapshots remain in storage and become unreachable if no other branch or tag references them. Run `drift gc --dry-run` to review unreachable snapshots, then `drift gc` to reclaim the disk space.
func DeleteTag ¶
DeleteTag removes a tag by name. It returns ErrTagNotFound if the tag does not exist. The existence check and the ref delete are guarded by the workspace lock so that two concurrent DeleteTag calls cannot race on the same name (TOCTOU).
func DetectFileTypeLabel ¶
DetectFileTypeLabel returns a human-readable label for the file type of the given snapshot file entry, by reading its chunks from storage and detecting the filetype engine. Image files include parsed dimensions when available. It returns "binary" when the chunks cannot be read or no engine matches.
func ImportFileFromBranch ¶
func ImportFileFromBranch(ctx context.Context, store storage.Storer, workDir, branchName, filePath string, cfg *core.CoreConfig) (*core.FileEntry, error)
ImportFileFromBranch reconstructs a single file from the target branch's HEAD snapshot and writes it to the current workspace at the same relative path. The workspace lock is acquired for the duration. The index is NOT updated: the imported file appears as a new (added) file in 'drift status' and is captured by the next 'drift save', which is the intended workflow.
This is a non-merge file-level cherry-pick: it does not touch any other workspace files, does not move HEAD, and does not create a snapshot. It is useful for bringing a single file from an experimental branch into the current branch without switching.
Returns the imported FileEntry and an error. ErrBranchNotFound is returned when the branch does not exist; ErrSnapshotNotFound when the branch has no snapshots; ErrFileNotFound when the file is not present in the branch's HEAD snapshot.
func InitProject ¶
InitProject initializes a new drift repository at the given path using the default on-disk storage backend. It is a thin wrapper around InitProjectWithFactory with defaultStoreFactory.
func InitProjectWithFactory ¶
func InitProjectWithFactory(path string, factory StoreFactory) error
InitProjectWithFactory initializes a new drift repository at the given path, using the provided StoreFactory to construct the storage backend. The factory is the seam for injecting a non-default backend in tests.
func ListBranches ¶
ListBranches returns all branch references and the name of the current branch (without the "heads/" prefix). If HEAD is not a symbolic reference the current branch name is empty.
func OpenProject ¶
OpenProject opens an existing drift repository at the given path using the default on-disk storage backend, returning the store, the loaded config, and any error. It is a thin wrapper around OpenProjectWithFactory with defaultStoreFactory.
func OpenProjectWithFactory ¶
func OpenProjectWithFactory(path string, factory StoreFactory) (storage.Storer, *core.Config, error)
OpenProjectWithFactory opens an existing drift repository at the given path, using the provided StoreFactory to construct the storage backend. The factory is the seam for injecting a non-default backend in tests.
func PullDryRun ¶
func PullDryRun(ctx context.Context, store storage.Storer, workDir, remoteName, branch string, all bool) (*remote.SyncStats, error)
PullDryRun returns what would be pulled without downloading. It acquires the workspace lock so the dry-run reflects a consistent local state.
func PushDryRun ¶
func PushDryRun(ctx context.Context, store storage.Storer, workDir, remoteName, branch string, all bool) (*remote.SyncStats, error)
PushDryRun returns what would be pushed without uploading. It acquires the workspace lock so the dry-run reflects a consistent local state.
func RebuildIndexFromSnapshot ¶
func RebuildIndexFromSnapshot(ctx context.Context, store storage.Storer, snapID core.SnapshotID) error
RebuildIndexFromSnapshot regenerates the staging index from a snapshot's file entries. Used by switch (snapshot_branch.go) and pull (sync.go) after the branch tip changes, so the index reflects the new tip rather than the old workspace state. save.go and restore.go do not use this — save builds the index from freshly chunked fileEntries, and restore filters out failed files via failedSet, so they keep their own inline loops.
func ReleaseWorkspaceLock ¶
func ReleaseWorkspaceLock(cwd string)
ReleaseWorkspaceLock removes the workspace lock file, but only if it is owned by the current process. This prevents a process from clobbering a lock that another operation has acquired after this process stopped using it (e.g. a stale lock that was stolen and refreshed between acquire and release). The error is intentionally ignored: if the lock is already gone or is no longer owned by us there is nothing useful for callers to do, and a leftover lock will be reclaimed by the stale-timeout in AcquireWorkspaceLock.
func RemoveStalePidFile ¶
RemoveStalePidFile removes watch.pid if it contains the given PID. This is used by the _watch_daemon child process to clean up after an init failure (before RunDaemonLoop installs its own cleanup handlers), so that a subsequent `drift watch status` does not see a stale PID pointing at an already-exited process. The PID check avoids clobbering a different daemon that won the start race.
func RenameBranch ¶
RenameBranch renames a branch from oldName to newName. It refuses to rename:
- "main" (the default, protected branch)
- a non-existent branch
- to a name that already exists
If the renamed branch is the current branch (HEAD points to it), HEAD is updated to point to the new name. Only references are modified; snapshots remain untouched.
The operation is ordered as SetRef(new) then DeleteRef(old) so that a crash leaves a duplicate rather than a missing branch, which is safer to recover.
func RenameTag ¶
func RenameTag(ctx context.Context, store storage.Storer, cwd string, oldName, newName string) error
RenameTag renames an existing tag. The new name is NFC-normalized and validated before use. The operation is ordered as SetRef(new) then DeleteRef(old) so a crash leaves a duplicate rather than a missing tag, mirroring RenameBranch's safety property. The existence checks and ref writes are guarded by the workspace lock so that concurrent RenameTag calls cannot race on the same names (TOCTOU).
func ResolveBranchTips ¶
ResolveBranchTips returns a map from snapshot hash to the list of branch names whose tip (Target) points directly at that snapshot. A snapshot that is not the tip of any branch gets no entry.
This mirrors git's --decorate=short behavior: the branch column in 'log' shows where each branch head sits, leaving the rest of the chain unlabeled so the user can see at a glance where branches diverge.
The returned branch names are sorted alphabetically for stable display.
func ResolveCurrentBranchName ¶
ResolveCurrentBranchName returns the name of the current branch (without the "heads/" prefix), or "" if HEAD is detached or unreadable.
func ResolveHeadSnapshot ¶
ResolveHeadSnapshot returns the HEAD snapshot, or nil if none exists.
When HEAD is a symbolic reference to a branch, the branch's target snapshot is returned. Storer.GetRef is contractually required to fully resolve symrefs: a GetRef("HEAD") call on a symref HEAD must return a Reference whose Target is the final snapshot hash (not the intermediate branch ref). This matches the documented contract of storage.Storer.GetRef and the filesystem backend's implementation in internal/storage/backends/filesystem/ref.go. Callers therefore never need a second GetRef to chase symrefs. resolveHead (in resolve.go) relies on the same contract; the two functions intentionally use a single GetRef.
func ResolveSnapshotRef ¶
func ResolveSnapshotRef(ctx context.Context, store storage.Storer, id string) (*core.Snapshot, error)
ResolveSnapshotRef resolves a snapshot reference to a snapshot.
Snapshot reference syntax (see docs/cli-design.md "版本引用语法"):
- id:<hash-prefix> — match by snapshot hash prefix (>= minHashPrefixLen chars)
- tag:<name> — resolve via tags/<name> reference
- branch:<name> — resolve via heads/<name> reference (branch head)
- head — current HEAD snapshot
- <bare-name> — equivalent to branch:<bare-name>
The colon-prefixed syntax replaces the earlier @-prefixed form (@id:..., @tag:..., @branch:..., @head) which collided with PowerShell's splat operator and required quoting on Windows. Colons are ordinary characters in all common shells (PowerShell, bash, zsh, fish, cmd) and are already rejected by refname.Validate, so they cannot appear in branch or tag names.
Returns ErrSnapshotNotFound if the referenced snapshot does not exist. Returns an error wrapping ErrAmbiguousID if the hash prefix matches more than one snapshot (the message lists the matching short IDs). Returns an error if the hash prefix is shorter than minHashPrefixLen.
func ResolveTagTips ¶
ResolveTagTips returns a map from snapshot hash to the list of tag names whose Target points directly at that snapshot. A snapshot with no tags gets no entry.
Tags live exclusively as `tags/<name>` refs: `drift save --tag` creates refs after the snapshot is written, `drift tag add` creates a ref pointing at an existing snapshot, and `tag delete`/`tag rename` mutate refs. New snapshots no longer embed a Tags field, so refs are the authoritative source — this function reads them so the log view reflects the current tag state regardless of when tags were attached. Old snapshots with embedded Tags fields are merged in by the log layer's mergeTags for backward compatibility.
The returned tag names are sorted alphabetically for stable display.
func RestoreSnapshot ¶
func RestoreSnapshot(ctx context.Context, store storage.Storer, workDir string, snapshotID core.SnapshotID, filePath string, noBackup bool, cfg *core.CoreConfig) (backupID string, err error)
RestoreSnapshot restores files from snapshotID into workDir. When filePath is empty the entire snapshot is restored (workspace files absent from the snapshot are removed); otherwise only that single file is restored and the index is updated for it. When noBackup is false a backup snapshot of the current workspace is created first and its short ID is returned in backupID (empty when no backup was needed, e.g. ErrNothingToSave). cfg may be nil (core.DefaultConfig is used). The named return err is wrapped by a defer so that on failure the backup ID (if any) is appended for rollback guidance.
Rollback strategy: restore is not transactional across multiple files. Per-file atomicity is provided by writeFileFromChunks (temp file + rename), so a write failure never leaves a half-written file. When a restore fails partway, restoreFilesToWorkspace skips the cleanup phase (deletion of non-snapshot files) and updates the index to reflect only successfully restored entries, so the workspace stays as consistent as possible. The backup snapshot (when enabled) captures the pre-restore workspace state so the user can manually roll back with `drift restore <backupID>`.
func RunDaemonLoop ¶
func RunDaemonLoop(ctx context.Context, store storage.Storer, cwd string, interval int, keep int, cfg *core.CoreConfig)
RunDaemonLoop runs the watch daemon loop. It periodically detects workspace changes and creates auto-snapshots when changes are found. It prunes old auto-snapshots to keep at most `keep` entries.
func SetConfigValue ¶
func SetConfigValue(ctx context.Context, store storage.Storer, cfg *core.Config, key, value string) error
SetConfigValue writes a user-configurable key into cfg and persists the updated config to storage. Only user-facing keys (user.name, user.email) are accepted; algorithm tuning parameters (chunk sizes, compression) are intentionally not exposed — they are hardcoded in core.DefaultConfig and should not be tuned by end users. Returns an error if the key is unknown.
func SnapshotFileDiff ¶
func SnapshotFileDiff(ctx context.Context, store storage.Storer, snapshot *core.Snapshot) (added []core.FileEntry, modified []core.FileEntry, deleted []string, err error)
SnapshotFileDiff diffs the given snapshot against its predecessor, returning the added, modified, and deleted file sets. When the snapshot has no predecessor (initial snapshot), every file is treated as added.
Modification is detected by comparing file Hash (BLAKE3), consistent with countSnapshotDiff. Hash changes iff size or chunk list changes, so this is equivalent to comparing (Size, Chunks) but simpler.
func SortSnapshotSummariesNewestFirst ¶
func SortSnapshotSummariesNewestFirst(snaps []*core.SnapshotSummary)
SortSnapshotSummariesNewestFirst sorts snapshot summaries in reverse chronological order (newest first), using the PrevID chain depth as a secondary sort key when timestamps are equal.
Primary sort key is timestamp (descending). When timestamps are equal (rapid successive saves), it uses the PrevID chain: if A.PrevID == B.ID then A is newer than B. This is stable for unrelated summaries.
func StartDaemon ¶
StartDaemon starts a background watch daemon for the project at cwd. It returns the PID of the started process.
func StopDaemon ¶
StopDaemon stops the watch daemon for the project at cwd. Returns the number of auto-saves created and snapshots pruned during the session.
func SwitchBranch ¶
func SwitchBranch(ctx context.Context, store storage.Storer, workDir string, name string, create, noAutosave bool, author string, cfg *core.CoreConfig) (string, string, int, error)
SwitchBranch switches to the target branch. If create is true, it creates the branch first. It auto-saves current changes, updates HEAD symref, and restores the target snapshot to workspace. Returns autosave snapshot short ID (empty if nothing to save), the source branch name, and the number of files that differ between the source and target branch snapshots.
When noAutosave is true, the auto-save step is skipped and the workspace must be clean (no uncommitted changes); otherwise ErrUncommittedChanges is returned. This supports the 'drift switch --no-autosave' flow for users who have already manually saved and want to avoid an extra [auto] snapshot.
func TouchWorkspaceLock ¶
TouchWorkspaceLock refreshes the workspace lock's timestamp to prevent it from being considered stale during a long-running operation. It should be called periodically by operations that may exceed lockStaleTimeout (e.g. snapshotting a very large workspace).
If the lock has been stolen by another process (PID mismatch), TouchWorkspaceLock returns ErrLockLost. The caller MUST abort the operation immediately — continuing to modify the store while another process holds the lock will cause corruption. A missing or unparseable lock file is treated as ErrLockLost as well, since the lock should not disappear while the operation holds it.
func UndoLastSave ¶
func UndoLastSave(ctx context.Context, store storage.Storer, workDir string, cfg *core.CoreConfig) error
UndoLastSave reverts the last save operation by moving HEAD back to the previous snapshot. The undone snapshot becomes unreachable (will be collected by gc). It refuses if there are uncommitted workspace changes.
If HEAD is a symbolic reference to a branch, the branch's target is moved back. If HEAD is detached, HEAD's own target is moved back. The workspace files are not touched; the index is rebuilt from the previous snapshot so that subsequent status/save operations reflect the new HEAD.
Workspace sync: the on-disk files still reflect the undone snapshot. To make the workspace match the new HEAD, run `drift restore <prevID>` after a successful undo. Without this, `drift status` will report the workspace as "dirty" (the files match the old HEAD, not the new one).
func WalkSnapshotChain ¶
func WalkSnapshotChain(ctx context.Context, store storage.Storer, startHash core.Hash) ([]*core.SnapshotSummary, error)
WalkSnapshotChain walks the PrevID chain starting from startHash and returns snapshot summaries in chain order (newest first). The walk stops at the first missing snapshot, a nil PrevID, or a zero PrevID hash. It is context-cancellable.
This is the core of 'drift log' default and --branch modes: by walking only the current branch's chain, inherited commits from parent branches are included (matching git log semantics), giving the user the full evolution history of the branch.
Types ¶
type ChangeSummary ¶
type ChangeSummary struct {
Added []string
Modified []string
Deleted []string
// UntrackedSymlinks is the number of symbolic links present in the
// workspace. Drift's snapshot schema cannot represent symlink targets,
// so symlinks are silently skipped during save and are never restored.
// This count surfaces them so the CLI can warn the user that those
// entries are not under version control.
UntrackedSymlinks int
}
ChangeSummary summarizes workspace changes since last save.
func DetectChanges ¶
func DetectChanges(ctx context.Context, store storage.Storer, workDir string, cfg *core.CoreConfig) (*ChangeSummary, error)
DetectChanges compares the workspace against the stored index and returns changes.
It acquires the workspace lock so that the workspace scan and the index it is compared against cannot be mutated mid-comparison by a concurrent save, switch, or restore (which would otherwise produce a tear: half the files from the old state, half from the new).
type ChunkRef ¶
ChunkRef records the (snapshot, file, index) context of a chunk reference, for verbose output that identifies where each chunk lives.
type ChunkStatus ¶
type ChunkStatus string
ChunkStatus describes the verification result of a single chunk.
const ( ChunkOK ChunkStatus = "OK" ChunkCorrupt ChunkStatus = "CORRUPT (hash mismatch)" ChunkMissing ChunkStatus = "MISSING" )
type CloneOptions ¶
type CloneOptions struct {
TargetDir string
WorkDir string
RemoteURL string
RemoteType string
User string
Password string
// RemoteName is the name under which the cloned remote is registered
// in .drift/remotes.json and credentials.json. Defaults to "origin"
// when empty. Allowing the caller to choose the name prevents the
// second clone of the same URL with different credentials from
// silently overwriting the first remote's stored password.
RemoteName string
}
CloneOptions holds all parameters for CloneRemote.
type CloneResult ¶
type CloneResult struct {
Dir string
Snapshots int
Branches int
Tags int
Branch string
CredentialsSaved bool
}
CloneResult reports the outcome of a clone operation.
func CloneRemote ¶
func CloneRemote(ctx context.Context, opts CloneOptions) (*CloneResult, error)
CloneRemote downloads a remote drift repository into a new directory.
type ContentDiffResult ¶
type ContentDiffResult struct {
Stdout string
Stderr string
// Kind classifies the result for structured consumers: "added",
// "deleted", "unchanged", "text", or "binary". Empty when the result
// is a warning/error with no classification.
Kind string
// Diff holds the raw diff text produced by the engine for text files.
// Empty for non-text results.
Diff string
// OldSize and NewSize are the byte sizes of the old and new versions.
// For an added file OldSize is zero; for a deleted file NewSize is zero.
OldSize int64
NewSize int64
// OldDimensions and NewDimensions hold image dimension strings (e.g.
// "1920x1080") for image files. Empty for non-image results.
OldDimensions string
NewDimensions string
}
ContentDiffResult holds the output of a content-level diff for a single file. Stdout is the content to print to stdout (diff text, metadata, or status messages like "(no change)"). Stderr is a warning/hint to print to stderr (empty when none).
The structured fields (Kind, Diff, OldSize, NewSize, OldDimensions, NewDimensions) carry machine-readable metadata for JSON output. The text renderer (cmd.printContentDiff) ignores them and only consumes Stdout / Stderr; JSON renderers read them to build a structured envelope without re-running the diff algorithm.
func DiffFileInSnapshots ¶
func DiffFileInSnapshots(ctx context.Context, store storage.Storer, workDir string, snap1, snap2 *core.Snapshot, filePath string) ContentDiffResult
DiffFileInSnapshots computes a content-level diff for a single file between two snapshots. Both versions are streamed from chunks; the engine is selected from the snap2 header. It returns the diff content (and any warning) without printing; the caller renders the result.
func DiffWorkspaceFileVsSnapshot ¶
func DiffWorkspaceFileVsSnapshot(ctx context.Context, store storage.Storer, workDir string, snapshot *core.Snapshot, filePath string) (ContentDiffResult, error)
DiffWorkspaceFileVsSnapshot computes a content-level diff for a single file: workspace vs snapshot. The workspace file is opened with os.Open and streamed through the engine, and snapshot content is streamed from chunks via a chunkReader, so the file bytes are never buffered whole in memory. It returns the diff content (and any hint) without printing; the caller renders the result.
type ExportResult ¶
ExportResult reports the outcome of a snapshot export operation.
func ExportSnapshot ¶
func ExportSnapshot(ctx context.Context, store storage.Storer, snapID core.SnapshotID, outputPath string) (*ExportResult, error)
ExportSnapshot reconstructs all files from the given snapshot and writes them to a zip archive at outputPath. Files are streamed chunk-by-chunk into the zip writer, so peak memory is bounded by the largest chunk rather than the largest file. Directory entries are preserved.
The output path's parent directory is created if it does not exist. If the output file already exists it is overwritten.
type FileDiffResult ¶
FileDiffResult holds a file-level diff between two versions: lists of added, modified, and deleted file paths.
func DiffSnapshots ¶
func DiffSnapshots(snap1, snap2 *core.Snapshot) FileDiffResult
DiffSnapshots computes a file-level diff between two snapshots: files added, modified, or deleted going from snap1 to snap2. It returns the classified file lists without printing; the caller renders the result.
func DiffWorkspaceVsSnapshot ¶
func DiffWorkspaceVsSnapshot(ctx context.Context, workDir string, snapshot *core.Snapshot, cfg *core.CoreConfig) (FileDiffResult, error)
DiffWorkspaceVsSnapshot computes a workspace-vs-snapshot diff: files added, modified, or deleted relative to the snapshot. The workspace is walked once; each file is classified by size (and by content hash on size match). It returns the classified file lists without printing; the caller renders the result.
type FileStat ¶
type FileStat struct {
Path string
Insertions int
Deletions int
Binary bool
OldSize int64
NewSize int64
}
FileStat holds per-file change statistics for --stat output. Path is the workspace-relative file path. Insertions and Deletions are the unified-diff line counts (zero for binary files). Binary is true for non-text files or read errors, where Ins/Del are not meaningful. OldSize and NewSize are the byte sizes before and after the change (whichever side exists).
func ComputeStatSnapshots ¶
func ComputeStatSnapshots(ctx context.Context, store storage.Storer, snap1, snap2 *core.Snapshot) ([]FileStat, error)
ComputeStatSnapshots computes per-file change statistics between two snapshots without printing. It is the structured counterpart to DiffSnapshots: where DiffSnapshots classifies files into added/modified/ deleted buckets, ComputeStatSnapshots also reads chunk data for modified files and runs the text engine to count insertions/deletions. The caller renders the result.
func ComputeStatWorkspace ¶
func ComputeStatWorkspace(ctx context.Context, store storage.Storer, workDir string, cfg *core.CoreConfig, snap *core.Snapshot) ([]FileStat, error)
ComputeStatWorkspace computes per-file change statistics between the workspace and a snapshot without printing. It walks the workspace via fsutil.Walk so that the .drift/ directory and ignore-file patterns are honored, and compares same-size files by BLAKE3 hash so that content-only changes are not silently skipped. The caller renders the result.
type FileViewResult ¶
type FileViewResult struct {
Path string
Kind string
Size int64
ModTime int64
Dimensions string
Content []byte
}
FileViewResult holds the content and metadata of a file read from a snapshot.
type GCReport ¶
type GCReport struct {
SnapshotsRemoved int
ChunksRemoved int
FreedBytes int64
AutoKept int
LoosePacked int
PacksRewritten int
}
GCReport describes the outcome of a garbage collection pass.
func CollectGarbage ¶
func CollectGarbage(ctx context.Context, store storage.Storer, workDir string, dryRun bool, keepAuto int) (GCReport, error)
CollectGarbage removes snapshots and chunks that are no longer reachable from any branch or tag reference. When dryRun is true nothing is deleted; the report reflects what would be reclaimed. FreedBytes is computed in both modes (best-effort via GetChunk) and is an estimate when dryRun.
keepAuto preserves the N most recent unreachable [auto] snapshots (those whose message starts with the auto-save prefix) from deletion, acting as a safety net against accidental data loss. Their chunks are also kept.
GC does not touch workspace files, but it must not run concurrently with CreateSnapshot: a save in progress may be about to link a chunk that GC would otherwise delete as unreachable. Acquiring the workspace lock serializes GC against save/switch/restore, which are the only operations that add new chunks or snapshots.
type IntegrityReport ¶
type IntegrityReport struct {
TotalBlocks int
Corrupt int
Missing int
SnapshotCorrupt int
FileHashMismatch int
VerboseRefs []ChunkRef // populated only when verbose=true
}
IntegrityReport contains the results of a full repository integrity check.
func VerifyIntegrity ¶
func VerifyIntegrity(ctx context.Context, store storage.Storer, workDir, filter string, verbose bool) (*IntegrityReport, error)
VerifyIntegrity verifies the integrity of all chunks in the repository by recomputing their BLAKE3 hashes. It collects unique chunk hashes from all snapshots, verifies each once, and returns a structured report. When filter is non-empty, only files matching the glob pattern are checked. When verbose is true, per-chunk references are collected for detailed output.
workDir is accepted for API consistency with other porcelain functions that operate on a workspace. The current verification logic is repository-wide and does not constrain checks to workDir, but the parameter is retained so future per-workspace scoping can be added without breaking callers.
type PullResult ¶
PullResult reports the outcome of a pull operation.
func PullFromRemote ¶
func PullFromRemote(ctx context.Context, store storage.Storer, workDir, remoteName, branch string, all bool) (*PullResult, error)
PullFromRemote downloads remote objects to local. It acquires the workspace lock for the duration of the pull because pull writes to the local store and may rebuild the index.
type PushResult ¶
PushResult reports the outcome of a push operation.
func PushToRemote ¶
func PushToRemote(ctx context.Context, store storage.Storer, workDir, remoteName, branch string, all bool) (*PushResult, error)
PushToRemote uploads local objects to the named remote. It acquires the workspace lock for the duration of the push so that concurrent workspace-modifying operations (snapshot, switch, restore) do not observe an inconsistent local state while push is reading from it.
type StoreFactory ¶
StoreFactory builds a storage.Storer rooted at the given .drift path. It is the seam used by InitProjectWithFactory / OpenProjectWithFactory to inject a non-default backend (e.g. the in-memory backend in tests).
type TagInfo ¶
TagInfo is the user-facing representation of a tag, augmented with the target snapshot's message and timestamp so callers can display a tag list without a second round-trip per tag.
type WatchState ¶
type WatchState struct {
StartTime int64 `json:"start_time"`
Interval int `json:"interval"`
AutoSaves int `json:"auto_saves"`
MaxSaves int `json:"max_saves"`
LastSaveTime int64 `json:"last_save_time"`
LastSaveChanges string `json:"last_save_changes"`
Pruned int `json:"pruned"`
LastError string `json:"last_error"`
Paused bool `json:"paused"`
}
WatchState summarizes the runtime state of a watch daemon.
func DaemonStatus ¶
DaemonStatus checks whether a watch daemon is running for the project at cwd. If the daemon is alive, it returns the state and true. If the daemon is not running, it cleans up stale files and returns nil, false, nil.
func PauseDaemon ¶
func PauseDaemon(ctx context.Context, cwd string) (*WatchState, error)
PauseDaemon marks the running daemon as paused by setting the Paused flag in the watch state file. The daemon loop re-reads this flag at the start of each tick and skips detection while paused. Returns an error if no daemon is running or the daemon is already paused.
func ResumeDaemon ¶
func ResumeDaemon(ctx context.Context, cwd string) (*WatchState, error)
ResumeDaemon clears the Paused flag in the watch state file so the running daemon resumes detection on its next tick. Returns an error if no daemon is running or the daemon is not paused.
Source Files
¶
- branch.go
- branch_history.go
- chunk.go
- clone.go
- diff_snapshots.go
- diff_stat.go
- diff_types.go
- diff_workspace.go
- errors.go
- export.go
- file_type.go
- file_view.go
- gc.go
- import_file.go
- integrity.go
- lock.go
- log.go
- project.go
- resolve.go
- restore.go
- snapshot.go
- snapshot_branch.go
- snapshot_hash.go
- snapshot_worker.go
- status.go
- switch.go
- sync.go
- tag.go
- watch_daemon.go
- watch_loop.go
- watch_proc_unix.go
- workspace.go