If I have uncommitted changes in my branch master and I checkout a feature branch and commit them, then checkout master again, then "git status" will not show those changes since they've been committed to a different branch.
How can I do that so that I can go back to the master branch and have the changes still there and still UNCOMMITTED, exactly the way they were before I checked out the feature branch that I committed them to?
CodePudding user response:
git stash
git checkout <featurebranch>
git stash apply
git commit -a -m "saved changes"
git checkout -
git stash pop
CodePudding user response:
There are some different ways to achieve what you want. To most direct way is while you are on master
:
git show feature-branch | git apply
But since the goal is (I assume) to make a commit from the uncommitted changes, you can also just cherry-pick the commit:
git cherry-pick feature-branch
CodePudding user response:
People have given you several recipes (my own would be to use git cherry-pick -n
, which is a lot like j6t's answer but doesn't require a pipe). A more interesting thing is to understand why this works the way it does.
Git stores commits: that is, the stuff in the repository is, mainly, commit objects, with their big ugly hash IDs. These objects exist in a big, read-only, (mostly) append-only database of "all Git objects". Each commit stores two things, namely a full snapshot of every file, plus some metadata. The snapshot is in a special, compressed-and-Git-ified format in which the file contents are all de-duplicated against all existing stored files (including those in the same commit as well as in earlier commits). Git actually achieves this by storing the files as blob objects in the big database.
(Besides the stored objects, Git also has a names database, which maps names—branch names, tag names, remote-tracking names, and the like—to hash IDs. There are numerous additional files / databases, such as reflogs, the file(s) that implement Git's index, and so on. So there's a lot of stuff going on in the hidden .git
directory. But the big all-objects database, and the smaller names database, are the two most important: those are what git clone
gets to see. Cloning copies the objects database, and does funky stuff with the names database rather than directly copying it, and the remaining ancillary files in the new repository are constructed from scratch.)
This leaves us with a seemingly impossible problem, though: the files in any given commit are frozen, Git-ified, and generally quite useless to anything but Git itself. No other program can even read them, and nothing—not even Git itself—can write them. What good are files that can neither be read nor written?
There's an obvious answer. We might as well ask: What good is an archive? It's good for extraction, of course. We extract the files from some archive, and now we have ordinary files, in ordinary folders in the ordinary storage on our (ordinary?) computer. We can now get our (ordinary?) work done.
So that's what Git does. Git provides an area—something we call the working tree or work-tree—where we get our work done. But—and this is the key insight that you need for your question—these files are not in Git at all. Sure, Git extracted them earlier, from some commit. Sure, we've been working on them for a while. Sure, we're going to tell Git to put them into a new commit, using git add
and then git commit
as usual. But while they're ordinary files being ordinary in our ordinary working tree, they're not in Git.
We run git commit
and make a new snapshot-and-metadata, which are now frozen for all time. Or we run:
git checkout feature-branch
or:
git switch feature-branch
Sometimes, when we do this, Git says okay, I did it, you're all set. Sometimes Git says sorry, can't do that, would lose some of your changes. The description of when and why we get these errors is complicated because it gets into the gory details of Git's index and your working tree (see Checkout another branch when there are uncommitted changes on the current branch). But at a sort of basic level, Git can do this if and only if the "switch to other branch" operation doesn't require removing-and-replacing the files we've been changing.
The branch-switching-step logically means: remove, from my work area, every file that came out of the commit I'm using now; replace, into my work area, every file that comes from the commit I'm switching to. But Git commits de-duplicated files. Many of those files will turn out to be exact duplicates. If Git did this remove-and-replace operation the slow and stupid way, removing every file and replacing it, many files would wind up the same in the end. So Git tries to be smart about it: for each file that's a total duplicate, just do nothing. As long as all of our "changes" were to do-nothing files, we're good!
(The actual implementation of this "R&R or do-nothing" for each file involves three copies of each file, namely the ones in the two commits and the one in the current index, which is why it's so messy when we get into the low-level details. But the principle is clear enough.)
So, we wind up "on" the desired branch, because all our changes were in these do-nothing files. Then we add and commit, and then we git switch
back to master
or main
or whatever branch we were on before we switched to the feature branch.
Now, remember how we noted that switching branches means R&R all files, or at least all the ones that are different. And we just committed changes to some files: these are the files we care about. So the files we care about are changed and therefore must be R&R-ed. They will be, and now our changes are "gone".
But here's what else we know. Our switching worked, so the files we just committed are the only things we "changed" as we made the new commit in the other branch. Therefore, the result of git show feature-branch
or git diff feature-branch~1 feature-branch
are exactly those changes we "want back". We can therefore git show feature-branch | git apply
or git cherry-pick -n feature-branch
.
The difference between git show feature-branch | git apply
and git cherry-pick -n
is that git apply
modifies only the working tree copies, while git cherry-pick -n
modifies both the working tree copies and the index copies. So we get git add
"for free", whether we want it or not. That, in turn, can guide you as to which of these two alternatives you'll prefer. But both will work fine.
CodePudding user response:
You can create a diff between your commits on the feature branch and then apply the changes on the master branch. This will cause duplication though, so be careful, you might get merge conflicts later when you chose to merge your feature branch.
# While you're on the feature branch, get the commit-id of HEAD
git log -1
# Then use the COMMIT-ID to create a diff from the previous commit on this feature branch and write this to the diff file.
git diff <COMMIT-ID>^ <COMMIT-ID> > changes.diff
# Now, checkout to the master branch
git checkout master
# Apply the changes, they will not be commited
git apply changes.diff
Just be careful, not to merge the feature branch here now.
CodePudding user response:
Use git stash
, but when you stash the changes to master and switch to the feature branch, use git stash apply
instead of git stash pop
to apply the changes to feature. After you commit everything, you can return to master and the stash will still be available to use git stash pop
to reapply them to your working directory.
git stash
git switch feature
git stash apply
# commit as usual
git switch master
git stash pop