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 on Windows post-receive hook to create bundle
  • Merge two Git repos and keep the history
  • git svn rebase: Incomplete data: Delta source ended unexpectedly
  • Will too many tags cause any issues in Git?
  • git merge “deleted by us”
  • Git: handling an appcache?
  • > $ 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?

  • Can I use git to manage a website even if other people update it without using git?
  • Efficiently backup many versions of a git repo with branch namespacing
  • Can't clone and commit with gitolite
  • Git: Generate patch of all commits made on “feature” branch without referring to commit IDs
  • Difference between git subtree and git filter-banch
  • Create sublime text plugin support both ST2 and ST3
  • 11 Solutions collect form web for “Handling file renames in git”

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

    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 its showing that the file is renamed, you’re good to go.

    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

    Step1: rename the file from oldfile to newfile

    git mv #oldfile #newfile
    

    Step2: git commit and add comments

    git commit -m "rename oldfile to newfile"
    

    Step3: push this change to remote sever

    git push origin #localbranch:#remotebranch
    

    Let’s think about your files from git perspective.

    Keep in mind git doesn’t track any metadata about your files

    Your repository has (among others)

    $ cd repo
    $ ls
    ...
    iphone.css
    ...
    

    and it is under git control:

    $ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
    file is tracked
    

    Test this with:

    $ touch newfile
    $ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
    (no output, it is not tracked)
    $ rm newfile
    

    When you do

    $ mv iphone.css mobile.css
    

    From git perspective,

    • there is no iphone.css (it is deleted -git warns about that-).
    • there is a new file mobile.css.
    • Those files are totally unrelated.

    So, git advises about files it already knows (iphone.css) and new files it detects (mobile.css) but only when files are in index or HEAD git starts to check their contents.

    At this moment, neither “iphone.css deletion” nor mobile.css are on index.

    Add iphone.css deletion to index

    $ git rm iphone.css
    

    git tells you exactly what has happened: (iphone.css is deleted. Nothing more happened)

    then add new file mobile.css

    $ git add mobile.css
    

    This time both deletion and new file are on index. Now git detects context are the same and expose it as a rename. In fact if files are 50% similar it will detect that as a rename, that let you change mobile.css a bit while keeping the operation as a rename.

    See this is reproducible on git diff. Now that your files are on index you must use --cached. Edit mobile.css a bit, add that to index and see the difference between:

    $ git diff --cached 
    

    and

    $ git diff --cached -M
    

    -M is the “detect renames” option for git diff. -M stands for -M50% (50% or more similarity will make git express it as a rename) but you can reduce this to -M20% (20%) if you edit mobile.css a lot.

    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.

    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.

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

    In cases where you really have to rename the files manually, for eg. using a script to batch rename a bunch of files, then using git add -A . worked for me.

    For Xcode users: If your rename your file in Xcode you see the badge icon change to append. If you do a commit using XCode you will actually create a new file and lose the history.

    A workaround is easy but you have to do it before commiting using Xcode:

    1. Do a git Status on your folder. You should see that the staged changes are correct:

    renamed: Project/OldName.h -> Project/NewName.h
    renamed: Project/OldName.m -> Project/NewName.m

    1. do commit -m ‘name change’

    Then go back to XCode and you will see the badge changed from A to M and it is save to commit furtur changes in using xcode now.

    Git Baby is a git and github fan, let's start git clone.