How to name and retrieve a stash by name in git?

2019-01-16 00:11发布

问题:

I was always under the impression that you could give a stash a name by doing git stash save stashname, which you could later on apply by doing git stash apply stashname. But it seems that in this case all that happens is that stashname will be used as the stash description.

Is there no way to actually name a stash? If not, what would you recommend to achieve equivalent functionality? Essentially I have a small stash which I would periodically like to apply, but don't want to always have to hunt in git stash list what its actual stash number is.

回答1:

This is not the solution you're looking for!

You'll find better luck looking at the solutions posted in the other answers in the comments below:

  • comment below
  • comment below
  • another answer

I'm leaving this thread here for posterity, as I cannot unaccept the answer.


You can actually find the stash by name using git's regular expression syntax for addressing objects:

stash^{/<regex>}
:/<regex>

For example, when saving your stash with a save name:

git stash save "guacamole sauce WIP"

... you can use a regular expression to address that stash:

git stash apply stash^{/guacamo}

This will apply the youngest stash that matches the regular expression guacamo. That way, you don't have to know what number the stash is at in the stack, you just have to know its name. There is no terser syntax for this, but you can create an alias in your .gitconfig file:

[alias]
sshow = "!f() { git stash show stash^{/$*} -p; }; f"
sapply = "!f() { git stash apply stash^{/$*}; }; f"

You can then use git sapply <regex> to apply that stash (without dropping).
You can then use git sshow <regex> to show: files changed, insertions, and deletions

EDIT: Props to this StackOverflow answer on how to use bash arguments in git aliases.

EDIT 2: This answer used to contain drop and list aliases, but I've since removed them, since drop requires the stash@{n} syntax while list didn't filter the stashes at all. If anyone knows how to resolve a stash SHA-1 hash to a stash ref, then I could implement the other commands as well.

EDIT 3: Per isyi's suggestion I've added a patch flag to show what the contents of the stash are when showing one.



回答2:

This is how you do it:

git stash save "my_stash"

where "my_stash" is the stash name...

Some more useful things to know: All the stashes are stored in a stack. Type in :

git stash list

This will list down all your stashes.

To apply a stash and remove it from the stash stack, You can give,

git stash pop stash@{n}

To apply a stash and keep it in the stash stack, type:

git stash apply stash@{n}

Where n in the index of the stashed change.



回答3:

You can turn a stash into a branch if you feel it's important enough:

git stash branch <branchname> [<stash>]

from the man page:

This creates and checks out a new branch named <branchname> starting from the commit at which the <stash> was originally created, applies the changes recorded in <stash> to the new working tree and index, then drops the <stash> if that completes successfully. When no <stash> is given, applies the latest one.

This is useful if the branch on which you ran git stash save has changed enough that git stash apply fails due to conflicts. Since the stash is applied on top of the commit that was HEAD at the time git stash was run, it restores the originally stashed state with no conflicts.

You can later rebase this new branch to some other place that's a descendent of where you were when you stashed.



回答4:

git stash save is deprecated now, instead you can use git stash push -m "message"

You can procude like this:

git stash push -m "message"

where "message" is your stash name...



回答5:

Stashes are not meant to be permanent things like you want. You'd probably be better served using tags on commits. Construct the thing you want to stash. Make a commit out of it. Create a tag for that commit. Then roll back your branch to HEAD^. Now when you want to reapply that stash you can use git cherry-pick -n tagname (-n is --no-commit).



回答6:

If you are just looking for a lightweight way to save some or all of your current working copy changes and then reapply them later at will, consider a patch file:

# save your working copy changes
git diff > some.patch

# re-apply it later
git apply some.patch

Every now and then I wonder if I should be using stashes for this and then I see things like the insanity above and I'm content with what I'm doing :)



回答7:

I have these two functions in my .zshrc file:

function gitstash() {
    git stash push -m "zsh_stash_name_$1"
}

function gitstashapply() {
    git stash apply $(git stash list | grep "zsh_stash_name_$1" | cut -d: -f1)
}

Using them this way:

gitstash nice

gitstashapply nice


回答8:

Alias

