84

I've moved a bunch of files around manually without thinking, and can't find a way to get git to recognize that the files are just moved and not actually different files. Is there a way to do this other than removing old and adding the new (and thus losing the history), or redoing all the changes with git-mv?

Michael
  • 1,380
  • 2
  • 10
  • 14
  • 5
    Git automatically picks up copies and moves of a file. Why do you say it doesn't pick up the moves? – Jeremy Wall Sep 16 '09 at 03:32
  • 1
    Duplicate? http://stackoverflow.com/questions/433111/how-to-make-git-mark-a-deleted-and-a-new-file-as-a-file-move ...some good answers at this other question (helped me at least!) – Drew Noakes Jun 11 '10 at 13:17
  • Edit: I just noted that "git log" works fine on a moved file as long as I call "git commit -a" first. –  Aug 23 '11 at 08:37
  • 2
    @JeremyWall I've found that git often gets moves of a file wrong unless I explicity use `git mv`. – Warren Dew Mar 16 '14 at 22:08
  • Possible duplicate of [Is it possible to move/rename files in git and maintain their history?](http://stackoverflow.com/questions/2314652/is-it-possible-to-move-rename-files-in-git-and-maintain-their-history) – Kristján Dec 09 '15 at 16:12
  • With Git 2.18 (Q2 2018), `git status` should now show you the renames (instead of delete/add files). See "[How to tell Git that it's the same directory, just a different name(https://stackoverflow.comhttps://stackoverflow.com/a/50573107/6309)". – VonC May 28 '18 at 20:37

6 Answers6

79

To have git remove files that are removed or moved already, just enter

git add -u
Hugo
  • 4,004
  • 1
  • 31
  • 33
  • 9
    +1: Thanks, that's exactly what was missing. When `git status` doesn't grok a move or rename, `git add -u` fixes it. – rsenna Dec 27 '11 at 13:03
  • 13
    Can you explain what this is doing exactly? – OlivierBlanvillain Aug 22 '13 at 13:50
  • 5
    @OlivierBlanvillain: 'git add -u' stages all files that git is tracking and have also changed in the working directory. Note that if you moved the file to a directory Git isn't tracking, 'add -u' won't detect the move. If you later add the destination directory to Git and re-do 'add -u', Git will then pick up the move correctly. – JS. Oct 21 '15 at 23:03
  • Note that you should specify filenames if you have other files you don't want to stage. – wjandrea Nov 12 '18 at 02:19
  • https://git-scm.com/docs/git-add#Documentation/git-add.txt--u – Renat Aug 13 '21 at 11:53
23

I think it already does this. Now, I could be wrong, but I've read that git tracks files based on their contents not based on their position in the file system or based on delta/differences. In the stack I think it shows it as if the files are being removed and the then re-added, but I think I've tried this once and it still maintained the history, due to the aforementioned way that git tracks things.

Still would be helpful for someone to verify if I'm correct or not. Sorry if I misunderstood your question.

Jorge Israel Peña
  • 36,800
  • 16
  • 93
  • 123
  • 19
    This is correct. There should be no difference between moving a file (and using "git rm" and "git add" on the old and new files, respectively), and using "git mv". Michael: if the problem is that you are not seeing the pre-move history in "git log FILE", try using "git log --follow FILE". – Phil Sep 16 '09 at 03:36
  • 11
    Didn't work for me. Git saw the file as being deleted and a new file being added. Not sure what I did wrong. Could be a bitbucket issue, because it looked as if Git detected the file as being renamed when I did the commit. – James McMahon May 21 '12 at 17:47
  • 2
    it all depends on how different the file is after moving it, which is dumb. If you rename dummy.class to clean_refactor.clas, you will *most probably* change the contents of the file, by renaming the class name at least. These changes can make git not realize that dummy.class and clean_refactor.class belong to the same history :( – Rafa Jan 30 '13 at 14:31
  • @Rafa renaming a file like that, in my experience, git recognizes that the file is being renamed. In the git internals, the contents are mainly what is stored, the name of the contents isn't as important. If you didn't change the contents, it'll remain the same blob, same hash, but it'll just be named differently in the tree. – Jorge Israel Peña Jan 30 '13 at 23:51
  • 3
    @JorgeIsraelPeña just renaming the file won't be a problem. But often you rename a file during some refactoring, and the changes in the code related to that refactoring will confuse git. – Rafa Jan 31 '13 at 23:32
  • @Rafa oh okay. In that case the file content (blob) is modified (via refactoring) so it generates a different sha hash, so I imagine git would think of that as a completely different file, not the same as merely differing in name. – Jorge Israel Peña Feb 01 '13 at 01:18
  • 1
    @JorgeIsraelPeña yes, I know. Hence my rant :) In svn the history of the file wouldn't get lost, including its refactoring. I would be able to see in the log that the class B used to be named A and was refactored into B. – Rafa Feb 03 '13 at 19:18
  • If you read the documentation on the `git commit` command, it has specific flags for controlling how different a file can be and still be considered the same file. If you don't use `git mv` and then commit the file to the new name/location without content changes as an independent commit, you're relying on these automatic limits to detect the move. Also if you move the file with `git mv` and then make changes, the set of the move and modify may exceed the limits and cause git to treat it as a delete and create in the same commit. – mtalexan Oct 11 '18 at 20:42
12

git doesn't track the history of individual files and it doesn't treat moves and copies specially, that is there is no special metadata that indicates that a move or copy occurred. Instead each git commit is a complete snapshot of the working tree.

If you want to see moves in git log you can supply -M in addition to an option that lists which files have changed, e.g.

git log --summary -M

git will look at the adjacent trees in the commit history and infer if any files where moved by each commit.

To find copies as well as renames you can use the -C option, you can supply it twice to make git look harder for possible copy sources at the expense of some performance.

git log --summary -M -C -C

Note, that as git doesn't store file history (only commit history), even if you did git rm and git mv the file, you wouldn't lose any history. All changes to the path would still be recorded and visible in a git log.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 2
    You can also set `diff.renames` config variable to `true` either in `.git/config` (repository config), or in `~/.gitconfig` (user's config). See git-config manpage for details. – Jakub Narębski Sep 16 '09 at 09:09
5

To better understand why Git does do rename detection instead of (more common) explicit rename tracking, and how git log path limiting works, you can read read Linus's ultimate content tracking tool blog post by Junio C Hamano, maintainer of Git (and references therein).

Jakub Narębski
  • 309,089
  • 65
  • 217
  • 230
3

You can move/rename the new file back to its old name and path outside of git and then use git mv to stage just the move; for example in bash:

mv $NEW $OLD && git mv $OLD $NEW

Its somewhat cumbersome1, especially if you have to do it by hand. But it has the advandage that it leaves other changes such as changing the namespace or the class name unstaged so that you can inspect them and only stage them when you intent to do so.


1 I hope to find a better alternative and will update my answer when I find it.

Example: I moved a bunch of files fom oldDir to newDir and started enthusiastically with some other changes. Now I want to check what other modifications were made. Using and resulted in the following (on a single line):

git status --short |
gawk '/^\?\?/ && match($0, /newDir\/(*.\.cs)/, a) {print "newDir/" a[1] " " "oldDir/" a[1]}' |
xargs -n 2 bash -c 'mv $0 $1; git mv $1 $0'

Now git status shows the renames as "Changes to be committed" and the textual modifications under "Changes not staged for commit"

Kasper van den Berg
  • 8,951
  • 4
  • 48
  • 70
  • I may just have an old git, but If the files have been changed too much, then it will mark $OLD as deleted and $NEW as created. Any edits should be committed before a "git mv". – jbo5112 Nov 13 '19 at 00:49
0

It happens to me, when I move and edit the file, it will no longer recognize it as moving file but a new one, so I lost history.

What I do it is to create 2 separated commits, one when I move the file, and then another editing the file. In this way I keep the history.

Jaider
  • 14,268
  • 5
  • 75
  • 82