As a bit of additional explanation, note that git stash
makes either two commits, or three commits. The default is two; you get three if you use any spelling of the --all
or --include-untracked
options.
These two, or three, commits are special in one important way: they are on no branch. Git locates them through the special name stash
.1 The most important thing, though, is what Git lets you—and makes you—do with these two or three commits. To understand this we need to look at what’s in those commits.
What’s inside a stash
Every commit can list one or more parent commits. These form a graph, where later commits point back to earlier ones. The stash normally holds two commits, which I like to call i
for the index / staging-area contents, and w
for the work-tree contents. Remember also that each commit holds a snapshot. In a normal commit, this snapshot is made from the index / staging-area contents. So the i
commit is in fact a perfectly normal commit! It’s just not on any branch:
...--o--o--o <-- branch (HEAD)
|
i
If you’re making a normal stash, the git stash
code makes w
now by copying all your tracked work-tree files (into a temporary auxiliary index). Git sets the first parent of this w
commit to point to the HEAD
commit, and the second parent to point to commit i
. Last, it sets stash
to point to this w
commit:
...--o--o--o <-- branch (HEAD)
|
i-w <-- stash
If you add --include-untracked
or --all
, Git makes an extra commit, u
, in between making i
and w
. The snapshot contents for u
are those files that are untracked but not ignored (--include-untracked
), or files that are untracked even if they are ignored (--all
). This extra u
commit has no parent, and then when git stash
makes w
, it sets w
‘s third parent to this u
commit, so that you get:
...--o--o--o <-- branch (HEAD)
|
i-w <-- stash
/
u
Git also, at this point, removes any work-tree files that wound up in the u
commit (using git clean
to do that).
Restoring a stash
When you go to restore a stash, you have the option of using --index
, or not using it. This tells git stash apply
(or any of the commands that internally use apply
, such as pop
) that it should use the i
commit to attempt to modify your current index. This modification is done with:
git diff <hash-of-i> <hash-of-i's-parent> | git apply --index
(more or less; there are a bunch of nitty details that get in the way of the basic idea here).
If you omit --index
, git stash apply
completely ignores the i
commit.
If the stash has only two commits, git stash apply
can now apply the w
commit. It does this by calling git merge
2 (without allowing it to commit or treat the result as a normal merge), using the original commit on which the stash was made (i
‘s parent, and w
‘s first parent) as the merge base, w
as the --theirs
commit, and your current (HEAD) commit as the target of the merge. If the merge succeeds, all is good—well, at least Git thinks so—and the git stash apply
itself succeeds. If you used git stash pop
to apply the stash, the code now drops the stash.3 If the merge fails, Git declares the apply to have failed. If you used git stash pop
, the code retains the stash and delivers the same failure status as for git stash apply
.
But if you have that third commit—if there is a u
commit in the stash you are applying—then things change! There is no option to pretend that the u
commit does not exist.4 Git insists on extracting all the files from that u
commit, into the current work-tree. This means the files must either not exist at all, or have the same contents as in the u
commit.
To make that happen, you can use git clean
yourself—but remember that untracked files (ignored or not) have no other existence inside a Git repository, so be sure these files can all be destroyed! Or, you can make a temporary directory, and move the files there for safekeeping—or even do another git stash save -u
or git stash save -a
, since those will run git clean
for you. But that just leaves you with another u
-style stash to deal with later.
1This is in fact refs/stash
. This matters if you make a branch named stash
: the branch’s full name is refs/heads/stash
, so these are not in conflict. But don’t do that: Git won’t mind, but you will confuse yourself.
2The git stash
code actually uses git merge-recursive
directly here. This is necessary for multiple reasons, and also has the side effect of making sure Git does not treat it as a merge when you resolve conflicts and commit.
3This is why I recommend avoiding git stash pop
, in favor of git stash apply
. You get a chance to review what got applied, and decide whether it was actually applied correctly. If not, you still have your stash which means you can use git stash branch
to recover everything perfectly. Well, assuming the lack of that pesky u
commit.
4There really should be: git stash apply --skip-untracked
or something. There should also be a variant that means drop all those u
commit files into a new directory, e.g., git stash apply --untracked-into <dir>
, perhaps.
lint-staged:bin Running `lint-staged@10.1.1` +0ms
lint-staged:bin Options parsed from command-line: {
allowEmpty: false,
concurrent: true,
configPath: undefined,
debug: true,
maxArgLength: 131072,
stash: true,
quiet: false,
relative: false,
shell: false
} +1ms
lint-staged Loading config using `cosmiconfig` +0ms
lint-staged Successfully loaded config from `/Users/william/airbitz/tmp/test-lint/.lintstagedrc`:
lint-staged { '*.js': 'false' } +42ms
lint-staged:cfg Validating config +0ms
Running lint-staged with the following config:
{
'*.js': 'false'
}
lint-staged Unset GIT_LITERAL_PATHSPECS (was `undefined`) +2ms
lint-staged:run Running all linter scripts +0ms
lint-staged:resolveGitRepo Resolving git repo from `/Users/william/airbitz/tmp/test-lint` +0ms
lint-staged:resolveGitRepo Unset GIT_DIR (was `undefined`) +0ms
lint-staged:git Running git command [ 'rev-parse', '--show-toplevel' ] +0ms
lint-staged:resolveGitRepo Resolved git directory to be `/Users/william/airbitz/tmp/test-lint` +12ms
lint-staged:resolveGitRepo Resolved git config directory to be `/Users/william/airbitz/tmp/test-lint/.git` +0ms
lint-staged:git Running git command [ 'diff', '--staged', '--diff-filter=ACMR', '--name-only', '-z' ] +12ms
lint-staged:run Loaded list of staged files in git:
lint-staged:run [ 'foo.js' ] +20ms
lint-staged:chunkFiles Resolved an argument string length of 43 characters from 1 files +0ms
lint-staged:chunkFiles Creating 1 chunks for maxArgLength of 131072 +0ms
lint-staged:gen-tasks Generating linter tasks +0ms
lint-staged:gen-tasks Generated task:
lint-staged:gen-tasks {
lint-staged:gen-tasks pattern: '*.js',
lint-staged:gen-tasks commands: 'false',
lint-staged:gen-tasks fileList: [ '/Users/william/airbitz/tmp/test-lint/foo.js' ]
lint-staged:gen-tasks } +2ms
lint-staged:make-cmd-tasks Creating listr tasks for commands 'false' +0ms
lint-staged:task cmd: false +0ms
lint-staged:task args: [] +0ms
lint-staged:task execaOptions: { preferLocal: true, reject: false, shell: false } +1ms
Preparing... [started]
lint-staged:git Backing up original state... +0ms
lint-staged:git Getting partially staged files... +1ms
lint-staged:git Running git command [ 'status', '--porcelain' ] +14ms
lint-staged:git Found partially staged files: [] +13ms
lint-staged:git Getting deleted files... +0ms
lint-staged:git Running git command [ 'ls-files', '--deleted' ] +13ms
lint-staged:git Found deleted files: [] +8ms
lint-staged:git Backing up merge state... +0ms
lint-staged:file Reading file `/Users/william/airbitz/tmp/test-lint/.git/MERGE_HEAD` +0ms
lint-staged:file Reading file `/Users/william/airbitz/tmp/test-lint/.git/MERGE_MODE` +0ms
lint-staged:file Reading file `/Users/william/airbitz/tmp/test-lint/.git/MERGE_MSG` +1ms
lint-staged:file File `/Users/william/airbitz/tmp/test-lint/.git/MERGE_HEAD` doesn't exist, ignoring... +0ms
lint-staged:file File `/Users/william/airbitz/tmp/test-lint/.git/MERGE_MODE` doesn't exist, ignoring... +0ms
lint-staged:file File `/Users/william/airbitz/tmp/test-lint/.git/MERGE_MSG` doesn't exist, ignoring... +0ms
lint-staged:git Done backing up merge state! +1ms
lint-staged:git Running git command [
'stash',
'save',
'--include-untracked',
'lint-staged automatic backup'
] +9ms
lint-staged:git Running git command [ 'stash', 'list' ] +42ms
lint-staged:git Running git command [ 'stash', 'apply', '--quiet', '--index', 'stash@{0}' ] +16ms
lint-staged:git Restoring merge state... +101ms
lint-staged:git Done restoring merge state! +0ms
lint-staged:git Done backing up original state! +0ms
Preparing... [completed]
Running tasks... [started]
Running tasks for *.js [started]
false [started]
false [failed]
→
Running tasks for *.js [failed]
→
Running tasks... [failed]
Applying modifications... [started]
Applying modifications... [skipped]
→ Skipped because of errors from tasks.
Reverting to original state because of errors... [started]
lint-staged:git Restoring original state... +7ms
lint-staged:git Running git command [ 'reset', '--hard', 'HEAD' ] +50ms
lint-staged:git Running git command [ 'stash', 'list' ] +10ms
lint-staged:git Running git command [ 'stash', 'apply', '--quiet', '--index', 'stash@{0}' ] +16ms
Reverting to original state because of errors... [failed]
→ bar.js already exists, no checkout
error: could not restore untracked files from stash
Cleaning up... [started]
Cleaning up... [skipped]
→ Skipped because of previous git error.
✖ lint-staged failed due to a git error.
Any lost modifications can be restored from a git stash:
> git stash list
stash@{0}: On master: automatic lint-staged backup
> git stash pop stash@{0}
✖ false found some errors. Please fix them and try committing again.
bar.js already exists, no checkout
error: could not restore untracked files from stash
Replies
Other info: When I run the command git stush, xcode asked me do I want to resave. I said yes
I made an experiment to a sample xcode project I created. I wrote a line of code. I used git stash(which is the command I used when I lost my xcode files in the first time) and after executing the command the code disappeared.I made a file and git stash again, lost the file.
I think this is an important tip.
Good news, I successfully recover the ‘sample’ one with git stash apply{stash1} but no help for the ‘real’ one.
I have three suggestions to help you. The first suggestion is to install Sourcetree, which is a free git app. Sourcetree shows your stashes in the sidebar of the repository window and provides a GUI to apply (restore) stashes. Using a GUI is generally easier than using the command line.
Second, move your question to the Continuous Integration and Source Control section of the forum. Your problem involves version control. You’ll have a better chance of getting an answer there.
Third, you need to provide a lot more information for anyone to help you. Start by telling us how you created the git repository. Did you create it when you created the Xcode project or did you create the repository from the command line? Describe the changes you made to your project after creating the git repository. What files did you add to the project? What files did you change? Did you make any commits before you did the stash? List the exact git stash command you entered and the exact command you entered to apply (restore) the stash.
I will install Sourcetree
>Did you create it when you created the Xcode project or did you create the repository from the command line?
Yes, I created the Git reposity when I created the project.I added some Swift files, some View Controller and Table View Controller and Table View Cell and storyboard files.
> What files did you change?
What change do you mean. If writing code to it is considered as change, then I changed all files.
>Describe the changes you made to your project after creating the git repository.
Nothing just creating Podfiles, create some files writing the code.
>List the exact git stash command you entered and the exact command you entered to apply (restore) the stash.
git stash
git stash -u
git stash pop
git add .
git stash apply stash{1}
If you open the repository for your project in Sourcetree and there are no stashes in the sidebar on the left side of the window, the stash is lost. Unless you backed up the project before you did the stash, all the work you stashed is lost and I don’t know of any way for you to recover it.
I don’t why, but it appears the stash was removed before you could apply it.
I can’t help you recover your lost work, but I can suggest a better strategy for the future. Instead of stashing a large number of changes without committing any of the changes, create a new branch after creating your project. Work on your project from the new branch. When you add source code files and storyboard files to your project, commit them. When you add new code and it works, commit the code that works. Make frequent small commits to minimize any potential data loss.
The advantage of working off a new branch is you can work on your project without affecting any working code. If your new changes don’t work, you can just delete the branch. If your new changes work, you can merge them into the master branch.
Fortunately, I found the stashes in the SourceTree and git stash apply stash{1} and get those files back.Thank you very much for the help and the SourceTree.
Before this thing happened(lost the files), I have same project but in different places(Mac screen and Finder). But the one in Finder is more complete, more code written, more files. After recovering the file, the recovered project is the one which is less complete(the one in Mac screen), but I want the project in the Finder. Do you know how to do that?
Double-click the Xcode project file that’s in the Finder. Now you’ll be working on the Finder project in Xcode.
If you somehow find the Finder version of the project isn’t tracking version control changes, choose Source Control > Create Git Repositories to put the Finder version under version control. If you are using Xcode 8 or earlier, the menu item name is Create Working Copy instead of Create Git Repositories.
Thanks for that. I finally know what is going. I actually ‘git stash’ to this project a long ago. And I successfully get back the files. Now, I also recovered the files, but it’s the files which are the ones before the first ‘git stash’ in the long time ago. Any suggestions?
I have 8 stash@{}s when I run the git stash list
I had a bunch of staged and unstaged changes and I wanted to quickly switch to another branch and then switch back.
So I staged my changes using:
$ git stash push -a
(In hindsight I probably could have used --include-untracked
instead of --all
)
Then when I went to pop the stash I get a whole lot of errors along the lines of:
$ git stash pop
foo.txt already exists, no checkout
bar.txt already exists, no checkout
...
Could not restore untracked files from stash entry
There doesn’t seem to be any changes restored from the stash.
I also tried $ git stash branch temp
but that shows the same errors.
I did figure out a way around this which was to use:
$ git stash show -p | git apply
Disaster averted for now but this raises some questions.
Why did this error happen in the first place and how do I avoid it next time?
I managed to recreate your issue. It seems if you stash untracked files and then you create those files (in your example, foo.txt
and bar.txt
), then you have local changes to untracked files that would be overwritten when you apply git stash pop
.
To get around this issue, you can use the following command. This will override any unsaved local changes so be careful.
git checkout stash -- .
Here is some further information I found on the previous command.
I’m having problems with recovering changes from a stashed and untracked file in git. See minimal example below:
mkdir test_stash
cd test_stash/
git init
echo "text" | tee a.txt b.txt
git add a.txt
git commit -m "First commit"
git stash -u #stash b.txt
echo "newtext" > b.txt
git add b.txt
git commit -m "Second commit"
git stash apply
This returns me an error:
b.txt already exists, no checkout
Could not restore untracked files from stash entry
The example alone is a bit silly, but I ran into this problem when stashing changes before pulling from remote and then finding out that a new file had been created on remote with the same name.
After some googling I was able to recover the changes with:
git checkout stash -- .
git checkout stash^3 -- .
git reset HEAD . #to unstage
but this seems quite hacky. Isn’t there a way to force my git stash apply
, thus bringing my workspace to the original state before the stash? The changes on b.txt are already committed anyway, so it’s not like I would risk losing unsaved changes.