Skip to content

Instantly share code, notes, and snippets.

@brookinc
Last active November 23, 2021 09:34
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brookinc/e2589a8c5ca33f804e4868f6bfc18282 to your computer and use it in GitHub Desktop.
Save brookinc/e2589a8c5ca33f804e4868f6bfc18282 to your computer and use it in GitHub Desktop.
A script to `git stash` only the currently staged changes.
#!/usr/bin/env bash
# This script stashes the currently staged changes, and leaves everything else in the working directory as-is.
# (source: https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible/39644782#39644782)
# Prompt for the desired repo path
REPOPATH=
read -p "Enter the repo path, or press ENTER for current dir: " REPOPATH
# Read the desired stash description from the command line, or prompt the user for it if necessary
STASHNAME=$1
while [ "$STASHNAME" = "" ] ; do
read -p "Enter a description for this stash: " STASHNAME
done
#git -C "$REPOPATH" log -1
# Stash everything temporarily. Keep staged files, discard everything else after stashing.
#git --git-dir $REPOPATH/.git stash --keep-index
git -C "$REPOPATH" stash --keep-index
# Stash everything that remains (only the staged files should remain). This is the stash we want to keep, so give it a name.
git -C "$REPOPATH" stash save "$STASHNAME"
# Apply the original stash to get us back to where we started
git -C "$REPOPATH" stash apply stash@{1}
# Create a temporary patch to reverse the originally staged changes and apply it
git -C "$REPOPATH" stash show -p | git -C "$REPOPATH" apply -R
# Delete the temporary stash
git -C "$REPOPATH" stash drop stash@{1}
@srgvg
Copy link

srgvg commented Feb 15, 2019

Nice script. Now you assume your first stach is stash@{1}, which might not always be true. I think i might be safer to also give a name to the first stash, and reference it when reverse applying, then dropping it. That would make your script more robust if there are already other stashes saved.

@srgvg
Copy link

srgvg commented Feb 15, 2019

FYI - I needed a script that did something similarly: discard just the changes in the working dir:
( https://github.com/srgvg/dotfiles/blob/master/bin/git-undoworkingdir )

#!/bin/bash                                                                                                             
                                                                                                                        
# c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t                                                                  
# vi: set shiftwidth=4 tabstop=4 noexpandtab:                                                                           
# :indentSize=4:tabSize=4:noTabs=false:                                                                                 
                                                                                                                        
set -o nounset                                                                                                          
set -o errexit                                                                                                          
set -o pipefail                                                                                                         
                                                                                                                        
# based on https://gist.github.com/brookinc/e2589a8c5ca33f804e4868f6bfc18282                                            
# That original script stashes the currently staged changes, and leaves everything else in the working directory as-is. 
# (source: https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible/39644782#39644782)
# this script extends this as to basically throw away all unstaged changes                                              
# and also makes referencing stashes more robust                                                                        
                                                                                                                        
function stashref() {                                                                                                   
    local stashname=$1                                                                                                  
    git stash list | grep "${stashname}" | head -n1 | cut -d: -f1                                                       
}                                                                                                                       
                                                                                                                        
# Stash everything temporarily. Keep staged files, discard everything else after stashing.                              
git stash save --quiet --keep-index _tmp_all                                                                            
                                                                                                                        
# Stash everything that remains (only the staged files should remain). This is the stash we want to keep, so give it a name.
git stash save --quiet _tmp_index                                                                                       
                                                                                                                        
# Apply the original stash to get us back to where we started, then drop that stash                                     
git stash pop  --quiet "$(stashref _tmp_all)"                                                                           
                                                                                                                        
# Create a temporary patch to reverse the stash with just the index and apply it                                        
git stash show -p "$(stashref _tmp_index)" | git apply --index --reverse                                                
                                                                                                                        
# Now we have just the working dir changes without an index, and keep that stash as a backup of the real change here    
TS="$(date +%H%M%S)"                                                                                                    
git stash save "unstaged@${TS}" | sed 's/and index //'                                                                  
git stash show -p "$(stashref unstaged@${TS})"                                                                          
                                                                                                                        
# Now re-apply the index, as index, then drop that stash                                                                            
git stash pop  --quiet --index "$(stashref _tmp_index)"                                                                 

@typebrook
Copy link

Good example! But I would rather like to add alias into .gitconfig like:

[alias]
    stashstaged = !git stash --keep-index && \
                   git stash && \
                   git stash apply stash@{1} && \
                   git stash show -p | git apply -R && \
                   git stash drop stash@{1}

@Gerst20051
Copy link

Thanks @typebrook that works great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment