Usage
Command Structure
bulkfilepr apply [options]
bulkfilepr uses a single apply command with various options to control its behavior.
Command-Line Options
| Option | Argument | Required | Notes |
|---|---|---|---|
--mode | <mode> | Yes | Update mode: upsert, exists, or match |
--repo-path | <path> | Yes | Destination file path inside the repository (relative to repo root) |
--new-file | <path> | Yes | Path on disk to the new file content |
--repo | <dir> | No | Repository directory to operate on (default: .) |
--branch | <name> | No | Branch name for the changes (auto-generated if omitted) |
--commit-message | <msg> | No | Commit message (default: chore: update {repo-path}) |
--pr-title | <title> | No | Pull request title (default: Update {repo-path}) |
--pr-body | <body> | No | Pull request body content |
--draft | - | No | Create the PR as a draft |
--dry-run | - | No | Perform checks only, make no actual changes |
--remote | <name> | No | Git remote name to push to (default: origin) |
--expect-sha256 | <hex> | Conditional | Expected SHA-256 hash (required when --mode match). Multiple hashes can be comma-separated to match any of them |
--version | - | No | Print version number and exit |
Update Modes
The --mode option controls when files are updated:
upsert
Always write the new file (create if missing, update if exists). If the file already exists and the content is identical to the new content, no action is taken.
Use case: Ensuring a standard file is present and up-to-date across all repositories.
exists
Only update if the file already exists at the destination path. If the file is missing, no action is taken. If the file exists but content matches the new content, no action is taken.
Use case: Updating existing configuration files without creating them in repositories that don’t have them.
match
Only update if the file exists AND its SHA-256 hash matches one of the values provided in --expect-sha256. This ensures you only update files at specific known versions. Multiple comma-separated hashes can be provided to match any of several versions.
Use case: Safely updating files when you need to verify they haven’t been customized from known baselines.
Examples:
.github/workflows/ci.ymlDockerfile.eslintrc.jsonCODEOWNERS
Branch Naming
When --branch is not specified, bulkfilepr automatically generates a branch name using the pattern:
bulkfilepr/{hash}
where {hash} is the first 12 characters of the SHA-256 hash of the new file content. This ensures:
- Deterministic branch names for identical content
- Different branches for different file versions
- Easy identification of bulkfilepr-managed branches
Idempotency and Branch Existence
bulkfilepr is designed to be idempotent. If the branch to be created already exists (either locally or on the remote), the command exits successfully with exit code 0. This allows the command to be run multiple times safely without creating duplicate branches or PRs.
Behavior when branch exists:
- The tool checks if the branch exists before attempting to create it
- If found, it assumes a previous successful run and exits with code 0
- Output indicates:
Action: branch already exists (idempotent - no action taken) - No git operations are performed
This makes bulkfilepr safe to use in automation and retry scenarios.
Branch State Handling
bulkfilepr has intelligent handling for repository branch states:
On Default Branch
If you’re already on the default branch with a clean working tree, bulkfilepr proceeds normally.
On Non-Default Branch (Clean Working Tree)
If you’re on a non-default branch but your working tree is clean (no uncommitted changes), bulkfilepr will automatically switch to the default branch and proceed with the operation.
Example:
# Currently on 'feature-branch' with no uncommitted changes
bulkfilepr apply --mode upsert --repo-path README.md --new-file ~/standard/README.md
# → Switches to default branch (e.g., 'main') and proceeds
On Non-Default Branch (Dirty Working Tree)
If you’re on a non-default branch AND have uncommitted changes, bulkfilepr exits with a non-zero exit code to prevent data loss.
Error message:
Error: not on default branch and working tree is dirty: current branch is "feature-branch", expected "main". Please commit or stash your changes
This safety check ensures you don’t accidentally lose uncommitted work.
Safety Checks
Before making any changes (in both normal and dry-run modes), bulkfilepr performs the following safety checks:
-
Default Branch Detection: Uses
gh repo viewto determine the repository’s default branch name. - Branch State Verification:
- If on the default branch: proceeds to next check
- If on non-default branch with clean working tree: switches to default branch
- If on non-default branch with dirty working tree: exits with error
-
Clean Working Tree: Ensures there are no uncommitted changes after any branch switching. This prevents accidentally including unrelated changes in the PR.
- Branch Existence Check: Verifies the target branch doesn’t already exist (for idempotency).
If any of these checks fail, bulkfilepr exits with a non-zero exit code.
Dry Run Mode
The --dry-run flag is a critical safety feature that performs all checks and reports what would happen, but makes no actual changes:
What dry run does:
- ✅ Detects default branch
- ✅ Checks current branch state
- ✅ Verifies working tree cleanliness
- ✅ Evaluates mode conditions (file existence, content matching)
- ✅ Determines branch name
- ✅ Reports what would be updated
What dry run does NOT do:
- ❌ Does not switch branches
- ❌ Does not create branches
- ❌ Does not write files
- ❌ Does not stage, commit, or push
- ❌ Does not create PRs
Exit behavior: Dry run exits with code 0 if safety checks pass (regardless of whether it would take action), or non-zero if safety checks fail.
Mode Interactions with --expect-sha256
The --expect-sha256 option has specific interactions with update modes:
| Mode | --expect-sha256 | Behavior |
|---|---|---|
upsert | Optional (ignored) | Always attempts to write file |
exists | Optional (ignored) | Only updates if file exists |
match | Required | Only updates if file exists AND hash matches |
When using --mode match, you must provide --expect-sha256 or the command will exit with error code 2 (invalid usage).
Example:
# This will fail - missing --expect-sha256
bulkfilepr apply --mode match --repo-path config.yml --new-file ~/config.yml
# This works - hash provided
bulkfilepr apply --mode match --repo-path config.yml --new-file ~/config.yml \
--expect-sha256 a1b2c3d4e5f6...
Exit Codes
| Code | Meaning | Examples |
|---|---|---|
| 0 | Success | File updated, no action needed, branch already exists, dry run passed |
| 1 | Operational failure | Unsafe repo state, git/gh command failure, push/PR failure |
| 2 | Invalid usage | Missing required flags, invalid mode, missing --expect-sha256 for match mode |
Note: Exit code 0 is returned for several “success” scenarios:
- File was successfully updated and PR created
- No action was needed (content already matches)
- Branch already exists (idempotent behavior)
- Mode conditions not met (e.g., file doesn’t exist with
--mode exists)
Output Format
bulkfilepr provides clear, human-readable output for all operations:
Default branch: main
Mode: upsert
Action: updated
Branch: bulkfilepr/a1b2c3d4e5f6
PR URL: https://github.com/owner/repo/pull/123
Possible actions:
updated- File was updated and PR createdno action taken- Mode conditions not met or content already matcheswould update (dry run)- Dry run mode, would have updatedbranch already exists (idempotent - no action taken)- Branch exists, assuming previous success
Each action includes relevant context like branch name, reason for no action, or PR URL.