751

I've moved a file manually and then I've modified it. According to Git, it is a new file and a removed file. Is there any way to force Git into treating it as a file move?

Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • 3
    For a given file `old_file.txt`, then `git mv old_file.txt new_file.txt` is equivalent to `git rm --cached old_file.txt`, `mv old_file.txt new_file.txt`, `git add new_file.txt`. – Jarl Sep 04 '12 at 08:18
  • 6
    Jarl: no it's not. If there are also changes within the file, `git mv` will not add them to the cache, but `git add` will. I prefer to move the file back so that I can use `git mv`, then `git add -p` to review my change set. – dhardy Feb 10 '14 at 15:59
  • please checkout my script https://github.com/Deathangel908/python-tricks/blob/master/git_delete_add_to_rename.py – deathangel908 Dec 09 '15 at 09:54
  • 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) – Michael Freidgeim Apr 12 '16 at 07:09
  • 2
    Git has improved in the last 8 years, if it's just one file, the top answer https://stackoverflow.com/a/433142/459 did nothing … but you can follow https://stackoverflow.com/a/1541072/459 to get an rm/add updated to a mv/modify status. – dlamblin Sep 07 '17 at 09:02
  • 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.com/a/50573107/6309)". – VonC Jul 17 '18 at 20:06
  • One gotcha - the original file must no longer exist! A SQL Compare tool I use keeps the original file but makes it size 0 bytes. Then git obviously doesn't sees it as a rename – Reversed Engineer Mar 15 '19 at 14:31

15 Answers15

598

Git will automatically detect the move/rename if your modification is not too severe. Just git add the new file, and git rm the old file. git status will then show whether it has detected the rename.

additionally, for moves around directories, you may need to:

  1. cd to the top of that directory structure.
  2. Run git add -A .
  3. Run git status to verify that the "new file" is now a "renamed" file

If git status still shows "new file" and not "renamed" you need to follow Hank Gay’s advice and do the move and modify in two separate commits.

Community
  • 1
  • 1
Bombe
  • 81,643
  • 20
  • 123
  • 127
  • 132
    Clarification: 'not too severe' means that the new file and old file are >50% 'similar' based on some similarity indexes that git uses. – pjz Oct 19 '10 at 20:10
  • 239
    Something that hung me up for a few minutes: if the renamed files and deleted files are not staged for committing then they will show up as a delete and a new file. Once you add them to the staging index it will recognize it as a rename. – marczych Mar 14 '12 at 01:41
  • 31
    It is worth mentioning that when speaking of "_Git will automatically detect the move/rename_". It does so at the time you use `git status`, `git log` or `git diff`, **not** at the time you do `git add`, `git mv` or `git rm`. Further speaking of detecting rename, that only makes sense for staged files. So a `git mv` followed by a change in the file may look in `git status` as if it considers it as a `rename`, but when you use `git stage` (same as `git add`) on the file, it becomes clear that the change is too large to be detected as a rename. – Jarl Sep 04 '12 at 08:14
  • 1
    But then I can't use `git add -p` (or, my alias, `gap`) to review changes. – dhardy Feb 10 '14 at 15:34
  • 3
    One thing worth mentioning is that if you are deep inside a directory structure that you have moved files into, travel to the top of the tree before doing `git add -A .` to pick up the move. – jrhorn424 Mar 05 '14 at 16:32
  • 5
    The real key seems to be to add both the new and old locations in the same `git add`, which as @jrhorn424 suggests, should probably just be the whole repo at once. – brianary Aug 24 '15 at 18:57
  • 17
    This isn't infallible though, especially if the file changed and is small. Is there a method to specifically tell git about the move? – Rebs Jun 08 '16 at 00:56
  • If this hadn't been explained this clearly I never would've thought Git would just take care of the renaming. Had to rename an entire Android Studio project. Successfully moved the .git repo into the new project and with a bit of work with git add rm and status it worked seamlessly under Git Bash v2.9.3.1 – raddevus Aug 22 '16 at 17:46
  • 3
    Rename in git is a purely cosmetic feature. In its guts it still has one deletion and one addition, therefore you can't really *make* it do anything. If you really need renames to stay (for ease of review for example) commit rename as a separate commit to the content changes. – Yarek T Jan 28 '19 at 16:41
  • 18
    @YarekT Its not purely a cosmetic feature, because if git on commit has detected a rename, then viewing history on that file will give history back across the old filename. Which to me is the point of a rename vs delete+add. (Tested on git 2.20) – arberg Jul 06 '19 at 09:17
  • @Jarl This is not true. `touch lol.txt`, `git commit -m 'Add file'`, `git remove --cached lol.txt`, `mv lol.txt lol2.txt`, `git status`, `git log`, `git diff`, **No rename detected**, `git add lol2.txt`, **Git detects rename**. – Leo Apr 01 '20 at 10:57
  • 1
    What if you move the file on one branch and then another branch modifies the file in its old location? Isn't there a way to tell GIT to redirect the old file path's contents to the new file path upon merging? Even if the mapping had to be done manually, it would be better than nothing. – Matt Arnold Mar 08 '21 at 15:12
  • Yet another indication that Git is overhyped, overengineered garbage that fails at the simplest things. – antred Aug 01 '22 at 14:57
