1

Given a Git repository and a committed file a.

I remove the file with an O/S command: $ rm a

Calling git status returns:

On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    a

no changes added to commit (use "git add" and/or "git commit -a")

Next, I'm calling git rm followed by git status which yields:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        deleted:    a

Git's man pages description of the command git rm:

Remove files from the index, or from the working tree and the index.

From my understanding, what happened in the above command sequence is that git rm put the change of deleting the file into the staging area (which I'm using synonymously with index), instead of removing something from it.

What is my misconception here?

Max Herrmann
  • 305
  • 2
  • 15
  • 2
    Do you also think there's a difference between "subtracting two from ten" and "adding negative two to ten"? Sorry for going all Zen on you :-) – paxdiablo Aug 10 '17 at 11:24
  • 1
    You should know that git status is not a dump of what is staged, it is an interpretation. As an example, if you delete one file and add another, `git status` and various other tools will examine the file contents to see if these two put together is in fact a rename/move. However, even if `git status` says it is a rename, *it has not recorded it as a rename*. I don't know exactly what is being stored on disk when you ask git to remove a file but this knowledge about `git status` is handy to be aware of. – Lasse V. Karlsen Aug 10 '17 at 11:46
  • 1
    You might want to read chapter 7 of my very-slow-progress [book](http://web.torek.net/torek/tmp/book.pdf) :-) (ch 7 is not finished, but does cover adding and removing files in the index) – torek Aug 10 '17 at 15:24
  • @torek Hey, thanks a *Million*! – Max Herrmann Aug 10 '17 at 15:27

4 Answers4

5

What git rm does is add the change to the index to remove a file from the working directory. So you are staging the removal of a file. This may sound a bit weird but this is the clearest way you can think of it.

A commit contains changes, changes you have previously staged. Usual changes include new files and file changes, but also file removals. So the removal of a file is considered a change, and by calling git rm you are adding that change to the index.

This is btw. the reason why you can use things like git add -u to add all pending changes and also have file removals included: The file removal is a pending change, so when you add it, you are adding the change to remove the file.


In addition, what git rm also does it physically remove the file from the working directory. So if you did not delete the file using rm first, Git would have deleted it from the working directory as well. If the file is already removed, then Git will only stage the removal change. Related to this is git rm --cached which will also stage the removal of the file but will not physically remove the file from the working directory. So this will only stage the change to remove the file (although that change hasn’t been physically executed).

poke
  • 369,085
  • 72
  • 557
  • 602
  • 2
    Wow. Are there any textbooks available from you? – Max Herrmann Aug 10 '17 at 12:48
  • Haha, not yet but maybe I should start to compile some of my answer into book form ;D – poke Aug 10 '17 at 12:50
  • 2
    This is *not* the clearest way to think about what `git rm` does, because it incorrectly treats the index as a list of changes. The index is not a delta. Neither is a commit. These are both common misconceptions. – Mark Adelsberger Aug 10 '17 at 14:53
  • @MarkAdelsberger But changes are still how the whole Git interface treats it as. `git status` shows you the *changes*, you add *changes* using `git add`, you add partial changes using `git add -p`, when you look at a commit, you see its diff. The interface is all about *changes*, even if that’s not what is stored internally. The index is a tree, fine, but when you do `git add` you still *apply* a change to it which causes it to change. – poke Aug 10 '17 at 15:26
  • @poke - First of all, no; not the "whole" git interface. For example, you do not `add` changes as you claim; you `add` new versions of files. Only certain commands reinterpret the index as a diff from `HEAD`. Second, regardless of how the interface interprets the index, it is conceptually incorrect (and misleading - see this question as proof) to view the index as a list of changes. – Mark Adelsberger Aug 10 '17 at 15:38
2

The index is not a list of changes. Go clone a repo containing a sizable project. Don't change anything, don't stage anything.

ls -l .git/index

Why is it so big? Isn't the index empty? Well, no. Because the index is not a list of changes.

The index contains a representation of a snapshot of the project. (Or, during a merge, multiple such representations.) A commit also is a snapshot of the project.

Many commands, such as status, interpret one state of the project (in this case the one that's staged) in terms of the difference from another version (in this case, the HEAD commit).

If I create a new repo, checked out at master, create file1 and file2, and commit these, then the new commit contains two files and so does the index.

As the documentation accurately states, git rm file1 would then remove file1 from the index. git status would then compare the index to the existing commit, and seeing that file1 was in the latter but not the former, it will say that "delete file1" is a change staged for commit.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
1

In order to understand the man page of git rm you need to understand how the staging area (i.e. index) and git status work.

You can think of working tree, staging area and commit referenced by head as three folders with the same content in a clean situation (i.e. git status shows no diff):

 Working   Staging   HEAD  
--------- --------- ------ 
 a         a         a     

The question is how git status detect changes?:

  • unstaged changes: diff between Working and Staging
  • staged changes: diff between Staging and HEAD

When you execute rm a, you are removing a only from the working copy, obtainin this:

 Working   Staging   HEAD  
--------- --------- ------ 
 -         a         a   

git status detect one unstaged change because there is a difference between working and staging, and no staged changes since there is no difference between staging and HEAD

When you execute git rm a, as the documentation states, a is removed also from the staging area, resulting in:

 Working   Staging   HEAD  
--------- --------- ------ 
 -         -         a   

Which allows git status to detect one staged change and no unstaged changes.

Following this way of thinking you can understand some other commands suggested by git status itself:

  • git add <file>... - to update what will be committed, i.e. copy file from working to staging
  • git-commit - Record changes to the repository, i.e. copy every file from staging to HEAD (also creates a new commit and moves HEAD)
  • git checkout -- <file>... to discard changes in working directory, i.e. copy from staging to working copy
  • git reset HEAD <file> to unstage, i.e. copy from HEAD to staging

As a side note, git rm --cached a will result in this:

 Working   Staging   HEAD  
--------- --------- ------ 
 a         -         a   

Git status will show the removal of a as staged for commit, since there is a difference between staging and head, but also a as untracked: there is a difference between working and staging, but since the file is missing from the staging area, git status detects it as untracked file instead of unstaged change

Francesco
  • 4,052
  • 2
  • 21
  • 29
0

actually git rm deletes files from your local directory and the changes will be pushed to your repository within the next commit. So the deleted file can't be seen in your repository anymore. While using rm the file will only be deleted from your local repository and the changes won't be written to your origin repo. Therefore, you don't have to use rm before git rm.

S Ktn
  • 1
  • 2