Odd `git mv` behaviour
Asked Answered
F

1

1

Last month I started to contribute to a GitHub repository by forking the corresponding repo, creating a feature branch and then submitting a pull request. While repeating that process for a couple of days, I ran into a weird problem when renaming files with the pre-installed Linux command mv and also with the Git command git mv.

The actual problem is, that depending on when you move/rename a file with git mv, when you git add it and at what point you edit the renamed file, you either get:

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

        renamed:    somethingelse -> something

Or this:

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

        new file:   something
        deleted:    somethingelse

To demonstrate this, I have written a test:

#!/bin/bash

# To my knowledge, this “problem” only occurs with new files in a Git repo
printf "COMMAND: mkdir -v gitrepo\n\n"
mkdir -v gitrepo

printf "\nCOMMAND: cd gitrepo\n\n"
cd gitrepo

printf "\nCOMMAND: git init\n\n"
git init

printf "\nCOMMAND: git status\n\n"
git status

printf "\nCOMMAND: touch something\n\n"
touch something

printf "\nCOMMAND: git status\n\n"
git status

printf "\nCOMMAND: git add something\n\n"
git add something

printf "\nCOMMAND: git status\n\n"
git status

printf '\nCOMMAND: git commit -m "Added something"\n\n'
git commit -m "Added something"

printf "\nCOMMAND: git status\n\n"
git status

printf "\nCOMMAND: git mv something somethingelse\n\n"
git mv something somethingelse

printf "\nCOMMAND: git status\n\n"
git status

# Type in the following on line 1: First line of code
printf "\nCOMMAND: vim somethingelse\n\n"
vim somethingelse

printf "\nCOMMAND: git status\n\n"
git status

printf "\nCOMMAND: git add somethingelse\n\n"
git add somethingelse

printf "\nCOMMAND: git status\n\n"
git status

printf '\nCOMMAND: git commit -m "Renamed something to somethingelse and edited somethingelse"\n\n'
git commit -m "Renamed something to somethingelse and edited somethingelse"

printf "\nCOMMAND: git status\n\n"
git status

printf "\nCOMMAND: git mv somethingelse something\n\n"
git mv somethingelse something

printf "\nCOMMAND: git status\n\n"
git status

# If you add something to the first line, the rename will not be detected by Git
# However, if you instead create 2 newlines and fill line 3 with new code,
# the rename gets detected for whatever reason
printf "\nCOMMAND: vim something\n\n"
vim something

printf "\nCOMMAND: git status\n\n"
git status

printf "\nCOMMAND: git add something\n\n"
git add something

printf "\nCOMMAND: git status\n\n"
git status

printf '\nCOMMAND: git commit -m "Renamed somethingelse to something and edited something"\n\n'
git commit -m "Renamed somethingelse to something and edited something"

printf "\nCOMMAND: git status\n\n"
git status

cd .. && rm -fr gitrepo && printf "\nREMOVED gitrepo folder\n"
printf "\nDONE.\n"

For some reason, this mostly affects “new files” and not the ones which already exist in a repository. If you clone my fork of the Spoon-Knife repository with git clone https://github.com/christianheinrichs/Spoon-Knife.git for example and then apply the work flow of the linked test script, you will see that in most cases you will be able to rename the README.md file to README for example, edit it and it will still count as a rename instead of a new file/deleted split.

Although I could reproduce the new file/deleted behavior on the cloned Spoon-Knife fork repo, I am not exactly sure how I did that and believe me when I say that I tried to figure it out.

So what exactly is going on here that I don't understand?

See: https://gist.github.com/christianheinrichs/e50bfdd5eec70a606fa6ce4a88c5951b#file-git_mv-test-sh-L65

Frank answered 15/9, 2017 at 6:53 Comment(0)
U
4

git does not keep a flag saying "this newname file was initially called oldname file" :

git mv oldname newname

# is exactly equivalent to :  

mv oldname newname
git rm oldname
git add newname

When displaying the status of a file, git tries to guess if it was a rename or a delete + add by comparing the content of the files, and seeing how similar they are.

So : if you start by git mv a file, and then edit the file, depending on how much the file is modified, git may or may not be able to see that it all started with a mv.

See also the answer to this question : How does Git know that file was renamed?

Unvarnished answered 15/9, 2017 at 7:1 Comment(6)
Note also that when you run git diff you can enable or disable rename detection, and set the "similarity threshold" number. When git status runs git diff for you, for this case, it sets the similarity threshold to 50%: rename detection is always on, and the threshold is fixed.Bonaparte
@Bonaparte I assume you're talking about git diff -M[<n>], --find-renames[=<n>]? That's a nice feature I didn't know about and might partially explain the issue in my second question.Frank
@Unvarnished Thank you for your answer. However, before I accept it, can you explain the Git behaviour in the second question I attached? Also, why doesn't Git detect the rename if you add something to the first line, but does so if you instead create 2 newlines and fill line 3 with new code?Frank
@legec Nevermind, I moved the second question here and accepted your answer.Frank
@ChristianHeinrichs: yes, I mean the -M option. It does in fact explain the whole thing.Bonaparte
Update to git status comment: as of Git 2.18.0, git status has configurable rename detection.Bonaparte

© 2022 - 2024 — McMap. All rights reserved.