Repository: gitpython-developers/GitPython (python)

Issue:
Use of GIT_REPO env-var for setting Repo path in 2.0.9
[This change](https://github.com/gitpython-developers/GitPython/commit/26bb778b34b93537cfbfd5c556d3810f2cf3f76e) in `2.0.9` is a bit problematic:

- Even if a `path` is explicitly provided to the constructor, the `GIT_DIR` environment variable is used instead

- For things like git hooks, `GIT_DIR` is set to `.git` by default. This will result in [`self.git_dir`](https://github.com/gitpython-developers/GitPython/blob/master/git/repo/base.py#L156) being set to `.git` (`os.path.abspath` instead of `os.path.normpath` would fix this). Consequently, subsequent commands fails (eg: `repo.git.status()`)

Pull request description:
FIX #535: expand also GIT_DIR var on Repo-construction
+ Ignore "empty" GIT_DIR vars.
+ Improve documentation on the constructor `path` parameter.
Minor repo-code and doc correcions.
  + Expansion of paths also `osp.normalize()` them.
  + Make Repo-fields --> class-fields to avoid initializations on
construct.
  + Explain and rename `git.repo.fun.find_git_dir()` is for submodules
(`find_submodule_git_dir()`).


Patch:
diff --git a/git/repo/base.py b/git/repo/base.py
index 21d129e9..0f85e3d9 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -30,10 +30,9 @@ from git.objects import Submodule, RootModule, Commit
 from git.refs import HEAD, Head, Reference, TagReference
 from git.remote import Remote, add_progress, to_progress_instance
 from git.util import Actor, finalize_process, decygpath, hex_to_bin
-
 import os.path as osp
 
-from .fun import rev_parse, is_git_dir, find_git_dir, touch
+from .fun import rev_parse, is_git_dir, find_submodule_git_dir, touch
 
 
 log = logging.getLogger(__name__)
@@ -50,7 +49,7 @@ __all__ = ('Repo',)
 
 
 def _expand_path(p):
-    return osp.abspath(osp.expandvars(osp.expanduser(p)))
+    return osp.normpath(osp.abspath(osp.expandvars(osp.expanduser(p))))
 
 
 class Repo(object):
@@ -69,6 +68,11 @@ class Repo(object):
     'git_dir' is the .git repository directory, which is always set."""
     DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
 
+    git = None  # Must exist, or  __del__  will fail in case we raise on `__init__()`
+    working_dir = None
+    _working_tree_dir = None
+    git_dir = None
+
     # precompiled regex
     re_whitespace = re.compile(r'\s+')
     re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
@@ -95,8 +99,9 @@ class Repo(object):
                 repo = Repo("~/Development/git-python.git")
                 repo = Repo("$REPOSITORIES/Development/git-python.git")
 
-            In *Cygwin*, path may be a `'cygdrive/...'` prefixed path.
-
+            - In *Cygwin*, path may be a `'cygdrive/...'` prefixed path.
+            - If it evaluates to false, :envvar:`GIT_DIR` is used, and if this also evals to false,
+              the current-directory is used.
         :param odbt:
             Object DataBase type - a type which is constructed by providing
             the directory containing the database objects, i.e. .git/objects. It will
@@ -109,40 +114,41 @@ class Repo(object):
         :raise InvalidGitRepositoryError:
         :raise NoSuchPathError:
         :return: git.Repo """
+        epath = path or os.getenv('GIT_DIR')
+        if not epath:
+            epath = os.getcwd()
         if Git.is_cygwin():
-            path = decygpath(path)
-
-        epath = _expand_path(path or os.getcwd())
-        self.git = None  # should be set for __del__ not to fail in case we raise
-        if not osp.exists(epath):
+            epath = decygpath(epath)
+        epath = _expand_path(epath or path or os.getcwd())
+        if not os.path.exists(epath):
             raise NoSuchPathError(epath)
 
-        self.working_dir = None
-        self._working_tree_dir = None
-        self.git_dir = None
-        curpath = os.getenv('GIT_DIR', epath)
-
-        # walk up the path to find the .git dir
+        ## Walk up the path to find the `.git` dir.
+        #
+        curpath = epath
         while curpath:
             # ABOUT osp.NORMPATH
             # It's important to normalize the paths, as submodules will otherwise initialize their
             # repo instances with paths that depend on path-portions that will not exist after being
             # removed. It's just cleaner.
             if is_git_dir(curpath):
-                self.git_dir = osp.normpath(curpath)
-                self._working_tree_dir = osp.dirname(self.git_dir)
+                self.git_dir = curpath
+                self._working_tree_dir = os.path.dirname(self.git_dir)
                 break
 
-            gitpath = find_git_dir(osp.join(curpath, '.git'))
-            if gitpath is not None:
-                self.git_dir = osp.normpath(gitpath)
+            sm_gitpath = find_submodule_git_dir(osp.join(curpath, '.git'))
+            if sm_gitpath is not None:
+                self.git_dir = osp.normpath(sm_gitpath)
+            sm_gitpath = find_submodule_git_dir(osp.join(curpath, '.git'))
+            if sm_gitpath is not None:
+                self.git_dir = _expand_path(sm_gitpath)
                 self._working_tree_dir = curpath
                 break
 
             if not search_parent_directories:
                 break
-            curpath, dummy = osp.split(curpath)
-            if not dummy:
+            curpath, tail = osp.split(curpath)
+            if not tail:
                 break
         # END while curpath
 
diff --git a/git/repo/fun.py b/git/repo/fun.py
index c5a8a52b..39e55880 100644
--- a/git/repo/fun.py
+++ b/git/repo/fun.py
@@ -16,7 +16,7 @@ import os.path as osp
 from git.cmd import Git
 
 
-__all__ = ('rev_parse', 'is_git_dir', 'touch', 'find_git_dir', 'name_to_object', 'short_to_long', 'deref_tag',
+__all__ = ('rev_parse', 'is_git_dir', 'touch', 'find_submodule_git_dir', 'name_to_object', 'short_to_long', 'deref_tag',
            'to_commit')
 
 
@@ -47,7 +47,8 @@ def is_git_dir(d):
     return False
 
 
-def find_git_dir(d):
+def find_submodule_git_dir(d):
+    """Search for a submodule repo."""
     if is_git_dir(d):
         return d
 
@@ -60,12 +61,13 @@ def find_git_dir(d):
     else:
         if content.startswith('gitdir: '):
             path = content[8:]
+
             if Git.is_cygwin():
                 ## Cygwin creates submodules prefixed with `/cygdrive/...` suffixes.
                 path = decygpath(path)
             if not osp.isabs(path):
                 path = osp.join(osp.dirname(d), path)
-            return find_git_dir(path)
+            return find_submodule_git_dir(path)
     # end handle exception
     return None
 


Test patch:
diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py
index fcaad04b..0ebd9ec7 100644
--- a/git/test/test_submodule.py
+++ b/git/test/test_submodule.py
@@ -7,34 +7,24 @@ from unittest.case import skipIf
 
 import git
 from git.cmd import Git
-from git.compat import (
-    string_types,
-    is_win,
-)
+from git.compat import string_types, is_win
 from git.exc import (
     InvalidGitRepositoryError,
     RepositoryDirtyError
 )
 from git.objects.submodule.base import Submodule
-from git.objects.submodule.root import (
-    RootModule,
-    RootUpdateProgress,
-)
+from git.objects.submodule.root import RootModule, RootUpdateProgress
 from git.repo.fun import (
-    find_git_dir,
-    touch,
+    find_submodule_git_dir,
+    touch
 )
 from git.test.lib import (
     TestBase,
-    with_rw_repo,
+    with_rw_repo
 )
 from git.test.lib import with_rw_directory
-from git.util import (
-    to_native_path_linux,
-    join_path_native,
-    HIDE_WINDOWS_KNOWN_ERRORS,
-)
-
+from git.util import HIDE_WINDOWS_KNOWN_ERRORS
+from git.util import to_native_path_linux, join_path_native
 import os.path as osp
 
 
@@ -775,7 +765,7 @@ class TestSubmodule(TestBase):
         else:
             assert osp.isfile(module_repo_path)
             assert sm.module().has_separate_working_tree()
-            assert find_git_dir(module_repo_path) is not None, "module pointed to by .git file must be valid"
+            assert find_submodule_git_dir(module_repo_path) is not None, "module pointed to by .git file must be valid"
         # end verify submodule 'style'
 
         # test move