Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git command to save a stash without modifying working tree?

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.

like image 899
Eliot Avatar asked Jun 11 '11 10:06

Eliot


People also ask

What command pushes your uncommitted changes onto a stack to save for later?

The answer to this issue is the git stash command. Stashing takes the dirty state of your working directory — that is, your modified tracked files and staged changes — and saves it on a stack of unfinished changes that you can reapply at any time (even on a different branch).

How do I stash files only?

To stash a specific file, use the “git stash push” command and specify the file you want to stash. However, the other tracked files that may be modified in your current working directory are untouched.


2 Answers

Thanks to Charles' tip, I whipped up a bash script to do exactly what I wanted (I was running into issues implementing this as only an alias). It takes an optional stash message just like git stash save. If none is supplied it will use the default message generated by git stash.

#!/bin/sh # # git-stash-snap # Save snapshot of working tree into the stash without modifying working tree. # First argument (optional) is the stash message. if [ -n "$1" ]; then         git update-ref -m "$1" refs/stash "$(git stash create \"$1\")" else         HASH=`git stash create`         MESSAGE=`git log --no-walk --pretty="tformat:%-s" "$HASH"`         git update-ref -m "$MESSAGE" refs/stash "$HASH" fi 

Edit: As pointed out in a comment below, saving this script as git-stash-snap somewhere in your path is sufficient to be able to invoke it by typing git stash-snap.

The nice thing here is that even if you drop a stash made with this method, you will still be able to see the stash message using git log [commit-hash] of the dangling commit!

Edit: since git 2.6.0 you can add --create-reflog to update-ref and then git stash list will show this even if git stash was not used before.

Edit: Git has introduced a new stash subcommand called stash push so I have updated my recommendation for naming this script from git-stash-push to git-stash-snap.

like image 56
Eliot Avatar answered Sep 24 '22 12:09

Eliot


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).

like image 21
Mariusz Pawelski Avatar answered Sep 25 '22 12:09

Mariusz Pawelski