id = "rsync" name = "rsync" homepage = "https://rsync.samba.org" description = "rsync is an open source utility that provides fast incremental file transfer." executables = ["rsync"] platforms = ["macos", "linux"] since = "2.6.9" version_from = { command = ["rsync", "--version"] } [dotfiles] # rsync(1) is the only place rsync names a per-user file, and it names ~/.popt: # "If you want all your transfers to default to comparing nanoseconds, you can # create a ~/.popt file and put these lines in it: rsync alias -a -a@-1", and # again for a custom option, "putting this line in the file ~/.popt (the # following defines the -Z option...)". rsync parses its command line with # popt, which reads /etc/popt and $HOME/.popt; every line there is prefixed # with the program it applies to, so the file is shared with other popt tools. # # ~/.rsync-filter is deliberately NOT here: -F expands to # --filter='dir-merge /.rsync-filter', a per-directory file read from inside # whatever tree is being transferred, not a default read from the home # directory. rsyncd.conf is a daemon file under /etc. paths = ["~/.popt"] exclude = [] # popt aliases are command-line defaults. rsync's own authentication is a # password prompt or the RSYNC_PASSWORD environment variable; nothing is # written to disk. secret = [] # Parsed once at startup, before the transfer begins. side_effect = false # No machine_lines: an alias is a shorthand the user defined and wants # everywhere. A path baked into one would be, but inventing a prefix for that # would mean guessing at the alias names. [commands] format = 1 features = ["variant_conditions"] label = "Synchronize one source" description = "One source to one destination. Directory contents: src/ copies children; directory itself: src copies the directory. SSH checks run only when requested. Daemon URLs, two remote ends, custom remote commands and passwords are outside this form." quality = "curated" base = "fig:rsync" required_capabilities = ["rsync.ssh-protect-args"] parser = { end_of_options = "--", interleave = "permute", long_equals = true, short_attached = true, short_bundles = true } [[commands.source]] id = "rsync-manpage" kind = "curated" url = "https://download.samba.org/pub/rsync/rsync.1" revision = "2026-10-27" evidence = "Archive, recursive, compress, verbose, exclude, delete, dry-run, itemize-changes, one local/SSH source and destination. -s protects remote arguments and requires both rsync versions at least 3.0.0." [[commands.field]] id = "archive" label = "Archive / preserve attributes" description = "Archive is a group of preservation options (-rlptgoD), not just recursion. It stays one --archive flag." type = "boolean" option = ["--archive", "-a"] sources = ["rsync-manpage"] [[commands.field]] id = "recursive" label = "Recurse into directories" type = "boolean" option = ["--recursive", "-r"] sources = ["rsync-manpage"] [[commands.field]] id = "compress" label = "Compress during transfer" type = "boolean" option = ["--compress", "-z"] sources = ["rsync-manpage"] [[commands.field]] id = "verbose" label = "Verbosity" type = "count" option = ["--verbose", "-v"] sources = ["rsync-manpage"] [[commands.field]] id = "exclude" label = "Exclude patterns (ordered)" description = "Literal patterns passed to rsync in this order; Otty never expands globs." type = "string" option = ["--exclude"] multiple = true sources = ["rsync-manpage"] [[commands.field]] id = "delete" label = "Delete extra destination files" description = "Deletes destination entries not present in the source. Review a dry run first." type = "boolean" option = ["--delete"] effect = "delete" sources = ["rsync-manpage"] [[commands.field]] id = "dry_run" label = "Dry run / preview only" type = "boolean" option = ["--dry-run", "-n"] sources = ["rsync-manpage"] [[commands.field]] id = "itemize_changes" label = "Itemize changes" type = "boolean" option = ["--itemize-changes", "-i"] sources = ["rsync-manpage"] [[commands.field]] id = "protect_args" label = "Protect SSH arguments" description = "Always includes -s for SSH endpoints; both rsync installations must support it." fixed = ["-s"] enabled_when = { any = [{ field = "source", op = "variant_is", value = "ssh" }, { field = "destination", op = "variant_is", value = "ssh" }] } sources = ["rsync-manpage"] [[commands.field]] id = "source" label = "Source" description = "src/ transfers its contents; src transfers the directory itself. Keep the trailing slash exactly as intended." position = 0 min = 1 sources = ["rsync-manpage"] [[commands.field.variant]] id = "ssh" label = "SSH" format = "{host}:{path}" parts = [ { id = "host", type = "string", provider = "builtin:ssh.hosts", validation = { min_length = 1, forbid_chars = "/:@ \t\r\n" } }, { id = "path", type = "string", validation = { min_length = 1 } }, ] [[commands.field.variant]] id = "local" label = "Local" format = "{value}" parts = [{ id = "value", type = "path", path = { kind = "any", role = "input", must_exist = true, trailing_slash = "meaningful", transforms = ["dot_slash_if_dash", "dot_slash_if_colon"] } }] [[commands.field]] id = "destination" label = "Destination" position = 1 min = 0 sources = ["rsync-manpage"] [[commands.field.variant]] id = "ssh" label = "SSH" format = "{host}:{path}" parts = [ { id = "host", type = "string", provider = "builtin:ssh.hosts", validation = { min_length = 1, forbid_chars = "/:@ \t\r\n" } }, { id = "path", type = "string", validation = { min_length = 1 } }, ] [[commands.field.variant]] id = "local" label = "Local" format = "{value}" parts = [{ id = "value", type = "path", path = { kind = "any", role = "output", must_exist = false, transforms = ["dot_slash_if_dash", "dot_slash_if_colon"] } }] [[commands.constraint]] kind = "at_most_one" fields = ["source", "destination"] when = { all = [{ field = "source", op = "variant_is", value = "ssh" }, { field = "destination", op = "variant_is", value = "ssh" }] } message = "Only one endpoint may use SSH." [[commands.constraint]] kind = "at_most_one" fields = ["source", "destination"] when = { all = [{ task = "push-ssh" }, { not = { field = "destination", op = "variant_is", value = "ssh" } }] } message = "The Push to SSH task requires an SSH destination." [[commands.task]] id = "preview" label = "Preview synchronization" set = { archive = [true], dry_run = [true], itemize_changes = [true] } lock = ["dry_run"] fields = ["source", "destination", "archive", "delete", "exclude", "verbose"] required = ["source", "destination"] [[commands.task]] id = "local-backup" label = "Local backup" set = { archive = [true] } lock = ["archive"] fields = ["source", "destination", "delete", "dry_run", "exclude", "verbose"] required = ["source", "destination"] [[commands.task]] id = "push-ssh" label = "Push to SSH" description = "Choose the SSH variant for the destination, then explicitly check both rsync installations before inserting." set = { archive = [true], compress = [true] } lock = ["compress"] fields = ["source", "destination", "archive", "delete", "dry_run", "exclude"] required = ["source", "destination"] [[commands.group]] id = "basic" label = "Transfer" fields = ["source", "destination", "archive", "recursive", "compress", "dry_run"] [[commands.group]] id = "advanced" label = "Advanced" fields = ["verbose", "exclude", "delete", "itemize_changes", "protect_args"]