sapply = "!f() { git stash apply \"$(git stash list | awk -F: --posix -vpat=\"$*\" \"$ 0 ~ pat {print $ 1; exit}\")\"; }; f"

Usage

git sapply "<regex>"

  • compatible with Git for Windows

Edit: I sticked to my original solution, but I see why majority would prefer Etan Reisner's version (above). So just for the record:

sapply = "!f() { git stash apply \"$(git stash list | grep -E \"$*\" | awk \"{ print $ 1; }\" | sed -n \"s/://;1p\")\"; }; f"


回答9:

This answer owes much to Klemen Slavič. I would have just commented on the accepted answer but I don't have enough rep yet :(

You could also add a git alias to find the stash ref and use it in other aliases for show, apply, drop, etc.

[alias]
    sgrep = "!f() { ref=$(git --no-pager stash list | grep "$1" | cut -d: -f1 | head -n1); echo ${ref:-<no_match>}; }; f"
    sshow = "!f() { git stash show $(git sgrep "$1") -p; }; f"
    sapply = "!f() { git stash apply $(git sgrep "$1"); }; f"
    sdrop = "!f() { git stash drop $(git sgrep "$1"); }; f"

Note that the reason for the ref=$( ... ); echo ${ref:-<no_match>}; pattern is so a blank string is not returned which would cause sshow, sapply and sdrop to target the latest stash instead of fail as one would expect.



回答10:

