octa --compare-folders¶
Compare two or more folders file by file and print one row per file. Exit
code is 0 when every file is the same in every folder and 1 otherwise,
so it can gate a CI step the same way --validate-schema
does.
Synopsis¶
| Flag | Required | Meaning |
|---|---|---|
--compare-folders |
yes | Compare the FOLDER arguments that follow (two or more). |
--no-recursive |
no | Compare only the top level of each folder. |
-f, --format FORMAT |
no | Output format: tsv (default), json, or csv. |
A FOLDER is a local path or a cloud URL: s3://bucket/exports/,
az://container/prefix/, gs://bucket/prefix/, sftp://host/path/ or
davs://host/path/. Local and cloud folders mix freely.
How files are matched¶
Files are matched by their path inside each folder: 2024/sales.csv
under one folder is compared with 2024/sales.csv under every other.
Subfolders are included unless you pass --no-recursive.
Contents are read only where they decide the answer:
- Two copies of different sizes are different. Nothing is read.
- Two or more copies of the same size are read in full and compared by a BLAKE3 hash of their bytes. Equal size alone never counts as the same.
- A file that exists in one folder only is never read.
Output¶
One row per file path, sorted by path, on stdout:
| Column | Meaning |
|---|---|
path |
The path inside the folders. |
status |
same, different or missing (see below). |
in |
The folders that have the file. |
missing from |
The folders that do not. |
size: FOLDER |
Size in bytes, one column per folder. |
modified: FOLDER |
Last-modified time, one column per folder. |
hash: FOLDER |
BLAKE3 hash, filled only where the contents were read. |
status |
Meaning |
|---|---|
same |
Every folder has the file and every copy is identical. |
different |
At least two copies differ. The file may also be missing from a folder. |
missing |
Some folders lack the file, and the copies that exist are identical. |
The status words are stable English, so a script can match on them. Folder columns are named after the last part of each folder's path; when two folders end the same way, more of the path is added until the names differ.
Progress goes to stderr, so a pipe stays parseable: a listing FOLDER
line per folder and, on a terminal, a count while contents are read.
Exit codes¶
| Code | When |
|---|---|
0 |
Every file is the same in every folder. |
1 |
A file differs or is missing, a folder hit the file limit, or an error. |
An incomplete comparison is not a pass: a folder that stopped at the file
limit exits 1 even when every listed file matched.
The file limit¶
Each folder is listed up to the Compare folders file limit from
Settings (100,000 files per folder by default). When a folder holds more,
the listing stops there, stderr warns which folder was cut short, and the
exit code is 1. Raise folder_compare_max_files in settings.toml, or
set folder_compare_max_files_unlimited = true, to list everything.
Cloud folders and credentials¶
A cloud URL uses the saved cloud connection that covers it, the same one
the app's sidebar uses. Without one, the ambient credentials are used:
AWS_* variables or a cached SSO login for S3, az login for Azure
(plus AZURE_STORAGE_ACCOUNT), gcloud application-default credentials
for GCS. WebDAV always needs a saved connection. See
Cloud storage: Credentials.
Examples¶
Two local folders¶
$ octa --compare-folders exports/before exports/after
listing before
listing after
path status in missing from size: before ...
new.csv missing after before ...
sales.csv different before, after 1204 ...
stock.csv same before, after 880 ...
$ echo $?
1
A local folder against a bucket¶
Top level only, in a CI step¶
- name: Check the published folder matches the build
run: octa --compare-folders dist/ s3://acme-site/ --no-recursive
See also¶
octa --diff: two tables, row by row.octa --schema-drift: which files in one folder disagree about their columns.- In the app: Compare folders
- MCP:
compare_folders