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
git
does not keep a flag saying "thisnewname
file was initially calledoldname
file" :When displaying the status of a file,
git
tries to guess if it was arename
or adelete + 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 amv
.See also the answer to this question : How does Git know that file was renamed?