Handling file renames in Git
I'd read that when renaming files in Git, you should commit any changes, perform your rename and then stage your renamed file. Git will recognise the file from the contents, rather than seeing it as a new untracked file, and keep the change history.
However, doing just this tonight I ended up reverting to git mv
.
> $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
I renamed my stylesheet in Finder from iphone.css
to mobile.css
:
> $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: css/iphone.css
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# css/mobile.css
So Git now thinks I've deleted one CSS file, and added a new one. It is not what I want. Let’s undo the rename and let Git do the work.
> $ git reset HEAD .
Unstaged changes after reset:
M css/iphone.css
M index.html
I am back to where I began:
> $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
Let's use git mv
instead:
> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# renamed: css/iphone.css -> css/mobile.css
#
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: index.html
#
It looks like we're good. So why didn't Git recognise the rename the first time around when I used Finder?
For git mv
the manual page says
The index is updated after successful completion, […]
So, at first, you have to update the index on your own
(by using git add mobile.css
). However git status
will still show two different files:
$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
# new file: mobile.css
#
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: iphone.css
#
You can get a different output by running git commit --dry-run -a
, which results in what you
expect:
Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
# renamed: iphone.css -> mobile.css
#
I can't tell you exactly why we see these differences
between git status
and git commit --dry-run -a
, but
here is a hint from Linus:
git really doesn't even care about the whole "rename detection" internally, and any commits you have done with renames are totally independent of the heuristics we then use to show the renames.
A dry-run
uses the real renaming mechanisms, while a
git status
probably doesn't.
You have to add the two modified files to the index before Git will recognize it as a move.
The only difference between mv old new
and git mv old new
is that the git mv
also adds the files to the index.
mv old new
then git add -A
would have worked, too.
Note that you can't just use git add .
, because that doesn't add removals to the index.
See Difference between "git add -A" and "git add ."
For Git 1.7.x the following commands worked for me:
git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.'
There was no need for git add
, since the original file (i.e., css/mobile.css) was already in the committed files previously.
The best thing is to try it for yourself.
mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a
Now git status
and git commit --dry-run -a
shows two different results where git status
shows bbb.txt as a new file/ aaa.txt is deleted, and the --dry-run
commands shows the actual rename.
~/test$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: bbb.txt
#
# 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: aaa.txt
#
/test$ git commit --dry-run -a
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# renamed: aaa.txt -> bbb.txt
#
Now go ahead and do the check-in.
git commit -a -m "Rename"
Now you can see that the file is in fact renamed, and what's shown in git status
is wrong.
Moral of the story: If you're not sure whether your file got renamed, issue a "git commit --dry-run -a". If it's showing that the file is renamed, you're good to go.
Step 1: rename the file from oldfile to newfile
git mv #oldfile #newfile
Step 2: git commit
and add comments
git commit -m "rename oldfile to newfile"
Step 3: push this change to the remote sever
git push origin #localbranch:#remotebranch