1

I had local changes that included some new files and some file changes. My aim was to stash both the new files and the changes, then update the master branch, and finally to restore all changes in order to commit them.

For that, I thought I can use "git stash push -all". After doing so and updating the master branch I wanted to restore the files/file changes. Therefore, I proceeded with "git stash pop" which resulted in two untracked files being restored but by far not everything. What went wrong exactly?

I learned that "-all" means even untracked files get stashed. I assume that means files that should normally be ignored by git (gitignore file) are also in the stash now, is that correct?

Before making the mess worse, how can I restore everything correctly? I'm new to git.

Thanks in advance

isherwood
  • 58,414
  • 16
  • 114
  • 157
  • Can you please check your `history` and see if you actually did `git stash push --all` (note the double-dash) and **not** `git push --all` or something. Also: Does the top commit's date/time in `git log --graph --stat stash@{0}` match the date/time you used that command? – Jay Jun 29 '21 at 16:22
  • Thanks for answering. Ok I mean I basically did the following: git stash --all -> before it gave any response I did command C to stop it as it was not doing anything for a while git stash push -a -> same, also interrupted it before a response. git stash push --all -> after a while I got the response: "Saved working directory and index..." So maybe I screwed that up, could that be the case? – charlie_7521 Jun 29 '21 at 16:57
  • I guess that could be the case. But usually git doesn't lose file changes. Maybe it just created more than one stash. Have a good look at `git stash list` and `git log --graph --stat stash@{0}` (use higher numbers than `0` to see older stashes) to see where the files went. If you want to apply a certain stash, I would recommend using `apply` instead of `pop`, because `pop` will delete the stashed changes, `apply` will keep them in the stash. Also: some restored changes may not visible in `git status` if your updated master contains already identical files; or they are in updated .gitignore – Jay Jun 29 '21 at 17:19

2 Answers2

1

This is strange, it should work as you expect, I even tried for you:

I have put some content to originally empty file a and created file b, there is also ignored file i:

$ git status 
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   a

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    b

no changes added to commit (use "git add" and/or "git commit -a")
$ ls i
i
$ cat .gitignore 
i

Let me stash it --all:

$ git stash --all
Saved working directory and index state WIP on master: 2830eef Add gitignore

b and i are gone, a is empty again:

$ ls
a
$ cat a
$

Popping it back:

$ git stash pop
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   a

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    b

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (2009a694e40ac6b029d3f525a21c1c920e78a880)

Everything is back:

$ ls
a  b  i
$ cat a
random 
$
Roman Pavelka
  • 3,736
  • 2
  • 11
  • 28
  • Thank you so much, that's really awesome of you!! My untracked files were basically restored but not the changes. I'll read your post more carefully and check if I did something different than you. Could there be something specific to the changes that I missed? – charlie_7521 Jun 29 '21 at 16:51
  • No idea :( It should work as you expect and in my little experiment it does. – Roman Pavelka Jun 29 '21 at 17:15
  • 1
    thanks for the experiment :). I‘ll try it again in some other repo to figure out what went wrong. – charlie_7521 Jun 29 '21 at 21:15
1

As you've discovered, the --all flag (note two dashes here) means all files, including untracked and ignored files. This flag can also be spelled -a (one dash, and a by itself).1 There is a different flag, --include-untracked or -u, which means all files, including untracked files, but not including untracked-and-ignored files.2

It's important to understand how git stash works. This has become fancier than the last time I described it, because git stash can now build restricted stashes based on pathspecs. Still, it's worth reading through How to recover from "git stash save --all"? The stash code still uses the two-or-three commit mechanism I describe there, and still runs the equivalent of git reset and git clean: it's just that if you do provide a pathspec, the commits made, and the files reset and/or cleaned, are restricted.

In your particular case, interrupting git stash push could have left you with several stashes and a partially-cleaned repository. Subsequent additional stashes would then save the partially-cleaned state. The rewrite from shell code to C code makes this sort of piecemeal behavior less likely, but you might be using a version of Git that still uses a shell script to implement git stash.

Recovery would be a matter of finding all of the commits made, and using them to get the files back.