179

Do the move and the modify in separate commits.

Hank Gay
  • 70,339
  • 36
  • 160
  • 222
  • 21
    I understand that Git is can handle moves and modifications at the same time. When programming in Java and using an IDE, renaming a class is both a modification and a move. I understand that Git even should be able to automatically figure it out when there's a move (out of a remove and a creation). – Pablo Fernandez Jan 11 '09 at 16:19
  • 1
    Perl requires this too, and I have never not had Git detect the move/rename. – jrockway Jan 12 '09 at 15:17
  • 11
    @jrockway, I had. Happens easily with small files, I guess they "become 'too different' to mean a move". – ANeves Dec 15 '10 at 16:24
  • Yeah, it's probably a matter of how different they are. I did a refactoring which involved moving 80 files around and changing them at the same time.. Needless to say, git regarded them as deletions/additions and I had to re-do it in 2 steps. 1. Move file, stage, commit 2. Change file, stage, commit – Kostas Konstantinidis Sep 21 '12 at 08:33
  • 2
    Is there any way to do this automated? I have many files in the index, want to first commit the move, and then the change, but it's hard to do manually – ReDetection Feb 12 '16 at 11:52
  • 12
    One thing to note is that if `git diff` doesn't recognize the rename across one commit, it won't recognize it across two commits. You would need use `-M` aka `--find-renames` to do that. So if the motivation that led you to this question is to see the rename in a pull request (speaking from experience), splitting it into two commits will not further you towards that goal. – mattliu Mar 02 '17 at 06:21
  • I don't understand why this is a popular answer. There is a way to do it in one commit, and there is a way of getting it back to the right state if you accidentally made it into a delete and add. One way is to do it again (stash the contents of your file(s) somewhere else first. – dlamblin Sep 07 '17 at 08:57
  • @dlamblin The reason I said to do it this way is because quite often, the rename and the modification are conceptually distinct, so you shouldn't conflate them. – Hank Gay Sep 07 '17 at 14:28
  • Easier said than done when the "modifications" are Git changing all the CRLF line endings. The solution is to create a ".gitattributes" text file in the root directory containing "* -crlf" (without quotes) before doing "git add". You shouldn't commit this file unless you want Git to permanently ignore line endings. – Fax Nov 27 '18 at 11:42
  • But how to do when it's done? Like said @pupeno It doesn't work for some Java class renaming, in particular an interface which is quite empty. In fact you just can't do a rename and a full modification in the same commit (in 2 commits yes, but it may not compile on the intermediate commit) My **tests**: 1. do the refactor, 2. Copy paste the old content of the class (it won't compile but git detect the move) 3. git add and commit the move 4. ctrl-z in the editor to get back the good content 5. git add -u &commit --amend- >it's reverted back to a delete/create – pdem Mar 21 '19 at 08:47
  • 5
    This seems like a low knowledge answer. An advanced version control system such as GIT should have a method of setting an added file as a renamed of a deleted one inside one commit. – cdalxndr Apr 08 '20 at 20:54
  • Yeah, I use a script for this now! https://gist.github.com/IanVaughan/2d3446d000cff42f4d1e8e932468cc1c – Ian Vaughan May 06 '20 at 14:40
  • 6
    @cdalxndr The fact that Git doesn't have a method to mark a rename has nothing to do with the quality of this answer. – KenIchi Jul 14 '20 at 11:32
  • Hi Hank, I'm stuck with a similar problem and was wondering if you might take a look: https://stackoverflow.com/q/63044843/470749 I appreciate any hints. Thanks! – Ryan Jul 23 '20 at 13:34
  • @HankGay Note that stashing renamed files and switching over to another branch results in also the same behaviour – Philippe Hebert Aug 22 '20 at 23:20
  • Git detects rename automatically. Detection is based on the similarity of files. Detection can be adjusted, but it's too much work. As far as I know. This answer is unfortunately most pragmatic way how to move a file and do not lose history. (e.g. in Mercurial you can simply say - this file was moved from this deleted.) – bugs_ Jan 14 '21 at 15:03
  • Git is not working as advertised for me. I am not doing ANY FILE MODIFICATION AT ALL, and Git is failing to recognize ANY of my file moves as "renames". Every single file move is there as two changes, a delete and an add. – East of Nowhere Jul 18 '22 at 12:39
  • It does not work in the case of GitHub PR. If you have several commits, it shows the difference between the branch's current state and the target branch's current state. It does not consider commits being separate or not. (unless we do rename and change in 2 different PRs) – Ardeshir Izadi Sep 19 '22 at 11:02
