9

First of all, I do know about --keep-index. This is not what I want because it still stashes all changes, but leaves the staged one in the worktree. I would like to only stash the unstaged files, if possible without adding all changes again with git stash --patch.

iGEL
  • 13,729
  • 9
  • 53
  • 65
  • 1
    Could [this question](https://stackoverflow.com/questions/39026156/how-to-git-stash-only-untracked-files) provide an answer? – evolutionxbox Mar 15 '18 at 14:29
  • I typically avoid using only stash in tricky cases like this and simply go with `commit` -- commit the things you want to keep, and after that stash the rest temporarily, if needed. – jsageryd Mar 17 '18 at 18:10
  • @jsageryd that doesn't work if you have a pre-commit hook and the unstaged changes don't satisfy that hook. – Penghe Geng Jul 18 '18 at 21:53
  • I never use `pre-commit` hooks for this reason -- in my opinion it is the wrong place to put validation since it hinders workflows like the one above. If you have a `pre-commit` hook you can pass the `-n` (`--no-verify`) option to `git commit` to bypass it. – jsageryd Jul 20 '18 at 08:30
  • @jsageryd so basically, you avoid using pre-commit hooks because git-stash sucks and can't stash only the unstaged changes? I think the solution to this is fix git so that it can stash only the unstaged changes... – scaly Jan 20 '21 at 04:49
  • @scaly Why do you care what gets stashed? If you want to keep the staged changes and later apply only the stashed unstaged changes, do that `git stash -k` to keep the staged changes then later do `git cherry-pick -nm2 stash` to apply only the stashed unstaged changes. See [here](https://stackoverflow.com/questions/50242489/how-to-ignore-added-hunks-in-git-stash-p/50570593#50570593) for an explanation. – jthill Jan 20 '21 at 05:31
  • @jthill No, that won't work. I care what gets stashed because I'm going to mutate the staged changes after the stash operation, so I don't want the stash to contain the pre-mutation form of the staged changes (because if it does, then when we reapply the stash, it will overwrite the mutations!). I don't understand why it's so hard for git to simply, stash the unstaged changes only. Why is this suddenly rocket science? – scaly Jan 20 '21 at 06:05
  • @jthill It might be worth mentioning also, that we are talking about a single file where certain lines are staged, and certain lines are unstaged. We want to stash the unstaged lines of that file, then mutate the staged lines before we commit them. Then reapply the unstaged lines. I tried your `git cherry-pick -nm2 stash` but it simply doesn't work, at least not in my version of git, which is 2.28.0 – scaly Jan 20 '21 at 06:07
  • `git cherry-pick -nm1 -Xours stash` might work though. I don't understand why I can't just `git stash pop -nm1 -Xours` though! – scaly Jan 20 '21 at 06:11
  • If it's worth fixing up stash pop to you, do that. I'm sure they'd accept a well-written patch. You can't just do it because nobody's cared enough to implement that. – jthill Jan 20 '21 at 06:15
  • @jthill I feel you. Well, the problem with `git cherry-pick -nm1 -Xours stash` is that it tends to lose changes that were unstaged but were on the periphery of some mutation that expanded the number of lines used by the line that was mutated. I guess it's too much to ask that the automatic heuristic would be able to intelligently resolve this... we could leave the working tree in a conflicted state in such a rare case though (this is for a shared pre-commit and post-commit hook to apply some code formatting to some code files). – scaly Jan 20 '21 at 06:21
  • @jthill Also the downside to using `cherry-pick` is that it automatically stages changes. So after we would reapply the unstaged changed, we want them to still be unstaged. However with `cherry-pick` they will be staged after the operation. The only way around that is to perform a `git reset` however, if we do a `reset` automatically, then it aborts any in-progress merge leaving a mess within the files. Seems like at every turn I'm running into all the limitations of git today. What a weird day. – scaly Jan 20 '21 at 06:27
  • `git cherry-pick -nm1 -Xtheirs stash` turned out to work the best for us. Because the stashed changes are "theirs" in this situation, and we want to definitely keep those. – scaly Jan 20 '21 at 07:12

3 Answers3

2

If you want to store the diff between the index (what's staged) and the worktree (what's not staged yet), this simply is git diff :

# store it :
git diff > stash.patch

# if you additionally want to put the unstaged changes away :
git stash -k

To apply at later these changes on the worktree (not on the index) : use git apply

git apply stash.patch

You could also use what gets stored in the stash to re-create that diff :

# stash the changes :
git stash -k

# to reapply them on the worktree at a later time :
#   the 'unstaged changes' are the diff between 
#    - what the index was (stash^2)
#    - and what the worktree was (stash)
git diff stash^2 stash | git apply -

# again : 'git apply' will apply the changes on the *worktree*, not the index
LeGEC
  • 29,595
  • 2
  • 37
  • 78
1

Best I can come up with is:

git commit -n -m temp
git stash push -u
git reset HEAD~1

This will commit without triggering any pre-commit hooks. Then it will stash the changes that remain (i.e. the unstaged changes from before). Finally, it will reset head back to the pre-commit state (before the "temp" commit).

scaly
  • 165
  • 12
0

You cannot do it direclty, but you can eventually isolate only unstaged or only staged changes, or both, without even using any git-stash command at all :


git switch -c separated-stashes

Equivalent to the old checkout -b : creates a new branch and switch on it. It won't change neither your worktree nor your index so you'll basically have the same git status' output as before, but this time on the new branch. Contrary to a simple git switch / git checkout, it won't warn you to stash or commit your changes before switching branches, because you are explicitly creating a brand new branch through -c/-b.


git commit -m "staged"

Create a first commit on the new branch containing only staged changes from the beginning.


git add -u && git commit -m "unstaged"

Create a second commit with unstaged changes from the beginning.


git switch - # == git checkout -

Go back to the previous branch you were on.

Now you have stashed everything and can cherry-pick what you need (staged / unstaged) wherever and whenever you want.


This might look a little cumbersome but you're free to define a git alias to automate it :

git config --global alias.bratisla '!git switch -c separated-stashes; git commit -m "staged changes"; git add -u; git commit -m "unstaged changes"; git switch -' # why this name ? : youtu.be/LpE1bJp8-4w
dilavasso
  • 1
  • 2
  • So it makes it impossible to use this logic in a pre-commit hook because you have to make new commits just to stash only the unstaged changes. This is super lame. – scaly Jan 20 '21 at 04:47
  • Well, OP didn't ask about a pre-commit hook compliant solution, it seems it was more a manual command line solution, and as stated in [this comment](https://stackoverflow.com/questions/49301304/stash-only-unstaged-changes-with-git-not-keep-index/64413946?noredirect=1#comment89846525_49301304) you don't necessarily want to make one, especially for stashing stuff. But if you have a way to assess the problem without having to commit so that you can make it a hook, I would love to ear it @scaly – dilavasso Feb 01 '21 at 21:37