cgcardona / muse public
museignore.md markdown
251 lines 6.2 KB
9ee9c39c refactor: rename music→midi domain, strip all 5-dim backward compat Gabriel Cardona <gabriel@tellurstori.com> 1d ago
1 # `.museignore` Reference
2
3 `.museignore` tells Muse which files to exclude from every snapshot.
4 It lives in the **repository root** (the directory that contains `.muse/` and
5 `muse-work/`) and uses the same syntax as `.gitignore`.
6
7 ---
8
9 ## Why it matters
10
11 `muse commit` snapshots everything in `muse-work/`. Without `.museignore`,
12 OS artifacts (`.DS_Store`), DAW temp files (`*.bak`, `*.tmp`), rendered
13 previews, and build outputs are included in the content-addressed object
14 store and contribute to diff noise on every commit.
15
16 `.museignore` lets you declare once what belongs in version history and
17 what doesn't.
18
19 ---
20
21 ## File location
22
23 ```
24 my-project/
25 ├── .muse/ ← VCS metadata
26 ├── muse-work/ ← tracked workspace (content here is snapshotted)
27 ├── .museignore ← ignore rules (lives here, next to muse-work/)
28 └── .museattributes ← merge strategies
29 ```
30
31 ---
32
33 ## Syntax
34
35 ```
36 # This is a comment — blank lines and # lines are ignored
37
38 # Match any file named exactly .DS_Store at any depth:
39 .DS_Store
40
41 # Match any .tmp file at any depth:
42 *.tmp
43
44 # Match .bak files only inside tracks/:
45 tracks/*.bak
46
47 # Match everything inside any directory named __pycache__:
48 **/__pycache__/**
49
50 # Anchor to repo root: only match renders/ at the top level of muse-work/:
51 /renders/
52
53 # Negation: un-ignore a specific file even if *.bak matched it:
54 !tracks/keeper.bak
55 ```
56
57 ### Rule summary
58
59 | Syntax | Meaning |
60 |---|---|
61 | `#` at line start | Comment, ignored |
62 | Blank line | Ignored |
63 | `*.ext` | Ignore all files with this extension, at any depth |
64 | `name` | Ignore any file named exactly `name`, at any depth |
65 | `dir/*.ext` | Ignore matching files inside `dir/` at that exact depth |
66 | `**/name` | Ignore `name` inside any subdirectory at any depth |
67 | `name/` | Ignore a directory (Muse tracks files; this is silently skipped) |
68 | `/pattern` | Anchor to root — only matches at the top level of `muse-work/` |
69 | `!pattern` | Negate — un-ignore a previously matched path |
70
71 **Last matching rule wins.** A negation rule later in the file overrides an
72 earlier ignore rule for the same path.
73
74 ---
75
76 ## Matching rules in detail
77
78 ### Patterns without a `/`
79
80 Matched against the **filename only**, so they apply at every depth:
81
82 ```
83 *.tmp → ignores tracks/session.tmp and session.tmp and a/b/c.tmp
84 .DS_Store → ignores any file named .DS_Store at any depth
85 ```
86
87 ### Patterns with an embedded `/`
88
89 Matched against the **full relative path** from the right, so they respect
90 directory structure:
91
92 ```
93 tracks/*.tmp → ignores tracks/session.tmp
94 does NOT ignore exports/tracks/session.tmp
95 **/cache/*.dat → ignores a/b/cache/index.dat
96 also ignores cache/index.dat
97 ```
98
99 ### Anchored patterns (leading `/`)
100
101 Matched against the **full path from the root**, so they only apply at the
102 top level of `muse-work/`:
103
104 ```
105 /renders/ → ignores the top-level renders/ directory entry
106 (directory patterns are skipped for files)
107 /scratch.mid → ignores scratch.mid at the root of muse-work/
108 does NOT ignore tracks/scratch.mid
109 ```
110
111 ### Negation (`!pattern`)
112
113 Re-includes a path that was previously ignored:
114
115 ```
116 *.bak
117 !tracks/keeper.bak → keeper.bak is NOT ignored despite *.bak above
118 ```
119
120 The last matching rule wins, so negation rules must come **after** the rule
121 they override.
122
123 ---
124
125 ## Dotfiles are always excluded
126
127 Regardless of `.museignore`, any file whose **name** begins with `.` is
128 always excluded from snapshots by `MidiPlugin.snapshot()`. This prevents
129 OS metadata files (`.DS_Store`, `._.DS_Store`) and editor state from
130 accumulating in the object store.
131
132 To include a dotfile, you would need a domain plugin that overrides this
133 default. The reference `MidiPlugin` does not support it.
134
135 ---
136
137 ## Domain plugin contract
138
139 Every domain plugin that implements `snapshot(live_state)` with a
140 ``pathlib.Path`` argument **must** honour `.museignore`. Use the helpers
141 provided by `muse.core.ignore`:
142
143 ```python
144 from muse.core.ignore import is_ignored, load_patterns
145
146 def snapshot(self, live_state: LiveState) -> StateSnapshot:
147 if isinstance(live_state, pathlib.Path):
148 workdir = live_state
149 repo_root = workdir.parent # .museignore lives here
150 patterns = load_patterns(repo_root)
151 files = {}
152 for file_path in sorted(workdir.rglob("*")):
153 if not file_path.is_file():
154 continue
155 rel = file_path.relative_to(workdir).as_posix()
156 if is_ignored(rel, patterns):
157 continue
158 files[rel] = hash_file(file_path)
159 return {"files": files, "domain": self.DOMAIN}
160 return live_state
161 ```
162
163 This is the exact pattern used by the reference `MidiPlugin`.
164
165 ---
166
167 ## Music domain recommended `.museignore`
168
169 ```
170 # OS artifacts
171 .DS_Store
172 Thumbs.db
173
174 # DAW session backups and temp files
175 *.bak
176 *.tmp
177 *.autosave
178
179 # Rendered audio (not source state)
180 renders/
181 exports/
182
183 # Plugin caches
184 __pycache__/*
185 *.pyc
186 ```
187
188 ---
189
190 ## Generic domain examples
191
192 ### Genomics
193
194 ```
195 # Pipeline intermediate files
196 *.sam
197 *.bam.bai
198 pipeline-cache/
199
200 # Keep the final alignments
201 !final/*.bam
202 ```
203
204 ### Scientific simulation
205
206 ```
207 # Raw frame dumps (too large to version)
208 frames/raw/
209
210 # Keep compressed checkpoints
211 !checkpoints/*.gz
212 ```
213
214 ### 3D Spatial
215
216 ```
217 # Preview renders and viewport caches
218 previews/
219 *.preview.vdb
220
221 # Shader compilation cache
222 **/.shadercache/
223 ```
224
225 ---
226
227 ## Interaction with `.museattributes`
228
229 `.museignore` and `.museattributes` are independent:
230
231 - `.museignore` controls **what enters the snapshot** at commit time.
232 - `.museattributes` controls **how conflicts are resolved** during merge.
233
234 A file that is ignored by `.museignore` is never committed, so it never
235 appears in a merge and `.museattributes` rules never apply to it.
236
237 ---
238
239 ## Implementation
240
241 Parsing and matching are in `muse/core/ignore.py`:
242
243 ```python
244 from muse.core.ignore import load_patterns, is_ignored
245
246 patterns = load_patterns(repo_root) # reads .museignore
247 ignored = is_ignored("tracks/x.tmp", patterns) # → True / False
248 ```
249
250 `load_patterns` returns an empty list when `.museignore` is absent (nothing
251 is ignored). `is_ignored` is a pure function with no filesystem access.