protonfs.migrations module

Versioned repo-state migrations (#67).

Migrations of on-disk .protonfs/ state existed already, but scattered and implicit: index schema upgrades happen silently on IndexStore load, device_id relocation to config.local.json runs opportunistically inside protonfs setup, and control-file backfill (.protonfs/ignore, include, .gitattributes, .gitignore) is also folded into setup. This module makes that set explicit, orderable, and previewable without replacing any of the existing implicit paths – a plain pull on an old repo must keep working exactly as before; this registry is an additional, explicit way to bring a repo fully up to date in one step.

Each Migration is idempotent and self-contained: is_applied probes the actual on-disk state (never trusts the layout-version marker alone), so migrations stay correct on an untouched 0.2.0-era repo, a partially-migrated repo, or an already-current one.

The layout_version marker lives in .protonfs/config.local.json (per-device, already the home for local/gitignored state – see config.py) and records the newest layout this specific on-disk checkout has been fully migrated to. It is written only after every registered migration reports itself applied; it is never consulted to SKIP a migration’s own is_applied check, only used as a fast, informational summary (e.g. for doctor/ status-style reporting).

Public API for protonfs upgrade (#66, a separate task) to call:

pending_migrations(root) -> list[Migration] run_migrations(root, dry_run=False) -> MigrationResult layout_version(root) -> int

Added in version 1.0.0.

class protonfs.migrations.Migration(id, version, description, is_applied, apply)[source]

Bases: object

One registered, idempotent repo-state migration.

version is the layout version this migration brings the repo to (migrations are registered in ascending order). is_applied must be a pure, side-effect-free probe of on-disk state; apply must be safe to call even when is_applied is already True (every migration here delegates to code that already guarantees that).

apply: Callable[[Path], None]
description: str
id: str
is_applied: Callable[[Path], bool]
version: int
class protonfs.migrations.MigrationResult(applied=<factory>, dry_run=False, layout_version_before=0, layout_version_after=0)[source]

Bases: object

Outcome of run_migrations.

applied: list[str]
dry_run: bool = False
layout_version_after: int = 0
layout_version_before: int = 0
protonfs.migrations.layout_version(root)[source]

The layout version this repo’s local checkout was last fully migrated to (0 if never recorded – e.g. a repo untouched since before this registry existed).

Return type:

int

protonfs.migrations.pending_migrations(root)[source]

Migrations not yet applied to root, in registration order.

Each migration’s is_applied probes actual on-disk state (not the layout-version marker), so this is correct for an untouched repo, a partially-migrated one, or an already-current one. Returns [] for a directory that was never protonfs setup at all (no .protonfs/config.json) – there is nothing to migrate.

Return type:

list[Migration]

protonfs.migrations.run_migrations(root, dry_run=False)[source]

Run all pending migrations against root, in order.

dry_run=True computes and returns the plan without applying anything or writing the layout-version marker. Safe to call repeatedly: migrations already applied are skipped, and running twice in a row is a no-op the second time (applied == []).

Return type:

MigrationResult