258

Suppose two set of changes are made in a project versioned by git. One set is staged and the other is not.

I would like to recheck staged changes by running my project at this state (before commit). What is a simple way to put away all unstaged changes and leave only staged? So I need unstaged changes to disappear from my project, but to be stored somewhere for further work.

This is sounds very much like git stash command. But git stash would put both unstaged and staged changes away from my project. And I can't find something like git stash uncached.

Ken Williams
  • 22,756
  • 10
  • 85
  • 147
klm123
  • 12,105
  • 14
  • 57
  • 95

7 Answers7

265

Update 2:
I'm not sure why people are complaining about this answer, it seems to be working perfectly with me, for the untracted files you can add the -u flag

The full command becomes git stash --keep-index -u

And here's a snippet from the git-stash help

If the --keep-index option is used, all changes already added to the index are left intact.

If the --include-untracked option is used, all untracked files are also stashed and then cleaned up with git clean, leaving the working directory in a very clean state. If the --all option is used instead then the ignored files are stashed and cleaned in addition to the untracked files.

And this is a gif of how it looks:

enter image description here

Update:

Even though this is the selected answer, a lot have pointed out that the [answer below](https://stackoverflow.com/a/34681302/292408) is the correct one, I recommend checking it out.

I tested my answer again today (31/1/2020) against git version 2.24.0, and I still believe that it's correct, I added a small note above about the untracked files. If you think it's not working please also mention your git version.

Old answer:
If the --keep-index option is used, all changes already added to the index are left intact:

git stash --keep-index

From the documentation of git-stash:

Testing partial commits

You can use git stash save --keep-index when you want to make two or more commits out of the changes in the work tree, and you want to test each change before committing:

# ... hack hack hack ...
$ git add --patch foo            # add just first part to the index
$ git stash save --keep-index    # save all other changes to the stash
$ edit/build/test first part
$ git commit -m 'First part'     # commit fully tested change
$ git stash pop                  # prepare to work on all other changes
# ... repeat above five steps until one commit remains ...
$ edit/build/test remaining parts
$ git commit foo -m 'Remaining parts'

But, if you just want to visually check the staged changes only, you can try difftool:

git difftool --cached
Mohammad AbuShady
  • 40,884
  • 11
  • 78
  • 89
  • 4
    see also `git stash [-p|--patch]` which feels like interactive stashing. From `man git stash` "With --patch, you can interactively select hunks from the diff between HEAD and the working tree to be stashed." – here Aug 01 '14 at 05:42
  • 1
    I usually `add -p`, `checkout -p` and `reset -p`, never tried `stash -p`, thanks for the tip :D – Mohammad AbuShady Aug 01 '14 at 09:44
  • 53
    Note that this answer will also stash the changes you have staged. – Ben Flynn Jan 07 '15 at 16:06
  • 3
    This answer isn't really useful as it will result in confusion. This answer is better https://stackoverflow.com/a/34681302/292408. – Elijah Lynn May 29 '18 at 20:42
  • 1
    @ElijahLynn I've linked to the other answer since i found a lot of people saying it's the better answer, thanks for your comment – Mohammad AbuShady May 29 '18 at 20:49
  • 4
    This answer *works fine* for me: the only things in the resulting stash are the unstaged changes. ‍♂️ – z0r Apr 03 '20 at 23:13
  • 6
    This answer works. The question was about staged vs. unstaged and does not mention anything about untracked files. – schminnie Jun 11 '20 at 07:55
  • 2
    For me it puts both the staged and unstaged files in the stash. – finrod Jun 10 '21 at 07:09
  • I just contributed this 'all in one' answer that includes how to do 'only staged', 'only unstaged', 'unstaged and untracked', 'staged/unstaged but also keep staged in index', 'staged/unstaged/untracked but also keep staged in index'. Hopefully it helps! https://stackoverflow.com/a/68241237/1137085 – Glenn 'devalias' Grant Jul 04 '21 at 02:36
  • This answer worked for me: `git version 2.16.6` – A. B Jul 23 '21 at 17:59
  • 1
    "I'm not sure why people are complaining about this answer, it seems to be working perfectly with me, for the untracted files you can add the -u flag" ... The issue isn't the untracked files. The issue is that the changes on the index _are also included in the stash_. This is a problem if you want to check out a different commit and pop your stash there, but don't want the changes that were on the index. – bobpaul Aug 10 '21 at 16:22
  • 2
    Here's a demonstration of the problem with this answer: https://gist.github.com/bobpaul/bded251b8637061e6a7094e6141ef38f – bobpaul Aug 10 '21 at 16:51
  • Hey @bobpaul, Thanks for your comment, I will check it out this weekend and get back to you – Mohammad AbuShady Aug 13 '21 at 09:50
  • 2
    This answer confuses `untracked` with `unstaged`. Untracked files are files that are new and not in Git yet. Unstaged files are files that have been changed, but those changes haven't been selected for commit yet. So a new file can be both untracked and unstaged. But if you make changes to an existing file that is already in Git, that file will not be untracked, but it will be unstaged. What the OP wants is a way to stash only those changes that are not selected for commit (unstaged), which is completely separate from untracked. – Stijn de Witt Dec 10 '21 at 09:44
  • Not a good answer. If you pop the stash immediately, it can sometimes create merge conflicts. You'd expect it to apply on the staged changes, but it does something else because the staged changes are also stashed (just _not_ removed from working directory). It's impossible to resolve the conflicts while still keeping the unstaged changes unstaged, because you have to stage your resolution. – ADTC Mar 27 '23 at 12:26
  • 1
    This fails if a single file has both staged and unstaged changes. Your example has files that are either wholly staged or wholly not staged – Baruch May 01 '23 at 07:44
208

The accepted answer also stashes staged changes as a few have pointed out. Here's a way to do it without getting your staged changes in the stash.

The idea is to do a temporary commit of your staged changes, then stash the unstaged changes, then un-commit the temp commit:

# temp commit of your staged changes:
$ git commit --message "WIP"

# -u option so you also stash untracked files
$ git stash -u

# now un-commit your WIP commit:
$ git reset --soft HEAD^

At this point, you'll have a stash of your unstaged changes and will only have your staged changes present in your working copy.

stephen.hanson
  • 9,014
  • 2
  • 47
  • 53
  • 46
    This is really the correct answer IMO. The `--keep-index` option in the current accepted answer still stashes what's in the index, it just *also* keeps it in the index. So then it's duplicated, and hilarity ensues. – Ken Williams Sep 16 '16 at 19:50
  • 6
    @KenWilliams hilarity tragedy – tuespetre Sep 13 '17 at 15:07
  • 2
    The `git add .` step might want to be improved by `git add --all` as that should grab the files in a directory above the current working directory too. – Elijah Lynn May 29 '18 at 20:34
  • 3
    This is the best answer so far, as the --keep-index option in the accepted answer is misleading. This should be the accepted answer. – Elijah Lynn May 29 '18 at 20:34
  • 2
    -1 This approach doesn't work in all scenarios. It commits the staged changes but the commit might fail if you have a pre-commit hook that checks the current working code. If your staged changes are good but your unstaged changes are not, this approach will fail to commit staged changes. – Penghe Geng Jul 18 '18 at 21:59
  • 5
    @PengheGeng you can run `git commit` with `--no-verify` to disable the commit hooks for this particular commit – Lasse Halberg Haarbye Jul 23 '19 at 10:14
  • Why didn't @kim123 come back and say, "Hey, I tried this and hilarity/tragedy ensued."? I checked at the official documentation and it seems like the index WON'T be restored redundantly. The command to force that to happen would be `git pop --index` which is described as "Tries to reinstate not only the working tree’s changes, but also the index’s ones." [https://git-scm.com/docs/git-stash#Documentation/git-stash.txt---index] Also the DAG of how `git stash` saves the tree shows that there is a direct delta from the HEAD to the working state. So it seems like the accepted answer works. – cardiff space man Jul 03 '20 at 00:24
  • 1
    I just contributed this 'all in one' answer that includes how to do 'only staged', 'only unstaged', 'unstaged and untracked', 'staged/unstaged but also keep staged in index', 'staged/unstaged/untracked but also keep staged in index'. Hopefully it helps! https://stackoverflow.com/a/68241237/1137085 – Glenn 'devalias' Grant Jul 04 '21 at 02:38
  • In powershell console `HEAD^` doesn't work for me, `HEAD~` does. – Dmitriy Work Jun 01 '22 at 01:07
37

I found the marked answer did not work for me since I needed something which truly stashed only my unstaged changes. The marked answer, git stash --keep-index, stashes both the staged and unstaged changes. The --keep-index part merely leaves the index intact on the working copy as well. That works for OP, but only because he asked a slightly different question than he actually wanted the answer for.

The only true way I've found to stash unstaged changes is to not use the stash at all:

git diff >unstaged.diff
git apply -R unstaged.diff

You can also use git restore . or git checkout -- . instead of git apply -R unstaged.diff.

Work work work...

git apply unstaged.diff
rm unstaged.diff
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
Binary Phile
  • 2,538
  • 16
  • 16
  • 1
    Here on `git version 2.6.1.windows.1`, `git stash -k` worked as described. – koppor Jan 10 '16 at 23:34
  • 2
    This should be the accepted answer! It is the only one in multiple stackoverflow threads that does what it claims and not relying on making temp commits! – user643011 Mar 18 '17 at 14:43
  • 2
    @user643011: Temp commits are not a bad thing in git. They cost nothing and do not harm anyone. – Fritz Sep 05 '17 at 09:19
  • 5
    @Fritz: No temp commits are not possible in some scenarios. It might fail if you have a pre-commit hook that checks the current working code. If your staged changes are good but your unstaged changes are not, this approach will fail to commit staged changes. – Penghe Geng Jul 18 '18 at 22:02
  • 2
    This doesn't include untracked files. You need to use "git ls-files" to find & include those in the diff patch – ACyclic Oct 21 '18 at 15:18
  • 1
    This definitely should be the accepted answer, better than temp commits when you are scripting a pre-commit hook. – codejedi365 Jun 13 '20 at 06:44
  • I just contributed this 'all in one' answer that includes how to do 'only staged', 'only unstaged', 'unstaged and untracked', 'staged/unstaged but also keep staged in index', 'staged/unstaged/untracked but also keep staged in index'. Hopefully it helps! https://stackoverflow.com/a/68241237/1137085 – Glenn 'devalias' Grant Jul 04 '21 at 02:39
  • Amazing, especially `git apply -R unstaged.diff`. – Géry Ogam Jan 31 '22 at 18:50
  • @PengheGeng Just skip the commit hook. – Matthew Read Dec 12 '22 at 19:32
  • 1
    **The only answer that really answers the question with the correct and sufficient solution. Thank you.** – ADTC Mar 27 '23 at 12:31
12

Since the various answers here so far seem to have their own intricacies/limitations, I wanted to provide a few more alternatives that cover all of the specific edge cases that I personally have needed.

tl;dr

List staged (without deleted) files:

git diff --staged --diff-filter=d --name-only

List unstaged (without deleted) files:

git diff --diff-filter=d --name-only

List unstaged/untracked files:

git ls-files --modified --others --exclude-standard

Stashing only staged files (originally from this StackOverflow answer, but tweaked slightly):

git stash push --include-untracked -- $(git diff --staged --diff-filter=d --name-only)

Stashing only unstaged (not untracked) files:

git stash push --keep-index -- $(git diff --diff-filter=d --name-only)

Stashing unstaged and untracked files:

git stash push --keep-index --include-untracked -- $(git ls-files --modified --others --exclude-standard)

Stashing staged/unstaged files, while also keeping the staged files in your index as well:

git stash push --keep-index

Stashing staged/unstaged/untracked files, while also keeping the staged files in your index as well:

git stash push --include-untracked --keep-index

Full Explanation

git stash push allows us to provide a <pathspec>, and only stash the files that match it:

git stash push -- <pathspec>

push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>…​]

Save your local modifications to a new stash entry and roll them back to HEAD (in the working tree and in the index). The part is optional and gives the description along with the stashed state.

<pathspec>…​

This option is only valid for push command.

The new stash entry records the modified states only for the files that match the pathspec. The index entries and working tree files are then rolled back to the state in HEAD only for these files, too, leaving files that do not match the pathspec intact.

-u, --include-untracked, --no-include-untracked

When used with the push and save commands, all untracked files are also stashed and then cleaned up with git clean.


git diff allows us to list the currently unstaged files with --name-only:

git diff --name-only

git diff [<options>] [--] [<path>…​]

This form is to view the changes you made relative to the index (staging area for the next commit).

--name-only

Show only names of changed files.

--diff-filter=\[(A|C|D|M|R|T|U|X|B)…​\[*\]\]

Select only files that are Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R), have their type (i.e. regular file, symlink, submodule, …​) changed (T), are Unmerged (U), are Unknown (X), or have had their pairing Broken (B). Any combination of the filter characters (including none) can be used.

