Home > Back-end >  How to make `git stash` refuse to smudge unstaged and staged changes?
How to make `git stash` refuse to smudge unstaged and staged changes?

Time:05-31

Question

How do I either

  1. make git stash error out if there are both unstaged and staged changes and I haven't specified either --staged (-S) or --keep-index (-k), or
  2. make git stash default to --keep-index if invoked without the --staged option?

Ideally as a global config option.

Motivation

Several times now:

  1. I carefully git add -p just the changes I want to commit (sometimes with temporary edits to files beforehand, when I want to pull apart two atoms of change which happen to touch the same lines or adjacent line, which git struggles with), then
  2. I think "I'll just quickly stash those other unstaged changes before taking the time to think of a good commit message", and so
  3. I type git stash, wasting the work of step 1, because git does the dumbest thing a program can do in that case - destroys user information by default without opportunity to abort or undo, by stashing all changes, both staged and unstaged changes, blended into one diff without any distinction.

Now if I was a computer program, I'd always just execute git commit -m 'TODO: --amend THIS COMMIT' before git stash, or I'd always remember to type git stash -k. But I find it more natural to think "I'll stash these unstaged changes real quick for later, then focus on the larger mental task of writing good commit message"

In fact come to think of it, I've been using git for close to a decade almost daily, and I have never wanted a git stash to stash staged changes, let alone fuse staged and unstaged changes back together. I can see how that could be useful if you're in the middle of staging one set of changes and then you want to stash the whole thing, but to me that's an extremely tiny window of time - most of the time, if I have staged changes and unstaged changes, they're meaningfully different pieces of change which I haven't finished separating out into the best atomic commits.

CodePudding user response:

According to the documentation, the index and the working tree are well separated into separated commits after the git stash command.

If your concern is to restore the index after a git stash pop I suggest using the --index option: git stash pop --index.

An alias can make transparent the use of this option:

git config alias.pop "! git stash pop --index"

CodePudding user response:

I'm sympathetic with the first part of the question, but I don't believe there is any such automatic option or configuration. I think the best alternative is Don't Do That — that is, if you're going to give a Git command, say what you mean, explicitly. That way, you know what will happen — because you said what should happen. (For the same reason I never say git pull, and I rarely say git rebase without saying --onto.)

At the same time, I'd like to push back sharply on the claims in the second part:

destroys user information

Uh, no. Only if you think that git commit destroys user information. stash saves user information, in a commit (actually a pair of commits) — and saves it in a way that makes it easy to restore.

blended into one diff without any distinction

That is not true. The index is saved as a separate commit. Thus it is possible to restore exactly the situation as it was, both the index and the worktree (except, of course, for the parts of the worktree that git stash doesn't touch by default, namely untracked and ignored files).

The way stash creation actually works is that the index commit has current head as parent, and the worktree commit has the index commit and the current head as parents. (In other words, the stash’s content is a merge commit!) You can see this by doing git log on a stash entry, and you will see the worktree commit followed by the index commit, either or both ready for you to restore.

Note that I am not defending git stash. It's weird and I generally try to avoid it (though sometimes it is a good solution to a specific problem).

  • Related