105

One branch (refactoringBranch) had a complete directory restructure. Files were moved chaosly, but the content was preserved.

I tried to merge: git merge --no-ff -Xrename-threshold=15 -Xpatience -Xignore-space-change refactoringBranch

git status shows about half of files renaming recognition. But out of 10000 files in the project half wasn't recognized as moved.

One example would be:

# On branch master
# Changes to be committed:

#   deleted:    404.php
#   new file:   public_html/404.php
    ...
#   deleted:    AnotherFile.php
#   new file:   public_html/AnotherFile.php
    ...
#   renamed:    contracts/css/view.css -> public_html/contracts/css/view.css

Suggestions?


Prehistory

The refactoring was made outside of git. I did the following:

  1. Created the refactoringBranch originating on master.
  2. Dropped the changed structure inside the refactoringBranch, meaning I had my changes in some other dir and just copy-pasted them over my git repository.
  3. Added and committed everything and then tried to merge.

This is was my workflow:

git checkout -b refactoringBranch
cp -R other/place/* ./
git add . -A
git commit -a -m "blabla"
git checkout master
git merge --no-ff -Xrename-threshold=15 -Xpatience -Xignore-space-change refactoringBranch

The problem arise on the git add . -A step probably. Because if rename detection was correct there, I'd assume the merge would go flawless.

Alex
  • 11,479
  • 6
  • 28
  • 50
  • I've checked with an external tool, the similarity between `404.php` in `master` and `public_html/404.php` on `refactoringBranch` appeared to be `95.37%`. – Alex Dec 10 '12 at 17:38
  • 1
    What external tool was that? Have you tested different rename thresholds with something like `git diff -M90% --stat master refactoringBranch` (trying with various values instead of 90%)? – John Bartholomew Dec 10 '12 at 18:05
  • The tool was `php.net/similar_text`. In my merge command I'm using threshold as low as 15 percent. I'd expect it to pass. – Alex Dec 10 '12 at 18:13
  • That workflow suggests that master is also your merge base. Is that correct? – John Bartholomew Dec 10 '12 at 18:17
  • Correct. I'm merging `refactoringBranch` into `master`. Also I've tried to `git diff -M90%` and other values. It pretty much detects that `404.php` has `85` chars difference, out of 4141 in one `master` and `4226` in the other branch. – Alex Dec 10 '12 at 18:19
  • So you have no changes on master? What do you expect the merge to do, then? – John Bartholomew Dec 10 '12 at 18:21
  • 1
    let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/20889/discussion-between-john-bartholomew-and-alex) – John Bartholomew Dec 10 '12 at 18:21
  • git rm --cached old_file_name; (rename file); git add new_file_name also worked. – jungyh0218 Apr 05 '21 at 05:09

10 Answers10

143

OS X is case-aware, but not sensitive. Git ​is​ case-sensitive. If you changed a file name and the only change was a case change, rename the file back to the way it was, then use git mv to rename instead.

andrhamm
  • 3,924
  • 4
  • 33
  • 46
  • 12
    Very simple solution that solved my issue. For those curious about what the "git mv oldfilename newfilename" instruction does here is the link to the doc: https://www.kernel.org/pub/software/scm/git/docs/git-mv.html. Basically it updates the index for both old and new files automatically. Thank you! – iusting Oct 25 '16 at 14:57
  • This fails with "permission denied" if you're changing a folder name to be the same except for different capitalization. However, https://stackoverflow.com/a/14580217/5593532 gets you around that problem (change it to a different intermediate name first). – David Kaufman Jun 30 '17 at 20:13
  • 5
    no need to "rename the file back to the way it was", just `git mv oldcase newcase` works. – Ashish Ranjan Oct 15 '19 at 11:29
  • 1
    @AshishRanjan, what if you can't remember the `oldcase`? A better way to recover the `old case` is to delete the file and restore to its original state with git (`git checkout -- `), then rename (`git mv`) – Dut A. Sep 27 '21 at 07:34
56

Rename detection:

My best guess is that rename detection is failing due to the very large number of candidates. The git source code is a little hard to follow in places, but it does appear that there are some hard-coded limits used in particular search steps of the rename detection algorithm (see diffcore-rename.c), as well as the configurable limit on the maximum number of pairs to look at (configuration keys diff.renameLimit and merge.renameLimit). This may be making detection fail even if you have set the configured limit suitably high. The configurable limit itself is clamped to the range [1, 32767].

Perhaps you can get around this by performing a restructuring step first: move files with git mv without making any content changes, to match the new layout, commit that on a new branch, and then replace it with your final version, which should have only content changes and no renames. Renames with no content changes might be detected more reliably. That's only practical if the restructuring you've done has been fairly simple, and I'm not certain that it will solve the rename detection failures.

Alternatively, perhaps you can split the changes up into separate commits with some simple file groupings, so that there are fewer candidates for rename detection in each commit.

Merging:

Unfortunately, by basing the new branch on top of master, you are giving git incorrect information about the merge. Independent of whether renames are correctly detected or not, when the newly created branch is merged with master it will overwrite everything in master, because from git's point of view, there are no changes in master that haven't already been included in the new branch.

John Bartholomew
  • 6,428
  • 1
  • 30
  • 39
  • similar discussion with configurable settings in git available [here](https://stackoverflow.com/questions/7830728/warning-on-diff-renamelimit-variable-when-doing-git-push) – Shadi Sep 15 '18 at 07:02
56

Here is a perfect way to allow git know you rename a file.

git mv old-file-name.ts new-file-name.ts

Then git will pick up those changes.

Enjoy.

Alex Onozor
  • 6,841
  • 1
  • 22
  • 26
  • 5
    Any way to apply this after renames have already been done? – Slbox Jun 08 '21 at 18:42
  • 2
    what is .ts and why does it work? if you mean just git mv file1 file2 then it DOES NOT work if you do a lot of changes in the files. – klm123 Jul 08 '21 at 19:24
33

Instead of git status, try git commit --dry-run -a, it detects renames better.

BoD
  • 10,838
  • 6
  • 63
  • 59
  • 8
    Why the minus points? This technique worked well in my case, an explanation would be nice. – BoD Jul 17 '14 at 15:01
  • 7
    You haven't explained anything. You use --dry-run which means only "simulated" commit. This technique is already used by op (just without --dry-run). – xZero Jul 03 '18 at 08:04
  • Identical results with git 2.8.0. – Slbox Jun 08 '21 at 18:41
  • Does not work for me. My git version is 2.34.1. I also tried https://stackoverflow.com/a/2641227/4726668 – ZeZNiQ Sep 06 '22 at 16:31
14

You could consider using git mv instead: https://www.kernel.org/pub/software/scm/git/docs/git-mv.html

It has been much more reliable in my experience.

sungiant
  • 3,152
  • 5
  • 32
  • 49
9

I just faced a similar issue, in my case the renaming was changed by another commit and an attempt to re-change it to another one failed on my Git for Windows, until I realised that there's a corresponding git config for it. In my system, git config listed one configuration as core.ignorecase=true.

Disabling it by setting it back to false using git config core.ignorecase false worked out for me. It's worth a quick check and try to see if this is causing the case detection failure in git. This has been tried on git version 2.33.0.windows.2.

Harshit Gupta
  • 187
  • 1
  • 11
2

Whenever I had to rename/move files and forgot to tell GIT explicitly about it I used

git add . -A

which auto-detects files that were moved around

Sergey Lukin
  • 471
  • 2
  • 17
2

The easiest way is to simply set

git config core.ignorecase false

and then redo the add and commit.

This is especially useful if you have already changed a ton of file names and don't want to do a git mv manually.

You can always overwrite back to true if for some reason you don't want to respect capitalization by default.

picklepick
  • 1,370
  • 1
  • 11
  • 24
0

In my case Git did not detect the case change in my file name. I was trying to change nuget.config to NuGet.config I worked around this by changing to anther file name, committing and then changing again to the name I wanted.

Kirsten
  • 15,730
  • 41
  • 179
  • 318
0

Another solution, if you don't mind it being split up into multiple commits, is to move the renamed files back to their original name, commit their changes, and then move them to their new names and commit that, making note in the commit message why you did that.

This is easier, of course, if you only have a few renamed files that weren't picked up correctly.

Hutch Moore
  • 124
  • 1
  • 12