It's unfortunate that git stash apply stash^{/<regex>} doesn't work (it doesn't actually search the stash list, see the comments under the accepted answer).

Here are drop-in replacements that search git stash list by regex to find the first (most recent) stash@{<n>} and then pass that to git stash <command>:

# standalone (replace <stash_name> with your regex)
(n=$(git stash list --max-count=1 --grep=<stash_name> | cut -f1 -d":") ; if [[ -n "$n" ]] ; then git stash show "$n" ; else echo "Error: No stash matches" ; return 1 ; fi)
(n=$(git stash list --max-count=1 --grep=<stash_name> | cut -f1 -d":") ; if [[ -n "$n" ]] ; then git stash apply "$n" ; else echo "Error: No stash matches" ; return 1 ; fi)
# ~/.gitconfig
[alias]
  sshow = "!f() { n=$(git stash list --max-count=1 --grep=$1 | cut -f1 -d":") ; if [[ -n "$n" ]] ; then git stash show "$n" ; else echo "Error: No stash matches $1" ; return 1 ; fi }; f"
  sapply = "!f() { n=$(git stash list --max-count=1 --grep=$1 | cut -f1 -d":") ; if [[ -n "$n" ]] ; then git stash apply "$n" ; else echo "Error: No stash matches $1" ; return 1 ; fi }; f"

# usage:

$ git sshow my_stash
 myfile.txt | 1 +
 1 file changed, 1 insertion(+)

$ git sapply my_stash
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   myfile.txt

no changes added to commit (use "git add" and/or "git commit -a")

Note that proper result codes are returned so you can use these commands within other scripts. This can be verified after running commands with:

echo $?

Just be careful about variable expansion exploits because I wasn't sure about the --grep=$1 portion. It should maybe be --grep="$1" but I'm not sure if that would interfere with regex delimiters (I'm open to suggestions).



回答11:

Alias This might be a more direct syntax for Unix-like systems without needing to encapsulate in a function. Add the following to ~/.gitconfig under [alias]

sshow = !sh -c 'git stash show stash^{/$*} -p' -
sapply = !sh -c 'git stash apply stash^{/$*}' -
ssave = !sh -c 'git stash save "${1}"' -

Usage: sapply regex

Example: git sshow MySecretStash

The hyphen at the end says take input from standard input.



回答12:

Use a small bash script to look up the number of the stash. Call it "gitapply":

NAME="$1"
if [[ -z "$NAME" ]]; then echo "usage: gitapply [name]"; exit; fi
git stash apply $(git stash list | grep "$NAME" | cut -d: -f1)

Usage:

gitapply foo

...where foo is a substring of the name of the stash you want.



回答13:

For everything besides the stash creation, I'd propose another solution by introducing fzf as a dependency. Recommend taking 5 minutes of your time and get introduced to it, as it is over-all great productivity booster.

Anyway, a related excerpt from their examples page offering stash searching. It's very easy to change the sciptlet to add additional functionality (like stash application or dropping):

fstash() {
    local out q k sha
    while out=$(
            git stash list --pretty="%C(yellow)%h %>(14)%Cgreen%cr %C(blue)%gs" |
            fzf --ansi --no-sort --query="$q" --print-query \
                --expect=ctrl-d,ctrl-b); do
        mapfile -t out <<< "$out"
        q="${out[0]}"
        k="${out[1]}"
        sha="${out[-1]}"
        sha="${sha%% *}"
        [[ -z "$sha" ]] && continue
        if [[ "$k" == 'ctrl-d' ]]; then
            git diff $sha
        elif [[ "$k" == 'ctrl-b' ]]; then
            git stash branch "stash-$sha" $sha
            break;
        else
            git stash show -p $sha
        fi
    done
}


回答14:

This is one way to accomplish this using PowerShell:

<#
.SYNOPSIS
Restores (applies) a previously saved stash based on full or partial stash name.

.DESCRIPTION
Restores (applies) a previously saved stash based on full or partial stash name and then optionally drops the stash. Can be used regardless of whether "git stash save" was done or just "git stash". If no stash matches a message is given. If multiple stashes match a message is given along with matching stash info.

.PARAMETER message
A full or partial stash message name (see right side output of "git stash list"). Can also be "@stash{N}" where N is 0 based stash index.

.PARAMETER drop
If -drop is specified, the matching stash is dropped after being applied.

.EXAMPLE
Restore-Stash "Readme change"
Apply-Stash MyStashName
Apply-Stash MyStashName -drop
Apply-Stash "stash@{0}"
#>
function Restore-Stash  {
    [CmdletBinding()]
    [Alias("Apply-Stash")]
    PARAM (
        [Parameter(Mandatory=$true)] $message,         
        [switch]$drop
    )

    $stashId = $null

    if ($message -match "stash@{") {
        $stashId = $message
    }

    if (!$stashId) {
        $matches = git stash list | Where-Object { $_ -match $message }

        if (!$matches) {
            Write-Warning "No stashes found with message matching '$message' - check git stash list"
            return
        }

        if ($matches.Count -gt 1) {
            Write-Warning "Found $($matches.Count) matches for '$message'. Refine message or pass 'stash{@N}' to this function or git stash apply"
            return $matches
        }

        $parts = $matches -split ':'
        $stashId = $parts[0]
    }

    git stash apply ''$stashId''

    if ($drop) {
        git stash drop ''$stashId''
    }
}

More details here



回答15:

Use git stash save NAME to save.

Then... you can use this script to choose which to apply (or pop):

#!/usr/bin/env ruby
#git-stash-pick by Dan Rosenstark

# can take a command, default is apply
command = ARGV[0]
command = "apply" if !command
ARGV.clear

stashes = []
stashNames = []
`git stash list`.split("\n").each_with_index { |line, index|
    lineSplit = line.split(": ");
    puts "#{index+1}. #{lineSplit[2]}"
    stashes[index] = lineSplit[0]
    stashNames[index] = lineSplit[2]
}
print "Choose Stash or ENTER to exit: "
input = gets.chomp
if input.to_i.to_s == input
    realIndex = input.to_i - 1
    puts "\n\nDoing #{command} to #{stashNames[realIndex]}\n\n"
    puts `git stash #{command} #{stashes[realIndex]}`
end

I like being able to see the names of the stashes and choose. Also I use Zshell and frankly didn't know how to use some of the Bash aliases above ;)

Note: As Kevin says, you should use tags and cherry-picks instead.



回答16:

Late to the party here, but if using VSCode, a quick way to do so is to open the command palette (CTRL / CMD + SHIFT + P) and type "Pop Stash", you'll be able to retrieve your stash by name without the need to use git CLI



回答17:

in my fish shell

function gsap
  git stash list | grep ": $argv" | tr -dc '0-9' | xargs git stash apply
end

use

gsap name_of_stash



标签: git git-stash