55

git diff -M or git log -M should automatically detect such changes as a rename with minor changes as long as they indeed are. If your minor changes are not minor, you can reduce the similarity threashold, e.g.

$ git log -M20 -p --stat

to reduce it from the default 50% to 20%.

Simon East
  • 55,742
  • 17
  • 139
  • 133
  • 21
    Is it possible to define threshold in the config? – ReDetection Feb 12 '16 at 11:56
  • 1
    It is the same "interactive window" that git log by itself provides, which allows in-terminal scrolling and exit with `q`. don't worry, it would be difficult to screw something up in a git log view by itself. – theannouncer May 28 '21 at 15:55
46

Here's a quick and dirty solution for one, or a few, renamed and modified files that are uncommitted.

Let's say the file was named foo and now it's named bar:

  1. Rename bar to a temp name:

    mv bar side
    
  2. Checkout foo:

    git checkout HEAD foo
    
  3. Rename foo to bar with Git:

    git mv foo bar
    
  4. Now rename your temporary file back to bar.

    mv side bar
    

This last step is what gets your changed content back into the file.

While this can work, if the moved file is too different in content from the original git will consider it more efficient to decide this is a new object. Let me demonstrate:

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

    new file:   .gitignore
    renamed:    README -> README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

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:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README.md
    modified:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ git mv README README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.
