Repository: gitpython-developers/GitPython
Pull request #773 (2018-06-14 19:39:06)

Update test_docs.py
Using "import as" is normally a time saver but for usability of the documentation, please consider removing osp and join with fully qualified calls for better snippet readability.

I had some co-workers end up using popen commands vs using this library when I recommended GitPython as they did not understand how it worked from skimming the docs.

Sorry for not writing a test that fails... only a syntax change for readability.


Patch:
diff --git a/AUTHORS b/AUTHORS
index b5f0ebdc..9c3a52c0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -26,5 +26,6 @@ Contributors are:
 -Mikuláš Poul <mikulaspoul _at_ gmail.com>
 -Charles Bouchard-Légaré <cblegare.atl _at_ ntis.ca>
 -Yaroslav Halchenko <debian _at_ onerussian.com>
+-Tim Swast <swast _at_ google.com>
 
 Portions derived from other open source works and are clearly marked.
diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst
index 7ac2eeea..a96d0d99 100644
--- a/doc/source/tutorial.rst
+++ b/doc/source/tutorial.rst
@@ -19,6 +19,7 @@ The first step is to create a :class:`git.Repo <git.repo.base.Repo>` object to r
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [1-test_init_repo_object]
     :end-before: # ![1-test_init_repo_object]
 
@@ -26,6 +27,7 @@ In the above example, the directory ``self.rorepo.working_tree_dir`` equals ``/U
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [2-test_init_repo_object]
     :end-before: # ![2-test_init_repo_object]
 
@@ -33,6 +35,7 @@ A repo object provides high-level access to your data, it allows you to create a
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [3-test_init_repo_object]
     :end-before: # ![3-test_init_repo_object]
 
@@ -40,6 +43,7 @@ Query the active branch, query untracked files or whether the repository data ha
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [4-test_init_repo_object]
     :end-before: # ![4-test_init_repo_object]
 
@@ -47,6 +51,7 @@ Clone from existing repositories or initialize new empty ones.
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [5-test_init_repo_object]
     :end-before: # ![5-test_init_repo_object]
 
@@ -54,6 +59,7 @@ Archive the repository contents to a tar file.
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [6-test_init_repo_object]
     :end-before: # ![6-test_init_repo_object]
 
@@ -66,6 +72,7 @@ Query relevant repository paths ...
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [7-test_init_repo_object]
     :end-before: # ![7-test_init_repo_object]
 
@@ -73,6 +80,7 @@ Query relevant repository paths ...
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [8-test_init_repo_object]
     :end-before: # ![8-test_init_repo_object]
 
@@ -80,6 +88,7 @@ You can also create new heads ...
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [9-test_init_repo_object]
     :end-before: # ![9-test_init_repo_object]
 
@@ -87,6 +96,7 @@ You can also create new heads ...
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [10-test_init_repo_object]
     :end-before: # ![10-test_init_repo_object]
 
@@ -94,6 +104,7 @@ You can traverse down to :class:`git objects <git.objects.base.Object>` through
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [11-test_init_repo_object]
     :end-before: # ![11-test_init_repo_object]
 
@@ -101,6 +112,7 @@ You can traverse down to :class:`git objects <git.objects.base.Object>` through
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [12-test_init_repo_object]
     :end-before: # ![12-test_init_repo_object]
 
@@ -108,6 +120,7 @@ The :class:`index <git.index.base.IndexFile>` is also called stage in git-speak.
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [13-test_init_repo_object]
     :end-before: # ![13-test_init_repo_object]
 
@@ -115,6 +128,7 @@ The :class:`index <git.index.base.IndexFile>` is also called stage in git-speak.
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [14-test_init_repo_object]
     :end-before: # ![14-test_init_repo_object]
 
@@ -126,6 +140,7 @@ Examining References
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [1-test_references_and_objects]
     :end-before: # ![1-test_references_and_objects]
 
@@ -133,6 +148,7 @@ Examining References
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [2-test_references_and_objects]
     :end-before: # ![2-test_references_and_objects]
 
