Repository: pypa/pipenv

Issue:
Local file with name of package causes strange Pipfile contents
When there is a file in the local directory that matches the name of a PyPI package, `pipenv install` of that package name does install the packages in to the virtualenv, but then writes confusing content in to Pipfile (presumably what it would do with a local repo).

##### Describe your environment

1. OS Type:

Ubuntu 17.10

1. Python version: `$ python -V`

Python 3.6.3

1. Pipenv version: `$ pipenv --version`

pipenv, version 9.0.1

##### Expected result

The package from PyPI installed and appropriately added to Pipfile.

##### Actual result

The Pipfile ends up with content pointing to the local directory.

##### Steps to replicate

In an empty directory:

```sh
touch influxdb
pipenv install influxdb
cat Pipfile
```

When I run this, I get:

```
% touch influxdb
% pipenv install influxdb
Creating a virtualenv for this project…
⠋Using base prefix '/usr'
New python executable in /home/daniel/.local/share/virtualenvs/reproduce-FQ7MTL5K/bin/python3
Also creating executable in /home/daniel/.local/share/virtualenvs/reproduce-FQ7MTL5K/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /home/daniel/.local/share/virtualenvs/reproduce-FQ7MTL5K
Creating a Pipfile for this project…
Installing influxdb…
Collecting influxdb
  Using cached influxdb-5.0.0-py2.py3-none-any.whl
Collecting requests>=2.17.0 (from influxdb)
  Using cached requests-2.18.4-py2.py3-none-any.whl
Collecting python-dateutil>=2.6.0 (from influxdb)
  Using cached python_dateutil-2.6.1-py2.py3-none-any.whl
Collecting six>=1.10.0 (from influxdb)
  Using cached six-1.11.0-py2.py3-none-any.whl
Collecting pytz (from influxdb)
  Using cached pytz-2017.3-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests>=2.17.0->influxdb)
  Using cached certifi-2017.11.5-py2.py3-none-any.whl
Collecting idna<2.7,>=2.5 (from requests>=2.17.0->influxdb)
  Using cached idna-2.6-py2.py3-none-any.whl
Collecting urllib3<1.23,>=1.21.1 (from requests>=2.17.0->influxdb)
  Using cached urllib3-1.22-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests>=2.17.0->influxdb)
  Using cached chardet-3.0.4-py2.py3-none-any.whl
Installing collected packages: certifi, idna, urllib3, chardet, requests, six, python-dateutil, pytz, influxdb
Successfully installed certifi-2017.11.5 chardet-3.0.4 idna-2.6 influxdb-5.0.0 python-dateutil-2.6.1 pytz-2017.3 requests-2.18.4 six-1.11.0 urllib3-1.22

Adding influxdb to Pipfile's [packages]…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (baaf70)!
% cat Pipfile
[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]

"6a95e85" = {path = "./influxdb"}


[dev-packages]
```
Traceback on dependency resolution error with editable git 
When I have an editable git repository and another requirement that causes a dependency resolution failure in my Pipfile, I get a `FileNotFoundError: [Errno 2] No such file or directory: '"/usr/bin/python3"': '"/usr/bin/python3"'` error.

My reproducer uses https://github.com/OddBloke/sphinx-git and python-novaclient==2.26.0 (which always causes a dependency resolution error, as mentioned in #1232).