dlamblin
  • 43,965
  • 20
  • 101
  • 140
  • 36
    This process serves no purpose. git doesn't add any metadata for renames, `git mv` is simply a convenience for a `git rm`/`git add` pair. If you've already done 'mv bar foo', then all you have to do is make sure that you've `git add foo` and `git rm bar` before making the commit. This could be as done as a single `git add -A` command, or possibly a `git add foo; git commit -a` sequence. – CB Bailey Oct 09 '09 at 06:48
  • 23
    All I know is that before I did it, Git didn't recognize it as a move. After I did this, Git recognized it as a move. –  Oct 15 '09 at 00:35
  • 10
    It recognizes it as a move. But it also has the change as an unstaged change now. Once you `add` the file again, git breaks the move/modify into a deleted/added again. – Michael Piefel Feb 22 '12 at 08:30
  • 6
    This process would work if you `git commit` after step 3, otherwise it is incorrect. Also, @CharlesBailey is correct, you could as easily do a normal `mv blah foo` at step 3 followed by a commit and get the same results. – DaFlame Feb 07 '14 at 15:36
  • 6
    "This process serves no purpose." - It serves a purpose when git has gotten confused and thinks a file has been deleted, and another file has been added. This clears up Git's confusion and marks the file as moved explicitly without having to do intermediate commits. – Danack Jun 06 '15 at 14:50
  • 1
    I just ran through this, and it did not work. I suspect it would work if I committed after step 3. A "normal" `git mv blah foo` does not work, that's how you get into this in the first case. – Tony Apr 14 '16 at 23:40
  • It works for my single file that was once moved, then un-added by accident, and when it was re-added it became a delete the old, add the new status. Following these steps, it became, once again, rename the old, modify the new status. – dlamblin Sep 07 '17 at 08:58
  • 3
    This process is useless because Git doesn't become "confused". Git decides if a move is a remove/add pair or a move based on how much content in the file has changed. I think what people are finding is that this process is resulting in Git showing a move in the staged changes, but then the content changes are unstaged. As soon as you stage the content changes, the staged move changes to a remove/add pair again. Sorry, but you can't subvert Git's power. Git makes the decision and there's nothing you can do about it. Git is God. – Neo Aug 10 '18 at 11:51
46

It's all a perceptual thing. Git is generally rather good at recognising moves, because GIT is a content tracker

All that really depends is how your "stat" displays it. The only difference here is the -M flag.

git log --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

git log --stat

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