Also, these upper-case letters can be downcased to exclude. E.g. --diff-filter=ad excludes added and deleted paths.


git ls-files allows us to list both the --modified files AND the untracked (--others) files:

git ls-files --modified --others --exclude-standard

git-ls-files - Show information about files in the index and the working tree

-m, --modified

Show modified files in the output

-o, --others

Show other (i.e. untracked) files in the output

--exclude-standard

Add the standard Git exclusions: .git/info/exclude, .gitignore in each directory, and the user’s global exclusion file.

Glenn 'devalias' Grant
  • 1,928
  • 1
  • 21
  • 33
10

As of Git 2.35 (Q1 2022), you can now stash staged changes via git stash --staged.

Thus, to stash unstaged (and untracked) changes:

git stash --staged          # stashes staged, leaving unstaged
git stash -u                # stashes unstaged & untracked
git stash pop "stash@{1}"   # pops staged back (but they're now unstaged)
git add -A                  # restages the previously staged changes

HOWEVER, this would FAIL if the staged & unstaged changes affects the same lines, as git stash --staged would "partially" fail in this situation by stashing the staged changes, but leave behind both staged & unstaged.
In this case, the below method (using git commit) might be better.


If you wanna add an git alias for it, add this to your .gitconfig:

[alias]
    stash-unstaged = "!cd "${GIT_PREFIX:-.}"; f() { \
        git stash --quiet --staged;                 \
        git stash -u \"$@\";                        \
        git stash pop --quiet \"stash@{1}\";        \
        git add -A;                                 \
    }; f"

