Strange git case - git stash followed by git stash apply lost uncommitted data?
Asked Answered
J

4

6

I have a file, let's say file.txt I have done git mv file.txt to file1.txt, then I created a new file called file.txt and worked on it. Unfortunately I didn't add that file to git yet. Anyway the problem is that I did git stash, then git stash apply, but the new file.txt disappeared... anyway to get it back?

Jorum answered 10/4, 2010 at 15:57 Comment(1)
When learning Git, it's not a bad idea to make a backup of the entire project directory (including .git subdir) before executing commands whose functionality you do not fully comprehend. I did this quite a few times when I first started using Git and saved myself a lot of pain. It's good practice to try to restore from what's left after a mistake, but sometimes you really do delete something permanently on accident. Hard resets, forced pushes, rebases, etc. by nature introduce risk of data loss, a fact which clearly stated in most tutorials I've read.Beforehand
F
10

The problem here is mostly a misunderstanding of what git stash save does. It saves only changes to tracked files. Untracked files are not saved by git stash. When you moved file.txt to file1.txt, the new file.txt is an untracked file and will not be saved by git stash. This isn't a bug, it's just the way that git stash behaves. It could be that the documentation for git stash should be more clear about this.

As the documentation for git stash save states, it will do a git reset --hard after saving your changes. It was the git reset --hard that overwrote the new file.txt. One might argue that git reset --hard should generate a warning if an untracked file will be overwritten, but I still wouldn't call this a bug. It's doing what it's supposed to do.

