Reputation: 167
I was working on the main branch, then I decided to create a new branch called test-branch
. I tried git checkout feature
first which warned me to stash
or commit
my changes. I stash
ed them using git stash save latest modification
and then I went to test-branch
using git checkout -b test-branch
. I was trying to add all files (not ignoring any) and commit to the branch. So I deleted everyting from .gitignore
. After running git add .
, I came back to main
without committing to test-branch
. I deleted that branch using git branch -D test-branch
. And then I used git stash apply
in main
. Now my code has gone to the last commit version and all the modifications I did after that commit exist no more. What do I do now ?
Before swtiching branches
PS C:\Users\Administrator\Desktop\projects\songs> git checkout feature
error: Your local changes to the following files would be overwritten by checkout:
.gitignore
app.py
music.db
static/css/styles.css
templates/favorites.html
templates/layout.html
Please commit your changes or stash them before you switch branches.
PS C:\Users\Administrator\Desktop\projects\songs> git stash save "latest modification"
Saved working directory and index state On main: latest modification
Unlink of file 'music.db' failed. Should I try again? (y/n) y
fatal: Could not reset index file to revision 'HEAD'.
After switching branches
PS C:\Users\Administrator\Desktop\projects\songs> git checkout -b test-branch
Switched to a new branch 'test-branch'
PS C:\Users\Administrator\Desktop\projects\songs> git st
M music.db
?? static/scripts/downloader.js
?? test/
PS C:\Users\Administrator\Desktop\projects\songs> git add .
I then came back again to main
PS C:\Users\Administrator\Desktop\projects\songs> git checkout main
Switched to branch 'main'
M .gitignore
M music.db
A static/scripts/downloader.js
A test/a.exe
A test/ipvalidator.exe
A test/nextvalidator.c
A test/nextvalidator.exe
Your branch is up to date with 'origin/main'.
PS C:\Users\Administrator\Desktop\projects\songs> git branch -D "test-branch"
Deleted branch test-branch (was 08f1d8e).
Then I applied the stash
and my changes are gone
PS C:\Users\Administrator\Desktop\projects\songs> git stash apply
error: Your local changes to the following files would be overwritten by merge:
.gitignore
Please commit your changes or stash them before you merge.
Aborting
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: music.db
new file: static/scripts/downloader.js
new file: test/a.exe
new file: test/ipvalidator.exe
new file: test/nextvalidator.c
new file: test/nextvalidator.exe
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: .gitignore
Untracked files:
__pycache__/
flask_session/
improvements.txt
run.ps1
static/downloads/
templates/test.html
test.py
venv/
Edit:
As per @ElderFuthark 's request, I ran git stash show --stat
and got the following:
PS C:\Users\Administrator\Desktop\projects\songs> git stash show --stat
.gitignore | 4 ++--
app.py | 11 +++++++----
music.db | Bin 69632 -> 86016 bytes
static/css/musicPlayer.css | 1 +
static/css/styles.css | 4 +++-
templates/favorites.html | 2 ++
templates/layout.html | 8 ++++++--
templates/play.html | 27 +++++++++++++++++++--------
8 files changed, 40 insertions(+), 17 deletions(-)
Upvotes: 1
Views: 660
Reputation: 487795
(see part 2 for what you can do)
There are multiple things to learn here, and also multiple different steps to take to recover everything. There's overlap between the two but they're not exactly the same.
One thing I would suggest as a learning item is "don't use git stash
" because it often makes a big mess. 😱 People do like it a lot, though.
Here are things you need to know before we get started on fixing things (you may well know some of these already, but I'll enumerate them here):
Git is mostly about commits. It's not really about files (though we store files in commits), and it's not really about branches (a poorly-defined word). We use branch names to help us (and Git) find commits, which then store the files.
Commits themselves have—or can have—parent/child relationships. These form things that Git calls branches, which are different from the branch names that Git also call branches.
Commits are numbered, with big ugly random-looking hash IDs or object IDs. These IDs are actually numbers expressed in hexadecimal. Every commit gets a unique ID, one that has never been used before for any commit anywhere ever, and one that will never be used again. So the ID works to find the commit. Git actually needs that ID, but they're so big and ugly and random-looking that humans are very bad at them, and we mostly don't use them: we mostly use branch names instead.
What's in a commit is a pair of things:
Each commit holds a full snapshot of every file (that it holds: some files get added or deleted over time).
Each commit also holds some metadata, or information about the commit itself: who made it and when, for instance. This metadata, for any given a commit, includes a list—usually just one element long—of the raw hash ID of the previous commit. Git calls this the parent of the commit, and it's how commits link together into "branches" (the group-of-commits kind of branch, not the name kind).
All of these parts are completely read-only, and they last as long as the commit itself lasts.
Because a commit is read-only, you can never actually work on a commit directly. Instead, the act of "checking out" a commit (with git checkout
or git switch
) copies the files out of the commit into a usable work area. Git calls this your working tree or work-tree and it's quite simply where you do your work.
Note that checking out some commit makes that commit the current commit. This current commit will, in the future, become the parent commit of your next new commit, unless of course you switch to another commit as your current commit.
For reasons known only to Git's inventor—though we can speculate all we want—Git doesn't just have two copies of each "active" file. There is indeed one saved permanently in the current commit, and a usable, edit-able copy in your working tree. Most version control systems do this, so that there are two copies. But Git keeps a third copy (or "copy" because it's in Git's internal pre-de-duplicated form) of each file that came out of a commit. This third copy is how Git makes the next commit, and it's stored in a very important, but poorly-named, place.
So, to review a bit:
We give a branch name to git checkout
or git switch
.
That name locates a commit by its hash ID. Git will, if this checkout or switch is successful, extract the files from that commit so that we can work on them. That commit—it hash ID—becomes the current commit, and that name, whatever it is, becomes the current branch name.
To check out that commit, Git must fill in both the working tree and this poorly-named third copy, from the files saved permanently in that commit (as permanent as the commit itself, which is "mostly permanent").
It's time to talk about that poorly-named area that holds the third copy of each file, and its relationship to your working tree. Git calls this thing by three different names:
It is the index. This name has no obvious meaning, which is both good and bad: good, because you probably won't bring a bunch of preconceived notions, and bad, because, well, the reaction to "index" is mostly "huh? what?"
It is also the staging area. This is often a better name because it talks about how we use it. We git add
a file to "stage" it for commit. But it's a bit misleading, because that file is already in the staging area even before we git add
it. Or rather, it's there if it was already in the current commit.
As a much older name, that's mostly going disused now, Git sometimes calls this the cache. You mostly see this in flags, as in git rm --cached
or git diff --cached
.
Anyway, what you need to know about this thing—which I'll call the index here—is that it holds your proposed next commit. That is, if you were to run git commit
right now, whenever "right now" is, you'll either get a new commit or an error. If you don't get an error, the new commit you will get holds, as its full snapshot of every file, all of the files that are in the index, and no other files at all. The specific version of the file that will be in the new commit is the data that we find in the index copy of the file.
Because this index copy is a third copy (but pre-de-duplicated so that git commit
goes fast), it's possible for it to be different from the current commit copy. It's also possible for it to be different from the working tree copy.
When we first check out some commit, Git will—usually; we'll see an exception in a little while—copy all of the files from that commit into its index / staging-area, right then. So the commit and the index will match. And, Git will usually copy all of the files from the commit/index (which match) to the working tree, right then, too. So the commit, the index, and the working tree all match.
When this is the case—when everything matches—git status
is usually pretty quiet:
$ git status
On branch main
nothing to commit, working tree clean
for instance.
If you change a file in the working tree and run git status
, you get:
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: Makefile
for instance. If you then git add
that file, this changes yet again:
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: Makefile
What's going on here is simple: git add
told Git: Read the working tree copy of the file. Compress it down into Git's internal de-duplicated storage format to make it ready for committing. Check to see if that's a duplicate of some existing file; if so, throw out the compressing work you did and re-use the duplicate, and if not, you now have the prepared compressed file ready for committing, and in either case, update your index so the next commit will use this updated version of the file.
In other words, git add
copies the file into the index. The index was ready to go into the next commit. It's still ready to go into the next commit. But now it has a different version of the added file.
If the fie you git add
is all-new—if it wasn't in the index before—Git still de-duplicates the content. This means that if you store ten files in a commit, but they're all identical, Git only stores one copy of it, just under ten names. Then Git updates the name-and-content data in the index / staging-area / cache, so that the next commit will have this file. If the file was in the index before, Git kicks out the old copy to make room for the new copy. In either case, the index continues to be ready to commit. The index always holds your proposed next snapshot.
The way you make commits, then, is:
git add
to update the proposed snapshot in the index; andgit commit
.When you run git commit
, Git:
Git then stuffs the new commit's hash ID into the branch name.
What this means is that we can draw a Git "branch" like this:
... <-F <-G <-H <-- your-branch (HEAD)
Here, the branch's name is your-branch
. You are "on" this branch because you used git switch
or git checkout
with the name your-branch
. The name holds the hash ID of commit H
—H
stands in for some big ugly hash ID like 30cc8d0f147546d4dd77bf497f4dec51e7265bd8
here. So H
is the current commit, and it's also the last commit "on" the branch.
The files you're working on / with came out of commit H
. They are now in your working tree, and in Git's index / staging-area.
Commit H
has a parent commit, which we're calling G
, that also has a snapshot and metadata, and that commit has a parent commit that we're calling F
, which also has a snapshot and metadata, and so on. But we're using commit H
.
You now modify some files and git add
them to update Git's index, and run git commit
. Git saves every file—including all the de-duplicated, unchanged duplicates from commit H
, which take no space since they're de-duplicated—into a new snapshot and makes a new commit, which gets a new unique hash ID, but we'll just call it I
. Commit I
has commit H
's hash ID as its parent, so that I
points backwards to H
:
...--F--G--H <-- your-branch (HEAD)
\
I
and now, as the final step of git commit
, Git stuffs I
's hash ID into the name your-branch
, so that this name points to I
instead of H
:
...--F--G--H
\
I <-- your-branch (HEAD)
(and now there's no reason to draw the commits with a bend in the line, but I left it in to make it obvious that it's the branch name that moved).
Suppose that before we make commit I
, we'd like to put it on a different branch. This is where that name HEAD
, in parentheses, comes in. If we want to make a new branch name, we can use git branch
to do that:
...--F--G--H <-- your-branch
We run:
git branch new-branch
and get:
...--F--G--H <-- new-branch, your-branch
We now need a way to say which branch name you're using, and that "way" is to attach the special name HEAD
to just one branch name:
...--F--G--H <-- new-branch, your-branch (HEAD)
This means that the branch you're "on" is your-branch
, not new-branch
. Both names select commit H
, though, and you can now:
git checkout new-branch # or git switch new-branch
to get:
...--F--G--H <-- new-branch (HEAD), your-branch
Remember that I said, above, that
When we first check out some commit, Git will—usually; we'll see an exception in a little while—copy all of the files from that commit into its index / staging-area, right then.
We just hit our first exception. Git is deliberately a little bit sneaky. If we switch from one branch name to another, Git uses the fact that commits hold de-duplicated files to avoid doing work. For each file that's a duplicate across the two commits, Git does nothing. When you're moving from commit H
to commit H
, all the files are, by definition, duplicates, so Git doesn't have to do anything at all—and it doesn't!
So, whether or not you have modified any files in the index and/or working tree since the git checkout
that copied commit H
's files into the index and working tree, your switch from H
to H
does absolutely nothing at all other than attach HEAD
to the other branch name.
This means that you can, at any time, create a new branch name and switch to it, and the only thing that actually changes is that the new name is now your current branch name. Your current commit and all the files in all three copies are all undisturbed.
You can combine the create-new-name and the switch-to-name with git checkout -b
or git switch -c
, as you did; this makes no real difference except that Git won't create the branch name unless the switch will work.
I saved this for a separate section because it gets a little complicated, and quite crucial for some cases, here.
As we've already seen, Git's index always holds your proposed next commit—or rather, its snapshot; Git doesn't assemble the metadata until you actually run git commit
. As such, the index holds copies (or de-duplicated "copies", really) of the files that will be in the next commit.
You will also, at times, see Untracked files
in the output of git status
. But what, precisely, is an untracked file? Fortunately the answer is ridiculously simple: An untracked file, in Git, is simply any file that is not currently in Git's index. Note that it does not matter whether the file is in any commit. It just has to not be in Git's index right now. (It also has to exist in your working tree, of course. Otherwise we could say that the file rumplestiltskin.straw
is untracked in almost every repository, since it isn't in the index, but it isn't in the working tree either. We don't usually remark on things that don't exist.)
By contrast, then, a tracked file is a file that is in Git's index. It does not have to be in any commit yet, and it does not even have to be in your working tree. So tracked files are simply those files in the proposed next commit (or its snapshot). Untracked files are those not in the proposed next commit. You can use git add
to make an untracked file tracked, and you can use git rm
—with or without --cached
—to remove a tracked file from Git's index. Using git rm
without --cached
removes it from both Git's index and your working tree, so that we don't think about it much any more, but git rm --cached
removes it from Git's index without removing it from your working tree, making it untracked.
Remember of course that Git's index gets filled in at times, so that the set of tracked files changes. And of course, you run git add
and/or git rm
and this can also change the set of tracked files. So "tracked" is not a permanent thing: it's always based on what is in Git's index right now.
When we run git status
, it often whines about various files being untracked. The purpose of this complaint is to remind us to git add
the files. But sometimes we don't want to git add
those files. This is what .gitignore
is about: it only affects untracked files, and it stops the whining.
It also affects any en-masse git add
we run, though. To make things much more convenient to use, Git lets us run git add .
or git add --all
, and Git scans the working tree at this point to find files to git add
. Git will:
git add
can even remove some files, though I won't cover that case here.But adding new-to-Git untracked files would be wrong if those files are supposed to be, and stay, untracked. So listing an untracked file in a .gitignore
tells Git not only to shut up about it, but also to not-add it with one of these "add all the files" operations.
Listing a tracked file in .gitignore
has no effect because such a file is already in Git's index. That isn't a key item in your question here, but it's important to know in general. The name .gitignore
is a bit misleading: it should perhaps be .git-do-not-complain-about-these-files-if-they-are-untracked-and-when-they-are-untracked-do-not-add-them-with-en-masse-git-add-commands-either-because-they-are-supposed-to-stay-untracked
, or something like that. But who wants to type that in as a file name? So .gitignore
it is.
git stash
(Note: Since Git version 2.13, git stash save
is deprecated in favor of git stash push
. Both do the same thing when used for the simple case I'll be describing here, but git stash push
has options to do fancier things.)
What git stash
does can be described simply—perhaps too simply, in some cases, but we won't worry about those here—as: make commits that are on no branch at all and then use git reset --hard
. That's what you didd here, with:
PS C:\Users\Administrator\Desktop\projects\songs> git stash save "latest modification" Saved working directory and index state On main: latest modification
Something went slightly wrong right after this point but for now I'm going to ignore that.
The git stash save
operation here made those two commits—a stash itself consists of either two or three commits, but we don't need to worry about the three-commit variety here—and those two commits saved:
git commit
would have made), andgit add -u
(update tracked files) and git commit
.The metadata in these funky git stash
commits is a little weird, though if you use git stash save
with a message, that sets the commit subject for the commit that git stash list
will show.
These two new commits are not put on any branch at all, which means git log
won't normally show them. (They are on refs/stash
so git log --all
does show them. They look weird because one of the commits is technically a merge commit, even though it's not the result of using git merge
and should not be treated as a merge commit
. This is one of the reasons you must use git stash
to deal with these special commits, and is a reason I recommend avoiding git stash
, as only git stash
can deal with them properly. That makes a lot of the usual Git toolbox less useful.)
Your computer-output starts here:
PS C:\Users\Administrator\Desktop\projects\songs> git checkout feature error: Your local changes to the following files would be overwritten by checkout: .gitignore app.py music.db static/css/styles.css templates/favorites.html templates/layout.html Please commit your changes or stash them before you switch branches.
This means your git checkout
didn't actually do anything. It simply reported which files in your working tree have un-committed work (possibly git add
-ed, possibly not git add
-ed, but not git commit
-ed) that, if Git were to switch to whichever commit is the last commit on branch name feature
, Git would have to remove from Git's index and your working tree.
Git didn't switch, so the files are all still intact (in both Git's index and your working tree).
PS C:\Users\Administrator\Desktop\projects\songs> git stash save "latest modification" Saved working directory and index state On main: latest modification
Here, git stash
made the two commits that saved the current index state
and the tracked files' state. Git then ran git reset --hard HEAD
to attempt to:
But something went wrong:
Unlink of file 'music.db' failed. Should I try again? (y/n) y
fatal: Could not reset index file to revision 'HEAD'.
Probably (since you're on Windows) some program had file music.db
open, which prevented its removal. It's not clear what else might have gone wrong in terms of resetting the index file, but this alone sufficed to make git reset --hard HEAD
fail.
PS C:\Users\Administrator\Desktop\projects\songs> git checkout -b test-branch Switched to a new branch 'test-branch'
This created a new branch name pointing to the same commit you were already on. The drawing I make here will be wrong, but I can't make the right one; only you can do that.
I--J <-- feature
/
...--G--H <-- main
\
K <-- somebranch, test-branch (HEAD)
PS C:\Users\Administrator\Desktop\projects\songs> git st M music.db ?? static/scripts/downloader.js ?? test/
Presumably git st
is an alias for git status --short
, so this git status
tells us that:
music.db
is tracked, and the committed and index versions match, but the index and working tree versions differ;static/scripts/downloader.js
is untracked; andtest/
(so, somewhere within that folder).PS C:\Users\Administrator\Desktop\projects\songs> git add .
This does an en-masse add of all modified files, i.e., music.db
, and all untracked files that got whined-about, i.e., static/scripts/downloader.js
and the files—we don't know their names—in test/
.
(Side note: while Windows uses a backwards slash, e.g., test\
, to separate the top level folder name from additional names in that folder, Git itself doesn't exactly believe in folders in the first place, and always uses a forward slash. Git's index can only store files, not folders, and it stores them under names like static/scripts/downloader.js
, with forward slashes. Git knows how to break that up into folder-and-file pieces and interact with the OS, which may demand backward slashes instead of forward ones, but internally Git always uses the forward ones. Since the index itself only holds file names, complete with forward slashes, Git is unable to save an empty directory in a commit: OS-level folder creation is always just implied here. If the index could hold directory entries here, Git would be able to store empty folders.)
The add, of course, updates Git's index. So the index now matches the working tree, except for truly-ignored files (files that are both untracked in the working tree and also listed in .gitignore
).
No commit happens yet, so that's all that happens. Next we have:
PS C:\Users\Administrator\Desktop\projects\songs> git checkout main Switched to branch 'main' M .gitignore M music.db A static/scripts/downloader.js A test/a.exe A test/ipvalidator.exe A test/nextvalidator.c A test/nextvalidator.exe Your branch is up to date with 'origin/main'.
Here, Git made use of that exceptional case I mentioned yet again. We did, however, have to switch commits. Unlike the H
-to-H
case I described, Git did have to switch from one commit to another. The (wrong) graph I drew above says that we switched from commit K
to commit H
:
I--J <-- feature
/
...--G--H <-- main (HEAD)
\
K <-- somebranch, test-branch
Let me augment this drawing now, to show the two commits that git stash
made, because they'll matter soon. Here they are:
I--J <-- feature
/
...--G--H <-- main (HEAD)
\
K <-- somebranch, test-branch
|\
i-w <-- refs/stash
Remember, your attempt to git checkout feature
had failed, leaving you on branch somebranch
. That's where you were when you ran git stash
. The two commits that Git made "hang off of" the then-current commit, which I've drawn as commit K
. Commit i
holds the index state and commit w
holds the git add -u
work-tree state. The git reset --hard
then tried to reset all tracked files back to their somebranch
state, but failed for music.db
at least.
Now, however, we're on commit H
as our current commit. The git switch
command was able to not erase music.db
while switching commits, because the de-duplicated copy of music.db
in commit K
matches the de-duplicated copy of music.db
in commit H
. So, even though that file is "modified" in the working tree—due to the failure of the git reset --hard
—Git was able to keep the modification in place in the working tree.
Having successfully done the git checkout
file-updating, git checkout
now runs a kind of git status --short
, minus some information. The goal of this git status --short
is to show you what's different in your index and/or working tree right now, vs what's in the commit that would have been written to your index, but didn't have to be:
M .gitignore M music.db
These two files differ. That is, git checkout
would have replaced both .gitignore
and music.db
with the copies from commit H
, but it didn't have to do that, so it skipped that. (Where are they different? Is it the index, the working tree, or both? Unlike git status --short
, this doesn't say.)
A static/scripts/downloader.js A test/a.exe A test/ipvalidator.exe A test/nextvalidator.c A test/nextvalidator.exe
These five files differ in that they are in your index and working tree right now but are not in commit H
.
PS C:\Users\Administrator\Desktop\projects\songs> git branch -D "test-branch" Deleted branch test-branch (was 08f1d8e).
This got rid of the name test-branch
, which used to find commit K
. Fortunately:
08f1d8e
. You can use this to find the commit.somebranch
in my drawings above—this same name also points to commit 08f1d8e
, so you can just use that name. (It's probably not somebranch
.)Next, you ran:
PS C:\Users\Administrator\Desktop\projects\songs> git stash apply error: Your local changes to the following files would be overwritten by merge: .gitignore Please commit your changes or stash them before you merge. Aborting
By whatever luck (good or bad), git stash apply
decided that it was unable to apply the stash. It therefore did nothing at all. Note the last line, Aborting
.
You then have a full git status
output (which seems a bit odd without a git status
command, but maybe git stash
ran that—I generally avoid git stash
and it has evolved a few times and I'm not sure which versions have done what, at this point). This gives us a lot of information:
Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: music.db new file: static/scripts/downloader.js new file: test/a.exe new file: test/ipvalidator.exe new file: test/nextvalidator.c new file: test/nextvalidator.exe
This is the list of files that differ between HEAD
(commit H
in my drawing) and Git's index, as if we'd run git diff --staged --name-status
, more or less. We see that music.db
in the index differs from music.db
in commit H
, and that the five files added earlier are still there in Git's index. So these are changes that git checkout main
was able to preserve in Git's index.
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: .gitignore
This is a list of files that differ between Git's index and your working tree, as if we'd run git diff --name-status
. There's just one file, .gitignore
, listed here. So this must be the edit you did to allow the static/
and test/
files to become tracked, which git checkout
was able to leave undisturbed when it made commit H
the current commit. It's not different in Git's index though: the git add
somehow skipped this one. It probably got restored by the git reset --hard
earlier (unlike music.db
where something went wrong). So that implies that the .gitignore
in commit K
matches the .gitignore
here—though some of this is guesswork. (It all depends on exactly what happened during the git reset --hard
failure.)
Untracked files: __pycache__/ flask_session/ improvements.txt run.ps1 static/downloads/ templates/test.html test.py venv/
This lists files that are in the working tree, that aren't in Git's index. I think they would have been added earlier by git add .
so they must not have existed while you were on commit K
. That seems ... odd, and casts some doubt on my analysis above. Again, given the failure of git reset --hard
, I'm just not sure about any of this.
Upvotes: 2
Reputation: 487795
(see also part 1)
The short version of this is:
These are the places to go to get files back.
Your git stash show --stat
shows a diff from the commit I've been labeling K
to the one in w
:
git stash show --stat .gitignore | 4 ++-- app.py | 11 +++++++---- music.db | Bin 69632 -> 86016 bytes static/css/musicPlayer.css | 1 + static/css/styles.css | 4 +++- templates/favorites.html | 2 ++ templates/layout.html | 8 ++++++-- templates/play.html | 27 +++++++++++++++++++--------
You also have, now, in your index, a version of music.db
(probably matching that in the w
commit here), and the A
dded files. You can git commit
this or git stash
again if you like, to make more commits. You still have your existing stash.
You can turn any stash into a branch with git stash branch
. If there is valuable data in the existing stash, that's my general recommendation: once it's a normal everyday branch, you can use all of Git's tools with it.
If there's nothing valuable in Git's index and your working tree right now, you can use git reset --hard HEAD
to reset both of those.
Since there was a problem with music.db
, you should find out why: there's probably a program that had, or still has, it open, preventing you from replacing the file. If you can terminate that program, or convince it to close the file, you can work on / with it again.
So, do each of these things—e.g., make a commit out of what you have now, if necessary and appropriate, fix whatever kept you from working with music.db
, and/or turn the existing stash into a branch with git stash branch
. If there's nothing valuable in the stash, drop the stash.
git stash branch
Before you can use git stash branch
, you need to be in a "clean" state—that is, one where git status
does not talk about changes that are to-be-committed and not-staged-for-commit. (Or, one where your git st
short-status alias does not show any M, A, D, etc., files; ??
untracked files are sometimes OK.)
Then you can run:
git stash branch new-branch-for-stash
What this does is:
i
and w
commits (that's commit K
in my drawings here); andgit stash apply --index
to restore both the index and working tree states.This almost always works (the exceptions include cases like "some program holds music.db
open so that we can't patch it"; note that music.db
is different between commit K
and w
here). It leaves you in a state where you can run git commit
, or git add
and git commit
, or git commit
followed by git add
and git commit
. Choose whichever you would like, to commit the changes you'd stashed, and get back to a "clean" git status
(except, perhaps, for untracked files).
Untracked files are the usual problem here, especially when .gitignore
has changed over time, because some files may actually be in some commits, when they shouldn't really be in those commits, and if you successfully check out such a commit, that file will now be in Git's index. Git will now want to remove the file if and when you switch from that commit to another commit that lacks the file.
The solution is usually the obvious trivial one: rename the untracked files out of the way, or entirely out of the working tree (which is also out of the way). That way, you still have the files and they are still not in the index. By using fresh names for them, they won't be clobbered by any commits where they were accidentally (or on purpose but incorrectly) stored.
For any files that aren't in Git, you'll need to restore them using some outside-Git mechanism.
Upvotes: 2