Search code examples
gitgit-stash

Found a specific stash how do I show it?


With the following command I found a stash that contains a certain untracked file:

git rev-list -g stash | xargs -n1 git ls-tree -r | sort -u | grep widget

This command returns:

100644 blob 05e25619a6f2649b7d635b5aa897cbfc68a4f15d widget/app.css

How do I now show this stash?

I tried:

git stash show -p stash@{100644}

What returned stash@{100644} is not a valid reference

Thank you


Solution

  • Consider using this (untested):

    git log -g stash --format='%H %gD' |
    while read hash rel; do
        git ls-tree -r $hash | sed "s:^:$rel :"
    done |
    grep widget
    

    Explanation

    Note that:

    git rev-list -g stash | xargs -n1 git ls-tree -r | sort -u | grep widget
    

    does not produce quite the output that you want. You got:

    100644 blob 05e25619a6f2649b7d635b5aa897cbfc68a4f15d   widget/app.css
    

    This is one line from git ls-tree -r output. The format of these output lines is mode type hashtabpath (with a literal tab character). The mode, in this case 100644, is from a limited and fixed set of possibilities: 100644 means blob that is not executable. (The type field is determined by the mode so it is redundant.) The hash of a blob is the blob's hash ID: in fact, except for 160000, which is called a commit and represents a gitlink, the hash is always some internal Git object hash within this repository. None of these is ever a stash commit hash ID or a relative number. That is, none of these can be given as stash@{number}.

    If we return to the git rev-list -g stash command, this simply walks the stash reflog. It is similar to running git reflog stash, except that instead of an output of the form:

    <hash> stash@{0}: <subject>
    <hash> stash@{1}: <subject>
      .
      .
      .
    

    it produces only the hash IDs. So the output from git rev-list contains the information you'd like printed. But that output goes into xargs -n1 git ls-tree -r, so xargs eats the output and feeds it to git ls-tree -r, which eats it up and never prints it out again.

    To get the information you want, then, you'll need to modify or replace your xargs command. A modification that would allow you to reach your goal would be to print the commit hash ID and/or stash-relative name at the left of each git ls-tree -r line.

    To get the stash-relative name, it might be wise to alter or replace the git rev-list output. Unfortunately git rev-list --format='%H %gD', which would be the logical way to handle this, does not work. We must use git log, which has a flaw: git log is a porcelain command rather than a plumbing command, so it obeys user configurations, which may change its behavior.1 But there's nothing we can do about that here, so we might as well press on.


    1It seems to me that git log needs a --porcelain flag a la git status, although I've always wondered why this flag is spelled --porcelain instead of --plumbing.


    An alternative sequence

    We'll start with:

    git log -g stash --format='%H %gD'
    

    and run its output through a command that will take the hash, use git ls-tree -r to list the entire contents of the commit whose hash is on the left, and prefix each line with the reflog-relative %gD output and a space. We'll use plain sh programming constructs for these, though this will be a bit slow:

    git log -g stash --format='%H %gD' |
    while read hash rel; do
        git ls-tree -r $hash | sed "s:^:$rel :"
    done
    

    Here, we run git ls-tree -r on the hash and pipe its output through sed, the stdin-stream editor. The editing command is to replace the beginning of the line (^) with the relative reference. We can use a colon : character as the delimiter because we know that no colons can appear in any branch name.2 This means that expanding $ref will not accidentally trigger an end-of-delimiter sequence. We use double quotes for the sed command argument so that $ref does get expanded, and that—plus the shell's while read loop construct—explains the entire loop.

    We can drop the sort -u as there's no reason to sort these and uniq-ing each line seems unproductive, but we still want the final | grep widget to this little git log | while one-liner into a solution that finds stashes that contain any path string that will match widget, such as stowidgetname (Stow ID, Get Name).


    2Colon is not the only option; see the git check-ref-format documentation. However, colon is visually distinctive and non-problematic. The usual slash delimiter is a bad choice in general because reflog names might contain slashes. In our particular case we know that the ref name is stash, which does not contain a slash, but it's nice to use something that will work for other names, should we ever try to repurpose this extended one-line script. $rel, being a reflog-relative expansion, will be the refname followed by @{ followed by a series of digits followed by }, so @{} and digits and any valid ref-name character are all bad choices. Control characters are bad choices in general as they are hard to see. That leaves space, tilde, caret, and colon from rule 4 as the obvious candidates. Caret is our beginning-of-line character so that's a bad choice, and space is visually ambiguous, so we're down to tilde and colon. I picked the colon.