There was no local .git repository on Windows in a "..Downloads/Training" folder, using per-installed git bash, I first typed
git init
touch .gitignore
in acccordance with the youtube tutorial (see link listed below) copied .gitignore file contents from previously created by visual studio repository, also added .gitignore by
git add .gitignore
, also added the only subfolder M01 in "..Downloads/Training" by typing
git add .
, commited the changes by
git commit -am "First commit"
, added the remote by typing
git remote add origin https:name_of_remote.com/my_repository_folder
, created a branch
git branch M01
, switched to the branch by
git switch M01
, also pushed the repo by typing
git push origin HEAD:M01
.
And the repository has been pushed successfully into that remote but now there is a problem:
I need to store the contents of each folder inside "..Downloads/Training" in a separate branch on the remote.
So if I create a new local folder M02 and a branch by typing git branch M02
, switching to it by git switch M02
, It shows me all of the contents that I have previously added into the M01 branch in the M02 branch, but If I remove the files from M02 by typing git rm . -r
(it deletes local files), it also deletes the files from both M01 branch and M02 branch.
Is there a way to store only M01 local folder in a M01 branch and the M02 local folder in a M02 branch?
Additional source tutorial links: (https://www.youtube.com/watch?v=g4BJXfmAevA)
You have learned some things that are wrong. Note: I have not watched the particular youtube tutorial you linked, so I'm not sure if it is good, bad, or indifferent. I base this paragraph solely on what you wrote in your question.
First, let's address what a Git repository is and is not:
A Git repository is a collection of commits. These commits are to be found using branch names, tag names, and other names, but it's not a collection of branches. It's a collection of commits. A loose analogy might be a collection of insects, where you label the insects: that makes the collection contain labels, but it's not a collection of labels. That is, the labels aren't the purpose of the collection.
The actual repository proper is stored in the .git
directory (or folder, if you prefer that term). The existence of this folder, with specific files and sub-folders that Git will need, is what tells Git that this is a repository. If the .git
directory itself is absent, or is missing these crucial files and/or sub-folders, Git will say that this is not a Git repository.
A bare repository starts and ends with the .git
folder (which is then often named repo.git
or some such, rather than just .git
). Technically a bare repository still has an index / staging-area, but that's just because Git's implementation of the index / staging-area is mainly a file in the .git
directory, named index
. (This file does not have to exist: Git will create it if needed.)
A non-bare repository, which is the kind you would normally work with, also has a working tree. The working tree is where you see and work with your files. These files are ordinary, everyday files on your computer, stored in folders the way your computer likes to store files. It's important to understand that these working tree files are not in the Git repository.
The repository—the .git
folder—stores commits and other internal Git objects. These take the form of an object database, in which Git looks up objects by their hash IDs: large, random-looking numbers, expressed in hexadecimal. All of these objects are in fact read-only: once stored in the database, no object can ever be changed. This means no commit or file can ever be changed.
The repository also stores a separate database of names: branch names, tag names, remote-tracking names, and other names. Each of these stored names holds exactly one hash ID. For branch names, the stored hash ID is always that of a commit. (Tag names are allowed to store other internal-Git-object hash IDs, that you don't normally interact with as directly.) This serves as a good (and fast) way to find particular commits.
Finding, extracting, using, and creating new commits is much of what we do with a Git repository. Since the repository is so commit-centric—and what we do with the repository is definitely commit-centric—it's important to know what a commit is and does, and how Git extracts one:
Each commit stores a full snapshot of all of your files. The files inside the commits are not in the ordinary computer file form.1 Because they are stored as Git objects, they're all read-only. Not even Git can change them. And, because the commit itself is a Git object, it has one of those big ugly hash IDs, so that Git can look it up in the database of objects.
Each commit also stores some metadata: information about the commit itself, such as who made it and when. In this metadata, Git stores the hash ID of a previous commit. More precisely, most commits store the hash ID of one previous commit, but there are some exceptions to this rule.
Because each commit is read-only, but you normally need to read and write your files, you'll tell Git to extract some commit. When you do that, Git will copy the files out of the commit. To do so, Git will read out the internal-only, read-only data from the internally stored files (with their internally-stored names) and turn that into the ordinary file-in-folder setup that your computer uses. Those files will go into your working tree.
In other words, when you run git checkout
or git switch
, Git performs an optimized (and safety-checked) variant of the following two-step sequence:
This is why each commit stores every file: because switching from one commit to another will remove all the files from the commit you're leaving behind, and extract, instead, all the files from the commit you're moving to. The commits themselves are completely read-only, so no commit changes in this process, and no files are lost. Only your working tree is emptied and re-filled.
Note that this talks about switching from one commit to another. Switching from one branch to another doesn't necessarily switch commits. To understand this correctly, we should draw a picture of the commits.
1They are stored in a special, read-only, Git-only form, where they are compressed and—important for Git's internal operations—de-duplicated. This de-duplication step allows every commit to store every file, without taking any extra space. In fact, many different files—plus other internal Git objects—may be stored inside a single computer file (named with some big ugly hash ID followed by .pack
), although sometimes file contents are stored as what Git calls loose objects. Either way, though, these files do not have ordinary file names, either.
Each commit has some big, ugly, random-looking hash ID. Rather than using these, let's use single uppercase letters to stand in for the hash IDs. And, most commits contain the hash ID of some previous commit. Rather than using that, let's draw an arrow, pointing from the later commit to the previous commit. We'll start with some commit whose hash ID is H
:
<-H
The commit H
points backwards to, we'll call commit G
:
<-G <-H
G
of course points to another, still-earlier commit, so we have to keep going:
... <-F <-G <-H
Eventually we'll have a chain of commits that points all the way back to the very first commit ever. Let's call that A
here, and draw in all the commits—but I'll get a bit lazy now and use lines to connect them, even though the arrows really only point backwards. Git can't go in and adjust the earlier commit to point forwards, because the earlier commit is frozen for all time, once it's made. So we have:
A--B--C--D--E--F--G--H
Here, commit H
is the last commit in the chain. From H
we—or Git—can work our way all the way back to the first commit in the chain, commit A
; there, everything stops, because there are no earlier commits.
To find commit H
quickly, we give it a branch name, like main
or master
.2 To represent this, let's put in the name, with an arrow coming out of it, pointing to commit H
:
...--G--H <-- main
If you now create a new name, such as M01
, that name selects the same commit, by default. We now have two names for commit H
:
...--G--H <-- main, M01
Your own Git software, working in your own repository—"your Git" for short—can only be on one of these two branches. To represent which one you're on, let's attach the special name HEAD
to one of these two branch names:
...--G--H <-- main (HEAD), M01
If we now run:
git switch M01
Git will move HEAD
over to the name M01
:
...--G--H <-- main, M01 (HEAD)
We're still using commit H
, because both names select the same commit. The set of files in your working tree will remain exactly the same, because we didn't change commits.
Now suppose we remove a bunch of files—perhaps an entire folder-full of files—from the working tree, and create some new files, and run git add .
to add the removing-and-creating and then run git commit
. When we make this new commit, Git will save all the files that are in Git's staging area at that time. The git add .
updated the staging area to match our working tree.3 This will make a new commit, which will get some random-looking hash ID, but we'll just call it "commit I
". Let's draw it in:
...--G--H
\
I
Commit I
has, as its parent, earlier commit H
. That's because we were using commit H
to make commit I
; we were, and still are, on branch M01
as git status
will say here, and just a moment ago—before we ran git commit
—the name M01
pointed to commit H
. But now that commit I
exists, the git commit
command wrote I
's hash ID into the current branch name, so what we have is this:
...--G--H <-- main
\
I <-- M01 (HEAD)
Commit I
contains the files we told Git it should have; branch name M01
selects commit I
; and HEAD
tells us we're on branch M01
. If we now run:
git switch main
Git will remove the files that go with commit I
and extract the files that go with commit H
. Our working tree will now match commit H
and we will have:
...--G--H <-- main (HEAD)
\
I <-- M01
as our commit picture.
2Your own Git probably defaults to master
while GitHub now default to main
, which creates some issues later.
3I've skipped over some important details about .gitignore
files, tracked files, untracked files, and how the index works here, so as to concentrate on the commits-and-branches.
So if I create a new local folder M02 and a branch by typing
git branch M02
, switching to it by git switch M02, It shows me all of the contents that I have previously added into the M01 branch in the M02 branch ...
The new branch and the old branch currently share the same final commit.
What you see when you look at your working tree are just your working tree files. They're not the same as the committed files, although if git status
says that everything matches, they do match the committed files. The committed files do not—cannot—change, ever.
Note that Git stores only files. The files in the commit at the end of your M01
branch may have names like M01/somefile
. That's the file's actual name: M01/somefile
. It's not a folder named M01
containing a file named somefile
. Your working tree has this setup—folders with files in them—and in your working tree, the slashes might even go the other way, M01\somefile
. In the commit, though, it's just a file named M01/somefile
. This particular point is usually not important, but it means Git is literally incapable of storing an empty folder (because an empty folder contains no files, and Git can only store files).4
but If a remove the files from M02 by typing
git rm . -r
(it deletes local files), it also deletes the files from both M01 branch and M02 branch.
The git rm -r .
operation clears out Git's index / staging-area and your working tree. The existing commits do not—and cannot—change. A future commit that you make right now will contain no files (because you removed all the files); as you put some files back and git add
them, the future commit will contain those files.
Git's index / staging-area thus acts as your proposed future commit. You create a file in your working tree and run git add
to copy it into Git's staging area. You git checkout
or git switch
to some existing commit to tell Git: remove all the current committed files and swap in the files from the other commit. This is completely safe if you've committed everything; it's not safe if you have uncommitted work, and git checkout
or git switch
will normally detect these not-safe cases and avoid clobbering the uncommitted work.5
4There are some tricks with Git submodules that can be used here, but let's not get into those.
5Note that git checkout
has a "dangerous" mode of operation that will clobber unsaved work. The new git switch
command implements the safe part of git checkout
, while the new git restore
command implements the unsafe part of git checkout
. So it's probably better to learn the new git switch
and git restore
, so that you always know if you're running a safety-checking command.
For various reasons, both "safe" commands will sometimes let you switch branches even with unsaved work. When they will let you switch, but you forgot to save (add-and/or-commit) first, you can just switch back. This gets quite complicated when one goes into full detail. See Checkout another branch when there are uncommitted changes on the current branch if you really want to know.
ElpieKay's answer shows you how to create more than one root commit. A root commit is a commit like our commit A
above: the very first one in a new, empty repository, that has no parent commit. Obviously that first commit can't have a previous commit, so Git has to be able to create a root commit. Using git checkout --orphan
or git switch --orphan
is another way to create a root commit.
There is a small but critical difference between checkout and switch here:
git checkout --orphan newbranch
leaves Git's index / staging-area full of files (and does not touch your working tree at all), but:
git switch --orphan newbranch
empties out Git's index / staging-area (and as a result, removes any corresponding tracked files from your working tree). If you intend to remove all tracked files, using git switch --orphan
saves you this step.