Skip to content

Instantly share code, notes, and snippets.

@ole
Last active August 20, 2025 13:30
Show Gist options
  • Save ole/fa9587ff1697c4ef160329d28c0b5224 to your computer and use it in GitHub Desktop.
Save ole/fa9587ff1697c4ef160329d28c0b5224 to your computer and use it in GitHub Desktop.
A shell script that swaps the staged and unstaged changes in the current Git repo.
#!/usr/bin/env fish
# Swaps the staged and unstaged changes.
#
# Useful if you want to make and commit a large-ish change B while you're
# in the middle of another change A (which you don't want to commit yet).
#
# Assumptions:
#
# - The changes for A are staged.
# - The changes for B are unstaged.
# - You want to commit B and then continue working on A.
#
# The script will *not* make the commit for B. It will only prepare the staging
# area for making the commit.
set LOG_PREFIX "[swap]"
set STAGED_STASH "swap-A-staged"
set UNSTAGED_STASH "swap-B-unstaged"
function exit_with_error
echo "$LOG_PREFIX Error occurred. Your stashes are preserved:"
echo "$LOG_PREFIX - $STAGED_STASH: Contains your original staged changes (A)"
echo "$LOG_PREFIX - $UNSTAGED_STASH: Contains your original unstaged changes (B)"
echo "$LOG_PREFIX Use 'git stash list' to see them and 'git stash apply stash@{N}' to restore"
exit 1
end
# Validate initial state.
if not git diff --cached --quiet
set has_staged_changes true
else
echo "$LOG_PREFIX No staged changes found. Nothing to swap."
exit 0
end
if git diff --quiet
echo "$LOG_PREFIX No unstaged changes found. Nothing to swap."
exit 0
end
# Check for untracked files (might be a sign of accidental usage).
set untracked_files (git ls-files --others --exclude-standard)
if test (count $untracked_files) -gt 0
echo "$LOG_PREFIX Warning: Untracked files detected. This script will stage ALL unstaged changes."
echo "$LOG_PREFIX Untracked files: $untracked_files"
read -P "$LOG_PREFIX Continue? [y/N] " -n 1 response
if test "$response" != "y" -a "$response" != "Y"
exit 0
end
end
# Step 1: Stash the currently staged files (A).
echo "$LOG_PREFIX Stashing staged changes (A)…"
git stash push --staged --message "$STAGED_STASH"
or exit_with_error
# Step 2: Stash the currently unstaged files (B).
echo "$LOG_PREFIX Stashing unstaged changes (B)…"
git stash push --include-untracked --message "$UNSTAGED_STASH"
or exit_with_error
# Step 3: Restore and stage the B changes.
echo "$LOG_PREFIX Staging B changes…"
git stash apply "stash@{0}" # The B stash is at the top of the stack
or exit_with_error
git add .
or exit_with_error
# Step 4: Re-apply the A changes (unstaged).
echo "$LOG_PREFIX Re-applying A changes as unstaged…"
git stash apply "stash@{1}" # The A stash is at position 1 on the stack
or exit_with_error
# Step 5: Handle Git problem: After `git stash apply`, newly added
# (untracked) files in A will now be staged, but we want them unstaged.
# Solution: Find new files that are currently staged AND were in the A stash
# and unstage them.
set STAGED_NEW (git diff --cached --name-only --diff-filter=A)
if test (count $STAGED_NEW) -gt 0
set STASH_FILES (git stash show "stash@{1}" --name-only)
# Unstage files that are in both lists.
for FILE in $STAGED_NEW
if contains "$FILE" $STASH_FILES
echo "$LOG_PREFIX Unstaging previously staged added file: $FILE"
git restore --staged "$FILE"
or exit_with_error
end
end
end
# Step 6: Clean up stashes only on complete success.
echo "$LOG_PREFIX Deleting backup stashes…"
git stash drop "stash@{0}"
or begin
echo "$LOG_PREFIX Warning: Failed to drop stash B, but operation completed successfully"
end
git stash drop "stash@{0}"
or begin
echo "$LOG_PREFIX Warning: Failed to drop stash A, but operation completed successfully"
end
echo "$LOG_PREFIX Successfully swapped staged and unstaged changes."
echo "$LOG_PREFIX - Previously unstaged changes (B) are now staged and ready to commit."
echo "$LOG_PREFIX - Previously staged changes (A) are now unstaged and ready for continued work."
echo "$LOG_PREFIX Note: if something unforeseen happened and you lost data, note lines of the form 'Dropped stash@{0} (c8162d685d9b7af16f1352c781762c8622197b7f)' printed by this script. The stashes should be recoverable by their hash even after deletion (e.g. use 'git branch tmp call c8162d685d9b7af16f1352c781762c8622197b7f'. If you no longer have the script output, call 'git fsck --no-reflog | grep \"dangling commit\"' to find them."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment