Git command to save a stash without modifying working tree?

Viewed 14945

I have been wanting to use a git command that saves a stash without modifying my working tree, as a lightweight backup that's safe from any git resets or whatever I might do to screw up my index. Basically the functional equivalent of "git stash save && git stash apply" except that the working copy is never touched, since this can make certain text editors/IDE's cranky.

Something like this is approaching what I want, but not quite:

git update-ref refs/stash `git stash create "Stash message"`

This works functionally, but the issue I'm having is that no stash message shows up in "git stash list" even though the actual stash commit does have my message in it. Considering how large a stash can get, stash messages are pretty important.

5 Answers

git stash store "$(git stash create)"

Will create stash entry similar to what you would get with git stash without actually touching and clearing your working directory and index.

If you check stash list or look at all commit graph (including stash) you'll see that it's similar result to what you would get with normal call to git stash. Just the message in stash list is different (normally it's something like "stash@{0}: WIP on master: 14e009e init commit", here we'll get "stash@{0}: Created via "git stash store"")

$ git status --short
M file.txt
A  file2.txt

$ git stash list

$ git stash store "$(git stash create)"

$ git stash list
stash@{0}: Created via "git stash store".

$ git stash show 'stash@{0}'
 file.txt  | 2 +-
 file2.txt | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

$ git log --oneline --graph --all
*   85f937b (refs/stash) WIP on master: 14e009e init commit
|\
| * 26295a3 index on master: 14e009e init commit
|/
* 14e009e (HEAD -> master) init commit

$ git status
M file.txt
A  file2.txt

A bit more explanation:

A git stash entry is represented using normal commits with some defined structure. Basically it is a regular commit object that has 2 parents (or 3 if you use --include-untracked option) (more info 1,2).

git stash create creates this commits that represents stash entry and returns you the object name (SHA-1) of commit object (the one that has 2 or 3 parents). It is a dangling commit (you can verify it by calling git fsck after git stash create). You need to make refs/stash point to this dangling commit and you do it by git stash store (or by git update-ref like in other answers, because git stash store uses git update-ref to do its work).

It's good to look at actual source code of git stash push and see that it's basically calling git stash create and git stash store and then does some logic to clean files (which one depends on what options you used in git stash push).

The following combines the answer by @Mariusz Pawelski and a similar answer to a related question, letting you comfortably stash with a message.

Using git stash store with a message

  1. Use git stash create to create a stash commit, and then save it to the stash using git stash store. This will not change any files in your worktree. And you can add a helpful message to find the right version again later.

    git stash store -m "saving intermediate results: my note 1" $(git stash create)
    
  2. When you decide you want to throw away your current work and restore to a previously stashed state, first do another git stash. This "throws away" any uncommitted changes, hiding them on the stash, so that there will be no merge conflicts when you apply another stashed state in the next steps.

    git stash
    
  3. Now look up what you have saved in your stash ref:

    $ git stash list
    
    stash@{0}: saving intermediate results: my note 1
    stash@{1}: saving intermediate results: my note 2
    
  4. And finally, to restore to a stashed previous work state, throwing away your current worktree state, you would do, using an index number from inside stash@{…} in the output above to identify the stash commit to restore to:

    git stash apply 1
    
  5. If you discover that you want your thrown-away work back: it is also saved in the stash and can be restored with the same technique as above.

Wrapping as a Git alias

The command used in step 1 above can be used with a git alias to make it more convenient. To create the alias (note the final # technique):

git config --global alias.stash-copy '!git stash store -m "saving intermediate results: $1" $(git stash create) #'

From now on, you can use it like this:

git stash-copy "my note"
Related