Repository: pypa/pipenv

Issue:
pipenv shell does not work when running Git for Windows' bash on Windows
##### Issue description

Running `pipenv shell` on windows when running bash in Git for Windows does not activate the virtual env successfully. It appears the actual activate script with the correct values is not being used.

##### Expected result

I would successfully be in the virtualenv

##### Actual result

The `VIRTUAL_ENV` and `PATH` variables are in the `C:` notation instead of `/c/` notation which would have worked.

```
declare -x VIRTUAL_ENV="C:\\Users\\user\\.virtualenvs\\project-OJ2s3Ey8"
declare -x PATH="C:/Users/user/.virtualenvs/project-OJ2s3Ey8/Scripts:/c/Users/user/bin:/mingw64/bin:/usr/local/bin:/usr/bin:/usr/bin:/mingw64/bin:/usr/bin:/c/Users/andy/bin:/c/Python37/Scripts:/c/Python37" etc...
```

Sourcing the virtualenv activate variable does work, even though Git for Windows uses `msys` and not `cygwin`. Did not test cygwin

##### Steps to replicate

1. `pipenv install sphinx`
1. `echo $$`
1. `pipenv shell`
1. `echo $$`
1. `pip freeze`
1. `echo $PATH`

Shows that the pip environment is the original python environment, not the virtualenv. The different pid verifies that a new shell is indeed being run, but the virtualenv is not activating

Also tried

1. `PIPENV_EMULATOR=bash pipenv shell`

Identical results

<details><summary>$ pipenv --support</summary>

Pipenv version: `'2018.7.1'`

Pipenv location: `'c:\\python37\\lib\\site-packages\\pipenv'`

Python location: `'c:\\python37\\python.exe'`

Other Python installations in `PATH`:

  - `3.7`: `C:\Python37\python.exe`

  - `3.7.0`: `C:\Python37\python.exe`
  - `3.7.0`: `C:\Windows\py.exe`

PEP 508 Information:

```
{'implementation_name': 'cpython',
 'implementation_version': '3.7.0',
 'os_name': 'nt',
 'platform_machine': 'AMD64',
 'platform_python_implementation': 'CPython',
 'platform_release': '10',
 'platform_system': 'Windows',
 'platform_version': '10.0.17134',
 'python_full_version': '3.7.0',
 'python_version': '3.7',
 'sys_platform': 'win32'}
```


Discussion:
I’m not very familiar with Git Bash (i.e. Msys Bash), so some help would be appreciated. Does this need to be in `/c/` form to work? Or would `C:/...` (forward slash instead of backward) have worked?
Gladly. I tested cygwin too now.

### Cygwin

Has the exact same failure for both `pipenv shell` and `pipenv 

- `PATH` is `D:/CygwinPortable/App/Cygwin/home/user/.virtualenvs/shm-5RC6sJB8/Scripts:/usr/local/bin:/usr/bin:/cygdrive/c/Python37/Scripts:/cygdrive/c/Python37` etc...
- `VIRTUALENV` is `D:\CygwinPortable\App\Cygwin\home\user\.virtualenvs/shm-5RC6sJB8`

This does not work either, even if `PIPENV_EMULATOR=bash`. cygwin will only work if `VIRTUALENV` was /cygdrive/d/CygwinPortable/App/Cygwin/home/user/.virtualenvs/shm-5RC6sJB8`, which is why the `activate` script would have successfully determined.

*Edit*:

Cygwin also gives the following error

```
PIPENV_EMULATOR=bash pipenv shell
Launching subshell in virtual environment▒
: No such file or directory/Cygwin/home/user/.bashrc
```

Where the real windows path for `.bashrc` is

```
cygpath -w ~/.bashrc
D:\CygwinPortable\App\Cygwin\home\user\.bashrc
```

#### Answer

* The `C:/` (or `D:/`) notation in `PATH` will not work in cygwin. It must be `/cygdrive/c/` or `/cygdrive/d/`

### Msys

