Reputation: 39
The definition of git rm --cached
:
Use this option to unstage and remove paths only from the index. Working tree files, whether modified or not, will be left alone.
What exactly are paths?
Upvotes: 3
Views: 1701
Reputation: 489253
A path is just a file name. For instance:
README.md
is a file name, and also a path. The path has no folder-like parts in it.src/main.py
is a file name, and also a path. The path has a folder-like part, src
, and a file-like part, main.py
, and your computer probably demands that Git actually create a folder/directory named src
before Git can create a file named main.py
. To Git this is mostly just a file named src/main.py
, but Git will accommodate your computer's quirk of demanding this weird separation of "files" and "folders", or whatever you like to call them.Note that Git doesn't store folders. Files are just files, in the index / staging-area, even if they have folder-ish parts in their names. Git builds new commits from whatever is in the index at the time you run git commit
,1 so commits cannot store empty folders because the index cannot store a name that ends with a slash.2
The index itself, which is also called the staging area, or (rarely these days) the cache, is a weird but crucial thing Git sticks in between the current commit and your work-tree. Your work-tree is where you have your files, in their normal everyday form, that you can work with—but GIt doesn't use them to make new commits; Git uses the index. So the index can be described, fairly accurately,3 as your proposed next commit. Running git add
copies a file from your work-tree into the index.4 That makes the file ready to commit, unless of course you change it: then you have to copy it into the index again.
A file that exists in your work-tree is yours to do with as you will. Git won't overwrite it except:
git merge
that requires putting Git's best effort at merging into the file, orIf the same path name, i.e., all the folder-y parts plus the final file name, is in the index, that work-tree file is tracked. If not, the work-tree file is untracked.
Since Git makes the next commit from whatever is in the index, each tracked file goes into that next commit (in the form it has in the index, which is why you have to keep git add
-ing it). If the file isn't in the index, the next commit won't have that file.
1If you use git commit -a
or some other forms of git commit
, these effectively run git add
for you just before actually committing. But that's just a short-hand version of git add <those files>; git commit
: Git still builds the commit from the index / staging-area.
2This is not strictly true. Git can put such a path in the index. But parts of Git keep insisting that this is a thing Git calls a gitlink, which is part of how Git keeps track of what Git calls submodules. They therefore don't work right to hold empty folders / directories: these things turn into broken submodules, i.e., gitlinks without the right information to make the rest of it work.
3The index has some additional roles, especially during conflicted merges, that make this not entirely right. But it's a reasonable mental model: you can think of the index as holding copies of all of the files that will go into the next commit. They start out being copies of files from the current commit. See also footnote 4.
4Technically, the index holds a reference to an internal Git blob object, rather than a copy of the file. But this technical difference only really shows up if you start looking inside the index, with git ls-files --stage
for instance, or start using git update-index
to put object hash IDs into it. If you just think of the index as holding a Git-ified copy of each file that will go into the next commit, that mental model works pretty well.
rm --cached
gets trickyIf we're in a normal everyday repository, we might add a totally new file:
$ git checkout -b feature master
<make all-new file>
$ git add newfile.ext
$ git commit
The new commit has a new file, that isn't in the old commit. The new branch feature
has the file in its tip commit, and master
doesn't have the file.
Unsurprisingly, if we now:
$ git checkout master
the file newfile.ext
vanishes from the work-tree.
More generally, suppose you move from a commit that does have the file to one that doesn't. That is, you run git checkout
for a commit (at the tip of some branch) that doesn't have file newfile.ext
in it. But newfile.ext
is there now, in both your work-tree and in the index. So Git has to pull the copy out of the index. It also removes the copy from your work-tree. That's what we just did, switching from develop
(which has a new file) to master
(which doesn't have the file).
Now let's go back to develop
:
$ git checkout develop
The commit at the tip of develop
does have file newfile.ext
. You don't have it in index or work-tree right now, so it's safe for Git to copy newfile.ext
from the tip commit of develop
into the index, and then copy that into your work-tree. And now you do have the file, and life is good.
But: uh oh, we shouldn't have committed newfile.ext
after all. It's a configuration file, or a database of live users, or whatever. We don't want it in feature
. So we remove it:
$ git rm newfile.ext
$ git commit
But, uh oh, we've lost our configuration (or database or whatever it was)! So instead of git rm
, let's not do the above two commands. Instead, let's do:
$ git rm --cached newfile.ext
$ git commit
This time Git removes newfile.ext
from the index, but not from our work-tree. The file is still there in our work-tree. It's gone from the index so it is not in the new commit on feature
.
But now, if we check out the old commit on feature
, e.g., by its hash ID, what happens? Well, that commit does have newfile.ext
in it. Git wants to put that file into the index and copy it into the work-tree.
We'll usually get a complaint, that this git checkout
would overwrite newfile.ext
. Sometimes—it's a little tricky exactly when—Git will overwrite newfile.ext
anyway! And if we force the checkout, Git will overwrite newfile.ext
as we asked it to. And now that file is in both the index, as seen in the commit where we saved it, and in regular (non-Git-ified) form in our work-tree.
Since it's in both index and work-tree, if we now git checkout feature
or git checkout master
—both of which identify commits that don't have the file—Git will now remove the file, from both index and work-tree. Our precious config or database or whatever it is, is gone again!
So, know what git rm --cached
does, but remember that it sets up these traps as well: commits in which the file does exist, and may overwrite something precious when you check them out. You cannot change those existing commits: they will have that file forever. The best you can do is avoid them, or stop using them altogether, if this is a problem.
Upvotes: 2
Reputation: 134521
It means just that, the removed paths are removed from the index, but doesn't touch your working directory. This translates to, the files are removed from your staging area, but you will keep the files in your working directory. If you were to commit now, it will effectively remove those files from your repository.
Since your working directory is untouched, it will be as if you "added" those files again since they do not exist in the repository anymore. If you switched to a different branch or reset back to your head, you'll be returned to the state where the files are deleted and those files will be gone.
This could be useful if you're removing broken files with the intent to fix them. The repository will have them removed but you will still have the files where you can make the necessary changes.
Upvotes: 0