Search code examples
gitgit-stash

Is there a way to re-order Git stashes?


In Git one can make multiple stashes:

git stash save "quick temp stash"
git stash save "another quick temp stash"
git stash save "This is important but I need to put it on the back burner"
git stash save "This is almost certainly garbage, but just in case ..."

Now, I know that I can get those stashes back in any order:

git stash pop stash@{3} # recover quick temp stash
git stash pop stash@{2} # recover another quick temp stash

But obviously the more convenient syntax is preferable:

git stash pop

That's not just because there's less to type, but also because there's less thinking required: if I have several stashes I have to look through them, and maybe git stash show a couple, until I find the one I want. But if I just keep the most recent "temp" stash at the top (followed by the next temp stash, etc.) I don't have to think at all; I just pop.

So, my question is, is there any way I can re-order my stashes, so that I can think about when they should be "popped" at the time I save them, rather than at the time I "pop" them. This would allow me to still save "This is almost certainly garbage, but just in case ..." and "This is important but I need to put it on the back burner" stashes, but in the back of the stash list where they don't complicate accessing simpler quick stashes.


Solution

  • As in Whymarrh's answer and Donnie's comment, I think you're probably better served by just committing. I will note, though, that:

    If you really wanted to keep using stashes and reorder those, you could adjust refs/stash as you work ...

    It's possible to do this without using git stash pop at all. It's just tricky. (Edit: I see on re-reading that this was the idea in Whymarrh's answer.)

    The reflog file, .git/logs/refs/stash, holds reflog entries 1 through N (however many exist). The stash reference itself holds entry zero.

    A drop operation consists of removing the specific reflog entry (git reflog delete knows how to handle the special zero case):

    drop_stash () {
            assert_stash_ref "$@"
    
            git reflog delete --updateref --rewrite "${REV}" &&
                    say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
                    die "$(eval_gettext "\${REV}: Could not drop stash entry")"
    
            # clear_stash if we just dropped the last stash entry
            git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
            clear_stash
    }
    

    (where clear_stash deletes refs/stash itself). The $REV argument is refs/stash@{N}, or refs/stash if you didn't specify a particular one.

    The store operation inserts the entry at zero, using git update-ref:

    [snip]
            w_commit="$1"
            if test -z "$stash_msg"
            then
                    stash_msg="Created via \"git stash store\"."
            fi
    
            git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
            ret=$?
            test $ret != 0 && test -z "$quiet" &&
            die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
            return $ret
    

    It's therefore possible, albeit a bit tricky, to achieve a "roll" operation (if you're familiar with Forth or Postscript). To roll the bottom three entries up one step, for instance, changing:

    E  stash@{4}
    D  stash@{3}
    C  stash@{2}
    B  stash@{1}
    A  stash@{0}
    

    into:

    E  stash@{4}
    D  stash@{3}
    B  stash@{2}
    A  stash@{1}
    C  stash@{0}
    

    you would just copy C to the bottom, as if via a new store, then drop stash@{3} (which moved up because of the insertion at zero).

    In fact, you can do this by running git stash -q store and git stash -q drop, with appropriate arguments.