Msys pretends to be unix. So everything is in `/c/` notation. Only when non-msys programs (like python) are called, msys it translate back to `C:\`. So python for Windows sees `C:\` like it expects.

#### Answer

* The `C:/` (or `D:/`) notation in `PATH` will not work in msys bash. It must be `/c/` or `/d/`

### Additional

I forgot to mention this before, but `pipenv run pip freeze` does work 100% correctly. As does `pipenv run bash`. This is only an issue with `pipenv shell`
For Cygwin—are you using the Cygwin Python, or using CPython’s standard Windows distribution inside Cygwin? Those are totally different, but I’d imagine Pipenv should work in the former case. The latter case is much more difficult, unfortunately (but maybe Python used in this context has the same characteristics as in Msys, and can be addressed similarly).

For Msys, the problem is difficult. Python does not know it is being run inside Msys, so we’ll need to do some poking ourselves. There are a few opportunities…

1. Check whether the Msys DLL is present in the process (most reliable if done right but OMG)
2. Inspect selected environment variables and try to guess this might be Msys
3. Require an explicit `PIPENV_EMULATOR` to work correctly

Maybe a combination like (1 or 2) and 3 would be the way to go. 
Some notes (after poking around with a VM):

* Python for Windows is hopeless in Cygwin, it’s best to ignore it.
* Cygwin Python actually identifies system as POSIX and does the right thing no problem.

So we only need to worry about Msys2 (I don’t want to care about Msys1, but they should be similar). Msys Python works similarly as Cygwin Python, and Pipenv functions correctly out of the box afaict, no problem here.

Python for Windows in Msys is the worst. Python identifies the system as Windows, and even `os.environ['PATH']` is in Windows style. The only thing possible is environment sniffing. There are two options:

* Detect the existence of `MSYSTEM` in environment variables.
* Detect whether there is a command called `cygpath`, and use it in the temporary bashrc if present.

The second option is intriguing, but I think I will go for the first. It is more straightforward and safer.

Finally, I don’t think we need to fix `VIRTUAL_ENV`. The activate file (the Bash one, not the BAT file) uses the Windows notation, so we should as well.

If I’ve learnt any life lessons from this adventure, it’s that both Msys and Cygwin are only work people who know what they are doing. Do not ever teach them to beginners, or they will hit a wall when the abstraction inevitably breaks. If you want Git on Windows, use it in cmd.exe or Powershell. Git Bash is as bad an option as you can get. I’d choose WSL all day every day.

Resolving patch:
diff --git a/pipenv/shells.py b/pipenv/shells.py
--- a/pipenv/shells.py
+++ b/pipenv/shells.py
@@ -134,6 +134,9 @@ def _iter_python(venv):
 
 
 class Bash(Shell):
+    def _format_path(self, python):
+        return python.parent.as_posix()
+
     # The usual PATH injection technique does not work with Bash.
     # https://github.com/berdario/pew/issues/58#issuecomment-102182346
     @contextlib.contextmanager
@@ -146,15 +149,25 @@ def inject_path(self, venv):
                 base_rc_src = 'source "{0}"\n'.format(bashrc_path.as_posix())
                 rcfile.write(base_rc_src)
 
-            export_path = 'export PATH="{0}:$PATH"\n'.format(
-                ":".join(python.parent.as_posix() for python in _iter_python(venv))
-            )
+            export_path = 'export PATH="{0}:$PATH"\n'.format(":".join(
+                self._format_path(python)
+                for python in _iter_python(venv)
+            ))
             rcfile.write(export_path)
             rcfile.flush()
             self.args.extend(["--rcfile", rcfile.name])
             yield
 
 
+class MsysBash(Bash):
+    def _format_path(self, python):
+        s = super(MsysBash, self)._format_path(python)
+        if not python.drive:
+            return s
+        # Convert "C:/something" to "/c/something".
+        return '/{drive}{path}'.format(drive=s[0].lower(), path=s[2:])
+
+
 class CmderEmulatedShell(Shell):
     def fork(self, venv, cwd, args):
         if cwd:
@@ -193,23 +206,37 @@ def fork(self, venv, cwd, args):
 SHELL_LOOKUP = collections.defaultdict(
     lambda: collections.defaultdict(lambda: Shell),
     {
-        "bash": collections.defaultdict(lambda: Bash),
-        "cmd": collections.defaultdict(lambda: Shell, {"cmder": CmderCommandPrompt}),
+        "bash": collections.defaultdict(
+            lambda: Bash, {"msys": MsysBash},
+        ),
+        "cmd": collections.defaultdict(
+            lambda: Shell, {"cmder": CmderCommandPrompt},
+        ),
         "powershell": collections.defaultdict(
-            lambda: Shell, {"cmder": CmderPowershell}
+            lambda: Shell, {"cmder": CmderPowershell},
+        ),
+        "pwsh": collections.defaultdict(
+            lambda: Shell, {"cmder": CmderPowershell},
         ),
-        "pwsh": collections.defaultdict(lambda: Shell, {"cmder": CmderPowershell}),
     },
 )
 
 
 def _detect_emulator():
+    keys = []
     if os.environ.get("CMDER_ROOT"):
-        return "cmder"
-    return ""
+        keys.append("cmder")
+    if os.environ.get("MSYSTEM"):
+        keys.append("msys")
+    return ",".join(keys)
 
 
 def choose_shell():
     emulator = PIPENV_EMULATOR.lower() or _detect_emulator()
     type_, command = detect_info()
-    return SHELL_LOOKUP[type_][emulator](command)
+    shell_types = SHELL_LOOKUP[type_]
+    for key in emulator.split(","):
+        key = key.strip().lower()
+        if key in shell_types:
+            return shell_types[key](command)
+    return shell_types[""](command)


Test patch: