Repository: pypa/pip

Issue:
pip.vcs.git should use --work-tree instead of implicit $CWD
The Git backend (`pip.vcs.git`) relies on the fact that Git uses `$CWD` as the repository directory. The problem is that the environment variables `$GIT_DIR`/`$GIT_WORK_TREE` can be used to overwrite that, and then always the same repository will be queried instead of the one identified in `location`.

So instead `$CWD`, `pip.vcs.git` should always specify `--work-tree`.

http://git-scm.com/blog/2010/04/11/environment.html

Alternatively, it could just unset those variables before invoking `git`.



Discussion:
Could you provide a test case that demonstrate where pip fails exactly?

Setup:

```
$ mkvirtualenv pip-test
$ pip install -e git+git@github.com:jkbr/httpie.git#egg=httpie
```

Fail:

```
$ GIT_DIR=/foo pip freeze
Error when trying to get requirement for VCS system Command /usr/local/bin/git config remote.origin.url failed with error code 1 in /Users/jakub/.virtualenvs/pip-test/src/httpie, falling back to uneditable format
Could not determine repository location of ~/.virtualenvs/pip-test/src/httpie
Pygments==1.6
## !! Could not determine repository location
httpie==0.6.0
requests==1.2.3
wsgiref==0.1.2
```

`GIT_DIR` will be set for example in scripts invoked as Git hooks.

I can confirm this problem, same issue here.

A real world example triggering this unwanted behavior is running pip from inside a git repository's post-update hook.
Inside the hook GIT_DIR will be set to '.', which causes pip to operate on that very repository instead of on the repository cloned by pip.

Besides setting --work-tree explicitly, another solution would be unsetting GIT_DIR (and GIT_WORK_TREE).

Resolving patch:
diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py
--- a/src/pip/_internal/utils/misc.py
+++ b/src/pip/_internal/utils/misc.py
@@ -627,7 +627,14 @@ def unpack_file(filename, location, content_type, link):
 def call_subprocess(cmd, show_stdout=True, cwd=None,
                     on_returncode='raise',
                     command_desc=None,
-                    extra_environ=None, spinner=None):
+                    extra_environ=None, unset_environ=None, spinner=None):
+    """
+    Args:
+      unset_environ: an iterable of environment variable names to unset
+        prior to calling subprocess.Popen().
+    """
+    if unset_environ is None:
+        unset_environ = []
     # This function's handling of subprocess output is confusing and I
     # previously broke it terribly, so as penance I will write a long comment
     # explaining things.
@@ -664,6 +671,8 @@ def call_subprocess(cmd, show_stdout=True, cwd=None,
     env = os.environ.copy()
     if extra_environ:
         env.update(extra_environ)
+    for name in unset_environ:
+        env.pop(name, None)
     try:
         proc = subprocess.Popen(
             cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
diff --git a/src/pip/_internal/vcs/__init__.py b/src/pip/_internal/vcs/__init__.py
--- a/src/pip/_internal/vcs/__init__.py
+++ b/src/pip/_internal/vcs/__init__.py
@@ -166,6 +166,8 @@ class VersionControl(object):
     dirname = ''
     # List of supported schemes for this Version Control
     schemes = ()  # type: Tuple[str, ...]
+    # Iterable of environment variable names to pass to call_subprocess().
+    unset_environ = ()  # type: Tuple[str, ...]
     default_arg_rev = None  # type: Optional[str]
 
     def __init__(self, url=None, *args, **kwargs):
@@ -422,7 +424,8 @@ def run_command(self, cmd, show_stdout=True, cwd=None,
             return call_subprocess(cmd, show_stdout, cwd,
                                    on_returncode,
                                    command_desc, extra_environ,
-                                   spinner)
+                                   unset_environ=self.unset_environ,
+                                   spinner=spinner)
         except OSError as e:
             # errno.ENOENT = no such file or directory
             # In other words, the VCS executable isn't available
diff --git a/src/pip/_internal/vcs/git.py b/src/pip/_internal/vcs/git.py
--- a/src/pip/_internal/vcs/git.py
+++ b/src/pip/_internal/vcs/git.py
@@ -27,6 +27,9 @@ class Git(VersionControl):
     schemes = (
         'git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file',
     )
+    # Prevent the user's environment variables from interfering with pip:
+    # https://github.com/pypa/pip/issues/1130
+    unset_environ = ('GIT_DIR', 'GIT_WORK_TREE')
     default_arg_rev = 'origin/HEAD'
 
     def __init__(self, url=None, *args, **kwargs):


Test patch:
diff --git a/tests/functional/test_vcs.py b/tests/functional/test_vcs.py
new file mode 100644
--- /dev/null
+++ b/tests/functional/test_vcs.py
@@ -0,0 +1,32 @@
+import os
+
+from pip._internal.utils.temp_dir import TempDirectory
+from pip._internal.vcs.git import Git
+
+
+def test_git_dir_ignored():
+    """
+    Test that a GIT_DIR environment variable is ignored.
+    """
+    git = Git()
+    with TempDirectory() as temp:
+        temp_dir = temp.path
+        env = {'GIT_DIR': 'foo'}
+        # If GIT_DIR is not ignored, then os.listdir() will return ['foo'].
+        git.run_command(['init', temp_dir], cwd=temp_dir, extra_environ=env)
+        assert os.listdir(temp_dir) == ['.git']
+
+
+def test_git_work_tree_ignored():
+    """
+    Test that a GIT_WORK_TREE environment variable is ignored.
+    """
+    git = Git()
+    with TempDirectory() as temp:
+        temp_dir = temp.path
+        git.run_command(['init', temp_dir], cwd=temp_dir)
+        # Choose a directory relative to the cwd that does not exist.
+        # If GIT_WORK_TREE is not ignored, then the command will error out
+        # with: "fatal: This operation must be run in a work tree".
+        env = {'GIT_WORK_TREE': 'foo'}
+        git.run_command(['status', temp_dir], extra_environ=env, cwd=temp_dir)