views:

223

answers:

3

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?

+3  A: 

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.

Charles Bailey
5 hours work was lost... Anyway where to report that?
khelll
@khelll: The git mailing list: [email protected]
Charles Bailey
Downvoter: I believe my answer is correct; if I'm wrong please show how the file contents might be recovered.
Charles Bailey
+1  A: 

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
macek
+4  A: 

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).

Dan Moulding
@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.
macek
@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`.
Dan Moulding
This is a good explanation for why that happened, but honestly this behavior of git is a bit shocking, and a lot damaging....
khelll
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.
Charles Bailey
@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.
Dan Moulding
@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.
Dan Moulding
@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.
Charles Bailey
@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.
Dan Moulding
@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.
Dan Moulding
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?
Charles Bailey
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?
Dan Moulding
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?
Charles Bailey
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.
Dan Moulding
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?
Charles Bailey
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.
Dan Moulding
"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?
Charles Bailey
*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.
Dan Moulding
Merely questionable? What would be the optimal behaviour of git stash in this situation in your opinion?
Charles Bailey
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.
Charles Bailey