The important thing to understand here -- and what would have saved you a lot of trouble -- is that git stash save does not save untracked files (and it probably shouldn't).

Frasch answered 10/4, 2010 at 16:48 Comment(19)
@Dan Moulding, I agree with this but if git stash is rolling back and overwriting uncommitted data, there needs to be some sort of notification.Costate
@smotchkkiss: That's reasonable. In this instance, the user was apparently expecting that the new file1.txt was going to be saved by git stash and re-applied by git stash apply. This is simply the wrong expectation. However, as you say, I think it would be reasonable to get a warning (and prompt for confirmation) if an untracked file will be overwritten by git reset --hard.Frasch
This is a good explanation for why that happened, but honestly this behavior of git is a bit shocking, and a lot damaging....Jorum
I don't believe it's a misunderstanding; I believe that it is a cast iron bug. There's no way that git should throw away the changes to an untracked file without warning. The file is tracked in the HEAD against which the user is trying to stash, just not in the index. For this reason I believe that git should definitely save the working tree version in the stash operation.Chrissa
@Charles: The new file and the renamed file, though they may have the same name, are two completely different files. Git is tracking only one of them. To say that the new one is tracked because Git knew about some other file by that same name in some other tree is just plain wrong. Look at what git status has to say about the state of the working tree after the original file is moved and the new one is created -- that pretty much says it all.Frasch
@Charles: Also, if there is a bug (and I still wouldn't say there is one), it's that Git doesn't warn that it's going to overwrite an untracked file. And you seem to agree with that. In that case, the bug is with git reset --hard, not with git stash save since the former is the one that overwrites the untracked file without warning.Frasch
@Dan: No git stash is the user operation that loses the file contents, so it's a bug in git stash. It overwrites the modified file with the HEAD version with a reset --hard. Because stash is resetting to a tracked version of the file, it should make sure that it has saved any version that it's overwriting. git stash should be a safe operation, it's not like reset --hard which is asking git to throw things away. I don't believe that the observed stash behaviour is acceptable.Chrissa
@Charles: You just said that Git should never throw away untracked files without warning. git reset --hard does do that, and it is the reason why git stash save is overwriting the untracked file. git stash save doesn't save untracked files. At the point where git stash save was run in this case, Git considers file.txt an untracked file. So it isn't and shouldn't be saved. The best you can get is a warning that an untracked file will be overwritten. But it's git reset --hard that needs to be changed if we want to get that behavior.Frasch
@Charles: Consider this: Git doesn't create a blob that represents a file until you've added the file to the index. In order for git stash to save a file, it needs that file to have a blob representation in the repo. The new file.txt was never added to the index, so Git never generated a blob for that file. Since it doesn't have a blob for the file, there is no way that it can save the file in the stash. You're suggesting that Git start tracking a file (by creating a blob for it) that was never explicitly added. That's simply not how it's designed to work.Frasch
Read the docs again. git stash is supposed to save your local modifications before performing a reset --hard. Removing a file, then re-adding one with different contents is surely a local change. git stash; git stash apply should be a no-op (OK, strictly a git reset). Your argument about there not being a blob for it is clearly bogus. If you have unstaged local modifications to files that haven't been removed from the index then git stash creates new blobs for the changed files. git stash already performs a git add -u so why shouldn't it create blobs all files that it removes?Chrissa
It saves local modifications to tracked files only. It creates new blobs for files that already are tracked. Nowhere else does Git create blobs for files that aren't (and never have been) tracked. And it shouldn't. In this case, the user wanted to keep the file that was overwritten. But what if the user didn't want it? If stash did what you are asking for, then now the repository has data in it that the user never asked to be there. Git's not supposed to do that. Bottom line is this: if you want git stash to save the new "file.txt" you've got to git add it first. Is that asking too much?Frasch
There's a big difference between not saving the changes to an untracked file because it's going to be left alone and not saving the changes to an untracked file and then wiping it. Are you seriously saying that a command that is documented as saving local modifications should, in some circumstances wipe them?Chrissa
What, exactly, is a local modification? It's when you make a change to a file that Git is already tracking, that's what. Creating a brand new file is not making a modification. So, yes, I maintain that git stash is behaving exactly as advertised. In no case should it wipe modifications. But if it's going to wipe a file that it knows nothing about (an untracked file), then I personally think it would be a good idea for it to issue a warning (much like git checkout does in the same circumstance). I absolutely disagree that git stash should save untracked files under any circumstance.Frasch
git stash undoes index and working tree changes. If the file is in the HEAD commit, removed from the index and re-added in the working tree then yes, that's got to be a local modification. If you agree that "In no case should it wipe modifications.", are you saying that the advertised behaviour should change? Do you think that a git stash should fail if you have an untracked file that would be altered by the reset?Chrissa
If that were true, then git status would show the new file as "Changed but not updated". But it doesn't. It clearly shows that Git considers this an untracked file. You seem to be arguing that not only is git stash broken, but git status is broken as well.Frasch
"Changed but not updated" would imply working tree changes but no index changes, with a file removed from the index but in the HEAD revision that clearly isn't the case. I don't think that git status is broken, but I don't understand what you seem to be interpreting from its output. Again, do you think that stash should fail if you have an untracked file that would be altered by the reset or do you think that stash's behaviour is fine as it is?Chrissa
No. You can modify a file, add it to the index, modify it some more and the file will be reported as "changed but not updated". That's working tree changes and index changes. If status says a file is untracked, I interpret that as meaning the file is untracked. How do you interpret it? Also, if stash doesn't save untracked files, why do you think it should save a file that is marked untracked by status? I think the overall current behavior is suboptimal. stash is doing what it's supposed to, but the fact that git reset --hard will wipe out untracked files without warning is questionable.Frasch
Merely questionable? What would be the optimal behaviour of git stash in this situation in your opinion?Chrissa
In simple terms this is what I think should happen. git stash should undo all the changes between HEAD and the current working tree, also resetting the index to HEAD. git stash followed immediately by git stash apply should reset the working tree to the state that it was in before the stash. To me, these are key and inevitably lead to the fact that if HEAD contains a file with a particular contents and the working tree version has a different contents then the state of the working tree file must be saved whether or not the index contains that a version of that file or not.Chrissa
C
4

This looks like serious (i.e. data loss) bug in stash. Please report it. Unfortunately, I don't believe that there's any way to get the new file.txt back.

This bug has now been fixed in git >=1.7.1.1.

Chrissa answered 10/4, 2010 at 16:18 Comment(2)
@khelll: The git mailing list: [email protected]Chrissa
Downvoter: I believe my answer is correct; if I'm wrong please show how the file contents might be recovered.Chrissa
C
2

for the future, use git stash -u to stash uncommitted files (http://www.kernel.org/pub/software/scm/git/docs/git-stash.html) You can do this starting from git version 1.7 i believe.

Counter answered 1/11, 2012 at 17:34 Comment(0)
A
1

This is post is to simply illustrate the recreation process without getting crammed into a comment. Note: Using Git version 1.7.0.2

To recreate:

~/test $ git init
~/test $ echo "hello" > file.txt
~/test $ git add .
~/test $ git commit -m "init commit"

~/test $ git mv file.txt file1.txt
~/test $ echo "new data" > file.txt
~/test $ git stash
~/test $ git stash apply

~/test $ cat file.txt
cat: file.txt: No such file or directory

~/test $ cat file1.txt
hello
Alagoas answered 10/4, 2010 at 16:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.