How to Support Case-only Rename in Git

As you may know in IntelliJ IDEA 2016.2 EAP we’ve finally fixed a very annoying issue that didn’t allow to commit case-only renames in IntelliJ IDEA. For a number of reasons, the solution was not so trivial, we decided to share some of its implementation details with you.

When you are on a case-sensitive file system (Linux), case-only renames are just like any other renames, so you’re all good. However, when the file system you’re using is case-insensitive (like Windows or Mac), some of many Git commands will treat changing the file name case as a change, while others won’t. Because of that, a seemingly simple rename of “Myclass.java” to “MyClass.java” makes you face errors you clearly don’t expect. The nature of these errors is in that Git status remains the same, but most of the Git commands will not work with the new name, because the index and the repository still contain the old name.

Turns out that there’s an easy fix for this: just use git mv --force Myclass.java MyClass.java to perform a case-only rename. This will make Git see this rename as a change, and will record it to the index. Of course, we’ve implemented it, and now when you rename a file from IntelliJ IDEA, it will use this command to actually perform it.

However, that is only the tip of an iceberg. I’m not going to mention several places when we relied on system case-sensitivity settings for file paths comparison, which didn’t allow to put two case-only-different paths into a HashSet, and so on. :) Just had to do some debugging for a while and fix all these places.

Things got really interesting when we tried to commit these rename changes, which were already properly recognized by Git. In Git CLI, as well as in most of the clients, you just call git commit and commit everything that is staged. However, in IntelliJ IDEA we call git commit --only -- <paths> which lets commit only selected files, regardless of whether they are staged or not. This allows you, the user, to select paths from the UI and not think about Git indexes. This approach has, of course, a certain downside, but we are working on improvements, and are going, for example, to provide experienced users with a way to manipulate the Git index in the upcoming releases.

The problem here is that Git doesn’t allow the --only syntax for case-only renames, so git commit --only MyClass.java Myclass.java simply fails. We could call git commit without options and commit everything what’s in the index, but what if you’d like to exclude some files from that commit? We had several options on how to deal with these cases, and we decided to stick to the following procedure:

  1. Check what is staged besides case-only renames.
  2. Reset staged changes which are not selected for commit.
  3. Stage what else is needed to be commited.
  4. Commit the staging area.
  5. Stage back the changes unstaged before commit.

If you’re an experienced Git user, you might have noticed that there is a highly improbable case when this procedure will cause an issue: if you have a case-only rename, and you have a file that you don’t want to commit together with this case-only rename, and this file is both partially staged, and partially not-staged, and this separation is important to you (e.g. contains different sorts of changes), then the procedure above will make all changes in the file staged. However, we felt that it is nearly impossible case, and decided not to handle it until we have a GUI for Git staging area and the >git add -p feature.

Please note that as it was described in the beginning of the article, if you rename a file outside of IntelliJ IDEA (via the Terminal or from Finder or Explorer app) without properly calling git mv -f, you’re still doomed. Sorry for that, but that’s because Git won’t see the change at all, and it is not clear how to overcome this problem in IntelliJ IDEA. We are open for suggestions, and would like to implement them if they won’t affect performance or usability in ordinary situations.

Hope you’ll find IntelliJ IDEA helpful enough when you face a case-only rename. If you find any issues with the described approach, please report here or to the tracker.

image description