Rather than immediately closing this as a duplicate of Checkout another branch when there are uncommitted changes on the current branch, let's try a slightly different approach.
... I have made a new branch from an another branch for example branch B from branch A.
That is, you ran:
git checkout branch-a
git checkout -b branch-b
or:
git checkout -b branch-b branch-a
or one of many other possible commands.
Then, I have made a lot of modifications in my code inside the branch B ...
Here's the root of the issue: you have made changes to your work-tree. You have not changed any branch. Branch B is still exactly the same as Branch A. You have changes, but they are not on any branch at all. They are uncommitted changes.
The tricky bit here is that Git has little concern for branch names. What Git cares about are commits.
Each commit has a big ugly hash ID, such as 08da6496b61341ec45eac36afcc8f94242763468
. That hash ID is, in effect, the true name of the commit. All other names are just ways to come up with the hash ID.
The commit itself stores things. Once you make a commit, it stores a snapshot of all of the files you've committed. This snapshot can never be changed—not one bit. The hash ID, which seems random, is actually a cryptographic checksum of everything in the commit, including the saved snapshot (along with more stuff). So if you were to somehow change something, you'd have a new and different commit, with a new and different big ugly hash ID. The existing commit would still be there, with its existing hash ID.
Git really, really cares about these commits. Git will try hard never to lose any of them. Even if you make one by mistake, that includes a password in a file for instance, or a giant file that should never have been committed, it's hard to get rid of it.1 (Try not to accidentally commit such things.)
A branch name, however, is just a name. All it does is hold one hash ID. A name like branch-a
or branch-b
or master
is a way of remembering the big ugly hash ID, since otherwise it's too hard to remember.
When you work with commits in Git, there is one big stumbling block: The files stored in a commit are compressed and frozen. This is a feature: since a file inside a commit can't be changed, a future commit can just re-use the file. So you can make a thousand commits with a big file in them, and if the big file is the same every time, there's really only one copy of the big file.
But it also means that in order to work with the files, Git has to extract them from the commit. That's what your work-tree is all about. (The work-tree has several variants of what to call it, including working directory or working tree; they're all pretty similar here.) It's where you can see and work with your files.
The files in your work-tree are not a commit. Changing them has no effect on any existing commit. So you can change them all you like, and nothing has happened in Git. Things have only happened in your work-tree, which right now is just for you to mess about with however you like.
When you go to make a new commit, there's something else tricky. Git doesn't make new commits from your work-tree! This is kind of peculiar—most other version control systems do make new commits from your work-tree. But Git is different: it makes new commits from something it calls, variously, the index, or the staging area, or—rarely these days—the cache.
The index starts out holding copies of all the files from the commit you selected when you did a git checkout
.2 You have to copy any updated files back into the index, using git add
, to get the updated versions to be used in the next commit.
Hence, this thing with the three names—index, staging area, and cache—can perhaps best be described as your proposed next commit. When you run git commit
, Git will freeze whatever is in the index into the new commit.
But right now, both names, branch-a
and branch-b
, name the same commit. So when you do:
git checkout branch-a
Git checks the actual hash ID and sees that it's the same commit. Nothing has to be done, so Git just updates its special file named HEAD
to put the name branch-a
into it.3 You have not changed commits, but now you are on branch-a
. Run:
git checkout branch-b
and you still have not changed commits, but now HEAD
has branch-b
in it so you're on branch-b
.
Once you git add
your files and then git commit
, you will have a new commit, with a new big ugly hash ID. Git will store the new commit's hash ID into whichever name is in HEAD
, and now Git will really, seriously care about all those files, because now they are part of a commit.
1It's not impossible, just hard.
2Technically, what's in the index is not actually copies of the frozen-format committed files, but rather references to the frozen-format committed files. Since they can't be changed, this is a distinction without a difference, from your own point of view. It makes the index take less space, but has no effect on how you use the index.
3This special file is usually just a file named .git/HEAD
. It should be spelled in all uppercase like this, even if you're on a system where head
in all lowercase works, because every once in a while,4 the lowercase head
refers to the wrong file. Git has inside it some string-compares, and uses the right special file if you spell it HEAD
.
4The once-in-a-while today is when using git worktree add
. Who knows when it might be, in the future, though.