Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to re-order Git stashes?

Tags:

git

git-stash

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.

like image 400
machineghost Avatar asked May 23 '17 18:05

machineghost


2 Answers

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

If you do this, make sure to use Git 2.36 (Q2 2022), as it would be too slow if you were to roll out a large number of commits.

"git stash drop"(man) is reimplemented as an internal call to reflog_delete() function, instead of invoking "git reflog delete"(man)via run_command() API.

So instead of a subshell for each stash drop => reflog delete, all those stash drop calls remain in the same process, using internal functions.

See commit 758b4d2, commit 7d3d226, commit 76bccbc (02 Mar 2022) by John Cai (john-cai).
(Merged by Junio C Hamano -- gitster -- in commit a2fc9c3, 16 Mar 2022)

reflog: libify delete reflog function and helpers

Helped-by: Ævar Arnfjörð Bjarmason
Signed-off-by: John Cai

Currently stash shells out to reflog in order to delete refs.
In an effort to reduce how much we shell out to a subprocess, libify the functionality that stash needs into reflog.c.

Add a reflog_delete function that is pretty much the logic in the while loop in builtin/reflog.c cmd_reflog_delete().
This is a function that builtin/reflog.c and builtin/stash.c can both call.

like image 175
VonC Avatar answered Oct 07 '22 20:10

VonC


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.

like image 26
torek Avatar answered Oct 07 '22 19:10

torek