A fast and efficient command-line tool for comparing and synchronizing directories.
- Recursive directory comparison - Scan subdirectories automatically
- Multiple output formats - Text, JSON, or summary output
- Fast concurrent processing - Uses Swift's modern concurrency for optimal performance
- Smart file comparison - Quickly identifies identical, modified, and unique files
- Fuzzy filename matching - Match files with similar names using configurable precision threshold
- Clear exit codes - Easy integration with scripts and CI/CD pipelines
- Pattern-based file filtering - Use .filesignore to exclude files from operations
- One-way sync - Mirror source directory to destination
- Two-way sync - Bidirectional synchronization with conflict resolution
- Conflict resolution strategies - Keep newest, source, destination, or skip
- Dry-run mode - Preview changes before applying them
- Safe operations - Creates intermediate directories and validates paths
The latest macOS build can be downloaded from the GitHub Releases page.
See the Building section below for instructions on building from source.
Files supports .filesignore files to exclude certain files and directories from comparison and sync operations. This works similarly to .gitignore.
The tool automatically looks for .filesignore files in three locations (in order):
- User home directory:
~/.filesignore- Global patterns for all operations - Source/left directory:
<source-dir>/.filesignore - Destination/right directory:
<dest-dir>/.filesignore
Patterns from all found files are merged together.
*- Matches any characters except/?- Matches a single character except/**- Matches zero or more directories/at start - Pattern is relative to directory root/at end - Pattern matches directories only!- Negates a pattern (includes files that were previously excluded)#- Comment line- Empty lines are ignored
By default, .filesignore files themselves are always ignored and will not be copied during sync operations. This prevents the ignore configuration from being propagated to destination directories.
To explicitly include .filesignore in sync operations, add a negation pattern:
!.filesignore
# Ignore build artifacts
*.o
*.class
build/
# Ignore dependencies
node_modules/
vendor/
# Ignore version control
.git/
.svn/
# Ignore OS files
.DS_Store
Thumbs.db
# Ignore all log files
*.log
# But keep important.log
!important.log
# Ignore config.json only at root
/config.json
# Ignore all .txt files in build directory and subdirectories
build/**/*.txt
You can disable .filesignore pattern matching with the --no-ignore flag:
files compare dir1 dir2 --no-ignore
files sync source dest --no-ignoreFiles has three main commands: compare (default), sync, and cp.
Compare two directories and report differences:
files compare <left-directory> <right-directory> [options]
# or simply (compare is the default command):
files <left-directory> <right-directory> [options]--recursive/--no-recursive- Scan subdirectories recursively (default: recursive)--match-precision THRESHOLD- Fuzzy filename matching threshold from 0.0 to 1.0 (default: 1.0 for exact matching). Lower values enable matching files with similar names using Levenshtein distance--verbose,-v- Show detailed output with all file paths--format FORMAT- Output format:text(default),json,summary--no-ignore- Disable .filesignore pattern matching--help,-h- Show help message--version- Show version information
0- Directories are identical1- Differences found2- Error occurred (invalid directory, access denied, etc.)
Copy new and modified files from source to destination (without deletions):
files cp <source-directory> <destination-directory> [options]This is a convenience command equivalent to files sync --no-deletions with one-way mode. It's useful for updating a destination directory with new and changed files from source while preserving any extra files in the destination.
--show-more-right- Scan leaf directories on the right side for additional diff information--match-precision THRESHOLD- Fuzzy filename matching threshold from 0.0 to 1.0 (default: 1.0 for exact matching)--dry-run- Preview changes without applying them--verbose,-v- Show detailed output with all operations--format FORMAT- Output format:text(default),json,summary--no-ignore- Disable .filesignore pattern matching
# Preview what would be copied
files cp /source /backup --dry-run --verbose
# Copy new and modified files
files cp /source /backup --verboseSynchronize two directories:
files sync <source-directory> <destination-directory> [options]--two-way- Enable bidirectional sync (default: one-way)--conflict-resolution STRATEGY- For two-way sync:newest(default),source,destination,skip--recursive/--no-recursive- Scan subdirectories recursively (default: recursive)--deletions- Delete files in destination that don't exist in source (one-way sync only, default: false)--show-more-right- Scan leaf directories on the right side for additional diff information (one-way sync without deletions only)--match-precision THRESHOLD- Fuzzy filename matching threshold from 0.0 to 1.0 (default: 1.0 for exact matching). Lower values enable matching files with similar names using Levenshtein distance--dry-run- Preview changes without applying them--verbose,-v- Show detailed output with all operations--format FORMAT- Output format:text(default),json,summary--no-ignore- Disable .filesignore pattern matching
One-way sync (default): Copies and updates files from source to destination
- Copies files that exist only in source
- Updates files that differ between source and destination
- By default, does NOT delete files that exist only in destination
- Use
--deletionsflag to delete extra files in destination (mirrors source exactly)
Two-way sync (--two-way): Bidirectional synchronization
- Copies files that exist only in either directory to the other
- Never deletes files (syncs in both directions)
- Resolves conflicts for modified files based on
--conflict-resolutionstrategy
newest- Keep the file with the most recent modification time (default)source- Always prefer the source filedestination- Always prefer the destination fileskip- Skip conflicting files, leave both unchanged
The --match-precision option enables fuzzy matching of filenames using Levenshtein distance algorithm. This is useful when comparing directories with files that may have typos, slight variations, or systematic naming differences.
-
Threshold value: A number from 0.0 to 1.0
1.0(default): Only exact filename matches0.8: Allows ~20% character differences (recommended for typo detection)0.5: Allows ~50% character differences (very permissive)0.0: Matches any files (not recommended)
-
Matching is based on filename only, not the full path
-
Exact matches are always preferred over fuzzy matches
-
One-to-one mapping: Each right file can only match one left file
- Typo detection: Find files with misspelled names (e.g., "report.txt" vs "reprot.txt")
- Version variations: Match files with version numbers (e.g., "file_v1.txt" vs "file_v2.txt")
- Renamed files: Identify files that were slightly renamed between directories
- Import cleanup: Find near-duplicate files from different sources
# Compare with fuzzy matching (80% similarity threshold)
files /backup /current --match-precision 0.8 --verboseOutput shows fuzzy-matched files as "modified":
Modified (2):
~ report.txt (matched with: reprot.txt)
~ document.txt (matched with: documnet.txt)
# Sync files even if names have minor differences
files sync /source /dest --match-precision 0.8 --verboseThis will:
- Match "report.txt" (source) with "reprot.txt" (destination)
- Update the file with correct content
- Create new "report.txt" in destination
# Use higher threshold (90%) for stricter matching
files /dir1 /dir2 --match-precision 0.9Only files with very similar names will match (e.g., "file1.txt" and "file2.txt" won't match, but "report.txt" and "reprot.txt" will).
files /path/to/dir1 /path/to/dir2Output:
β Directories differ
Only in LEFT (3):
Use --verbose to see file list
Only in RIGHT (2):
Use --verbose to see file list
Modified (1):
Use --verbose to see file list
Summary: 3 left-only, 2 right-only, 1 modified, 10 unchanged
files /path/to/dir1 /path/to/dir2 --verboseOutput:
β Directories differ
Only in LEFT (3):
- old-file.txt
- deprecated/config.json
- temp/data.csv
Only in RIGHT (2):
+ new-feature.swift
+ assets/logo.png
Modified (1):
~ config.yaml
Summary: 3 left-only, 2 right-only, 1 modified, 10 unchanged
files /path/to/dir1 /path/to/dir2 --format jsonOutput:
{
"onlyInLeft": [
"old-file.txt",
"deprecated/config.json"
],
"onlyInRight": [
"new-feature.swift"
],
"modified": [
"config.yaml"
],
"common": [
"README.md",
"main.swift"
],
"summary": {
"onlyInLeftCount": 2,
"onlyInRightCount": 1,
"modifiedCount": 1,
"commonCount": 2,
"identical": false
}
}files /path/to/dir1 /path/to/dir2 --format summaryOutput:
Identical: no
Only in left: 3
Only in right: 2
Modified: 1
Unchanged: 10
Total files: 16
Compare only top-level files without scanning subdirectories:
files /path/to/dir1 /path/to/dir2 --no-recursive#!/bin/bash
if files /backup /current --format summary; then
echo "Backup is up to date"
else
echo "Backup needs updating"
fiCopy and update files from source to destination (extra files in destination are kept):
files sync /source/dir /backup/dir --dry-run --verboseOutput:
π DRY RUN - No changes will be made
Would perform 4 operation(s)
Copy (3):
would copy new-file.txt
would copy docs/guide.md
would copy src/main.swift
Update (1):
would update config.yaml
Execute the sync:
files sync /source/dir /backup/dir --verboseOutput:
Performed 4 operation(s)
Copy (3):
copied new-file.txt
copied docs/guide.md
copied src/main.swift
Update (1):
updated config.yaml
Summary: 4 succeeded, 0 failed, 0 skipped
Mirror source to destination exactly (deletes extra files in destination):
files sync /source/dir /backup/dir --deletions --dry-run --verboseOutput:
π DRY RUN - No changes will be made
Would perform 5 operation(s)
Copy (3):
would copy new-file.txt
would copy docs/guide.md
would copy src/main.swift
Update (1):
would update config.yaml
Delete (1):
would delete old-file.txt
Execute the sync:
files sync /source/dir /backup/dir --deletions --verboseOutput:
Performed 5 operation(s)
Copy (3):
copied new-file.txt
copied docs/guide.md
copied src/main.swift
Update (1):
updated config.yaml
Delete (1):
deleted old-file.txt
Summary: 5 succeeded, 0 failed, 0 skipped
Synchronize two directories bidirectionally, keeping the newest version of conflicting files:
files sync /dir1 /dir2 --two-way --conflict-resolution newest --verboseOutput:
Performed 4 operation(s)
Copy (3):
copied unique-in-dir1.txt
copied unique-in-dir2.txt
copied another-file.md
Update (1):
updated conflicting-file.txt
Summary: 4 succeeded, 0 failed, 0 skipped
Always prefer the source directory for conflicts:
files sync /source /dest --two-way --conflict-resolution sourceSync unique files but skip conflicting files:
files sync /dir1 /dir2 --two-way --conflict-resolution skip --verbosefiles sync /source /dest --dry-run --format jsonOutput:
{
"operations": [
{
"type": "copy",
"path": "new-file.txt",
"source": "/source/new-file.txt",
"destination": "/dest/new-file.txt"
},
{
"type": "update",
"path": "modified.txt",
"source": "/source/modified.txt",
"destination": "/dest/modified.txt"
}
],
"summary": {
"total": 2,
"succeeded": 0,
"failed": 0,
"skipped": 2
}
}#!/bin/bash
# Mirror production to backup with exact mirroring (including deletions)
echo "Checking what would change..."
files sync /production /backup --deletions --dry-run --format summary
read -p "Proceed with sync? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
files sync /production /backup --deletions --verbose
if [ $? -eq 0 ]; then
echo "Backup completed successfully"
else
echo "Backup failed!"
exit 1
fi
fiKeep two working directories in sync:
# Sync laptop and desktop project directories
files sync ~/projects/myapp /mnt/desktop/projects/myapp --two-way --conflict-resolution newestswift build -c release \
-Xswiftc -O \
-Xswiftc -whole-module-optimization \
-Xswiftc -cross-module-optimization
You can copy it to your PATH:
cp .build/release/files /usr/local/bin/Or create a symlink:
ln -s $(pwd)/.build/release/files /usr/local/bin/filesLICENSE: MIT