esync

Directory watching and remote syncing
Log | Files | Refs | README | LICENSE

2026-03-08-include-filter-design.md (2284B)


      1 # Include Filter Design
      2 
      3 ## Problem
      4 
      5 In a large monorepo, you may only want to sync specific subtrees (e.g., `project/src` but not `project/tmp`). The current config only supports `ignore` patterns, which means you must enumerate everything you *don't* want. An `include` list inverts this: state what you *do* want, then refine with `ignore`.
      6 
      7 ## Design Spirit
      8 
      9 **Include is simple and explicit.** It names the directories you care about. Leave fine-grained filtering to `ignore`.
     10 
     11 ## TOML Config
     12 
     13 ```toml
     14 [settings]
     15 include = ["src", "docs/api"]   # path prefixes relative to sync.local
     16 ignore  = [".git", ".DS_Store"] # applied within included paths
     17 ```
     18 
     19 - `include = []` (default) means include everything — fully backwards compatible.
     20 - When specified, only paths under those prefixes are synced.
     21 - `ignore` further refines within included paths.
     22 - Patterns are path prefixes relative to `sync.local`, not globs or basenames.
     23 
     24 ## Evaluation Order
     25 
     26 ```
     27 file path relative to sync.local
     28   -> does it fall under an include prefix? (empty = yes to all)
     29     -> no  -> skip
     30     -> yes -> does it match an ignore pattern?
     31       -> yes -> skip
     32       -> no  -> sync it
     33 ```
     34 
     35 ## Changes
     36 
     37 ### Config (`internal/config/config.go`)
     38 
     39 - Add `Include []string` to `Settings` struct with `toml:"include"` tag.
     40 - Update `DefaultTOML()` to document `include = []`.
     41 
     42 ### Watcher (`internal/watcher/watcher.go`)
     43 
     44 - Add `shouldInclude(path)` using path-prefix matching against the path relative to the watched root.
     45 - In `addRecursive()`: skip directories that are neither a prefix of, nor prefixed by, an include path. (If include is `["src/api"]`, we must still traverse `src/` to reach `src/api/`.)
     46 - In the event loop: check include before ignore. Reject events outside included prefixes.
     47 
     48 ### Syncer (`internal/syncer/syncer.go`)
     49 
     50 When include patterns are present, emit rsync filter rules:
     51 
     52 1. `--include=<prefix>/` for each ancestor directory needed for traversal.
     53 2. `--include=<prefix>/**` for each include entry.
     54 3. `--exclude=*` to block everything else.
     55 
     56 Existing `--exclude` patterns from ignore lists move *before* the final `--exclude=*` so they can refine within included paths.
     57 
     58 ### No changes needed to
     59 
     60 SSH config, log config, rsync flags (archive/compress/etc.), CLI entry point.