{"id":34537064,"url":"https://github.com/cdevroe/bulldozer","last_synced_at":"2026-05-24T20:37:10.915Z","repository":{"id":70829408,"uuid":"332942064","full_name":"cdevroe/bulldozer","owner":"cdevroe","description":"A simple PHP script to move images from one directory into /yyyy/mm/dd directories based on date created.","archived":false,"fork":false,"pushed_at":"2026-05-05T15:18:11.000Z","size":37,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-05T17:22:52.716Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cdevroe.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2021-01-26T02:02:52.000Z","updated_at":"2026-05-05T15:21:17.000Z","dependencies_parsed_at":null,"dependency_job_id":"5ea4700c-12e1-4105-8fd2-7ce5ea7e19a7","html_url":"https://github.com/cdevroe/bulldozer","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/cdevroe/bulldozer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdevroe%2Fbulldozer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdevroe%2Fbulldozer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdevroe%2Fbulldozer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdevroe%2Fbulldozer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cdevroe","download_url":"https://codeload.github.com/cdevroe/bulldozer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdevroe%2Fbulldozer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33450401,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-24T19:21:36.376Z","status":"ssl_error","status_checked_at":"2026-05-24T19:21:10.562Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-24T06:06:21.067Z","updated_at":"2026-05-24T20:37:10.910Z","avatar_url":"https://github.com/cdevroe.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bulldozer\n\nBulldozer is a small PHP CLI script that organizes images into `yyyy/mm/dd` directories based on the image date.\n\nIt targets **PHP 8.2 or newer** and has no Composer dependencies.\n\nBy [Colin Devroe](http://cdevroe.com/projects/bulldozer)\n\n## Setup\n\nEdit the configuration block at the top of `bulldozer.php`:\n\n```php\n$config = [\n    'source' =\u003e '/path/to/images_to_organize',\n\n    'locations' =\u003e [\n        'default' =\u003e '/path/to/image_library',\n        'backup' =\u003e '/Volumes/BackupDrive/image_library',\n    ],\n];\n```\n\nThe source and destination directories must already exist. Bulldozer creates the dated `yyyy/mm/dd` directories inside the destination as needed.\n\n## Config File\n\nYou can also put configuration in JSON and load it with `--config`:\n\n```json\n{\n  \"source\": \"/path/to/images_to_organize\",\n  \"destination\": \"/path/to/image_library\",\n  \"date\": \"mtime\",\n  \"timezone\": \"America/New_York\",\n  \"duplicates\": \"rename\"\n}\n```\n\nRun with:\n\n```sh\nphp bulldozer.php --config=/path/to/bulldozer.config.json --json\n```\n\nCLI flags override config-file values. For safety, config files cannot enable\n`--run`; write operations still require the explicit `--run` CLI flag.\n\n## Usage\n\nAlways start with a dry run:\n\n```sh\nphp bulldozer.php\n```\n\nActually copy files:\n\n```sh\nphp bulldozer.php --run\n```\n\nMove files instead of copying them:\n\n```sh\nphp bulldozer.php --mode=move --run\n```\n\nUse a configured destination:\n\n```sh\nphp bulldozer.php --location=backup --run\n```\n\nOverride the configured source and destination:\n\n```sh\nphp bulldozer.php --source=/path/to/source --destination=/path/to/library --run\n```\n\nLegacy usage still works:\n\n```sh\nphp bulldozer.php backup true\n```\n\n## Options\n\n```text\n--config=PATH              Load a JSON config file. CLI flags override config values.\n--source=PATH              Source directory. Defaults to the configured source.\n--destination=PATH         Destination directory. Overrides --location.\n--location=NAME            Configured destination name. Default: default.\n--run                      Actually copy or move files. Default is dry run.\n--dry-run                  Force dry run.\n--mode=copy|move           Copy or move files. Default: copy.\n--date=exif|mtime          Date source. Default: exif with mtime fallback.\n--duplicates=rename|skip   Rename or skip filename collisions. Default: rename.\n--timezone=TIMEZONE        Timezone for EXIF and mtime dates.\n--all-files                Process every file except ignored system files.\n--json                     Print machine-readable JSON instead of text summary.\n--manifest=PATH            Write a JSON manifest of planned or completed actions.\n--quiet                    Suppress verbose per-file output.\n--verbose                  Print per-file progress. Ignored when --json is used.\n--log[=PATH]               Write a log file.\n--hooks[=PATH]             Load a PHP hooks file.\n--help                     Show help.\n```\n\n## Agent Usage\n\nAgents should dry-run first and use JSON plus a manifest:\n\n```sh\nphp bulldozer.php \\\n  --source=/path/to/source \\\n  --destination=/path/to/library \\\n  --date=mtime \\\n  --timezone=America/New_York \\\n  --json \\\n  --manifest=/tmp/bulldozer-manifest.json\n```\n\nThen inspect the manifest before running:\n\n```sh\nphp bulldozer.php \\\n  --source=/path/to/source \\\n  --destination=/path/to/library \\\n  --date=mtime \\\n  --timezone=America/New_York \\\n  --json \\\n  --manifest=/tmp/bulldozer-manifest.json \\\n  --run\n```\n\nAgent-specific guidance lives in `AGENTS.md`. More detailed references are in\n`docs/cli-reference.md`, `docs/manifest-schema.md`, `docs/hooks.md`, and\n`docs/agent-examples.md`.\n\n## Exit Codes\n\n```text\n0  Success.\n1  Usage, configuration, or validation error.\n2  Run completed with file operation errors.\n3  Run completed with hook, log, or manifest reporting errors.\n```\n\n## Dates\n\nBy default, Bulldozer tries EXIF image dates first:\n\n1. `DateTimeOriginal`\n2. `DateTimeDigitized`\n3. `DateTime`\n\nIf no usable EXIF date is found, it falls back to filesystem modified time.\n\nUse `--date=mtime` to skip EXIF and use modified time for every file.\n\nUse `--timezone=America/New_York` if you want to force the timezone used when building date folders. Without this option, Bulldozer uses PHP's configured default timezone.\n\n## Duplicates\n\nThe default duplicate behavior is `rename`:\n\n```text\nIMG_0001.jpg\nIMG_0001-1.jpg\nIMG_0001-2.jpg\n```\n\nUse `--duplicates=skip` to leave colliding files untouched.\n\nWhen duplicates are skipped, Bulldozer records whether the files appear to be the same by comparing file size first, then SHA-256 hashes only when sizes match.\n\n## Logging\n\nUse `--log` to write a basic operation log:\n\n```sh\nphp bulldozer.php --log --run\n```\n\nThat writes to `bulldozer.log` next to the script. You can also choose a path:\n\n```sh\nphp bulldozer.php --log=/tmp/bulldozer.log --run\n```\n\nThe log includes run configuration, skipped files, directory creation, copy/move operations, duplicate handling, hook errors, and final stats.\n\n## Hooks\n\nA full plugin system would add too much complexity for a small CLI script. The script instead supports a deliberately small event hook file for developers who need to run extra code as files are planned, copied, moved, skipped, or when a run starts or finishes.\n\nCreate a hook file:\n\n```php\n\u003c?php\n\nreturn static function (BulldozerHooks $hooks): void {\n    $hooks-\u003eon('file.copied', static function (array $event): void {\n        echo \"Copied {$event['source']} to {$event['destination']}\" . PHP_EOL;\n    });\n};\n```\n\nRun with hooks:\n\n```sh\nphp bulldozer.php --hooks=/path/to/bulldozer-hooks.php --run\n```\n\nAvailable events:\n\n```text\nrun.start\ndirectory.planned\ndirectory.created\ndirectory.error\nfile.planned\nfile.copied\nfile.moved\nfile.duplicate\nfile.skipped\nfile.error\nrun.finish\n```\n\nHook failures are logged and counted as errors, but they do not stop the run.\n\n## Notes\n\n- The script is intended for images: JPG, PNG, HEIC, TIFF.\n- EXIF reading is available for formats supported by PHP's `exif` extension,\n  typically JPEG and TIFF. HEIC files usually fall back to modified time.\n- Use `--all-files` if you want Bulldozer to process non-image files too.\n- Move mode uses `rename()` first, then falls back to copy-and-delete when moving across filesystems.\n\n## Version History\n\n- **2026.05** - May 5, 2026\n    - Target PHP 8.2+.\n    - Added dry-run by default, explicit `--run`, copy/move modes, safer\n      duplicate handling, EXIF date support, streaming traversal, logging, and\n      optional event hooks.\n    - Added agent-ready JSON output, manifest output, stable exit codes, CLI\n      reference docs, and a smoke test.\n- **2021.01** - January 25, 2021\n    - Initial public release.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcdevroe%2Fbulldozer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcdevroe%2Fbulldozer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcdevroe%2Fbulldozer/lists"}