git help log

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.
Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144
Kent Fredric
  • 56,416
  • 14
  • 107
  • 150
  • 107
    Sorry if this seems a bit pedantic, but "Git is generally rather good at recognising moves, because GIT is a content tracker" seems like a non-sequitur to me. It's a content tracker, yes, and perhaps it happens to be good at detecting moves, but the one statement doesn't really follow from the other. Just because it's a content tracker the move detection isn't necessarily good. In fact, a content tracker could have no move detection at all. – Laurence Gonsalves Sep 10 '10 at 23:48
  • Very nice answer. But one question: At least what you see with git `log -M -C --find-copies-harder` might be more compact than what git stores in the commit (for example if you copied or renamed a very big file and git didn't detect this). Is there a way to detect such things at commit time? – Daniel Alder Oct 13 '13 at 14:51
  • If you rename a directory containing massive assets (with no actual change) then `git add` it, Git may sometimes fail at detecting that this is a rename. This has a major impact as this will push a new instance of your blob. Not *that* perceptual. – Warren Seine Feb 27 '14 at 07:41
  • 1
    @WarrenSeine when you rename a directory, the SHA1's of the files in that directory do not change. All you have is a new TREE object with the same SHA1s. There's no reason a directory rename would cause significant data changes. – Kent Fredric Feb 27 '14 at 21:53
  • @KentFredric how to let know "git commit" that you should consider that rename instead of add and delete – Patrick May 06 '16 at 10:16
  • @Patrick can you ask the question differently? It doesn't make sense to me. But 'd say the answer is "you don't" and "it wouldn't be useful anyway" – Kent Fredric May 07 '16 at 11:41
  • 1
    @KentFredric You showed that using -M with "git log" we can check whether file is renamed or not and i tried it and its working fine, but when i post my code for review and see that file in gerrit (https://www.gerritcodereview.com/) there it shows the file is newly added and previous was deleted. So is there an option in "git commit" using which i do commit and gerrit shows it properly. – Patrick May 07 '16 at 11:57
  • 2
    No. I didn't show that at all. I showed Git is capable of *pretending* an add + delete is a rename due to the content being the same. There's no way for it to *know* which happened, and it doesn't care. All git remembers is "added" and "deleted". "Renamed" is never recorded. – Kent Fredric May 07 '16 at 13:05
  • 1
    For example, I did a lot of "Copy + Edit" on a repo lately. Most ways of viewing it only see "new file", but if you pass `git log -M1 -C1 -B1 -D --find-copies-harder`, git can "discover" that the new file might have been copied first. Sometimes it does this right, other times, it finds entirely unrelated files that happened to have identical content. – Kent Fredric May 07 '16 at 13:07
  • This doesn't answer the question. Should be a comment not an answer. – cdalxndr Apr 08 '20 at 20:52
  • @cdalxndr the point is you *cant*, git has no concept of "deletes" or "moves", it works it out after the fact from the difference, using heuristics. – Kent Fredric Jun 15 '20 at 08:36
  • “Git is a content tracker” is a common refrain but it's disingenuous nonsense. Git tracks changes. It remembers that this content was that content before it changed, and here's the timestamp and the person who made the change. Yet somehow remembering that a file was renamed in the process is a bridge too far, so we have to do some detective work to try and recreate what happened. – Luke Maurer Jun 30 '21 at 17:30
34

Or you coud try the answer to this question here by Amber! To quote it again:

First, cancel your staged add for the manually moved file:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

Then, use Git to move the file:

$ git mv path/to/oldfile path/to/newfile

Of course, if you already committed the manual move, you may want to reset to the revision before the move instead, and then simply git mv from there.

isherwood
  • 58,414
  • 16
  • 114
  • 157
Zingam
  • 4,498
  • 6
  • 28
  • 48
  • 9
    Note that this does not work if you also have substantial changes, which happens usually on small files. It gets committed as 'delete' and 'create'. In such case I guess the only option is to _"Do the move and the modify in separate commits"_ as suggested on another answer. – Albert Vila Calvo Nov 12 '20 at 13:44
23

If you're talking about git status not showing the renames, try git commit --dry-run -a instead

BoD
  • 10,838
  • 6
  • 63
  • 59
11

If you're using TortoiseGit it's important to note that Git's automatic rename detection happens during commit but the fact that this is going to happen isn't always displayed by the software beforehand. I had moved two files to a different directory and performed some slight edits. I use TortoiseGit as my commit tool and the Changes made list showed the files being deleted and added, not moved. Running git status from the command line showed a similar situation. However after committing the files, they showed up as being renamed in the log. So the answer to your question is, as long as you haven't done anything too drastic, Git should pick up the rename automatically.

Edit: Apparently if you add the new files and then do a git status from the command line, the rename should show up before committing.

Edit 2: In addition, in TortoiseGit, add the new files in the commit dialog but don't commit them. Then if you go into the Show Log command and look at the working directory, you'll see if Git has detected the rename before committing.

The same question was raised here: https://tortoisegit.org/issue/1389 and has been logged as a bug to fix here: https://tortoisegit.org/issue/1440 It turns out it's a display issue with TortoiseGit's commit dialog and also kind of exists in git status if you haven't added the new files.

MrTux
  • 32,350
  • 30
  • 109
  • 146
Giles Roberts
  • 6,407
  • 6
  • 48
  • 63
  • 2
    You are right, even if TortoiseGit shows delete + add, even if git status shows delete + add even if git commit --dry-run shows delete + add, after git commit I see rename and not delete + add. – Tomas Kubes May 09 '14 at 11:40
  • 1
    I'm pretty sure the automatic rename detection happens during *history retrieval*; in the commit it is always add+delete. That also explains the schizophrenic behavior you describe. Hence the options here: http://stackoverflow.com/a/434078/155892 – Mark Sowul Mar 24 '16 at 18:59
10

The other answers already cover that you can simply git add NEW && git rm OLD in order to make git recognize the move.

However, if you have already modified the file in the working directory, the add+rm approach will add the modifications to the index, which may be undesired in some cases (e.g. in case of substantial modifications, Git might not recognize anymore that it is a file rename).

Let's assume you want to add the rename to the index, but not any modifications. The obvious way to achieve this, is to do a back and forth rename mv NEW OLD && git mv OLD NEW.

But there is also a (slightly more complicated) way to do this directly in the index without renaming the file in the working tree:

info=$(git ls-files -s -- "OLD" | cut -d' ' -f-2 | tr ' ' ,)
git update-index --add --cacheinfo "$info,NEW" &&
  git rm --cached "$old"

This can also be put as an alias in your ~/.gitconfig:

[alias]
    mv-index = "!f() { \
      old=\"$1\"; \
      new=\"$2\"; \
      info=$(git ls-files -s -- \"$old\" | cut -d' ' -f-2 | tr ' ' ,); \
      git update-index --add --cacheinfo \"$info,$new\" && \
      git rm --cached \"$old\"; \
    }; f"
Michael
  • 8,362
  • 6
  • 61
  • 88
coldfix
  • 6,604
  • 3
  • 40
  • 50
  • +1, sadly there is no way to do that directly with git mv, like ex with `--cached` in git rm. In my case I moved an entire directory, so I use a slightly different way: `git ls-files -s -- OLD |sed 's#\tOLD/#\tNEW/#'` followed with `git rm -r --cached OLD`. NB: the `\t` and trailing `/` in sed match the Tab char preceding the path and the directory separator, use the full path from the root of your repo (this ensures you match only that dir and not any similarly named file/directory). – Thomas Guyot-Sionnest May 30 '22 at 04:20
8

Use git mv command to move the files, instead of the OS move commands: https://git-scm.com/docs/git-mv

Please note that git mv command only exists in Git versions 1.8.5 and up. So you may have to update your Git to use this command.

mostafa.elhoushi
  • 3,758
  • 1
  • 17
  • 10
4

I had this problem recently, when moving (but not modifying) some files.

The problem is that Git changed some line endings when I moved the files, and then wasn't able to tell that the files were the same.

Using git mv sorted out the problem, but it only works on single files / directories, and I had a lot of files in the root of the repository to do.

One way of fixing this would be with some bash / batch magic.

Another way is the following

  • Move the files and git commit. This updates the line endings.
  • Move the files back to their original location, now that they have the new line endings, and git commit --amend
  • Move the files again and git commit --amend. There is no change to the line endings this time so Git is happy
cedd
  • 1,741
  • 1
  • 21
  • 34
0

For me it worked to stash save all the changes before the commit and pop them out again. This made git re-analyze the added / deleted files and it correctly marked them as moved.

Alex Ilie
  • 33
  • 5
0

The way I understand this question is "How to make git recognize a deletion of an old file and creation of a new file as a file move".

Yes in the working directory once you delete an old file and inserted an old file, git status will say "deleted: old_file" and "Untracked files: ... new_file"

But in the staging index/level once you add and remove file using git it will be recognized as a file move. To do so, assuming you have done the deletion and creation using your Operation System, give the following commands:

git add new_file
git rm old_file

If the contents of the file are 50% or more similar, running git status command should give you:

renamed: old_file -> new_file
harshainfo
  • 504
  • 7
  • 11
  • 1
    `git status` will say "`deleted: old_file`" and "`Untracked files: ... new_file`": Not since Git 2.18: https://stackoverflow.com/a/50573107/6309 – VonC Jul 17 '18 at 20:07
-1

There is a probably a better “command line” way to do this, and I know this is a hack, but I’ve never been able to find a good solution.

Using TortoiseGIT: If you have a GIT commit where some file move operations are showing up as load of adds/deletes rather than renames, even though the files only have small changes, then do this:

  1. Check in what you have done locally
  2. Check in a mini one-line change in a 2nd commit
  3. Go to GIT log in tortoise git
  4. Select the two commits, right click, and select “merge into one commit”

The new commit will now properly show the file renames… which will help maintain proper file history.

Simon East
  • 55,742
  • 17
  • 139
  • 133
Jon Rea
  • 9,337
  • 4
  • 32
  • 35
  • The result of doing this is the same as moving and modifying the file in the same commit . It simply does not work. – Jolta Jun 28 '23 at 11:19
-3

When I edit, rename, and move a file at the same time, none of these solutions work. The solution is to do it in two commits (edit and rename/move seperate) and then fixup the second commit via git rebase -i to have it in one commit.

Hativ
  • 1,500
  • 1
  • 16
  • 24