views:

626

answers:

6

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
#

Rename 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. Not what I want, lets undo the rename and let git do the work.

> $ git reset HEAD .
Unstaged changes after reset:
M   css/iphone.css
M   index.html

Back to where I began.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Lets 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
#

Looks like we're good. So why didn't git recognise the rename the first time around when I used Finder?

+2  A: 

you have to git add css/mobile.css the new file and git rm css/iphone.css, so git knows about it. then it will show the same output in git status

you can see it clearly in the status output (the new name of the file):

# Untracked files:
#   (use "git add <file>..." to include in what will be committed)

and (the old name):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

i think behind the scenes git mv is nothing more than a wrapper script which does exactly that: delete the file from the index and add it under a different name

knittl
I didn't think I had to `git rm css/iphone.css` because I thought this would remove the existing history. Maybe I'm misunderstanding the workflow in git.
Greg K
@Greg K: `git rm` will not remove history. It only removes an entry from the index so that the next commit will not have the entry. However, it will still exist in the ancestral commits.What you may be confused about is that (for example) `git log -- new` will stop at the point where you committed `git mv old new`. If you want to follow renames, use `git log --follow -- new`.
Chris Johnsen
+2  A: 

EDIT: I added Chris Johnsen's comment to my answer

For git mv the man 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 mechnisms, while a git status probably doesn't.

tanascius
You failed to mention the step where you did `git add mobile.css`. Without it `git status -a` would only have ‘seen’ the removal of the previously tracked `iphone.css` file but would not have touched the new, untracked `mobile.css` file.Also, `git status -a` is invalid with Git 1.7.0 and later. “"git status" is not "git commit --dry-run" anymore.” in http://www.kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt . Use `git commit --dry-run -a` if you want this functionality.As others have said, just update the index and `git status` will just work as the OP expects.
Chris Johnsen
if you do a normal `git commit` it will not commit the renamed file and the working tree is still the same. `git commit -a` defeats pretty much every aspect of git's workflow/thinking model—every change is committed. what if you only want to rename the file, but commit changes to `index.html` in another commit?
knittl
@Chris: yep, of course I added `mobile.css` which I should have mentioned. But that's the point of my answer: the man page says that `the index is updated` when you use `git-mv`. Thanks for the `status -a` clarification, I used git 1.6.4
tanascius
@knittl: I forgot to write that I added mobile.css before running `git status` (but you can tell that I did so by the output). I edited the answer and it should show me point much better, now
tanascius
+1  A: 

You didn't stage the results of your finder move. I believe if you did the move via Finder and then did git add css/mobile.css ; git rm css/iphone.css, git would compute the hash of the new file and only then realize that the hashes of the files match (and thus it's a rename).

MikeSep
+2  A: 

Git will recognise the file from the contents, rather than seeing it as a new untracked file

That's where you went wrong.

It's only after you add the file, that git will recognize it from content.

hasen j
A: 

How about the history for the old file?

Will this be copied to the new file(after rename), can we track the history of the old file to the new file.

Please let me know.

-Jee

jee
A: 

@GregK

"I just moved a bunch of files into a public_html dir, that are tracked in git. Having performed git add . and git commit, it still showed a bunch of 'deleted' files in git status."

Sure. "git add ." tells git to copy any changes in files in the working copy to the index. It doesn't tell it to remove any files deleted from the working copy. You need an explicit "git rm" to do that. There's no "git mirror-working-copy-to-index" command, which seems to be what you were expecting "git add ." to do.

mabraham
Your point about `git add .` not staging removals is right, but “mirror-working-copy-to-index” is available as `git add -A` (or `--all` if you like the long options).
Chris Johnsen