What's the purpose of git-mv?

Viewed 160607

From what I understand, Git doesn't really need to track file rename/move/copy operations, so what's the real purpose of git mv? The man page isn't specially descriptive...

Is it obsolete? Is it an internal command, not meant to be used by regular users?

8 Answers
git mv oldname newname

is just shorthand for:

mv oldname newname
git add newname
git rm oldname

i.e. it updates the index for both old and new paths automatically.

There's a niche case where git mv remains very useful: when you want to change the casing of a file name on a case-insensitive file system. Both APFS (mac) and NTFS (windows) are, by default, case-insensitive (but case-preserving).

greg.kindel mentions this in a comment on CB Bailey's answer.

Suppose you are working on a mac and have a file Mytest.txt managed by git. You want to change the file name to MyTest.txt.

You could try:

$ mv Mytest.txt MyTest.txt
overwrite MyTest.txt? (y/n [n]) y
$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

Oh dear. Git doesn't acknowledge there's been any change to the file.

You could work around this with by renaming the file completely then renaming it back:

$ mv Mytest.txt temp.txt
$ git rm Mytest.txt
rm 'Mytest.txt'
$ mv temp.txt MyTest.txt
$ git add MyTest.txt 
$ git status
On branch master
Your branch is up to date with 'origin/master'.

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

    renamed:    Mytest.txt -> MyTest.txt

Hurray!

Or you could save yourself all that bother by using git mv:

$ git mv Mytest.txt MyTest.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.

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

    renamed:    Mytest.txt -> MyTest.txt

git mv moves the file, updating the index to record the replaced file path, as well as updating any affected git submodules. Unlike a manual move, it also detects case-only renames that would not otherwise be detected as a change by git.

It is similar (though not identical) in behavior to moving the file externally to git, removing the old path from the index using git rm, and adding the new one to the index using git add.

Answer Motivation

This question has a lot of great partial answers. This answer is an attempt to combine them into a single cohesive answer. Additionally, one thing not called out by any of the other answers is the fact that the man page actually does mostly answer the question, but it's perhaps less obvious than it could be.

Detailed Explanation

Three different effects are called out in the man page:

  1. The file, directory, or symlink is moved in the filesystem:

    git-mv - Move or rename a file, a directory, or a symlink

  2. The index is updated, adding the new path and removing the previous one:

    The index is updated after successful completion, but the change must still be committed.

  3. Moved submodules are updated to work at the new location:

    Moving a submodule using a gitfile (which means they were cloned with a Git version 1.7.8 or newer) will update the gitfile and core.worktree setting to make the submodule work in the new location. It also will attempt to update the submodule.<name>.path setting in the gitmodules(5) file and stage that file (unless -n is used).

As mentioned in this answer, git mv is very similar to moving the file, adding the new path to the index, and removing the previous path from the index:

mv oldname newname
git add newname
git rm oldname

However, as this answer points out, git mv is not strictly identical to this in behavior. Moving the file via git mv adds the new path to the index, but not any modified content in the file. Using the three individual commands, on the other hand, adds the entire file to the index, including any modified content. This could be relevant when using a workflow which patches the index, rather than adding all changes in the file.

Additionally, as mentioned in this answer and this comment, git mv has the added benefit of handling case-only renames on file systems that are case-insensitive but case-preserving, as is often the case in current macOS and Windows file systems. For example, in such systems, git would not detect that the file name has changed after moving a file via mv Mytest.txt MyTest.txt, whereas using git mv Mytest.txt MyTest.txt would successfully update its name.

Maybe git mv has changed since these answers were posted, so I will update briefly. In my view, git mv is not accurately described as short hand for:

 # not accurate: #
 mv oldname newname
 git add newname
 git rm oldname

I use git mv frequently for two reasons that have not been described in previous answers:

  1. Moving large directory structures, where I have mixed content of both tracked and untracked files. Both tracked and untracked files will move, and retain their tracking/untracking status

  2. Moving files and directories that are large, I have always assumed that git mv will reduce the size of the repository DB history size. This is because moving/renaming a file is indexation/reference delta. I have not verified this assumption, but it seems logical.

Related