Skip to content

File Swapping

sysid edited this page Feb 24, 2026 · 5 revisions

File Swapping

File swapping lets you temporarily replace project files with development-specific versions stored in your vault. Perfect for local overrides that shouldn't be committed.

Only change structure (move, delete, rename) when vault is in swapped-out state!

The Swap Concept

Unlike guarding (permanent move to vault), swapping is temporary:

NORMAL STATE                         AFTER "SWAP IN"
project/                             project/
└── application.yml (original) ───►  └── application.yml (from vault)

vault/swap/                          vault/swap/
└── application.yml (dev version)    └── application.yml.rsenv_original (backup)

Key difference from guarding:

  • Guard: Permanent. File lives in vault, symlink in project.
  • Swap: Temporary. Original backed up, vault version copied to project.

Commands

Initialize Swap Files

First, move your development override files to the vault:

rsenv swap init config/application.yml

This:

  1. Moves config/application.yml from project to vault/swap/config/application.yml
  2. Project file is removed (now only exists in vault)

Use swap in to put the vault version back into the project.

Swap In

Replace project files with vault versions:

rsenv swap in config/application.yml

This:

  1. Creates sentinel in vault: application.yml.{hostname}.rsenv_active (copy of vault content)
  2. Moves project file to vault backup: vault/swap/application.yml.rsenv_original
  3. Moves vault override to project (vault file is removed)

Swap Out

Restore original files:

rsenv swap out config/application.yml

This:

  1. Moves project file back to vault (preserving your changes)
  2. Restores backup from vault: vault/swap/application.yml.rsenv_original → project
  3. Removes sentinel from vault

Check Status

rsenv swap status

Output:

Swap status:
  config/application.yml: IN (swapped on myhost)
  config/database.yml: OUT (original in project)

Swap Out Entire Vault

Swap out all files in the current project's vault:

rsenv swap out

Swap Out All Vaults

Batch restore across all projects:

rsenv swap out --global

Or with a custom vault base directory:

rsenv swap out --global --vault-base ~/my-vaults

Useful before:

  • Committing changes
  • Switching git branches
  • Sharing code with others

Check Status Across All Vaults

See which vaults have active swaps:

rsenv swap status --global

Output:

Active Swaps:

myproject-a1b2c3d4:
  config.yml [in (myhost)]

another-proj-d5e6f7g8:
  settings.json [in (myhost)]

For shell scripts, use --silent to get exit code only:

# Project-level: 0=clean, 1=dirty, 2=unmanaged
rsenv swap status --silent
case $? in
    0) echo "Clean — nothing swapped in" ;;
    1) echo "Dirty — files swapped in" ;;
    2) echo "Not an rsenv-managed project" ;;
esac

# Global: 0=clean, 1=dirty (across all vaults)
if rsenv swap status --global --silent; then
    echo "All clean"
else
    echo "Some files are swapped in"
fi

Useful for:

  • CI/CD pipelines to detect unwanted swaps
  • Pre-commit hooks
  • Shell prompts (project state indicator)
  • Automated cleanup scripts

Delete Swap Files

Remove files from swap management entirely:

rsenv swap delete config/application.yml

This removes from the vault:

  1. The override file (vault/swap/config/application.yml)
  2. Any backup file (config.yml.rsenv_original)

Project files are not touched.

Safety: Refuses to delete if the file is currently swapped in. Swap out first.

All-or-nothing: If any file fails validation, no files are deleted.

Use Cases

Local Development Overrides

# vault/swap/config/application.yml (development version)
server:
  port: 8080
database:
  host: localhost
  name: myapp_dev
logging:
  level: DEBUG
# project/config/application.yml (production version)
server:
  port: 443
database:
  host: prod-db.internal
  name: myapp
logging:
  level: WARN

Mock Service URLs

# Create dev version with mock services
rsenv swap init config/services.yml

# Edit vault version
vim $RSENV_VAULT/swap/config/services.yml
# Change: payment_api: https://api.stripe.com
# To:     payment_api: http://localhost:8081/mock

Local Credentials

# Your local database password
rsenv swap init .env.local

# Swap in when developing
rsenv swap in .env.local

# Swap out before commits
rsenv swap out .env.local

Hostname Tracking

Swap state is tracked per hostname, preventing conflicts when the same vault is accessed from multiple machines (e.g., shared NFS home directories).

How It Works

When you swap in, rsenv creates:

application.yml.myhost.rsenv_active

If someone else swaps in from a different host:

Error: File already swapped on host 'otherhost'

Resolving Host Conflicts

To swap in when another host has the file swapped:

# Option 1: Swap out on the other host first
# (on otherhost) rsenv swap out config/application.yml

# Option 2: Manually remove the sentinel from vault
rm $RSENV_VAULT/swap/config/application.yml.otherhost.rsenv_active
rsenv swap in config/application.yml

RSENV_SWAPPED Marker