(As far as I can tell, the git repository and the other requirement don't need to be at all related to end up with this traceback.)

##### Describe your environment

1. OS Type: Ubuntu 17.10
1. Python version: Python 3.6.3
1. Pipenv version: pipenv, version 9.0.1

##### Expected result

Dependency resolution error output, with no traceback.

##### Actual result

A traceback:

```pytb
Traceback (most recent call last):
  File "/home/daniel/.local/bin/pipenv", line 11, in <module>
    sys.exit(cli())
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/cli.py", line 1865, in install
    do_init(dev=dev, allow_global=system, ignore_pipfile=ignore_pipfile, system=system, skip_lock=skip_lock, verbose=verbose, concurrent=concurrent, deploy=deploy, pre=pre)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/cli.py", line 1311, in do_init
    do_lock(system=system, pre=pre)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/cli.py", line 1102, in do_lock
    pre=pre
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/utils.py", line 557, in resolve_deps
    resolved_tree = actually_resolve_reps(deps, index_lookup, markers_lookup, project, sources, verbose, clear, pre)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/utils.py", line 507, in actually_resolve_reps
    resolved_tree.update(resolver.resolve(max_rounds=PIPENV_MAX_ROUNDS))
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/resolver.py", line 102, in resolve
    has_changed, best_matches = self._resolve_one_round()
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/resolver.py", line 200, in _resolve_one_round
    for dep in self._iter_dependencies(best_match):
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/resolver.py", line 274, in _iter_dependencies
    for dependency in self.repository.get_dependencies(ireq):
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/repositories/pypi.py", line 153, in get_dependencies
    result = reqset._prepare_file(self.finder, ireq)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/req/req_set.py", line 523, in _prepare_file
    abstract_dist.prep_for_dist()
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/req/req_set.py", line 134, in prep_for_dist
    self.req_to_install.run_egg_info()
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/req/req_install.py", line 438, in run_egg_info
    command_desc='python setup.py egg_info')
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/utils/__init__.py", line 667, in call_subprocess
    cwd=cwd, env=env)
  File "/usr/lib/python3.6/subprocess.py", line 709, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.6/subprocess.py", line 1344, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: '"/usr/bin/python3"': '"/usr/bin/python3"'
```

##### Steps to replicate

1. Put this in a Pipfile:
```
[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]

sphinx-git = {git = "https://github.com/OddBloke/sphinx-git.git", editable = true}
python-novaclient = "~=2.26.0"

[dev-packages]
```
2. Run `pipenv install`.

```
$ pipenv install --verbose
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Warning: Your dependencies could not be resolved. You likely have a mismatch in your sub-dependencies.
  You can use $ pipenv install --skip-lock to bypass this mechanism, then run $ pipenv graph to inspect the situation.
Could not find a version that matches pbr!=2.1.0,<2.0,>=0.11,>=2.0.0
Tried: 0.5.2.5.g5b3e942, 0.5.0, 0.5.1, 0.5.2, 0.5.4, 0.5.5, 0.5.6, 0.5.7, 0.5.8, 0.5.10, 0.5.11, 0.5.12, 0.5.13, 0.5.14, 0.5.15, 0.5.16, 0.5.17, 0.5.18, 0.5.19, 0.5.20, 0.5.21, 0.5.22, 0.5.23, 0.6, 0.7.0, 0.8.0, 0.8.1, 0.8.2, 0.9.0, 0.9.0, 0.10.0, 0.10.0, 0.10.1, 0.10.1, 0.10.2, 0.10.2, 0.10.3, 0.10.3, 0.10.4, 0.10.4, 0.10.5, 0.10.5, 0.10.6, 0.10.6, 0.10.7, 0.10.7, 0.10.8, 0.10.8, 0.11.0, 0.11.0, 0.11.1, 0.11.1, 1.0.0, 1.0.0, 1.0.1, 1.0.1, 1.1.0, 1.1.0, 1.1.1, 1.1.1, 1.2.0, 1.2.0, 1.3.0, 1.3.0, 1.4.0, 1.4.0, 1.5.0, 1.5.0, 1.6.0, 1.6.0, 1.7.0, 1.7.0, 1.8.0, 1.8.0, 1.8.1, 1.8.1, 1.9.0, 1.9.0, 1.9.1, 1.9.1, 1.10.0, 1.10.0, 2.0.0, 2.0.0, 2.1.0, 2.1.0, 3.0.0, 3.0.0, 3.0.1, 3.0.1, 3.1.0, 3.1.0, 3.1.1, 3.1.1
CRITICAL:pip.utils:Error [Errno 2] No such file or directory: '"/usr/bin/python3"': '"/usr/bin/python3"' while executing command python setup.py egg_info
Traceback (most recent call last):
  File "/home/daniel/.local/bin/pipenv", line 11, in <module>
    sys.exit(cli())
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/cli.py", line 1865, in install
    do_init(dev=dev, allow_global=system, ignore_pipfile=ignore_pipfile, system=system, skip_lock=skip_lock, verbose=verbose, concurrent=concurrent, deploy=deploy, pre=pre)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/cli.py", line 1311, in do_init
    do_lock(system=system, pre=pre)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/cli.py", line 1102, in do_lock
    pre=pre
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/utils.py", line 557, in resolve_deps
    resolved_tree = actually_resolve_reps(deps, index_lookup, markers_lookup, project, sources, verbose, clear, pre)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/utils.py", line 507, in actually_resolve_reps
    resolved_tree.update(resolver.resolve(max_rounds=PIPENV_MAX_ROUNDS))
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/resolver.py", line 102, in resolve
    has_changed, best_matches = self._resolve_one_round()
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/resolver.py", line 200, in _resolve_one_round
    for dep in self._iter_dependencies(best_match):
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/resolver.py", line 274, in _iter_dependencies
    for dependency in self.repository.get_dependencies(ireq):
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/piptools/repositories/pypi.py", line 153, in get_dependencies
    result = reqset._prepare_file(self.finder, ireq)
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/req/req_set.py", line 523, in _prepare_file
    abstract_dist.prep_for_dist()
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/req/req_set.py", line 134, in prep_for_dist
    self.req_to_install.run_egg_info()
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/req/req_install.py", line 438, in run_egg_info
    command_desc='python setup.py egg_info')
  File "/home/daniel/.local/lib/python3.6/site-packages/pipenv/patched/pip/utils/__init__.py", line 667, in call_subprocess
    cwd=cwd, env=env)
  File "/usr/lib/python3.6/subprocess.py", line 709, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.6/subprocess.py", line 1344, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: '"/usr/bin/python3"': '"/usr/bin/python3"'
```


Discussion:
Thanks for the report, @OddBloke. This was supposed to be fixed in 9.0.0 but it looks like we may have missed something.


Resolving patch:
diff --git a/pipenv/utils.py b/pipenv/utils.py
--- a/pipenv/utils.py
+++ b/pipenv/utils.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import errno
 import os
 import hashlib
 import tempfile
@@ -33,7 +34,9 @@
 from piptools.scripts.compile import get_pip_command
 from piptools import logging
 from piptools.exceptions import NoCandidateFound
+from pip.download import is_archive_file
 from pip.exceptions import DistributionNotFound
+from pip.index import Link
 from requests.exceptions import HTTPError, ConnectionError
 
 from .pep508checker import lookup
@@ -282,6 +285,15 @@ def get_requirement(dep):
     :returns: :class:`requirements.Requirement` object
     """
     path = None
+    uri = None
+    cleaned_uri = None
+    editable = False
+    dep_link = None
+    # check for editable dep / vcs dep
+    if dep.startswith('-e '):
+        editable = True
+        # Use the user supplied path as the written dependency
+        dep = dep.split(' ', 1)[1]
     # Split out markers if they are present - similar to how pip does it
     # See pip.req.req_install.InstallRequirement.from_line
     if not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
@@ -297,23 +309,44 @@ def get_requirement(dep):
         markers = None
     # Strip extras from the requirement so we can make a properly parseable req
     dep, extras = pip.req.req_install._strip_extras(dep)
-    # Only operate on local, existing, non-URI formatted paths
-    if (is_file(dep) and isinstance(dep, six.string_types) and
-            not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST)):
+    # Only operate on local, existing, non-URI formatted paths which are installable
+    if is_installable_file(dep):
         dep_path = Path(dep)
-        # Only parse if it is a file or an installable dir
-        if dep_path.is_file() or (dep_path.is_dir() and pip.utils.is_installable_dir(dep)):
-            if dep_path.is_absolute() or dep_path.as_posix() == '.':
-                path = dep_path.as_posix()
-            else:
-                path = get_converted_relative_path(dep)
-            dep = dep_path.resolve().as_uri()
+        dep_link = Link(dep_path.absolute().as_uri())
+        if dep_path.is_absolute() or dep_path.as_posix() == '.':
+            path = dep_path.as_posix()
+        else:
+            path = get_converted_relative_path(dep)
+        dep = dep_link.egg_fragment if dep_link.egg_fragment else dep_link.url_without_fragment
+    elif is_vcs(dep):
+        # Generate a Link object for parsing egg fragments
+        dep_link = Link(dep)
+        # Save the original path to store in the pipfile
+        uri = dep_link.url
+        # Construct the requirement using proper git+ssh:// replaced uris or names if available
+        cleaned_uri = clean_git_uri(dep)
+        dep = cleaned_uri
+    if editable:
+        dep = '-e {0}'.format(dep)
     req = [r for r in requirements.parse(dep)][0]
+    # if all we built was the requirement name and still need everything else
+    if req.name and not any([req.uri, req.path]):
+        if dep_link:
+            if dep_link.scheme.startswith('file') and path and not req.path:
+                req.path = path
+                req.local_file = True
+                req.uri = None
+            else:
+                req.uri = dep_link.url_without_fragment
     # If the result is a local file with a URI and we have a local path, unset the URI
-    # and set the path instead
-    if req.local_file and req.uri and not req.path and path:
+    # and set the path instead -- note that local files may have 'path' set by accident
+    elif req.local_file and path and not req.vcs:
         req.path = path
         req.uri = None
+    elif req.vcs and req.uri and cleaned_uri and uri != req.uri:
+        req.uri = strip_ssh_from_git_uri(req.uri)
+        req.line = strip_ssh_from_git_uri(req.line)
+    req.editable = editable
     if markers:
         req.markers = markers
     if extras:
@@ -536,7 +569,7 @@ def resolve_deps(deps, which, which_pip, project, sources=None, verbose=False, p
     markers_lookup = {}
 
     python_path = which('python', allow_global=allow_global)
-    backup_python_path = shellquote(sys.executable)
+    backup_python_path = sys.executable
 
     results = []
 
@@ -627,7 +660,7 @@ def convert_deps_from_pip(dep):
     extras = {'extras': req.extras}
 
     # File installs.
-    if (req.uri or req.path or (os.path.isfile(req.name) if req.name else False)) and not req.vcs:
+    if (req.uri or req.path or is_installable_file(req.name)) and not req.vcs:
         # Assign a package name to the file, last 7 of it's sha256 hex digest.
         if not req.uri and not req.path:
             req.path = os.path.abspath(req.name)
@@ -851,6 +884,22 @@ def is_required_version(version, specified_version):
     return True
 
 
+def strip_ssh_from_git_uri(uri):
+    """Return git+ssh:// formatted URI to git+git@ format"""
+    if isinstance(uri, six.string_types):
+        uri = uri.replace('git+ssh://', 'git+')
+    return uri
+
+
+def clean_git_uri(uri):
+    """Cleans VCS uris from pip format"""
+    if isinstance(uri, six.string_types):
+        # Add scheme for parsing purposes, this is also what pip does
+        if uri.startswith('git+') and '://' not in uri:
+            uri = uri.replace('git+', 'git+ssh://')
+    return uri
+
+
 def is_vcs(pipfile_entry):
     import requirements
     """Determine if dictionary entry from Pipfile is for a vcs dependency."""
@@ -858,17 +907,13 @@ def is_vcs(pipfile_entry):
     if hasattr(pipfile_entry, 'keys'):
         return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
     elif isinstance(pipfile_entry, six.string_types):
-        # Add scheme for parsing purposes, this is also what pip does
-        if pipfile_entry.startswith('git+') and '://' not in pipfile_entry:
-            pipfile_entry = pipfile_entry.replace('git+', 'git+ssh://')
-        return bool(requirements.requirement.VCS_REGEX.match(pipfile_entry))
+        return bool(requirements.requirement.VCS_REGEX.match(clean_git_uri(pipfile_entry)))
     return False
 
 
 def is_installable_file(path):
-    import pip
-
     """Determine if a path can potentially be installed"""
+    import pip
     if hasattr(path, 'keys') and any(key for key in path.keys() if key in ['file', 'path']):
         path = urlparse(path['file']).path if 'file' in path else path['path']
     if not isinstance(path, six.string_types) or path == '*':
@@ -883,9 +928,15 @@ def is_installable_file(path):
             pass
         else:
             return False
+    if not os.path.exists(os.path.abspath(path)):
+        return False
     lookup_path = Path(path)
-    return lookup_path.is_file() or (lookup_path.is_dir() and
-            pip.utils.is_installable_dir(lookup_path.resolve().as_posix()))
+    absolute_path = '{0}'.format(lookup_path.absolute())
+    if lookup_path.is_dir() and pip.utils.is_installable_dir(absolute_path):
+        return True
+    elif lookup_path.is_file() and is_archive_file(absolute_path):
+        return True
+    return False
 
 
 def is_file(package):


Test patch:
diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py
--- a/tests/test_pipenv.py
+++ b/tests/test_pipenv.py
@@ -440,6 +440,30 @@ def test_windows_pinned_pipfile(self):
             assert 'tablib' in p.lockfile['default']
 
 
+    @pytest.mark.e
+    @pytest.mark.install
+    @pytest.mark.vcs
+    @pytest.mark.resolver
+    def test_editable_vcs_install_in_pipfile_with_dependency_resolution_doesnt_traceback(self):
+        # See https://github.com/pypa/pipenv/issues/1240
+        with PipenvInstance() as p:
+            with open(p.pipfile_path, 'w') as f:
+                contents = """
+[packages]
+pypa-docs-theme = {git = "https://github.com/pypa/pypa-docs-theme", editable = true}
+
+# This version of requests depends on idna<2.6, forcing dependency resolution
+# failure
+requests = "==2.16.0"
+idna = "==2.6.0"
+                """.strip()
+                f.write(contents)
+            c = p.pipenv('install')
+            assert c.return_code == 1
+            assert "Your dependencies could not be resolved" in c.err
+            assert 'Traceback' not in c.err or 'PermissionError' in c.err
+
+
     @pytest.mark.run
     @pytest.mark.install
     def test_multiprocess_bug_and_install(self):
@@ -602,12 +626,12 @@ def test_specific_package_environment_markers(self):
     @pytest.mark.tablib
     def test_install_editable_git_tag(self):
         with PipenvInstance() as p:
-            c = p.pipenv('install -e git+git://github.com/kennethreitz/tablib.git@v0.12.1#egg=tablib')
+            c = p.pipenv('install -e git+https://github.com/kennethreitz/tablib.git@v0.12.1#egg=tablib')
             assert c.return_code == 0
             assert 'tablib' in p.pipfile['packages']
             assert 'tablib' in p.lockfile['default']
             assert 'git' in p.lockfile['default']['tablib']
-            assert p.lockfile['default']['tablib']['git'] == 'git://github.com/kennethreitz/tablib.git'
+            assert p.lockfile['default']['tablib']['git'] == 'https://github.com/kennethreitz/tablib.git'
             assert 'ref' in p.lockfile['default']['tablib']
 
     @pytest.mark.run
@@ -1102,3 +1126,17 @@ def test_relative_paths(self):
             assert 'path' in dep
             assert Path(os.path.join('.', artifact_dir, file_name)) == Path(dep['path'])
             assert c.return_code == 0
+
+    @pytest.mark.install
+    @pytest.mark.local_file
+    def test_install_local_file_collision(self):
+        with PipenvInstance() as p:
+            target_package = 'alembic'
+            fake_file = os.path.join(p.path, target_package)
+            with open(fake_file, 'w') as f:
+                f.write('')
+            c = p.pipenv('install {}'.format(target_package))
+            assert c.return_code == 0
+            assert target_package in p.pipfile['packages']
+            assert p.pipfile['packages'][target_package] == '*'
+            assert target_package in p.lockfile['default']
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -250,3 +250,43 @@ def test_win_normalize_drive(self, input_path, expected):
     @pytest.mark.skipif(os.name == 'nt', reason='*nix file paths tested')
     def test_nix_normalize_drive(self, input_path, expected):
         assert pipenv.utils.normalize_drive(input_path) == expected
+
+    @pytest.mark.requirements
+    def test_get_requirements(self):
+        # Test eggs in URLs
+        url_with_egg = pipenv.utils.get_requirement('https://github.com/IndustriaTech/django-user-clipboard/archive/0.6.1.zip#egg=django-user-clipboard')
+        assert url_with_egg.uri == 'https://github.com/IndustriaTech/django-user-clipboard/archive/0.6.1.zip'
+        assert url_with_egg.name == 'django-user-clipboard'
+
+        # Test URLs without eggs pointing at installable zipfiles
+        url = pipenv.utils.get_requirement('https://github.com/kennethreitz/tablib/archive/0.12.1.zip')
+        assert url.uri == 'https://github.com/kennethreitz/tablib/archive/0.12.1.zip'
+
+        # Test VCS urls with refs and eggnames
+        vcs_url = pipenv.utils.get_requirement('git+https://github.com/kennethreitz/tablib.git@master#egg=tablib')
+        assert vcs_url.vcs == 'git' and vcs_url.name == 'tablib' and vcs_url.revision == 'master'
+        assert vcs_url.uri == 'git+https://github.com/kennethreitz/tablib.git'
+
+        # Test normal package requirement
+        normal = pipenv.utils.get_requirement('tablib')
+        assert normal.name == 'tablib'
+
+        # Pinned package  requirement
+        spec = pipenv.utils.get_requirement('tablib==0.12.1')
+        assert spec.name == 'tablib' and spec.specs == [('==', '0.12.1')]
+
+        # Test complex package with both extras and markers
+        extras_markers = pipenv.utils.get_requirement("requests[security]; os_name=='posix'")
+        assert extras_markers.extras == ['security']
+        assert extras_markers.name == 'requests'
+        assert extras_markers.markers == "os_name=='posix'"
+
+        # Test VCS uris get generated correctly, retain git+git@ if supplied that way, and are named according to egg fragment
+        git_reformat = pipenv.utils.get_requirement('-e git+git@github.com:pypa/pipenv.git#egg=pipenv')
+        assert git_reformat.uri == 'git+git@github.com:pypa/pipenv.git'
+        assert git_reformat.name == 'pipenv'
+        assert git_reformat.editable
+        # Previously VCS uris were being treated as local files, so make sure these are not handled that way
+        assert not git_reformat.local_file
+        # Test regression where VCS uris were being handled as paths rather than VCS entries
+        assert git_reformat.vcs == 'git'