1As with most POSIX (Unix-style) commands, "long" options like all are predeced by two dashes, and "short" (single letter) options are preceded by a single dash (hence git diff -c vs git diff --cc: the long cc option requires two dashes; -cc specifies the short c option twice, to no effect).

2All ignored files are, by Git's own definition, also untracked files, but not all untracked files are ignored. This comes about because the definition of an ignored file is a file that is present in your working tree, but absent from Git's index. The definition of a tracked file is a file that is present in Git's index. If a file is present in Git's index, it's tracked, and therefore not ignored. If it's untracked, it's absent from Git's index. It then can be ignored, but isn't necessarily ignored.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks a lot for the answer! the pain is that I'm rather new to git so I don't entirely understand your answer in the other thread. I've found this article: https://blog.tfnico.com/2012/09/git-stash-blooper-could-not-restore.html The author just used reset to get to a previous commit and then stashed properly. Would something like this also work? In my case I just have 2 files back that I would need to stash again (fuck me...) and then I could do checkout. But I have no idea if that would work. – charlie_7521 Jun 30 '21 at 15:40
  • When I do "git stash list --date=local" it just shows one stash created yesterday. But as far as I understand you mean that there are stashes not visible that are part of that latest stash because I interrupted the process. I guess screwing this up is also a way to learn git ;) – charlie_7521 Jun 30 '21 at 15:54
  • If the one stash that `git stash list` doesn't have the desired content in its commits, then, well, yes, the "f-word state" probably applies. The copy you made will be a good source for a new working tree though. Remember that Git is all about *commits* (snapshots of your files), which it stores in a read-only, Git-only form in the `.git` directory, in the form of those commits. The act of checking out a commit (`git checkout` or `git switch`) tells Git to extract all the files from the commit archive. The files you see and work with are the *extracted* files, not the committed ones. – torek Jul 02 '21 at 09:00
  • Git makes ordinary everyday (normal) commits from whatever you have Git store in Git's *index* AKA *staging area*. This is why you run `git checkout` (or `switch`) on a branch name or commit hash ID, then modify working tree files—these files *are not* in Git, they're for you to work on/with, Git just copied them *out* of a commit earlier—and then run `git add`. This extra `git add` step copies working tree files back into Git's index, ready for the *next* commit. – torek Jul 02 '21 at 09:04
  • This extra step, of maybe only *partly* updating Git's index, gives advanced users special abilities (which are easy to abuse, or misuse). You can update the whole thing with `git add -u` (update all tracked files), and as a beginner that's normally what you'd want to do anyway. But the fact that there *is* this third, in-between but Git-ized copy of every file, halfway between the commit (read-only, permanent) and your working tree (normal everyday files) ... well, this fact gets in your face a lot, with Git. – torek Jul 02 '21 at 09:07
  • When you run `git stash`, what `git stash` normally does is make *two* commits. The first one contains whatever is in Git's index, the same thing you'd get as a regular commit if you run `git commit`. The second one contains (effectively) the result of running `git add -u` and then `git commit`. These two commits are the "stash"; having made those two, Git runs `git reset --hard HEAD` to discard, from both its index and your working tree, the files you *were* working on, and instead check out the commit you initially checked out. Any updates you made are in the two commits in the stash. – torek Jul 02 '21 at 09:09
  • With `-a`, Git still makes those same two commits for the stash—but it also makes a *third* commit, puts the untracked files (only) into this commit, and then removes the untracked files from the working tree area. The only way to apply or pop the resulting stash is in a cleaned-out working tree (those same untracked files must still be removed). The blog post you found is from someone who, experimentally, discovered this, but doesn't realize quite what he discovered. – torek Jul 02 '21 at 09:12
  • If you hadn't made a backup, and *really* needed those original files, and if you're using a version of `git stash` where a control-C at the wrong time leaves un-find-able stashes, you could use `git fsck --lost-found` to get Git to find any such stash commits and make them, um, slightly usable. Recovering the data from them is tricky (and has to be done within the time limit imposed by `git gc`, which in this case can be as short as 14 days). Having copies, it's probably easier to just use the copies. – torek Jul 02 '21 at 09:15