Copycat is a cross platform file synchronization tool for local file systems similar to robocopy for Windows.
It's written in Java but compiled to native binaries for Windows/Linux/MacOS using the GraalVM - Community Edition.
Advantages over robocopy:
- exclude files/folders using relative paths and glob patterns
- cross-platform support
- ANSI-colored, concise console output
- Desktop notifications and tray icon on major sync events
- YAML config for defaults and multiple tasks
- Date/time filters (
--since,--before,--until) with natural language support
For Windows/Linux/macOS, self-contained single-file binaries can be downloaded at https://github.com/vegardit/copycat/releases/latest
No installation is required.
- Windows: download the
.exeand either run it directly or place it somewhere on yourPATH. - Linux/macOS: download the binary,
chmod +x copycat, then run it (optionally move to a directory on yourPATH, e.g./usr/local/bin).
Copycat provides a generated Bash completion script that you can download from https://github.com/vegardit/copycat/releases/download/latest/bashcompletion.sh
Example (Linux/macOS, or Git Bash on Windows):
curl -fsSL -o bash-completions.sh https://github.com/vegardit/copycat/releases/download/latest/bashcompletion.sh
source ./bash-completions.shTo enable it permanently, source it from your ~/.bashrc (or equivalent).
- One-time sync (with deletion):
- Windows:
copycat sync C:\src X:\dst --delete - Linux/macOS:
copycat sync /src /mnt/dst --delete
- Windows:
- Continuous sync (watch for changes):
copycat watch <SOURCE> <TARGET> - Increase verbosity with
-v,-vv,-vvv, or use-qfor quiet.
Copycat understands two commands:
syncis used to synchronize files from one directory to anotherwatchis used to continuously watch a directory for changes and instantly syncs the changes to a given target
$ copycat sync --help
Usage: copycat sync [-hqVv] [--allow-reading-open-files] [--copy-acl] [--delete] [--delete-excluded] [--dry-run]
[--exclude-hidden-files] [--exclude-hidden-system-files] [--exclude-older-files] [--exclude-other-links]
[--exclude-system-files] [--ignore-errors] [--ignore-symlink-errors] [--log-errors-to-stdout]
[--before <when>] [--config <path>] [--log-file <path>] [--max-depth <depth>] [--since <when>]
[--stall-timeout <duration>] [--threads <count>] [--timestamp-tolerance <duration>] [--until
<when>] [--filter (in|ex):<pattern>]... [--no-log <op>[,<op>...]]... [SOURCE] [TARGET]
Performs one-way recursive directory synchronization copying new files/directories.
Positional parameters:
[SOURCE] Directory to copy from files.
[TARGET] Directory to copy files to.
Options:
--allow-reading-open-files
On Windows, open source files with shared read access (best-effort).
May copy an inconsistent snapshot for actively written files and may skip copying some metadata.
--before <when> Sync only files modified before this date/time (exclusive). Format same as --since.
Mutually exclusive with --until.
--config <path> Path to a YAML config file.
--copy-acl Copy file permissions (ACL) for newly copied files.
--delete Delete extraneous files/directories from target.
--delete-excluded Delete excluded files/directories from target.
--dry-run Don't perform actual synchronization.
--exclude-hidden-files
Don't synchronize hidden files.
--exclude-hidden-system-files
Don't synchronize hidden system files.
--exclude-older-files Don't override newer files in target with older files in source.
--exclude-other-links Don't synchronize symlinks whose targets are missing or are neither files nor directories.
--exclude-system-files
Don't synchronize system files.
--filter (in|ex):<pattern>
Glob pattern for files/directories to be excluded (ex:<pattern>) from or
included (in:<pattern>) in sync.
-h, --help Show this help message and exit.
--ignore-errors Continue sync when errors occur.
--ignore-symlink-errors
Continue if creation of symlinks on target fails.
--log-errors-to-stdout
Log errors to stdout instead of stderr.
--log-file <path> Write console output also to the given log file.
--max-depth <depth> Maximum directory traversal depth from the source root.
0=only top-level files (no subdirs), 1=include immediate subdirectories, etc.
Default: unlimited.
--no-log <op>[,<op>...]
Don't log the given sync operation. Valid values: CREATE, MODIFY, DELETE, SCAN
-q, --quiet Quiet mode.
--since <when> Sync only files modified on or after this date/time. Accepts ISO-8601 (2024-12-25,
2024-12-25T14:30, 2024-12-25T14:30Z), durations (P3D, PT2H), or relative
expressions (3 days ago, yesterday 14:00).
--stall-timeout <duration>
Abort sync if no progress is observed for this long.
Examples: PT10M, 10m, 2h 30m. Use 0 to disable.
Bare numbers are minutes. Default: 10m
--threads <count> Number of concurrent threads. Default: 2
--timestamp-tolerance <duration>
Treat source and target files as unchanged when their last-modified timestamps differ by no
more than this duration.
Examples: 1s, 2s, PT0.5S. Use 0 to require exact timestamp matches. Default: 0
--until <when> Sync only files modified on or before this date/time (inclusive). Format same as --since.
Combined with --since to define a date range. Mutually exclusive with --before.
-v, --verbose Specify multiple -v options to increase verbosity.
For example `-v -v -v` or `-vvv`.
Source and target roots must not refer to the same directory or contain one another.
This rule also applies with filters, --max-depth, --dry-run, and --ignore-errors.
All tasks are checked before any task changes the filesystem.
For a missing target root, its parent directory must already exist.
Directory symlinks supplied as source or target roots select the directories they resolve to. A source root symlink produces an independent copy, and a target root symlink remains in place. Symlinks encountered inside the source directory are copied as links. Root resolution errors, including broken root symlinks, abort the command.
With --ignore-errors, failed entries are logged and counted while independent entries continue.
An incomplete source or target directory listing prevents processing that directory's entries.
If a target entry fails during deletion, its matching source entry and any descendants are also skipped for that run.
Run sync again to retry skipped work.
Signals and thread interruptions still abort the sync.
For sync, --delete-excluded takes effect only when --delete is enabled.
It does not enable deletion by itself.
Under the hood, the sync command uses a first-match-wins include/exclude filter engine with directory creation aligned to filtering:
- First matching
in:/ex:rule wins; unmatched paths are included by default. - Date filters (
--since,--before,--until) apply only to files; directories are never excluded based on modification time. - Hidden/system flags apply to both files and directories.
- Directory creation follows the filter rules:
- Directories are created when they contain at least one included entry.
- Empty directories are created if they are explicitly included (e.g. in:somedir) or when no filters are configured.
Examples:
:: Basic sync with deletion
copycat sync C:\myprojects X:\myprojects --delete --threads 4
:: Sync only files modified in the last 3 days
copycat sync C:\mydata X:\backup --since "3 days ago"
:: Sync files modified between specific dates
copycat sync C:\docs X:\archive --since 2024-01-01 --until 2024-12-31
:: Sync files modified since yesterday at 2 PM
copycat sync C:\work X:\backup --since "yesterday 14:00"Default values and/or multiple sync tasks can be configured using a YAML config file:
# default values for sync tasks
defaults:
copy-acl: false
# Optional (Windows): allow copying files that are currently open for writing by other processes (best-effort)
# allow-reading-open-files: false
delete: true
delete-excluded: true
dry-run: false
exclude-older-files: false
exclude-hidden-files: false
exclude-system-files: true
exclude-hidden-system-files: false
filters:
- ex:**/node_modules
- in:logs/latest.log # keep latest log file
- ex:logs/*.log # exclude all other log files
ignore-errors: false
ignore-symlink-errors: false
threads: 2
# Optional: tolerate small source/target last-modified timestamp differences (e.g. CIFS/SMB precision)
# timestamp-tolerance: 2s
# Optional: limit directory traversal depth (0=no subdirs)
# max-depth: 0
# Optional: sync only recent files
# since: "7 days ago" # or "2024-01-01" or "yesterday"
# until: "today" # inclusive upper bound (mutually exclusive with 'before')
# before: "tomorrow" # exclusive upper bound (mutually exclusive with 'until')
# one or more sync tasks
sync:
- source: C:\mydata
target: \\myserver\mydata
- source: D:\myotherdata
target: \\myserver\myotherdata
# Optional: sync only files modified in the last week
since: "7 days ago"
- source: E:\archives
target: \\backup\archives
# Optional: sync files from a specific date range
since: 2024-01-01
until: 2024-12-31To enable editor validation/autocompletion for config.yaml, a JSON schema is published at:
If you use the YAML Language Server (e.g. via VS Code), you can reference it by adding this as the first line of your YAML file:
# yaml-language-server: $schema=https://github.com/vegardit/copycat/releases/download/latest/config.schema.json$ copycat watch --help
Usage: copycat watch [-hqVv] [--allow-reading-open-files] [--copy-acl] [--delete-excluded] [--exclude-hidden-files]
[--exclude-hidden-system-files] [--exclude-system-files] [--log-errors-to-stdout]
[--config <path>] [--log-file <path>] [--max-depth <depth>] [--since <when>] [--until
<when>] [--before <when>] [--filter (in|ex):<pattern>]... [--no-log <op>[,<op>...]]...
[SOURCE] [TARGET]
Continuously watches a directory recursively for changes and synchronizes them to another directory.
Positional parameters:
[SOURCE] Directory to copy from files.
[TARGET] Directory to copy files to.
Options:
--allow-reading-open-files
On Windows, open source files with shared read access (best-effort).
May copy an inconsistent snapshot for actively written files and may skip copying some metadata.
--before <when> Sync only files modified before this date/time (exclusive). Format same as --since.
Mutually exclusive with --until.
--config <path> Path to a YAML config file.
--copy-acl Copy file permissions (ACL) for newly copied files.
--delete-excluded Delete excluded files/directories from target.
--exclude-hidden-files
Don't synchronize hidden files.
--exclude-hidden-system-files
Don't synchronize hidden system files.
--exclude-other-links Don't synchronize symlinks whose targets are neither files nor directories.
--exclude-system-files
Don't synchronize system files.
--filter (in|ex):<pattern>
Glob pattern for files/directories to be excluded (ex:<pattern>) from or
included (in:<pattern>) in sync.
-h, --help Show this help message and exit.
--log-errors-to-stdout
Log errors to stdout instead of stderr.
--log-file <path> Write console output also to the given log file.
--max-depth <depth> Maximum directory traversal depth from the source root.
0=only top-level files (no subdirs), 1=include immediate subdirectories, etc.
Default: unlimited.
--no-log <op>[,<op>...]
Don't log the given filesystem operation. Valid values: CREATE, MODIFY, DELETE
-q, --quiet Quiet mode.
--since <when> Sync only files modified on or after this date/time. Accepts ISO-8601 (2024-12-25,
2024-12-25T14:30, 2024-12-25T14:30Z), durations (P3D, PT2H), or relative
expressions (3 days ago, yesterday 14:00).
--until <when> Sync only files modified on or before this date/time (inclusive). Format same as
--since. Combined with --since to define a date range. Mutually exclusive with
--before.
-v, --verbose Specify multiple -v options to increase verbosity.
For example `-v -v -v` or `-vvv`.
Example:
$ copycat watch C:\myprojects X:\myprojectsDefault values and/or multiple sync tasks can be configured using a YAML config file:
# default values for sync tasks
defaults:
copy-acl: false
delete-excluded: true
exclude-hidden-files: false
exclude-system-files: true
exclude-hidden-system-files: false
# Optional: limit directory traversal depth for watching (0=no subdirs)
# max-depth: 0
filters:
- ex:**/node_modules
- in:logs/latest.log # keep latest log file
- ex:logs/*.log # exclude all other log files
# one or more sync tasks
sync:
- source: C:\mydata
target: \\myserver\mydata
- source: D:\myotherdata
target: \\myserver\myotherdataBy default all files are synced from source to target.
- Filters apply to both files and directories.
- Each filter is prefixed with
in:(include) orex:(exclude); the prefix is case-insensitive. - Paths are matched against the full source-relative or target-relative path using
/as separator; on Windows, backslashes in patterns are normalized to/. - Filters are evaluated in the order they are defined; the first matching filter decides:
ex:→ excludedin:→ included
- If no filter matches, the entry is included by default, even if only
in:filters are configured. - Date filters (
--since,--until,--before) are applied only to files, never to directories; hidden/system flags apply to both files and directories and are evaluated before glob filters.
Files/folders can be excluded/included using glob patterns.
The patterns must be prefixed with ex: for exclude patterns and with in: for include patterns. The prefix is case-insensitive, i.e. EX: and IN: can also be used.
The order in which patterns are declared is important. Copycat checks the relative path of each file to be synced against the configured list of include/exclude patterns, and the first matching pattern is acted on:
- if it is an exclude pattern, the file is not synced;
- if it is an include pattern, the file is synced;
- if no matching pattern is found, the file is synced.
-
YAML config example:
sync: - source: C:\mydata target: \\myserver\mydata filters: - ex:**/node_modules - in:logs/latest.log # keep latest log file - ex:logs/*.log # exclude all other log files
-
CLI example:
copycat sync --filter ex:**/node_modules --filter in:logs/latest.log --filter ex:logs/*.log C:\mydata \\myserver\mydata
- Copycat distinguishes between:
- Traversal: whether a directory is descended into.
- Inclusion/materialization: whether a directory itself is created on the target.
- A global
ex:**(orex:**/*) does not by itself stop traversal when there arein:rules that might match descendants (for examplein:**/patch.xml, ex:**still traverses intodev/...to findpatch.xml). - Directories on the target are created lazily:
- A directory is created when the first included child (file or subdirectory) under it is synced, or
- When the directory itself is explicitly included by a pattern.
- During
sync, a target file or symlink that conflicts with a source directory is replaced only when that directory needs to be created, unless selected for deletion. Target directory symlinks below the selected target root are not traversed. Synchronization assumes that other processes do not replace target directories during the run. - Empty directory semantics:
in:somedirincludes an emptysomedirdirectory (it will be created even without files).in:somedir/**matches only descendants ofsomedir, so an emptysomediris not created.
- Whitelist-style (copy only some patterns):
filters: - in:dir/subdir/*.log - in:**/patch.xml - ex:** # exclude everything else
- Blacklist-style (copy everything except some patterns):
filters: - ex:**/node_modules/** - in:logs/latest.log # match this file before excluding other logs - ex:logs/*.log
- Target-side filters use the same syntax and semantics as source filters (same
in:/ex:rules, first match wins, default include). - In
sync, the deletion pass runs only when--deleteis enabled:--deleteconsiders target entries that do not exist in the source; filters can protect some of them.- With
--delete-excludedalso enabled, it deletes excluded target entries even if they exist in the source.
- During
sync --delete, excluded entries are protected when--delete-excludedis disabled. An excluded directory protects its whole subtree. If an extraneous directory contains excluded descendants, sync deletes its eligible contents and keeps the ancestor directories needed to contain the protected entries. - Matching is done on target-relative paths, again using
/as separator (backslashes in patterns are normalized on Windows). sync --dry-runreports the copies and directory creation required after planned deletions, even when existing target files match the source.
You can filter files based on their modification time using --since, --until, and --before options:
Supported date/time formats:
- ISO-8601 dates:
2024-12-25,2024-12-25T14:30,2024-12-25 14:30:45 - Keywords:
yesterday,today,tomorrow(with optional time likeyesterday 14:00) - Relative expressions (case-insensitive, including ISO-8601 durations):
- ISO duration syntax:
[in] <duration> [ago], for examplePT1H,PT1H ago,in PT2H30M,P3D ago - Human-readable syntax:
[in] <amount><unit> [<amount><unit> ...] [ago], for example3h ago,2d 3h 15m ago,in 5 hours- Units for human-readable forms:
d/day/days,h/hour/hours,m/min/mins/minute/minutes,s/sec/secs/second/seconds - Order does not matter:
2d 3h,3h 2d,1h 30m, etc.
- Units for human-readable forms:
- Semantics:
in ...→ future (relative to now), for examplein 2 hours,in PT2H... ago→ past, for example3 days ago,PT1H ago- Without
inoragoit defaults to past, for example3h 30mandPT1Hare interpreted as 3h30m ago and 1 hour ago respectively.
- ISO duration syntax:
Examples:
# Sync files modified in the last week
copycat sync source/ target/ --since "7 days ago"
# Sync files modified in the last hour using ISO duration
copycat sync source/ target/ --since "PT1H ago"
# Sync files modified between specific dates
copycat sync source/ target/ --since 2024-01-01 --until 2024-06-30
# Sync files modified since yesterday at 2 PM
copycat sync source/ target/ --since "yesterday 14:00"
# Sync files modified today
copycat sync source/ target/ --since today- See CONTRIBUTING.md for guidelines.
- This project follows the Contributor Covenant; see CODE_OF_CONDUCT.md.
All files are released under the Apache License 2.0.
Individual files contain the following tag instead of the full license text:
SPDX-License-Identifier: Apache-2.0
This enables machine processing of license information based on the SPDX License Identifiers that are available here: https://spdx.org/licenses/.

