Git stash pop only if successfully stashed before
Edit, July 2022: Time (and Git) have moved on and depending on your Git version much of the below is not necessarily accurate any more. One of the most important changes is that there are now git stash push
and git stash create
commands. See the footnotes and comments.
As Xavier Álvarez noted and codeWizard wrote, it's probably wiser to avoid git stash
entirely here. For instance I'd look at using separate git fetch
and git rebase
steps (see Xavier's answer), and note that rebase now has --autostash
which essentially does just what you want, it's just not directly available via the git pull
convenience script.1
That said, there is a way to do what you've asked. It's a little bit tricky. It would be a lot easier if git stash save
had a "force" option similar to git commit --allow-empty
, but it doesn't have such an option.2 Instead, what you can do is detect whether git stash save
pushed a new stash. This too would be a lot easier if git stash save
had an exit status indicating whether it pushed a stash, but again it doesn't. That means we must rely on a different trick entirely. We start with two facts: git rev-parse
finds SHA-1s from "references", and git stash
uses one particular reference.
The git rev-parse
command will translate any reference into an SHA-1:
$ git rev-parse refs/remotes/origin/master
2635c2b8bfc9aec07b7f023d8e3b3d02df715344
A reference is just a name, usually starting with refs
, that names some SHA-1 ID. The most common ones are branches: refs/heads/branch
. You may have also used tags: refs/tags/tag
, and you have probably used remote-tracking branches like origin/master
, which is short for the full name, refs/remotes/origin/master
.
The stash
script uses refs/stash
, so we can simply run git rev-parse refs/stash
.3 We want to run it before git stash save
, then again after git stash save
. If the output changes, the git stash save
step must have pushed a new stash onto the stash stack.
We do have to be a bit careful since if the stash stack is empty (because the last stash was popped or dropped earlier, or no stashes have ever been created yet), git rev-parse
will give an error message and produce no SHA-1:
$ git rev-parse refs/stash
fatal: ambiguous argument 'refs/stash': unknown revision or path not in
the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Hence we actually need git rev-parse -q --verify refs/stash
, which silently produces nothing if the reference does not exist, and then we just need a little care in any shell script that uses the result:
oldsha=$(git rev-parse -q --verify refs/stash)
git stash -q save # add options as desired here
newsha=$(git rev-parse -q --verify refs/stash)
if [ "$oldsha" = "$newsha" ]; then
made_stash_entry=false
else
made_stash_entry=true
fi
... all of your other code goes here ...
if $made_stash_entry; then git stash pop; fi
1The git pull
command is basically a short-hand for git fetch
followed by git merge
, or, if you tell it, to run git fetch
followed by the usually-more-appropriate git rebase
. If you break it up into its two separate steps, though, you get a lot more control, along with the ability to inspect the incoming changes before merging or rebasing.
Edit, July 2022: git pull
is no longer a script and autostash works with it now. There were intermediate transition states along the way.
2You can effectively force stash creation using the relatively new create
and store
subcommands: create a stash, then store the resulting SHA-1, and you've forced a stash-save even if there is nothing to stash. But not everyone is up to date with a recent git, so for scripts, it's probably wiser to rely on the old way (or as noted earlier, not use stash at all, especially since it has various minor but annoying bugs, in various versions of Git).
Edit, July 2022: git stash
is no longer a script and has new options and verbs. See comments.
3It's wise to spell out the full name, because git rev-parse stash
will first look for a branch named stash
. This is true in general with all references when writing aliases or scripts: spell out full names (and use --
syntax as necessary) to make sure Git doesn't do what it thinks you meant, in odd corner cases.
Reading your explanation of why do you do what you do I'd probably go for a completely different approach. First, I'd fetch the remote you want to use:
git fetch <remote> (e.g. git fetch origin)
And then, I'd carry out a rebase against a specific branch of that remote:
git rebase <remote>/<branch> (e.g. git rebase origin/master)
This would merge your changes and you'd still be able to solve any conflicts.
If you don't like this approach, you might want to use git pull with the --no-commit flag instead:
git pull --no-commit
This way no autocommit would be performed after the merge.