rsenv tracks swap state via an environment variable in your vault's dot.envrc:

export RSENV_SWAPPED=1

Behavior

  • swap in: Adds RSENV_SWAPPED=1 (idempotent - only added once)
  • swap out: Removes marker only when ALL files are swapped out

Use in Scripts

Detect if development overrides are active:

# In your .envrc or scripts
if [[ -n "$RSENV_SWAPPED" ]]; then
    echo "Warning: Development overrides active"
fi

CI/CD Safety

# Fail CI if files are swapped
if [[ "$RSENV_SWAPPED" == "1" ]]; then
    echo "Error: Cannot build with swapped files"
    exit 1
fi

Dotfile Handling

Dotfiles (.gitignore, .env, .hidden/, etc.) in swap directories are automatically neutralized in the vault to prevent them from having active effects.

Why This Matters

Dotfiles in your vault could interfere with vault operations:

  • A .gitignore would affect what git tracks in the vault
  • A .envrc could load unexpected environment variables
  • Hidden directories might be invisible during maintenance

Naming Convention

rsenv uses a dot. prefix to neutralize dotfiles:

In Project In Vault
.gitignore dot.gitignore
.envrc dot.envrc
.hidden/config dot.hidden/config
.a/.b/.c dot.a/dot.b/dot.c

Automatic Behavior

Operation Dotfiles in vault
swap init Neutralized (dot.xxx)
swap out Neutralized (dot.xxx)
swap in Restored (.xxx in project)
guard add Neutralized (dot.xxx)

Safety Check

swap in refuses if a bare dotfile (e.g., .gitignore without dot.gitignore) exists in the vault. This prevents conflicts. Delete or rename the conflicting file first.

Workflow Example

Development Session

# Start work
cd ~/myproject
rsenv swap in config/application.yml

# Develop with local config...

# Before commit
rsenv swap out config/application.yml
git add -A
git commit -m "Feature complete"

CI/CD Safety

In your CI pipeline, ensure nothing is swapped:

# In CI script - check RSENV_SWAPPED marker
if [[ "$RSENV_SWAPPED" == "1" ]]; then
    echo "Error: Files are swapped in"
    exit 1
fi

Branch Switching

# Before switching branches
rsenv swap out --global

git checkout feature-branch

# After switching, swap back in if needed
rsenv swap in config/application.yml

File Locations

Vault Structure

vault/swap/
└── config/
    └── application.yml    # Your development version

Only change structure (move, delete, rename) when vault is in swapped-out state!

When Swapped In

project/config/
└── application.yml                      # Vault's version (moved from vault)

vault/swap/config/
├── application.yml.rsenv_original       # Backup of project's original
└── application.yml.myhost.rsenv_active  # Sentinel (copy of vault content)

Note: All swap artifacts (.rsenv_original, .rsenv_active) live in the vault, not the project.

Best Practices

Swap Out Before Commits

Never commit swapped-in files. Add a pre-commit hook:

#!/bin/bash
# .git/hooks/pre-commit
if rsenv swap status --quiet 2>/dev/null; then
    echo "Error: Files are swapped in. Run 'rsenv swap out' first."
    exit 1
fi

Use swap out --global Liberally

# Add alias
alias swapout='rsenv swap out --global'

# Before any commit
swapout && git commit

Document Swap Files

Create a README in your swap directory:

cat > $RSENV_VAULT/swap/README.md << 'EOF'
# Swap Files

## config/application.yml
Local development config:
- Uses localhost database
- Debug logging enabled
- Mock payment API

## .env.local
Local credentials (not in git)
EOF

Keep Swap Files Updated

When the original changes, update your swap version:

# See what changed
diff project/config/application.yml $RSENV_VAULT/swap/config/application.yml

# Update swap file
vim $RSENV_VAULT/swap/config/application.yml

Troubleshooting

"File already swapped on host X"

# Check status
rsenv swap status

# Remove the sentinel from vault and swap in
rm $RSENV_VAULT/swap/file.yml.otherhost.rsenv_active
rsenv swap in file.yml

Lost original file

The original is backed up with .rsenv_original suffix:

# Find backup
ls -la *.rsenv_original

# Manual restore
mv file.yml.rsenv_original file.yml
rm file.yml.*.rsenv_active

Swap file not in vault

# Initialize it first
rsenv swap init path/to/file.yml

Multiple swapped files

# Swap out entire vault (default)
rsenv swap out

# Swap out specific files
rsenv swap out file1.yml file2.yml file3.yml

# Or swap out all vaults
rsenv swap out --global

Guard vs Swap

Aspect Guard Swap
Purpose Protect secrets Development overrides
Duration Permanent Temporary
Mechanism Symlink File copy
Original location Vault (always) Project (when swapped out)
Git sees Symlink Real file
Use case API keys, certs Local configs, mock URLs

Related

rsenv Documentation

Getting Started
Features
Reference
Upgrading

Clone this wiki locally