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.

This entry was posted in New Features and tagged , . Bookmark the permalink.

11 Responses to How to Support Case-only Rename in Git

  1. Mike says:

    Is a case-only rename really a problem for IntelliJ to fix? If you are on a case-insensitive filesystem I don’t think anyone should rightfully expect their IDE to handle case-only renames. You can’t do it with command-line tools either.

    If I need a case-only rename (I am on a mac) I change the name to a temp name, commit, then change to the name I really want with fixed casing, then commit. However, even this causes a problem when other people on a case-insensitive filesystem do an update.

    BTW, mac’s can be case sensitive but you have to reformat your drive and select the case sensitive option in Disk Utility.

    • jlnr says:

      HFS+ and NTFS may be case-insensitive, but they are also case-preserving, so I don’t see why I can’t rightfully expect my tools to notice case changes in filenames. It’s not like these changes don’t exist on the filesystem level.

      I think it’s a great feature. I don’t want to think about my filesystem when renaming a class from HttpRequest to HTTPRequest or vice versa.

    • Arjen says:

      If IDEA doesn’t need to support renaming files on a case-insensitive filesystem, it shouldn’t offer that feature… and that also prehibits case-only renames of classes (at least in Java and PHP) since those also trigger an optional file rename.

      In my opinion it should support case-only renames of files, since it also offers any other rename of those files. From a user’s perspective a case-only rename is just like any other rename, which doesn’t introduce invalid characters for the file’s name.

      Its unfortunate such seemingly trivial things can turn out to be that complicated :)

    • Kirill Likhodedov says:

      It is possible to make a case-only rename in Finder => it should be possible to do that in IntelliJ IDEA => the VCS integration should issue a proper command to let the underlying VCS know about the rename (in particular, call git mv -f for Git).
      And if there is a valid Git staging area waiting for commit, the IDE should be able to commit it. That was our logic, and the expectation of our users as well.

      > You can’t do it with command-line tools either.

      But it _is_ possible with command-line tools.

      • Mike says:

        Any plans to support this for Subversion then? Not everyone has turned hipster and decided to use git even when doing centralized development.

  2. sefo says:

    Was this bug also fixed for mercurial ?

    • sefo says:

      Case-only renaming works in Idea, file is renamed but renaming is not tracked by Mercurial. I have to manually run ‘hg rename filename FileName’ instead. I think Idea should run that command automatically during renaming or am I wrong ?

  3. Piotr Kiraga says:

    Great fix. Unfortunately, my Intellij 2017.1.4 still suffers SVN commits failures in this case. Any plans to address this issue too?

Leave a Reply

Your email address will not be published. Required fields are marked *