museignore.md
markdown
| 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 `MusicPlugin.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 `MusicPlugin` 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 `MusicPlugin`. |
| 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. |