There are no such thing as branch-specific files
Git has:
- commits
- which contain files
- and which are found via branch names
but:
- two or more branch names can specify a single commit, and
- you can add and delete branch names at any time
so since branches don't really exist as a thing—they're completely changeable at all times—there literally can't be any branch-specific files. There are only commit-specific files.
You cannot ignore tracked files
In a comment, you say:
i thought after i add the file to .gitgnore in its current state it will be ignored from this time and forward
This is not the case.
Git does not ever really ignore files, and .gitignore
is the wrong name for what goes into this file. However, a correct name would be something like .git-do-not-complain-if-these-files-are-untracked-and-if-they-are-untracked-and-I-use-an-en-masse-add-command-do-not-make-them-tracked
. While that's what listing files here does, it's a ridiculous name for the file, so the Git guys chose .gitignore
: an inaccurate name, but one that's short and people can actually type in.
How to understand all of this
The trick to making this all make sense is to change your point of view. Those new to Git think that Git stores files, and uses branches to do that. Both of these ideas are wrong! The things Git stores are commits.
Now, commits do contain files. But it's an all-or-nothing deal: you either do have a commit—and thus all of its files—or you don't, and thus you don't have any of its files. The files stored inside each commit—each commit holds a full snapshot of every file; we'll come back to this idea in a moment—is stored in a special, read-only, Git-only, compressed and de-duplicated form. Nothing but Git can even read these files, and nothing—not even Git itself—can write to them. This means that the committed files are entirely useless for getting any work done.
Because committed files are useless (for getting work done that is), Git has to extract the committed files to a work-area. In this work area, the extracted files are expanded out to their normal everyday form. These are the files that you can see and work with. They are ordinary files. The only thing special about them is that Git extracted them from some commit, at some point.
Because they're copies that have literally been copied out of a commit, these files are not, in fact, in Git at all! They're just there for you to use, however you like. They are your files, in other words. Git's files are for Git, and yours are for you: Git just copies some out, sometimes, when you tell it to.
Besides the stored files—stored in this special Git-ified form—each commit stores some metadata, or information about the commit itself. This includes stuff like the author name and email address. We won't go into any detail here, but this metadata is crucial to Git: Git can't work without it.
The point of all of this is to see how the files you see and work with are not Git's files. They aren't in Git, so you can do anything you want with them. This is mostly a good thing—but since they're not in Git, you can also add more files to your work area, that didn't even come out of Git in the first place. These extra, added files are untracked files. There's a bit of mechanism here, and it's important to know about.
Git's index is how Git knows about files
Your working tree, where you have your files, is pretty simple, if you've been using computers and files for a while. It's just like any other set of files that you can work with. You can create and destroy files here all you like. Git just set up some files initially for you, by checking out some commit. Git used a branch name to find that commit and we'll come back to this idea later, but for now, we just note that your initial set of files probably came out of a commit just now. (If not, they came out of a commit some time ago.)
This means there are two copies of each of these files:
- One, a read-only copy, is saved in the current commit, in a safe form that will be accessible forever (or as long as the commit continues to exist).
- The other one is a regular file that lets you do whatever you want.
But, in between the commits—which store read-only, compressed and de-duplicated files in an internal Git-only format—and your working tree, where you have these ordinary files, Git adds a third copy—well, sort of a copy—of each of these files. This third "copy" is stored in what Git calls, variously, the index, or the staging area, or the cache. The three terms all refer to the same thing. What's really in here is the file's name and some other information, including information about a pre-de-duplicated copy. When the file just came out of some commit, that de-duplicated copy is the frozen-for-all-time copy in the repository.
Because that copy is frozen, all commits that use the same version of that file get to share it. So does Git's index. So it doesn't really take any space to speak of. That's the de-duplication in action.
If you change the work-tree copy, you'll need to get Git to update its index copy. This is what the git add
command is about. When you run git add
on a file you've changed, Git:
- compresses the file down to the special format;
- checks to see if it already has that one anywhere, and if so, uses the frozen one, updating Git's index;
- otherwise, has the frozen one ready to go into the next commit, and hence updates its index
—and this means that after git add
, the index is ready to go: you can make a new commit that has the updated file. All the existing files are still there, in Git's index. So what's going on is that the index has, at all times, the next commit in it, ready to go. The next commit is initially the same as the current commit.
If you git add
a file that isn't in the index yet, Git compresses the file down into the frozen format and creates a new entry in Git's index, for the new file. If you git rm
a file, Git removes the file from both its index and your work-tree, and now the next commit will completely lack the file. In this way, the index remains the proposed next commit.
If you remove a file from your work-tree, and then run git add
on that file, Git simply removes the index entry. The frozen file in the commit(s) is untouched by this process. That's the case for the git rm
as well: it only removes the index entry, not the underlying file data. That underlying file data can never be changed at all, and as long as some commit(s) are using it, it can't be removed either. So, again, the index holds the proposed next commit.
What this means is that git commit
merely needs to snapshot the index. Everything in the index is already in the frozen format, ready to go: it just has to be packaged up into a new commit. The set of files that go in this new commit are precisely those files that are in Git's index, at that time.
What you do in between git checkout
and git commit
, in your work-tree, is not really relevant here. But git add
copies from your work-tree to Git's index, and that does matter. So changing work-tree copies of files is useful, as long as you also git add
these changes.
If you don't git add
these changes, a work-tree file that came out of your current commit, and therefore is now "out of date" in the index (by comparison to what's in your work-tree), remains "out of date" there in a new commit, if you make one. That's fine: sometimes that's what you want! It's a bit annoying to have git status
tell you about these changes not staged for commit, but there is nothing wrong with this. However, note that if you run git add .
, the file is already in the index and will be updated in the index. An en-masse operation like git add .
or git add *
or git add -u
will see that the work-tree copy is updated as compared to the index copy, and will update the index copy from the work-tree, as usual.
What this means is that it's very important to know what's in the index. There are no user-oriented Git commands to list out the files in the index, but there is one that's not user-oriented: you can run git ls-files
(use git ls-files --stage
to get more detail; git ls-files
by itself just lists the names of files that are in Git's index; see the documentation for details).
Untracked files are those that are not in the index
Your work-tree is yours. Because of this, you can create files in it that are not yet in Git's index. The git status
command calls these files untracked. So if the commit you checked out has no file named F
, and you create a new file named F
, you now have an untracked file named F
.
You can also use git rm --cached
to remove a file copy from Git's index, without removing it from your work-tree. Suppose Git extracted some commit, and that commit came with a file named F
. This means there are currently three copies of F
: the read-only copy in the current commit, the copy in Git's index ready to go into the next commit, and the copy in your work-tree. If you now run git rm --cached F
, Git removes the index copy of F
. The read-only copy is still in the current commit, but your proposed next commit—the stuff in the index—lacks file F
. A new commit you make now will have no file F
.
Either way, file F
is now in your work-tree, but not in Git's index. That's what makes the file untracked. Because it's not in Git's index, it won't be in the next commit. If there's an F
in the current commit, the difference between the current and next commits is going to include the instruction: "delete file F
". If there's no F
in the current commit, there isn't going to be a difference concerning file F
, because it's not going to be in the current or next commit.
So an untracked file is very simple: it's a file that you have in your work-tree, but that is not in Git's index. But the way the file became untracked is less simple: maybe it's untracked because it wasn't committed, or maybe it's untracked because you removed it from Git's index. You get some control over this, after all.
By contrast, a file that is in Git's index is tracked. Maybe it's there because it was in the commit you checked out. Or, maybe it's