Repository: pre-commit/pre-commit

Issue:
[Regression] Worktree support
Latest release 2.9.3 broke git worktrees support

### version information

```
pre-commit version: 2.9.3
sys.version:
    3.6.8 (default, Apr 15 2020, 09:32:35)
    [GCC 9.3.0]
sys.executable: /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/bin/python3.6
os.name: posix
sys.platform: linux
```

### error information

```
An error has occurred: FatalError: git toplevel unexpectedly empty! make sure you are not inside the `.git` directory of your repository.
```

```
Traceback (most recent call last):
  File "/home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages/pre_commit/error_handler.py", line 65, in error_handler
    yield
  File "/home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages/pre_commit/main.py", line 330, in main
    _adjust_args_and_chdir(args)
  File "/home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages/pre_commit/main.py", line 147, in _adjust_args_and_chdir
    toplevel = git.get_root()
  File "/home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages/pre_commit/git.py", line 66, in get_root
    'git toplevel unexpectedly empty! make sure you are not '
pre_commit.errors.FatalError: git toplevel unexpectedly empty! make sure you are not inside the `.git` directory of your repository.
```

```
❯ pre-commit run -av bandit-salt
An error has occurred: FatalError: git toplevel unexpectedly empty! make sure you are not inside the `.git` directory of your repository.
Check the log at /home/vampas/.cache/pre-commit/pre-commit.log
```
Version 2.9.2 works fine:
```
❯ pip install pre-commit==2.9.2
Collecting pre-commit==2.9.2
  Using cached pre_commit-2.9.2-py2.py3-none-any.whl (184 kB)
Requirement already satisfied: importlib-metadata in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (3.1.1)
Requirement already satisfied: virtualenv>=20.0.8 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (20.2.2)
Requirement already satisfied: pyyaml>=5.1 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (5.3.1)
Requirement already satisfied: nodeenv>=0.11.1 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (1.5.0)
Requirement already satisfied: cfgv>=2.0.0 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (3.2.0)
Requirement already satisfied: identify>=1.0.0 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (1.5.10)
Requirement already satisfied: toml in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (0.10.2)
Requirement already satisfied: importlib-resources in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (3.3.0)
Requirement already satisfied: zipp>=0.5 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from importlib-metadata->pre-commit==2.9.2) (3.4.0)
Requirement already satisfied: zipp>=0.5 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from importlib-metadata->pre-commit==2.9.2) (3.4.0)
Requirement already satisfied: importlib-resources in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (3.3.0)
Requirement already satisfied: six<2,>=1.9.0 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from virtualenv>=20.0.8->pre-commit==2.9.2) (1.15.0)
Requirement already satisfied: filelock<4,>=3.0.0 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from virtualenv>=20.0.8->pre-commit==2.9.2) (3.0.12)
Requirement already satisfied: distlib<1,>=0.3.1 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from virtualenv>=20.0.8->pre-commit==2.9.2) (0.3.1)
Requirement already satisfied: importlib-metadata in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from pre-commit==2.9.2) (3.1.1)
Requirement already satisfied: appdirs<2,>=1.4.3 in /home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/lib/python3.6/site-packages (from virtualenv>=20.0.8->pre-commit==2.9.2) (1.4.4)
Installing collected packages: pre-commit
  Attempting uninstall: pre-commit
    Found existing installation: pre-commit 2.9.3
    Uninstalling pre-commit-2.9.3:
      Successfully uninstalled pre-commit-2.9.3
Successfully installed pre-commit-2.9.2
WARNING: You are using pip version 20.3.1; however, version 21.0 is available.
You should consider upgrading via the '/home/vampas/.dotfiles/.ext/pyenv/versions/3.6.8/envs/SaltPriv-3.6/bin/python3.6 -m pip install --upgrade pip' command.
~/projects/SaltStack/CVEs/salt/salt-priv/features/bandit (features/bandit|✔) SaltPriv-3.6
❯ pre-commit run -av bandit-salt
Run bandit against Salt..................................................^CInterrupted (^C): KeyboardInterrupt:
Check the log at /home/vampas/.cache/pre-commit/pre-commit.log
```


Discussion:
It works correctly for me, and the testsuite -- what version of `git` are you using and what is your working directory ?

```console
$ git rev-parse --show-cdup

$ git rev-parse --git-common-dir
/tmp/zz/astpretty/.git
$ pwd
/tmp/zz/wat
```
```
❯ git --version
git version 2.30.0
```
Let me run this in a public repo, can't share info on the repo I'm currently working, but basically, I do a bare clone into `/the/path` and then the repo branches go into `/the/path/branches/<branch-name>` and, for this particular case, a feature branch it goes into `/the/path/features/<feature-name>` which is also my cwd when I got the error.
The above all using worktrees.
ah it seems to be a check specifically for "is the worktree inside the git dir" -- I wasn't using a bare clone for the base repository so it didn't hit that

https://github.com/pre-commit/pre-commit/blob/7727f8777aeedafb567da063fc911414b46a6a3b/pre_commit/git.py#L64

Resolving patch:
diff --git a/pre_commit/git.py b/pre_commit/git.py
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -61,7 +61,7 @@ def get_root() -> str:
             'git failed. Is it installed, and are you in a Git repository '
             'directory?',
         )
-    if os.path.commonpath((root, git_dir)) == git_dir:
+    if os.path.samefile(root, git_dir):
         raise FatalError(
             'git toplevel unexpectedly empty! make sure you are not '
             'inside the `.git` directory of your repository.',


Test patch:
diff --git a/tests/git_test.py b/tests/git_test.py
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -3,6 +3,7 @@
 import pytest
 
 from pre_commit import git
+from pre_commit.error_handler import FatalError
 from pre_commit.util import cmd_output
 from testing.util import git_commit
 
@@ -18,6 +19,25 @@ def test_get_root_deeper(in_git_dir):
         assert os.path.normcase(git.get_root()) == expected
 
 
+def test_in_exactly_dot_git(in_git_dir):
+    with in_git_dir.join('.git').as_cwd(), pytest.raises(FatalError):
+        git.get_root()
+
+
+def test_get_root_bare_worktree(tmpdir):
+    src = tmpdir.join('src').ensure_dir()
+    cmd_output('git', 'init', str(src))
+    git_commit(cwd=str(src))
+
+    bare = tmpdir.join('bare.git').ensure_dir()
+    cmd_output('git', 'clone', '--bare', str(src), str(bare))
+
+    cmd_output('git', 'worktree', 'add', 'foo', 'HEAD', cwd=bare)
+
+    with bare.join('foo').as_cwd():
+        assert git.get_root() == os.path.abspath('.')
+
+
 def test_get_staged_files_deleted(in_git_dir):
     in_git_dir.join('test').ensure()
     cmd_output('git', 'add', 'test')
diff --git a/tests/main_test.py b/tests/main_test.py
--- a/tests/main_test.py
+++ b/tests/main_test.py
@@ -35,11 +35,6 @@ def test_adjust_args_and_chdir_not_in_git_dir(in_tmpdir):
         main._adjust_args_and_chdir(_args())
 
 
-def test_adjust_args_and_chdir_in_dot_git_dir(in_git_dir):
-    with in_git_dir.join('.git').as_cwd(), pytest.raises(FatalError):
-        main._adjust_args_and_chdir(_args())
-
-
 def test_adjust_args_and_chdir_noop(in_git_dir):
     args = _args(command='run', files=['f1', 'f2'])
     main._adjust_args_and_chdir(args)