README.md (4027B)
1 # dam (diff-and-merge) 2 3 Syncs line-oriented text files between a local directory and a remote host 4 over rsync, with a plan/apply workflow: `--plan` (the default) previews the 5 changes, `--pull` applies them locally, `--push` applies them to the remote. 6 7 **Merge strategy:** when a file exists on both sides, the result is the 8 de-duplicated **union** of its local and remote lines: the local lines in 9 their original order, with the lines only the remote has appended. When a 10 file exists on only one side it is propagated verbatim. The merged result is 11 the same in every mode; the mode only chooses which side gets written. 12 13 Order is never changed — pushing a reordered file (say, a playlist) 14 propagates the local order rather than fighting it. `dam` is intended for 15 list-like data (allow-lists, hosts files, playlists, word lists). Blank lines 16 and duplicate lines are **not** preserved by the merge. 17 18 `--local` and `--remote` may be **directories or single files**. When 19 `--local` is an existing directory, `dam` syncs the whole tree (recursing into 20 subdirectories); otherwise it treats both paths as a single file. To mirror a 21 directory into a new local location, create that directory first. 22 23 > **dam never deletes.** Because the merge is a union, a line removed on one 24 > side reappears from the other. Removing data is a manual edit on both sides 25 > (or a one-shot `rsync`), not something `dam` does. 26 27 Local files are written atomically (staged in a temp file and renamed), so an 28 interrupted run can't leave a half-written file. rsync runs with 29 `ssh -o BatchMode=yes` and an I/O `--timeout` so it fails fast in automation 30 rather than blocking on a prompt. 31 32 ## Building 33 34 ```sh 35 cargo build --release 36 ``` 37 38 ## Usage 39 40 ```sh 41 dam --host <host> --local <dir> --remote <dir> [--plan | --pull | --push] [--sudo | --doas] [--exit-code] [--timeout <secs>] 42 ``` 43 44 ### Modes 45 46 `--plan`, `--pull`, and `--push` are mutually exclusive; `--plan` is the default. 47 48 | Flag | Description | 49 |------|-------------| 50 | `--plan` | Show the changes each direction would make. Writes nothing. (default) | 51 | `--pull` | Apply the merge to the local directory (remote → local) | 52 | `--push` | Apply the merge to the remote host (local → remote) | 53 54 ### Options 55 56 | Flag | Description | 57 |------|-------------| 58 | `--host` | SSH host alias (from `~/.ssh/config`) to sync with | 59 | `--local` | Local directory containing the text files | 60 | `--remote` | Remote directory to sync from/to | 61 | `--sudo` | Elevate the **remote** rsync with `sudo` (via `--rsync-path`). Mutually exclusive with `--doas` | 62 | `--doas` | Elevate the **remote** rsync with `doas` (via `--rsync-path`). Mutually exclusive with `--sudo` | 63 | `--exit-code` | Exit 1 if `--plan` finds pending changes. Useful in CI | 64 | `--timeout` | rsync I/O timeout in seconds (default: 30; 0 disables) | 65 66 Run `dam --help` for full details. 67 68 `--sudo`/`--doas` elevate only the rsync process on the remote host (e.g. when 69 the remote path is root-owned) by passing `--rsync-path="sudo rsync"`. The 70 local rsync always runs unprivileged, so your own SSH config and keys are used. 71 72 ### Exit status 73 74 | Code | Meaning | 75 |------|---------| 76 | `0` | Success; nothing pending (or changes were applied with `--pull`/`--push`) | 77 | `1` | `--exit-code` was set with `--plan` and pending changes were found | 78 | `2` | One or more files failed to process | 79 80 Progress and the summary go to **stderr**; the plan diff goes to **stdout**, 81 so `dam --plan … > changes.diff` captures only the differences. 82 83 ### Examples 84 85 ```sh 86 # See what would change on either side 87 dam --host myserver --local ~/lists --remote /srv/lists 88 89 # Bring the local directory up to date 90 dam --host myserver --local ~/lists --remote /srv/lists --pull 91 92 # Bring the remote up to date 93 dam --host myserver --local ~/lists --remote /srv/lists --push 94 95 # Sync a single file 96 dam --host myserver --local ~/hosts.allow --remote /etc/hosts.allow --pull 97 98 # Fail if the two sides have drifted 99 dam --host myserver --local ~/lists --remote /srv/lists --plan --exit-code 100 ```