I'm attempting to create a new branch while using some of the uncommitted changes, is it possible to only import some of the changes to the new branch?
-
1The phrase "existing changes" is meaningless. There isn't really such a thing as a "change" in git. There are commits. Every commit is essentially a snapshot of _all the files_ at that moment. And "existing" is hard to understand, because _all_ the commits, and all the versions of all the files in all of them, "exist". Given that, can you express your question in a way that makes sense? – matt Aug 24 '20 at 22:41
-
Does this answer your question? [How to git-cherry-pick only changes to certain files?](https://stackoverflow.com/questions/5717026/how-to-git-cherry-pick-only-changes-to-certain-files) – jmargolisvt Aug 24 '20 at 22:42
-
1`@matt` I'm asking about uncommitted changes, and I've modified my question – Rafi Henig Aug 24 '20 at 22:43
-
Ok but you’re still doing it. A branch does not “import” anything. If you have just some stuff you want to commit on your branch, then git add just that stuff. What you git add is what you will commit. You can git add one file, or even just part of one file. – matt Aug 25 '20 at 13:11
-
`@matt` as far as I understand uncommitted changes are reside in the working-tree, isn't a new working-tree being created when switching between branches? – Rafi Henig Aug 25 '20 at 15:55
2 Answers
Matt's point above is that there aren't really any "uncommitted changes" at all. While git status
reports "changes staged for commit" and "changes not staged for commit", this report is itself a sort of lie that is meant to make Git more usable. (Whether it really achieves this is another question entirely.)
What this means in the end is that the solution is trivial: just create the new branch, add whatever you like, then run git commit
. (Do not use the -a
option to git commit
.)
Long: why this is the answer
The crucial part of understanding all of this is that:
- Git is all about commits.
- Commits contain snapshots, not differences.
- Git makes a new commit from a proposed snapshot, which sits in what Git calls, variously, the index, the staging area, and (rarely these days) the cache. These three names are all for a single thing.
- The files you can see and work with, in your working tree, are separate from the copies1 in Git's index.
What this last point means is that, when you're working with a commit, there are actually three copies of each file "active":
One copy is strictly read-only, being stored inside the current commit. You can have Git read it out, but nothing—not even Git itself—can change it.
A second copy (see footnote 1) resides in Git's index.
The copy that you see and work with, in your working tree, isn't actually used by Git itself (but see below). Git will, on
git checkout
, copy a committed file to both Git's index and your working tree. If you've changed your working tree copy, a newgit commit
won't use the updated file. Instead, it will commit the copy that's in Git's index.
This is why you always have to git add
files before committing.2 The git add
step tells Git: Copy my work-tree copy of this file into your index copy. As part of this copying process, Git actually prepares the to-be-committed version of the file (which is in a special, Git-ized, read-only format, and pre-de-duplicated so that all commits that use the same contents, simply re-use the prepared file).
In other words, what's in the index, at any point in time, is the set of files that will be in the next commit you will make. This leads directly to how git status
tells you what it tells you:
First,
git status
prints things likeon branch master
, and other useful stuff, that we'll ignore here.Next, for each file in the current commit, Git compares that file to the one in the index. If the two are the same, Git says nothing at all. If they're different, Git says that the file is staged for commit. If the file is missing in the index, Git says it's been deleted, and if the file in the index isn't in the commit, Git says it's a new file. But either way this is "staged for commit" because the index contains the proposed next commit. Some of what's in the index matches what's in the current commit, so Git just doesn't mention it.
Third, for each file in the index, Git compares that file to the one in your working tree. If the two are the same, Git says nothing at all. If they're different, Git says that the file is not staged for commit. If the file is missing, Git says it's been deleted.
Last, there's a special case for files that are in your working tree, but are not in the index. These are your untracked files. Git lists them separately. (If they're listed in a
.gitignore
, Git suppresses this complaint about the files being untracked.)
So whenever Git talks about changes, whether staged or unstaged, what it really means is that the various copies of files are different. The copies in commits literally can't be changed: they're frozen for all time, or at least, for as long as the commit itself exists. The copies in the index are in the frozen and de-duplicated format but can be changed, by overwriting them; that's what git add
does. The copies in your working tree are yours to deal with however you like. Except for git add
copying those into Git's index, and git checkout
replacing your working tree files with those from some commit, the working tree is yours to play with as you see fit.
1Technically, the index contains a reference to the de-duplicated internal Git blob object, rather than a copy of a file. Commits also use these blob objects, internally—so that's why the index "copy" of a file is ready to commit.
2When you use git commit -a
, Git essentially does a git add
of all your files for you. The word essentially in here papers over the way that Git does this, which is to use an extra, temporary index, in case the commit itself fails. This extra index allows Git to "undo" the add for that particular case. Things get even more complicated if you use git commit --only
: at this point Git makes two temporary index files. We'll ignore this case here.
Note that the git add
from git commit -a
is roughly equivalent to git add -u
: it only operates on files that Git already has in its index. If you have a totally new file, that does not exist in Git's index yet, you must still git add
that one manually.
What to know about branch names
I mentioned above that commits contain snapshots, which as we just saw, are built from the copies of your files that are in Git's index at the time you run git commit
. But there is more to know about each commit. Each one is numbered, with a random-looking (but not actually random at all) hash ID. Git uses these numbers internally to read the actual commit data from its big database of "all internal hashed objects".
Hence, to find a commit, Git needs its hash ID. But each commit itself has in it the hash ID of its immediate predecessor commit, which Git calls the parent of the commit. This means that if we have a long chain of commits, we can draw it like this:
... <-F <-G <-H
where each uppercase letter stands in for a big ugly hash ID. The last commit in the chain here is commit H
, and inside commit H
, Git has a full snapshot of every file, plus some information about the commit itself: who made it and when, for instance, and the hash ID of the immediately-previous commit G
.
By reading commit H
, then, Git can get the ID of earlier commit G
. Comparing the snapshot in G
to the snapshot in H
is how Git shows you what changed between those two commits. Equally importantly, this lets Git go back in time, because commit G
contains the hash ID of earlier commit F
. F
in turn contains the hash ID of an even-earlier commit, and so on.
The tricky part here is that Git has to find commit H
somehow. This is where branch names come in. A branch name simply holds the hash ID of the last commit in the chain. So we end up with:
...--F--G--H <-- master
The name master
lets Git find commit H
easily. That commit lets Git find every earlier commit. That's how history works, in Git: it's all backwards. We just start from the end, then work backwards as needed.
To create a new branch name, you must pick some existing commit. Typically you'll start with the current commit, which is the last commit on the current branch. The git branch
or git checkout -b
command will create the new branch name so that it selects the same commit:
...--F--G--H <-- master, newbranch
Now we need one more thing, which is a way to tell which of these two names we are using—the one git status
will print when it says on branch B
, for some B
. So we'll attach the special name HEAD
to exactly one branch name. If we are on branch master
, we have:
...--F--G--H <-- master (HEAD), newbranch
git checkout
has another trick
Note that both names select the same commit! When we git checkout newbranch
we get:
...--F--G--H <-- master, newbranch (HEAD)
but we are still using commit H
. That remains the current commit, and since we aren't changing commits, git checkout
doesn't touch either Git's index or our working tree. That means we can create a new branch and switch to it, as with git checkout -b
, without messing with any other state.
Now that we have this new branch and are using it, now we can use git add
(or even git add -p
) to selectively update particular files in Git's index. When we run git commit
, Git will package up its index and make a new commit I
:
...--F--G--H <-- master
\
I <-- newbranch (HEAD)
As soon as Git makes this new commit (from whatever is in Git's index at that time), Git writes the new commit's hash ID into the current branch, i.e., newbranch
, since HEAD
is attached to newbranch
. So now the name identifies the latest commit on the branch.
(It's now usually possible to switch back to master
if we like. The key here is that by writing unstaged files into Git's index to make them staged, then writing the index into a commit, all three of those files match. It's only files that won't need to be replaced where the index copy and the work-tree copy differ. For a lot more detail on this, see Checkout another branch when there are uncommitted changes on the current branch.)

- 448,244
- 59
- 642
- 775
-
`@torek` Thank you for your amazing detailed post! yet may I ask: aren't uncommitted changes reside in the working-tree, doesn't it mean if I switch between branches I loose those chances since new working-tree is being created for the new branch? – Rafi Henig Aug 25 '20 at 15:37
-
1@RafiHenig: the key here is that when "switching" from one branch to another, Git actually looks instead at the *commit hash ID*, not the branch name. If you're on branch B1 with hash H, and "switch" to branch B2 *also* with hash H, nothing has to change, and so nothing does change. Creating a new branch, using the current hash, then switching to that, guarantees that nothing has to change, so nothing does change. – torek Aug 25 '20 at 16:49
-
This sort of thing is why I like to say that branch *names* don't matter in Git. Of course they do matter in terms of finding commit hash IDs, but in the end, it's the hash ID that matters. – torek Aug 25 '20 at 16:50
-
Aside from that, there's one other helpful thing in Git (although `git checkout` messes with it by having too many modes and options: everything is clearer in Git 2.23 if you start using the new `git switch`). In particular, before switching from commit a12345 (or whatever) to commit 9876f (or whatever), checkout/switch will make sure that, if it's going to replace file *F* in your work-tree or index, file *F* is saved in the commit. If not, you'll get a complaint that it's not safe to switch to the other commit. See "checkout another branch with uncommitted changes" posting for details. – torek Aug 25 '20 at 16:53
-
Some of these commands are a bit opaque (and two of them are destructive). If you aren't sure what they do then its likely good to read this part before just trying them.
git add some_file some_directory
git add is able to just add by files or directories (in fact the common git add .
is just adding the current directory as the directory).
git commit -m "temp"
Create a temporary commit that we will throw away.
git add .
Add everything else so that we may stash it cleanly.
git stash
Store the current working state in a buffer (we are going to bring it back later).
git checkout -b some-branch
Bring the current working tree (including the "temp" commit) to your new branch that you want.
git checkout -
The '-' is a shortcut for going back to the previous branch that you were on.
git rev-parse HEAD^^ | pbcopy
This is a bunch all at once. . . and is likely best to just try them individually.
git rev-parse HEAD^ will return the most recent commit sha of the current branch.
Adding a second ^ like HEAD^^ will then references the second one. . . etc.
Piping to pbcopy (the system clipboard on MacOS) is just for convenience. You could omit this part and just highlight the output from your terminal as well.
git reset --hard <some_sha>
:warning: Destructive action :warning:
This will bring the current working state back to the desired commit (removing everything else).
In this instance, we have everything else stored in a stash so its OK.
git stash pop
Bring back the stuff that we had stored in our stash.
Here's one way (albeit VERY manual) on Mac:
git add <file(s)>
git commit -m "temp"
git add .
git stash
git checkout -b new-branch
git checkout -
git rev-parse HEAD^^ | pbcopy
git reset --hard <paste from clipboard>
git stash pop
Final state that this leaves you in:
You now have a branch "new-branch" with your code changes committed under a "temp" commit.
And you have your main branch without the changes that are in the new branch.
Mostly I'll use a flow kind of like this if I decide that the work that I'm doing on a branch isn't the direction that I want to go for the feature I'm developing. . . But maybe I don't want to completely delete the code (just in case I want it later).

- 21
- 1
- 4
-
1Pop is dangerous, it removes the stash. He only wants to Apply parts only... – Christoph Aug 25 '20 at 06:07
-
@Christoph would you mind talking more about what you mean? In my mind the final `git stash pop` is just cleaning up the temporary stash that we created in the third line. – j7hoenix Aug 25 '20 at 12:17
-
Exacly, cleaning or as the docs say ...Remove a single stashed state from the stash list... thus everything stashed is lost. – Christoph Aug 25 '20 at 14:29
-
Well its not lost, its just back where it was before we began the procedure. I didn't know about `apply` though. That's neat. Thanks. – j7hoenix Aug 25 '20 at 14:59
-
-
Nice call out! I tried it and was indeed missing a step. Edited. Thanks @Christoph – j7hoenix Aug 25 '20 at 16:03
-