Usage:

git stash-unstaged -m "Stash Message"


For Git versions <2.35,

You can stash unstaged (and untracked) by exploiting git commit to separate the staged and unstaged changes:

git commit -m "TEMP"        # commits staged, leaving unstaged
git stash -u                # stashes unstaged & untracked
git reset --soft HEAD^      # undo commit, bringing staged back

Unlike the previous method (using git stash --staged), this will still work even when staged & unstaged changes affects the same lines.
Tho, you MIGHT need to resolve merge conflicts when u pop the stash.


FYI, to stash staged changes, simply stash the unstaged first:

# Stash unstaged as shown previously
git commit -m "TEMP"
git stash -u
git reset --soft HEAD^

git stash -u                # stashes remaining staged
git stash pop "stash@{1}"   # pops unstaged back

Alias for these 2:

[alias]
    stash-unstaged = "!cd "${GIT_PREFIX:-.}"; f() { \
        git commit --quiet -m \"TEMP\";             \
        git stash -u \"$@\";                        \
        git reset --quiet --soft HEAD^              \
    }; f"
    
    stash-staged = "!cd "${GIT_PREFIX:-.}"; f() {   \
        git stash-unstaged --quiet;                 \
        git stash -u \"$@\";                        \
        git stash pop --quiet \"stash@{1}\";        \
    }; f"
