Search code examples
gitgithooksgit-stash

Git-hook to show if I've got a stash on the checked out branch


The following has happened a couple of time and I'm looking for an automated way to catch myself before I repeat the mistake. Solutions saying "just remember" or "just don't do that" won't be marked as correct.

Quite often, I'll be working on a git branch, have some uncommitted code, and quickly need to make a change to another branch. I use git stash to stash the uncommitted code, git checkout to swap to the other branch, I make and commit my changes, then I git checkout to the other branch. And hopefully, I remember to git stash pop. The problem is that I don't always remember, especially if I end up having to make a couple of changes on the other branch that end up taking a few hours/days.

Is there a git hook I can write that, when I checkout a branch that has a stash on it, will send me a little message? Something like:

$ git checkout foo
Switched to branch 'foo'
You have 1 stash on this branch, use `git stash pop` to unstash it.

It doesn't have to be fancy, but I think it'll save me a lot of headache. Answers like "just remember to run git stash list" won't be accepted.


Solution

  • As branches may share the same commits in their histories, it's hard to say if a stash is related with a specific branch. We can use the hook post-checkout to remind you of the possible stash entries you may forget.

    After we switch/checkout a branch, post-checkout is invoked. It receives 3 parameters: the previous head, the current head, and a flag indicating if it was a branch checkout or a file checkout. We can test some or all of the stash entries. If the current head is the first parent of a stash entry, we say the stash is on this branch.

    #!/bin/bash
    
    PREVIOUSHEAD=$1
    CURRENTHEAD=$2
    CHECKOUTFLAG=$3
    
    if [[ "$CHECKOUTFLAG" -eq 0 ]];then
        # ignore a file checkout
        exit 0
    fi
    
    export IFS=:
    git stash list | while read entry desc;do
        if [[ "$CURRENTHEAD" = $(git rev-parse "${entry}"^) ]];then
            echo "You have a stash on the current head:$entry:  $desc"
        fi
    done
    

    Here are some known issues I can think of. There are words like WIP on master or WIP on dev in the default stash description, but we do not use them in the hook. Multiple branches may refer to the same commit and a stash made on one of them can be applied to another head. Sometimes we may work on a detached HEAD which has no branch name and can also accept a stash apply. The hook just prints the remind messages as more as possible, which might be noisy.

    Another issue is that if you don't maintain stash entries well, the stash entry list gets long, and the hook may take quite a time. You can test only the top N entries in the while loop.

    The hook cannot report such a stash. Make a stash A on main, create a new commit and then make another stash B, switch to main to invoke the hook. As A's parent is not the current head, it's not reported. A further complicated case is that main has a number of diverged heads and stashes are made on them. Only the stashes related with the current head are reported. It's common when you do some experiments on the local branch and reset between these test heads. If you are also interested in these stash entries, the test in the while loop needs more codes.