|
| 1 | +"""Shared checkout settings and sync outcomes for CLI commands.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import dataclasses |
| 6 | +import typing as t |
| 7 | +from collections.abc import Mapping |
| 8 | + |
| 9 | +from libvcs import BaseSync, SyncPolicy, SyncResult, SyncTarget |
| 10 | + |
| 11 | +from vcspull.validator import validate_working_copy |
| 12 | + |
| 13 | + |
| 14 | +@dataclasses.dataclass(frozen=True) |
| 15 | +class SyncExecution: |
| 16 | + """Keep the backend instance and its complete synchronization result.""" |
| 17 | + |
| 18 | + project: BaseSync |
| 19 | + result: SyncResult |
| 20 | + |
| 21 | + |
| 22 | +def checkout_settings( |
| 23 | + config: Mapping[str, t.Any] | None, *, worktree: bool = False |
| 24 | +) -> tuple[SyncTarget | None, SyncPolicy]: |
| 25 | + """Translate a validated checkout without collapsing its selector kind. |
| 26 | +
|
| 27 | + >>> target, policy = checkout_settings({"tag": "v1", "sync": {"drift": "keep"}}) |
| 28 | + >>> target.tag, policy.drift |
| 29 | + ('v1', 'keep') |
| 30 | + >>> checkout_settings(None) |
| 31 | + (None, SyncPolicy(drift='follow', dirty='abort')) |
| 32 | + """ |
| 33 | + if config is None: |
| 34 | + return None, SyncPolicy() |
| 35 | + values = dict(config) |
| 36 | + validate_working_copy(values, worktree=worktree) |
| 37 | + target = SyncTarget( |
| 38 | + **{ |
| 39 | + key: values[key] |
| 40 | + for key in ("branch", "tag", "commit", "rev", "remote") |
| 41 | + if key in values |
| 42 | + } |
| 43 | + ) |
| 44 | + return target, SyncPolicy(**values.get("sync", {})) |
| 45 | + |
| 46 | + |
| 47 | +def sync_result_data(result: SyncResult) -> dict[str, t.Any]: |
| 48 | + """Serialize recovery identity and outcomes without exception objects. |
| 49 | +
|
| 50 | + >>> sync_result_data(SyncResult())["preservation_state"] |
| 51 | + 'not-needed' |
| 52 | + """ |
| 53 | + return { |
| 54 | + "update_state": result.update_state, |
| 55 | + "preservation_state": result.preservation_state, |
| 56 | + "recovery": dataclasses.asdict(result.recovery) if result.recovery else None, |
| 57 | + "conflicts": [dataclasses.asdict(conflict) for conflict in result.conflicts], |
| 58 | + "errors": [ |
| 59 | + {"step": error.step, "message": error.message} for error in result.errors |
| 60 | + ], |
| 61 | + } |
0 commit comments