Skip to content

Force Overwrite

Sometimes the simplest solution is the best: just overwrite it.


The Pattern

# Don't check, just write
cp -f "$SOURCE" "$TARGET"

Force overwrite skips existence checks entirely. The operation succeeds whether the target exists or not, and the final state is always the desired state.

flowchart LR
    A[Source] --> B[Overwrite]
    B --> C[Target = Source]

    %% Ghostty Hardcore Theme
    style A fill:#65d9ef,color:#1b1d1e
    style B fill:#fd971e,color:#1b1d1e
    style C fill:#a7e22e,color:#1b1d1e

Zero Decisions

Force overwrite has no branches in its logic. The target becomes the source, period. Previous state is irrelevant.


When to Use

Good Fit

  • Writing configuration files (same content = same result)
  • Syncing files where source is authoritative
  • Branch resets where local state should match remote
  • Cache population where stale data should be replaced
  • Artifact uploads where latest version wins

Poor Fit

  • Resources with history you want to preserve
  • Collaborative content (user edits would be lost)
  • Operations where overwrites have side effects
  • When you need to know if content actually changed

Examples

File Synchronization

# Copy file regardless of whether target exists
cp -f "$SOURCE_FILE" "$TARGET_FILE"
# Sync directory contents
rsync -a --delete source/ target/

Git Branch Reset

# Force-reset branch to match remote
git checkout -B "$BRANCH" "origin/$BRANCH"

The -B flag creates the branch if it doesn't exist, or resets it if it does.

Git Push with Lease

# Force push with safety check
git push --force-with-lease origin "$BRANCH"

--force-with-lease overwrites the remote but fails if someone else pushed changes you haven't seen. It's force overwrite with a safety net.

Configuration Files

# Generate config from template (always overwrites)
envsubst < config.template > config.yaml
# Write known-good config
cat > /etc/app/config.json << 'EOF'
{
  "setting": "value"
}
EOF

GitHub Actions Artifacts

- uses: actions/upload-artifact@v4
  with:
    name: build-output
    path: dist/
    overwrite: true  # Explicitly overwrite if exists

Safe Force Overwrite Patterns

Force with Lease (Git)

# Overwrites remote branch, but fails if remote changed unexpectedly
git push --force-with-lease origin feature-branch

This prevents accidentally overwriting someone else's commits while still enabling idempotent branch updates.

Atomic Write (Files)

# Write to temp file, then atomically move
cat > "$TARGET.tmp" << 'EOF'
content here
EOF
mv -f "$TARGET.tmp" "$TARGET"

Atomic write prevents partial content if the process is interrupted.

The Temp-Then-Move Pattern

Always use write-to-temp + atomic move for critical files. A mv on the same filesystem is atomic; a multi-megabyte write is not.

Backup Before Overwrite

# Preserve previous version just in case
cp "$TARGET" "$TARGET.bak" 2>/dev/null || true
cp -f "$SOURCE" "$TARGET"

Conditional Force Based on Content

# Only force overwrite if content differs
if ! diff -q "$SOURCE" "$TARGET" &>/dev/null; then
  cp -f "$SOURCE" "$TARGET"
  echo "Updated $TARGET"
else
  echo "No changes needed"
fi

This hybrid approach avoids unnecessary writes while still being idempotent.


GitHub Actions Examples

Cache Overwrite

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('package-lock.json') }}
    # Cache is overwritten on key match (save always runs)

Artifact Replacement

- name: Upload coverage report
  uses: actions/upload-artifact@v4
  with:
    name: coverage
    path: coverage/
    overwrite: true
    retention-days: 5

Environment Variable Override

- name: Set deployment version
  run: |
    # Always overwrites previous value
    echo "DEPLOY_VERSION=${{ github.sha }}" >> "$GITHUB_ENV"

Kubernetes Examples

ConfigMap Replacement

# Delete and recreate (force overwrite pattern)
kubectl delete configmap app-config --ignore-not-found
kubectl create configmap app-config --from-file=config/

Or use the declarative approach:

# Apply overwrites existing ConfigMap
kubectl apply -f configmap.yaml

Secret Rotation

# Force-replace secret
kubectl create secret generic db-creds \
  --from-literal=password="$NEW_PASSWORD" \
  --dry-run=client -o yaml | kubectl apply -f -

Edge Cases and Gotchas

Loss of History

Force overwrite destroys previous state:

# Previous content of config.yaml is gone
cp -f new-config.yaml config.yaml

Mitigation: Use version control or backups for important files.

Unexpected Content Changes

If source content changes between runs, target changes too:

# Run 1: writes v1.0
cp -f release.tar.gz /deploy/

# Run 2: writes v1.1 (source changed)
cp -f release.tar.gz /deploy/

Consideration: This might be desired (latest wins) or problematic (version mismatch).

Force Push Dangers

Never Force Push to Shared Branches

git push --force to main or master can destroy your team's work. Commits they pushed will vanish. Always use --force-with-lease at minimum.

# DANGEROUS: can destroy team members' work
git push --force origin main
# SAFER: fails if remote has unexpected commits
git push --force-with-lease origin main

Partial Writes

Non-atomic overwrites can leave corrupt state:

# If process dies mid-write, file is corrupt
cat > large-file.bin < /dev/stdin

Mitigation: Write to temp file, then atomic move.


Anti-Patterns

Force Push to Shared Branches

# Bad: destroys others' work without warning
git push --force origin main

# Better: use force-with-lease
git push --force-with-lease origin main

# Best: don't force push to main at all

Silent Overwrites of User Data

# Bad: user's customizations lost without warning
cp -f default.config ~/.myapp/config

# Better: merge or prompt
if [ -f ~/.myapp/config ]; then
  echo "Config exists. Overwrite? (y/n)"
fi

Overwrite Without Verification

# Bad: no way to know if overwrite was needed
cp -f source target

# Better: log whether content changed
if ! diff -q source target &>/dev/null; then
  cp -f source target
  echo "Updated target"
fi

Comparison with Other Patterns

Aspect Check-Before-Act Upsert Force Overwrite
Preserves history Yes Depends No
Requires existence check Yes No No
Shows if content changed Yes Sometimes No (unless you add diff)
Simplicity Medium High Highest

Summary

Force overwrite is the simplest idempotency pattern.

Key Takeaways

  1. Use for authoritative sources - when your source is always correct
  2. Add safety nets - --force-with-lease, atomic writes, backups
  3. Consider history - don't destroy data you might need
  4. Log changes - add diff checks if you need visibility into what changed

Comments