gabriel / muse public
sparse.py python
119 lines 4.0 KB
Raw
sha256:06dba78c2a78e251b580422dd1fd547f3c8357ff18f7709a860873b2d24dbbbf chore: bump version to 0.2.0rc14 Sonnet 4.6 patch 1 day ago
1 """muse/core/sparse.py — Sparse-checkout filter logic.
2
3 A sparse-checkout configuration is stored in ``.muse/sparse-checkout`` as a
4 JSON object::
5
6 {"mode": "cone", "patterns": ["src/", "tests/"]}
7
8 Two modes are supported:
9
10 ``cone``
11 Each pattern is a directory prefix (e.g. ``"src/"``). A file matches when:
12 - it is at the repository root (no ``/`` in path), OR
13 - its path starts with one of the stripped prefixes (``"src/"`` → ``"src/"``).
14
15 ``pattern``
16 Each pattern is a ``pathlib.PurePosixPath.match``-compatible glob
17 (e.g. ``"**/*.py"``, ``"src/**"``). A file matches when *any* pattern
18 matches its POSIX path.
19
20 When no ``sparse-checkout`` file exists the repository is in *full* mode —
21 every file in a snapshot manifest is materialised.
22 """
23
24 import fnmatch
25 import json
26 import pathlib
27 from typing import TypedDict
28
29 from muse.core.paths import muse_dir as _muse_dir
30 from muse.core.types import Manifest, load_json_file
31
32 class SparseConfig(TypedDict, total=False):
33 mode: str
34 patterns: list[str]
35
36 # ---------------------------------------------------------------------------
37 # Config I/O
38 # ---------------------------------------------------------------------------
39
40 def read_sparse_config(root: pathlib.Path) -> SparseConfig | None:
41 """Read the sparse-checkout config from *root*/.muse/sparse-checkout.
42
43 Returns ``None`` when sparse-checkout is not active.
44 """
45 cfg_path = _muse_dir(root) / "sparse-checkout"
46 if not cfg_path.exists():
47 return None
48 return load_json_file(cfg_path)
49
50 def write_sparse_config(root: pathlib.Path, config: SparseConfig) -> None:
51 """Persist *config* to *root*/.muse/sparse-checkout."""
52 cfg_path = _muse_dir(root) / "sparse-checkout"
53 cfg_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
54
55 def remove_sparse_config(root: pathlib.Path) -> None:
56 """Remove the sparse-checkout config (disable sparse mode)."""
57 cfg_path = _muse_dir(root) / "sparse-checkout"
58 if cfg_path.exists():
59 cfg_path.unlink()
60
61 # ---------------------------------------------------------------------------
62 # Matching
63 # ---------------------------------------------------------------------------
64
65 def matches_sparse(rel_posix: str, patterns: list[str], *, mode: str = "cone") -> bool:
66 """Return ``True`` when *rel_posix* should be materialised under *patterns*.
67
68 Args:
69 rel_posix: Repository-relative POSIX path (e.g. ``"src/foo.py"``).
70 patterns: List of cone-prefixes or glob patterns depending on *mode*.
71 mode: ``"cone"`` or ``"pattern"``.
72 """
73 if mode == "cone":
74 # Root-level files always match in cone mode.
75 if "/" not in rel_posix:
76 return True
77 for pat in patterns:
78 prefix = f"{pat.rstrip('/')}/"
79 if rel_posix.startswith(prefix):
80 return True
81 return False
82
83 # pattern mode — fnmatch-style globs; support ** as zero-or-more path segments
84 for pat in patterns:
85 if fnmatch.fnmatch(rel_posix, pat):
86 return True
87 # Also try pathlib match which handles ** for path separators
88 try:
89 if pathlib.PurePosixPath(rel_posix).match(pat):
90 return True
91 except Exception:
92 pass
93 return False
94
95 # ---------------------------------------------------------------------------
96 # Manifest filtering
97 # ---------------------------------------------------------------------------
98
99 def filter_manifest_sparse(
100 manifest: Manifest,
101 patterns: list[str],
102 *,
103 mode: str = "cone",
104 ) -> Manifest:
105 """Return a copy of *manifest* containing only sparse-matching entries.
106
107 Args:
108 manifest: Full snapshot manifest (path → object_id).
109 patterns: Sparse patterns.
110 mode: ``"cone"`` or ``"pattern"``.
111
112 Returns:
113 A new :class:`Manifest` with only the paths that match the sparse rules.
114 """
115 return {
116 path: obj_id
117 for path, obj_id in manifest.items()
118 if matches_sparse(path, patterns, mode=mode)
119 }
File History 1 commit
sha256:06dba78c2a78e251b580422dd1fd547f3c8357ff18f7709a860873b2d24dbbbf chore: bump version to 0.2.0rc14 Sonnet 4.6 patch 1 day ago