@@ -140,6 +156,7 @@ A :class:`symbolic reference <git.refs.symbolic.SymbolicReference>` is a special
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [3-test_references_and_objects]
     :end-before: # ![3-test_references_and_objects]
 
@@ -147,6 +164,7 @@ Access the :class:`reflog <git.refs.log.RefLog>` easily.
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [4-test_references_and_objects]
     :end-before: # ![4-test_references_and_objects]
 
@@ -156,6 +174,7 @@ You can easily create and delete :class:`reference types <git.refs.reference.Ref
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [5-test_references_and_objects]
     :end-before: # ![5-test_references_and_objects]
 
@@ -163,6 +182,7 @@ Create or delete :class:`tags <git.refs.tag.TagReference>` the same way except y
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [6-test_references_and_objects]
     :end-before: # ![6-test_references_and_objects]
 
@@ -170,6 +190,7 @@ Change the :class:`symbolic reference <git.refs.symbolic.SymbolicReference>` to
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [7-test_references_and_objects]
     :end-before: # ![7-test_references_and_objects]
 
@@ -183,6 +204,7 @@ In GitPython, all objects can be accessed through their common base, can be comp
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [8-test_references_and_objects]
     :end-before: # ![8-test_references_and_objects]
 
@@ -190,6 +212,7 @@ Common fields are ...
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [9-test_references_and_objects]
     :end-before: # ![9-test_references_and_objects]
 
@@ -197,6 +220,7 @@ Common fields are ...
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [10-test_references_and_objects]
     :end-before: # ![10-test_references_and_objects]
 
@@ -204,6 +228,7 @@ Access :class:`blob <git.objects.blob.Blob>` data (or any object data) using str
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [11-test_references_and_objects]
     :end-before: # ![11-test_references_and_objects]
 
@@ -217,6 +242,7 @@ Obtain commits at the specified revision
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [12-test_references_and_objects]
     :end-before: # ![12-test_references_and_objects]
 
@@ -224,6 +250,7 @@ Iterate 50 commits, and if you need paging, you can specify a number of commits
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [13-test_references_and_objects]
     :end-before: # ![13-test_references_and_objects]
 
@@ -231,6 +258,7 @@ A commit object carries all sorts of meta-data
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [14-test_references_and_objects]
     :end-before: # ![14-test_references_and_objects]
 
@@ -238,6 +266,7 @@ Note: date time is represented in a ``seconds since epoch`` format. Conversion t
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [15-test_references_and_objects]
     :end-before: # ![15-test_references_and_objects]
 
@@ -245,6 +274,7 @@ You can traverse a commit's ancestry by chaining calls to ``parents``
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [16-test_references_and_objects]
     :end-before: # ![16-test_references_and_objects]
 
@@ -257,6 +287,7 @@ A :class:`tree <git.objects.tree.Tree>` records pointers to the contents of a di
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [17-test_references_and_objects]
     :end-before: # ![17-test_references_and_objects]
 
@@ -264,6 +295,7 @@ Once you have a tree, you can get its contents
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [18-test_references_and_objects]
     :end-before: # ![18-test_references_and_objects]
 
@@ -271,6 +303,7 @@ It is useful to know that a tree behaves like a list with the ability to query e
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [19-test_references_and_objects]
     :end-before: # ![19-test_references_and_objects]
 
@@ -278,6 +311,7 @@ There is a convenience method that allows you to get a named sub-object from a t
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [20-test_references_and_objects]
     :end-before: # ![20-test_references_and_objects]
 
@@ -285,6 +319,7 @@ You can also get a commit's root tree directly from the repository
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [21-test_references_and_objects]
     :end-before: # ![21-test_references_and_objects]
 
@@ -292,6 +327,7 @@ As trees allow direct access to their intermediate child entries only, use the t
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [22-test_references_and_objects]
     :end-before: # ![22-test_references_and_objects]
 
@@ -304,6 +340,7 @@ Modify the index with ease
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [23-test_references_and_objects]
     :end-before: # ![23-test_references_and_objects]
 
@@ -311,6 +348,7 @@ Create new indices from other trees or as result of a merge. Write that result t
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [24-test_references_and_objects]
     :end-before: # ![24-test_references_and_objects]
 
@@ -321,6 +359,7 @@ Handling Remotes
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [25-test_references_and_objects]
     :end-before: # ![25-test_references_and_objects]
 
@@ -328,6 +367,7 @@ You can easily access configuration information for a remote by accessing option
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [26-test_references_and_objects]
     :end-before: # ![26-test_references_and_objects]
 
@@ -343,7 +383,9 @@ This one sets a custom script to be executed in place of `ssh`, and can be used
     with repo.git.custom_environment(GIT_SSH=ssh_executable):
         repo.remotes.origin.fetch()
 
-Here's an example executable that can be used in place of the `ssh_executable` above::
+Here's an example executable that can be used in place of the `ssh_executable` above:
+
+.. code-block:: shell
 
     #!/bin/sh
     ID_RSA=/var/lib/openshift/5562b947ecdd5ce939000038/app-deployments/id_rsa
@@ -359,6 +401,7 @@ Submodule Handling
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [1-test_submodules]
     :end-before: # ![1-test_submodules]
 
@@ -383,6 +426,7 @@ Diffs can be made between the Index and Trees, Index and the working tree, trees
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [27-test_references_and_objects]
     :end-before: # ![27-test_references_and_objects]
 
@@ -390,6 +434,7 @@ The item returned is a DiffIndex which is essentially a list of Diff objects. It
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [28-test_references_and_objects]
     :end-before: # ![28-test_references_and_objects]
 
@@ -413,6 +458,7 @@ To switch between branches similar to ``git checkout``, you effectively need to
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [29-test_references_and_objects]
     :end-before: # ![29-test_references_and_objects]
 
@@ -420,6 +466,7 @@ The previous approach would brutally overwrite the user's changes in the working
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [30-test_references_and_objects]
     :end-before: # ![30-test_references_and_objects]
 
@@ -430,6 +477,7 @@ In this example, we will initialize an empty repository, add an empty file to th
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: def test_add_file_and_commit
     :end-before: # ![test_add_file_and_commit]
 
@@ -441,6 +489,7 @@ In case you are missing functionality as it has not been wrapped, you may conven
 
 .. literalinclude:: ../../git/test/test_docs.py
     :language: python
+    :dedent: 8
     :start-after: # [31-test_references_and_objects]
     :end-before: # ![31-test_references_and_objects]
 
diff --git a/git/repo/base.py b/git/repo/base.py
index 26c7c7e5..023582b5 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -36,6 +36,11 @@ from .fun import rev_parse, is_git_dir, find_submodule_git_dir, touch, find_work
 import gc
 import gitdb
 
+try:
+    import pathlib
+except ImportError:
+    pathlib = None
+
 
 log = logging.getLogger(__name__)
 
@@ -116,6 +121,8 @@ class Repo(object):
             epath = decygpath(epath)
 
         epath = epath or path or os.getcwd()
+        if not isinstance(epath, str):
+            epath = str(epath)
         if expand_vars and ("%" in epath or "$" in epath):
             warnings.warn("The use of environment variables in paths is deprecated" +
                           "\nfor security reasons and may be removed in the future!!")
@@ -424,8 +431,7 @@ class Repo(object):
         :param config_level:
             For possible values, see config_writer method
             If None, all applicable levels will be used. Specify a level in case
-            you know which exact file you whish to read to prevent reading multiple files for
-            instance
+            you know which file you wish to read to prevent reading multiple files.
         :note: On windows, system configuration cannot currently be read as the path is
             unknown, instead the global path will be used."""
         files = None


Test patch:
diff --git a/git/test/test_docs.py b/git/test/test_docs.py
index 67ffb934..770f78e2 100644
--- a/git/test/test_docs.py
+++ b/git/test/test_docs.py
@@ -9,7 +9,7 @@ import os
 from git.test.lib import TestBase
 from git.test.lib.helper import with_rw_directory
 
-import os.path as osp
+import os.path
 
 
 class Tutorials(TestBase):
@@ -25,7 +25,6 @@ class Tutorials(TestBase):
     def test_init_repo_object(self, rw_dir):
         # [1-test_init_repo_object]
         from git import Repo
-        join = osp.join
 
         # rorepo is a Repo instance pointing to the git-python repository.
         # For all you know, the first argument to Repo is a path to the repository
@@ -35,7 +34,7 @@ class Tutorials(TestBase):
         # ![1-test_init_repo_object]
 
         # [2-test_init_repo_object]
-        bare_repo = Repo.init(join(rw_dir, 'bare-repo'), bare=True)
+        bare_repo = Repo.init(os.path.join(rw_dir, 'bare-repo'), bare=True)
         assert bare_repo.bare
         # ![2-test_init_repo_object]
 
@@ -52,19 +51,19 @@ class Tutorials(TestBase):
         # ![4-test_init_repo_object]
 
         # [5-test_init_repo_object]
-        cloned_repo = repo.clone(join(rw_dir, 'to/this/path'))
+        cloned_repo = repo.clone(os.path.join(rw_dir, 'to/this/path'))
         assert cloned_repo.__class__ is Repo     # clone an existing repository
-        assert Repo.init(join(rw_dir, 'path/for/new/repo')).__class__ is Repo
+        assert Repo.init(os.path.join(rw_dir, 'path/for/new/repo')).__class__ is Repo
         # ![5-test_init_repo_object]
 
         # [6-test_init_repo_object]
-        with open(join(rw_dir, 'repo.tar'), 'wb') as fp:
+        with open(os.path.join(rw_dir, 'repo.tar'), 'wb') as fp:
             repo.archive(fp)
         # ![6-test_init_repo_object]
 
         # repository paths
         # [7-test_init_repo_object]
-        assert osp.isdir(cloned_repo.working_tree_dir)                   # directory with your work files
+        assert os.path.isdir(cloned_repo.working_tree_dir)                   # directory with your work files
         assert cloned_repo.git_dir.startswith(cloned_repo.working_tree_dir)  # directory containing the git repository
         assert bare_repo.working_tree_dir is None                            # bare repositories have no working tree
         # ![7-test_init_repo_object]
@@ -148,7 +147,7 @@ class Tutorials(TestBase):
         self.assertEqual(new_branch.checkout(), cloned_repo.active_branch)     # checking out branch adjusts the wtree
         self.assertEqual(new_branch.commit, past.commit)                       # Now the past is checked out
 
-        new_file_path = osp.join(cloned_repo.working_tree_dir, 'my-new-file')
+        new_file_path = os.path.join(cloned_repo.working_tree_dir, 'my-new-file')
         open(new_file_path, 'wb').close()                             # create new file in working tree
         cloned_repo.index.add([new_file_path])                        # add it to the index
         # Commit the changes to deviate masters history
@@ -164,7 +163,7 @@ class Tutorials(TestBase):
         # now new_branch is ahead of master, which probably should be checked out and reset softly.
         # note that all these operations didn't touch the working tree, as we managed it ourselves.
         # This definitely requires you to know what you are doing :) !
-        assert osp.basename(new_file_path) in new_branch.commit.tree  # new file is now in tree
+        assert os.path.basename(new_file_path) in new_branch.commit.tree  # new file is now in tree
         master.commit = new_branch.commit            # let master point to most recent commit
         cloned_repo.head.reference = master          # we adjusted just the reference, not the working tree or index
         # ![13-test_init_repo_object]
@@ -194,7 +193,7 @@ class Tutorials(TestBase):
     def test_references_and_objects(self, rw_dir):
         # [1-test_references_and_objects]
         import git
-        repo = git.Repo.clone_from(self._small_repo_url(), osp.join(rw_dir, 'repo'), branch='master')
+        repo = git.Repo.clone_from(self._small_repo_url(), os.path.join(rw_dir, 'repo'), branch='master')
 
         heads = repo.heads
         master = heads.master       # lists can be accessed by name for convenience
@@ -266,7 +265,7 @@ class Tutorials(TestBase):
 
         # [11-test_references_and_objects]
         hct.blobs[0].data_stream.read()        # stream object to read data from
-        hct.blobs[0].stream_data(open(osp.join(rw_dir, 'blob_data'), 'wb'))  # write data to given stream
+        hct.blobs[0].stream_data(open(os.path.join(rw_dir, 'blob_data'), 'wb'))  # write data to given stream
         # ![11-test_references_and_objects]
 
         # [12-test_references_and_objects]
@@ -352,11 +351,11 @@ class Tutorials(TestBase):
         # Access blob objects
         for (path, stage), entry in index.entries.items():  # @UnusedVariable
             pass
-        new_file_path = osp.join(repo.working_tree_dir, 'new-file-name')
+        new_file_path = os.path.join(repo.working_tree_dir, 'new-file-name')
         open(new_file_path, 'w').close()
         index.add([new_file_path])                                             # add a new file to the index
         index.remove(['LICENSE'])                                              # remove an existing one
-        assert osp.isfile(osp.join(repo.working_tree_dir, 'LICENSE'))  # working tree is untouched
+        assert os.path.isfile(os.path.join(repo.working_tree_dir, 'LICENSE'))  # working tree is untouched
 
         self.assertEqual(index.commit("my commit message").type, 'commit')              # commit changed index
         repo.active_branch.commit = repo.commit('HEAD~1')                      # forget last commit
@@ -375,11 +374,11 @@ class Tutorials(TestBase):
         # merge two trees three-way into memory
         merge_index = IndexFile.from_tree(repo, 'HEAD~10', 'HEAD', repo.merge_base('HEAD~10', 'HEAD'))
         # and persist it
-        merge_index.write(osp.join(rw_dir, 'merged_index'))
+        merge_index.write(os.path.join(rw_dir, 'merged_index'))
         # ![24-test_references_and_objects]
 
         # [25-test_references_and_objects]
-        empty_repo = git.Repo.init(osp.join(rw_dir, 'empty'))
+        empty_repo = git.Repo.init(os.path.join(rw_dir, 'empty'))
         origin = empty_repo.create_remote('origin', repo.remotes.origin.url)
         assert origin.exists()
         assert origin == empty_repo.remotes.origin == empty_repo.remotes['origin']
@@ -482,8 +481,8 @@ class Tutorials(TestBase):
     def test_add_file_and_commit(self, rw_dir):
         import git
 
-        repo_dir = osp.join(rw_dir, 'my-new-repo')
-        file_name = osp.join(repo_dir, 'new-file')
+        repo_dir = os.path.join(rw_dir, 'my-new-repo')
+        file_name = os.path.join(repo_dir, 'new-file')
 
         r = git.Repo.init(repo_dir)
         # This function just creates an empty file ...
diff --git a/git/test/test_repo.py b/git/test/test_repo.py
index 8b43051e..e65ead89 100644
--- a/git/test/test_repo.py
+++ b/git/test/test_repo.py
@@ -110,6 +110,14 @@ class TestRepo(TestBase):
         assert not rw_repo.git.working_dir.endswith('.git')
         self.assertEqual(r_from_gitdir.git.working_dir, rw_repo.git.working_dir)
 
+    @with_rw_repo('0.3.2.1')
+    def test_repo_creation_pathlib(self, rw_repo):
+        if pathlib is None:  # pythons bellow 3.4 don't have pathlib
+            raise SkipTest("pathlib was introduced in 3.4")
+
+        r_from_gitdir = Repo(pathlib.Path(rw_repo.git_dir))
+        self.assertEqual(r_from_gitdir.git_dir, rw_repo.git_dir)
+
     def test_description(self):
         txt = "Test repository"
         self.rorepo.description = txt