EvitanRelta
  • 111
  • 1
  • 4
8

Git: Stash unstaged changes

This will stash all modifications that you did not git add:

git stash -k

Note that newly created (and non-added) files will remain in your working directory unless you also use the -u switch.

git stash -k -u 

Also, your working directory must be clean (i.e. all changes need to be added) when you git stash pop later on.

http://makandracards.com/makandra/853-git-stash-unstaged-changes

Cory Danielson
  • 14,314
  • 3
  • 44
  • 51
  • 20
    This is equivalent to `git stash --keep-index`. Staged files are included in the stash. – Benjamin Cheah Mar 09 '15 at 07:41
  • This is answer is misleading, `git stash --keep-index` stashes all the changes from the HEAD state in the working tree, i.e. both staged and unstaged changes. The difference with bare `git stash` is that it also puts the working tree in *index* state (instead of in *HEAD* state), which creates a conflict when you `git stash pop` since the latter expect the working tree to be in the HEAD state. – Géry Ogam Jan 31 '22 at 18:31
-1

Here is a way to make this very simple:

  1. Add the alias definitions below to your .zshrc or .bashrc or .bash_profile
  2. Now, anytime you are in this situation, just type gss and you will have 2 stashes - 1 with all the changes, another with only the staged changes

So, you can now apply the staged changes, see if those work, commit them if you want. Then, later you can also bring in the unstaged changes by applying the 'all WIP' stash and try those out.

alias gsts='git stash save'
alias gsv="git stash save --keep-index"

# How to Git Stash preserving staged/unstaged situation.
# 1. gsv will stash all and reset (remove) the unstaged, leaving only staged in staged state.
# 2. gsts will make a stash with your "good" (staged) files
alias gss='gsv all WIP && gsts staged WIP'
Michael Liquori
  • 619
  • 10
  • 16