When stashing staged deleted or renamed files, and then unstashing them, they are restored in both their deleted and non deleted state.
In the following example state:
$ git status s
A file0
D file1
R file2 -> file3
?? file4
running git stash push -k -u
and then git stash pop --index
will leave me with in the following state:
$ git status s
A file0
D file1
R file2 -> file3
?? file1
?? file2
?? file4
I would expect to end up in the original state, without the deleted files reappearing as untracked after pop
.
Any way around this?
Edit: Here's a script that recreates the issue (tested on Mac OS X 10.13.2 with git 2.16.1)
#!/usr/bin/env bash
echo -e "\nInitializing a fresh git dir..."
mkdir gitStashIssue && cd $_
rm -rf * .*
git init
echo -e "\nPreparing git state for test..."
# Create files and commit them
echo 'abc' > file1
echo 'aabbcc' > file2
echo 'aaabbbccc' > file3
echo 'aaaabbbbcccc' > file4
git add .
git commit -m 'initial commit'
# Make changes and add them to stage
echo `cat file1` >> file1
echo `cat file2` >> file2
git add .
# Make another change to a staged file without
# staging it, making it partially staged
echo `cat file1` >> file1
# Delete and rename files
git rm file3
git mv file4 fileRenamed
# Add untracked file
echo "untracked" > untrackedFile
# git status -s should now show
# MM file1
# M file2
# D file3
# R file4 -> fileREnamed
# ?? untrackedFile
echo -e "\nCurrent git status is:"
git status -s
echo -e "\nStasing changes..."
git stash save -u -k
# git status -s should now show
# M file1
# M file2
# D file3
# R file4 -> fileREnamed
# ?? file3
# ?? file4
echo -e "\ngit status after stashing files is:"
git status -s
echo -e "\ncleaning up deleted and renamed files..."
git clean ./ -f
echo -e "\ngit status after cleanup:"
git status -s
echo -e "\nCommiting unstashed changes..."
git commit -m 'commit unstashed changes'
# This causes a conflict in file1
# git status -s should now show
# UU file1
# ?? untrackedFile
git stash pop --index
echo -e "\ngit status after unstashing:"
git status -s
Re-applying your stashed changesPopping your stash removes the changes from your stash and reapplies them to your working copy. This is useful if you want to apply the same stashed changes to multiple branches.
To retrieve changes out of the stash and apply them to the current branch you're on, you have two options: git stash apply STASH-NAME applies the changes and leaves a copy in the stash. git stash pop STASH-NAME applies the changes and removes the files from the stash.
When the pop command runs, it's expected that files from the stash will overwrite the contents of the files in the local working tree, and the updated files will be staged in the git index. But if a git stash pop conflict arises, then the problematic files won't be added to the index.
Stage all your files that you need to stash. Run git stash --keep-index . This command will create a stash with ALL of your changes (staged and unstaged), but will leave the staged changes in your working directory (still in state staged).
Remember: git does not track file renames. It tracks file contents.
When the index contains a deletion and a creation for different files with the same contents, git's rename detection will (probably) conclude that's a rename, and helpfully display it as such. This doesn't actually change what's in the index, though. What "really" happened was indeed a deletion-then-creation, git is just trying to display it more helpfully.
Try it yourself:
$ cp file1 boink
$ rm file1
$ git add .
$ git status
Has the same effect as:
$ git mv file1 boink
$ git status
This rename detection only works on things that have been added to the index. Do git reset
now and you'll see what I mean:
$ git reset
$ git status
Changes not staged for commit:
deleted: file1
Untracked files:
boink
We can use the git command git ls-files
to list the files present in the index. In effect, these are the files git considers existent at the moment.
When we have a rename staged as above, the output from this is:
# git ls-files
boink
file2
file3
file4
As far as the index is concerned, file1 no longer exists. We deleted it, and then we created boink
. git status
is friendly and shows us the results of rename detection, but remember that the index doesn't care about that.
Now we run git stash -k -u
. -k
tells stash not to touch the index, so it doesn't. Consider for a moment the first paragraph of the manpage for stash
:
Use git stash when you want to record the current state of the working directory and the index, but want to go back to a clean working directory. The command saves your local modifications away and reverts the working directory to match the HEAD commit.
So: we have asked stash to save our local modifications (without touching the index), and revert the working directory to match the HEAD commit.
file1
exists in the HEAD commit, so it comes back. But file1
is no longer in the index, because we deleted it. Hence, file1
is now an untracked file. Since we're not touching the index, the new file boink
and the record that it was the result of a rename from file1
remains in the index. Hence you get the potentially surprising output from git status
:
Changes to be committed:
renamed: file1 -> boink
Untracked files:
file1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With