Max Herrmann
Max Herrmann

Reputation: 325

Unexpected behavior of 'git rm'

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?

Upvotes: 1

Views: 841

Answers (4)

Francesco
Francesco

Reputation: 4250

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

Upvotes: 1

Mark Adelsberger
Mark Adelsberger

Reputation: 45649

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.

Upvotes: 2

poke
poke

Reputation: 387667

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).

Upvotes: 5

S Ktn
S Ktn

Reputation: 1

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.

Upvotes: 0

Related Questions