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.