Repository: pre-commit/pre-commit
Issue: Pre-commit fails for git >=2.25 if repo is on a Windows subst drive

Cross reference for another issue with same apparent root cause: https://github.com/microsoft/vscode/issues/100274#issuecomment-646499795

Issue observed with pre-commit==2.7.1 and git 2.27.
Issue resolved with downgrading git to 2.21 (I only have access to certain versions on my work machine).

Steps to recreate for pre-commit (some taken from the above cross-reference):

- Install git >= 2.25 on Windows

- Create a subst drive (`mkdir C:\\subst_dir && subst Z: C:\\subst_dir`)

- Create a git repo in there (`mkdir Z:\\repo && cd /d Z:\\repo && git init`)

- Add some python code, configure pre-commit, and run pre-commit.

Failure observed: `An unexpected error has occurred: ValueError: path is on mount 'Z:', start on mount 'C:'`

Diagnosis - it appears that the use of `git rev-parse --show-toplevel` in `pre_commit.main.get_root()` is suffering the same issue as seen in cross-referenced ticket; git will "see through" the subst command and rather than return a path on the subst-defined Z: drive, it will return the path from the C: drive. With this, after `pre_commit.main._adjust_args_and_chdir()` calls `pre_commit.main.get_root()` and does a chdir to the returned location, the following call to `os.path.relpath(args.config)` then fails with the ValueError as above, because it sees the path to the config file being on `Z:` but the current location being on `C:`.

Afraid I don't have a suggested resolution but wanted to flag this up. I'm not too familiar with Windows systems and I'm a long way from Admin access on my work machine so opportunities for testing are limited; this was discovered as my scratch space for repos is a subst drive.


Changed files:
pre_commit/git.py

diff --git a/pre_commit/git.py b/pre_commit/git.py
index 156e53d..5096274 100644
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -47,21 +47,26 @@ def no_git_env(
 
 
 def get_root() -> str:
+    # Git 2.25 introduced a change to "rev-parse --show-toplevel" that exposed
+    # underlying volumes for Windows drives mapped with SUBST.  We use
+    # "rev-parse --show-cdup" to get the appropriate path, but must perform
+    # an extra check to see if we are in the .git directory.
     try:
-        root = cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()
+        root = os.path.realpath(
+            cmd_output('git', 'rev-parse', '--show-cdup')[1].strip(),
+        )
+        git_dir = os.path.realpath(get_git_dir())
     except CalledProcessError:
         raise FatalError(
             'git failed. Is it installed, and are you in a Git repository '
             'directory?',
         )
-    else:
-        if root == '':  # pragma: no cover (old git)
-            raise FatalError(
-                'git toplevel unexpectedly empty! make sure you are not '
-                'inside the `.git` directory of your repository.',
-            )
-        else:
-            return root
+    if os.path.commonpath((root, git_dir)) == git_dir:
+        raise FatalError(
+            'git toplevel unexpectedly empty! make sure you are not '
+            'inside the `.git` directory of your repository.',
+        )
+    return root
 
 
 def get_git_dir(git_root: str = '.') -> str: