[Python-modules-commits] [python-git] 03/08: Import python-git_2.0.5.orig.tar.gz

Barry Warsaw barry at moszumanska.debian.org
Wed Jun 15 10:56:31 UTC 2016


This is an automated email from the git hooks/post-receive script.

barry pushed a commit to branch master
in repository python-git.

commit 673a76fe139b987b1c91ee01bf88a3ea956b5129
Author: Barry Warsaw <barry at python.org>
Date:   Wed Jun 15 12:35:06 2016 +0300

    Import python-git_2.0.5.orig.tar.gz
---
 CHANGES                                   |  2 +-
 GitPython.egg-info/PKG-INFO               |  2 +-
 GitPython.egg-info/SOURCES.txt            |  1 +
 PKG-INFO                                  |  2 +-
 VERSION                                   |  2 +-
 doc/source/changes.rst                    | 28 ++++++++++
 doc/source/intro.rst                      | 24 +++++----
 git/__init__.py                           |  4 +-
 git/cmd.py                                | 21 ++++----
 git/compat.py                             |  2 +-
 git/diff.py                               | 26 ++++++++-
 git/index/fun.py                          |  2 +-
 git/objects/commit.py                     |  6 +--
 git/remote.py                             | 90 +++++++++++++++++++++++--------
 git/repo/base.py                          | 17 ++++--
 git/test/fixtures/blame_incremental       |  2 +-
 git/test/fixtures/commit_invalid_data     |  6 +++
 git/test/fixtures/diff_patch_unsafe_paths |  7 +++
 git/test/test_base.py                     |  2 +-
 git/test/test_commit.py                   |  7 +++
 git/test/test_diff.py                     | 15 +++---
 git/test/test_docs.py                     | 13 ++---
 git/test/test_git.py                      |  2 +-
 git/test/test_index.py                    |  2 +-
 git/test/test_remote.py                   |  8 +++
 git/test/test_repo.py                     |  2 +-
 git/util.py                               | 30 ++++++++---
 requirements.txt                          |  1 -
 28 files changed, 240 insertions(+), 86 deletions(-)

diff --git a/CHANGES b/CHANGES
index 9242253..aa8116b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,2 +1,2 @@
 Please see the online documentation for the latest changelog:
-https://github.com/gitpython-developers/GitPython/blob/0.3/doc/source/changes.rst
+https://github.com/gitpython-developers/GitPython/blob/master/doc/source/changes.rst
diff --git a/GitPython.egg-info/PKG-INFO b/GitPython.egg-info/PKG-INFO
index 0bafac6..b13be03 100644
--- a/GitPython.egg-info/PKG-INFO
+++ b/GitPython.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: GitPython
-Version: 2.0.2
+Version: 2.0.5
 Summary: Python Git Library
 Home-page: https://github.com/gitpython-developers/GitPython
 Author: Sebastian Thiel, Michael Trier
diff --git a/GitPython.egg-info/SOURCES.txt b/GitPython.egg-info/SOURCES.txt
index c1db831..3c82ba0 100644
--- a/GitPython.egg-info/SOURCES.txt
+++ b/GitPython.egg-info/SOURCES.txt
@@ -88,6 +88,7 @@ git/test/fixtures/cat_file.py
 git/test/fixtures/cat_file_blob
 git/test/fixtures/cat_file_blob_nl
 git/test/fixtures/cat_file_blob_size
+git/test/fixtures/commit_invalid_data
 git/test/fixtures/commit_with_gpgsig
 git/test/fixtures/diff_2
 git/test/fixtures/diff_2f
diff --git a/PKG-INFO b/PKG-INFO
index 0bafac6..b13be03 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: GitPython
-Version: 2.0.2
+Version: 2.0.5
 Summary: Python Git Library
 Home-page: https://github.com/gitpython-developers/GitPython
 Author: Sebastian Thiel, Michael Trier
diff --git a/VERSION b/VERSION
index e9307ca..e010258 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.0.2
+2.0.5
diff --git a/doc/source/changes.rst b/doc/source/changes.rst
index 51d3954..6a8e87d 100644
--- a/doc/source/changes.rst
+++ b/doc/source/changes.rst
@@ -2,6 +2,34 @@
 Changelog
 =========
 
+2.0.5 - Fixes
+=============
+
+* Fix: parser of fetch info lines choked on some legitimate lines
+
+2.0.4 - Fixes
+=============
+
+* Fix: parser of commit object data is now robust against cases where
+  commit object contains invalid bytes.  The invalid characters are now
+  replaced rather than choked on.
+* Fix: non-ASCII paths are now properly decoded and returned in
+  ``.diff()`` output
+* Fix: `RemoteProgress` will now strip the ', ' prefix or suffix from messages.
+* API: Remote.[fetch|push|pull](...) methods now allow the ``progress`` argument to
+  be a callable. This saves you from creating a custom type with usually just one
+  implemented method.
+
+2.0.3 - Fixes
+=============
+
+* Fix: bug in ``git-blame --incremental`` output parser that broken when
+  commit messages contained ``\r`` characters
+* Fix: progress handler exceptions are not caught anymore, which would usually just hide bugs
+  previously.
+* Fix: The `Git.execute` method will now redirect `stdout` to `devnull` if `with_stdout` is false, 
+  which is the intended behaviour based on the parameter's documentation.
+
 2.0.2 - Fixes
 =============
 
diff --git a/doc/source/intro.rst b/doc/source/intro.rst
index 78d4034..6c4e50f 100644
--- a/doc/source/intro.rst
+++ b/doc/source/intro.rst
@@ -13,6 +13,9 @@ The object database implementation is optimized for handling large quantities of
 Requirements
 ============
 
+* `Python`_ 2.7 or newer
+    Since GitPython 2.0.0. Please note that python 2.6 is still reasonably well supported, but might
+    deteriorate over time.
 * `Git`_ 1.7.0 or newer
     It should also work with older versions, but it may be that some operations
     involving remotes will not work as expected.
@@ -20,10 +23,11 @@ Requirements
 * `Python Nose`_ - used for running the tests
 * `Mock by Michael Foord`_ used for tests. Requires version 0.5
 
-.. _Git: http://git-scm.com/
-.. _Python Nose: http://code.google.com/p/python-nose/
+.. _Python: https://www.python.org
+.. _Git: https://git-scm.com/
+.. _Python Nose: https://nose.readthedocs.io/en/latest/
 .. _Mock by Michael Foord: http://www.voidspace.org.uk/python/mock.html
-.. _GitDB: http://pypi.python.org/pypi/gitdb
+.. _GitDB: https://pypi.python.org/pypi/gitdb
 
 Installing GitPython
 ====================
@@ -52,7 +56,7 @@ script:
 .. sourcecode:: none
 
     # python setup.py install
-    
+
 .. note:: In this case, you have to manually install `GitDB`_ as well. It would be recommended to use the :ref:`git source repository <source-code-label>` in that case.
 
 Getting Started
@@ -80,16 +84,16 @@ GitPython's git repo is available on GitHub, which can be browsed at:
 and cloned using::
 
 	$ git clone https://github.com/gitpython-developers/GitPython git-python
-	
+
 Initialize all submodules to obtain the required dependencies with::
-    
+
     $ cd git-python
     $ git submodule update --init --recursive
-    
+
 Finally verify the installation by running the `nose powered <http://code.google.com/p/python-nose/>`_ unit tests::
-    
+
     $ nosetests
-    
+
 Questions and Answers
 =====================
 Please use stackoverflow for questions, and don't forget to tag it with `gitpython` to assure the right people see the question in a timely manner.
@@ -101,7 +105,7 @@ Issue Tracker
 The issue tracker is hosted by github:
 
 https://github.com/gitpython-developers/GitPython/issues
-	
+
 License Information
 ===================
 GitPython is licensed under the New BSD License.  See the LICENSE file for
diff --git a/git/__init__.py b/git/__init__.py
index 4ed60c4..81752b4 100644
--- a/git/__init__.py
+++ b/git/__init__.py
@@ -9,13 +9,13 @@ import os
 import sys
 import inspect
 
-__version__ = '2.0.2'
+__version__ = '2.0.5'
 
 
 #{ Initialization
 def _init_externals():
     """Initialize external projects by putting them into the path"""
-    if __version__ == '2.0.2':
+    if __version__ == '2.0.5':
         sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'ext', 'gitdb'))
 
     try:
diff --git a/git/cmd.py b/git/cmd.py
index e4e3d6d..c29e348 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -13,6 +13,8 @@ import threading
 import errno
 import mmap
 
+from git.odict import OrderedDict
+
 from contextlib import contextmanager
 import signal
 from subprocess import (
@@ -42,7 +44,8 @@ from git.compat import (
 
 execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
                   'with_exceptions', 'as_process', 'stdout_as_string',
-                  'output_stream', 'with_stdout', 'kill_after_timeout')
+                  'output_stream', 'with_stdout', 'kill_after_timeout',
+                  'universal_newlines')
 
 log = logging.getLogger('git.cmd')
 log.addHandler(logging.NullHandler())
@@ -111,12 +114,7 @@ def handle_process_output(process, stdout_handler, stderr_handler, finalizer):
     def _dispatch_single_line(line, handler):
         line = line.decode(defenc)
         if line and handler:
-            try:
-                handler(line)
-            except Exception:
-                # Keep reading, have to pump the lines empty nontheless
-                log.error("Line handler exception on line: %s", line, exc_info=True)
-            # end
+            handler(line)
         # end dispatch helper
     # end single line helper
 
@@ -490,6 +488,7 @@ class Git(LazyMixin):
                 stdout_as_string=True,
                 kill_after_timeout=None,
                 with_stdout=True,
+                universal_newlines=False,
                 **subprocess_kwargs
                 ):
         """Handles executing the command on the shell and consumes and returns
@@ -544,7 +543,9 @@ class Git(LazyMixin):
             specify may not be the same ones.
 
         :param with_stdout: If True, default True, we open stdout on the created process
-
+        :param universal_newlines:
+            if True, pipes will be opened as text, and lines are split at
+            all known line endings.
         :param kill_after_timeout:
             To specify a timeout in seconds for the git command, after which the process
             should be killed. This will have no effect if as_process is set to True. It is
@@ -608,9 +609,10 @@ class Git(LazyMixin):
                          bufsize=-1,
                          stdin=istream,
                          stderr=PIPE,
-                         stdout=with_stdout and PIPE or None,
+                         stdout=PIPE if with_stdout else open(os.devnull, 'wb'),
                          shell=self.USE_SHELL,
                          close_fds=(os.name == 'posix'),  # unsupported on windows
+                         universal_newlines=universal_newlines,
                          **subprocess_kwargs
                          )
         except cmd_not_found_exception as err:
@@ -783,6 +785,7 @@ class Git(LazyMixin):
     def transform_kwargs(self, split_single_char_options=True, **kwargs):
         """Transforms Python style kwargs into git command line options."""
         args = list()
+        kwargs = OrderedDict(sorted(kwargs.items(), key=lambda x: x[0]))
         for k, v in kwargs.items():
             if isinstance(v, (list, tuple)):
                 for value in v:
diff --git a/git/compat.py b/git/compat.py
index 7bd8e49..76509ba 100644
--- a/git/compat.py
+++ b/git/compat.py
@@ -1,4 +1,4 @@
-#-*-coding:utf-8-*-
+# -*- coding: utf-8 -*-
 # config.py
 # Copyright (C) 2008, 2009 Michael Trier (mtrier at gmail.com) and contributors
 #
diff --git a/git/diff.py b/git/diff.py
index 7642694..9073767 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -15,12 +15,23 @@ from git.compat import (
     PY3
 )
 
-
 __all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE')
 
 # Special object to compare against the empty tree in diffs
 NULL_TREE = object()
 
+_octal_byte_re = re.compile(b'\\\\([0-9]{3})')
+
+
+def _octal_repl(matchobj):
+    value = matchobj.group(1)
+    value = int(value, 8)
+    if PY3:
+        value = bytes(bytearray((value,)))
+    else:
+        value = chr(value)
+    return value
+
 
 def decode_path(path, has_ab_prefix=True):
     if path == b'/dev/null':
@@ -32,6 +43,8 @@ def decode_path(path, has_ab_prefix=True):
                           .replace(b'\\"', b'"')
                           .replace(b'\\\\', b'\\'))
 
+    path = _octal_byte_re.sub(_octal_repl, path)
+
     if has_ab_prefix:
         assert path.startswith(b'a/') or path.startswith(b'b/')
         path = path[2:]
@@ -333,7 +346,16 @@ class Diff(object):
 
     @property
     def renamed(self):
-        """:returns: True if the blob of our diff has been renamed"""
+        """:returns: True if the blob of our diff has been renamed
+        :note: This property is deprecated, please use ``renamed_file`` instead.
+        """
+        return self.renamed_file
+
+    @property
+    def renamed_file(self):
+        """:returns: True if the blob of our diff has been renamed
+        :note: This property is deprecated, please use ``renamed_file`` instead.
+        """
         return self.rename_from != self.rename_to
 
     @classmethod
diff --git a/git/index/fun.py b/git/index/fun.py
index c1026fd..4dd32b1 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -93,7 +93,7 @@ def stat_mode_to_index_mode(mode):
         return S_IFLNK
     if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK:    # submodules
         return S_IFGITLINK
-    return S_IFREG | 0o644 | (mode & 0o100)       # blobs with or without executable bit
+    return S_IFREG | 0o644 | (mode & 0o111)       # blobs with or without executable bit
 
 
 def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1Writer):
diff --git a/git/objects/commit.py b/git/objects/commit.py
index dc722f9..58a8912 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -501,14 +501,14 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
 
         try:
             self.author, self.authored_date, self.author_tz_offset = \
-                parse_actor_and_date(author_line.decode(self.encoding))
+                parse_actor_and_date(author_line.decode(self.encoding, errors='replace'))
         except UnicodeDecodeError:
             log.error("Failed to decode author line '%s' using encoding %s", author_line, self.encoding,
                       exc_info=True)
 
         try:
             self.committer, self.committed_date, self.committer_tz_offset = \
-                parse_actor_and_date(committer_line.decode(self.encoding))
+                parse_actor_and_date(committer_line.decode(self.encoding, errors='replace'))
         except UnicodeDecodeError:
             log.error("Failed to decode committer line '%s' using encoding %s", committer_line, self.encoding,
                       exc_info=True)
@@ -518,7 +518,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
         # The end of our message stream is marked with a newline that we strip
         self.message = stream.read()
         try:
-            self.message = self.message.decode(self.encoding)
+            self.message = self.message.decode(self.encoding, errors='replace')
         except UnicodeDecodeError:
             log.error("Failed to decode message '%s' using encoding %s", self.message, self.encoding, exc_info=True)
         # END exception handling
diff --git a/git/remote.py b/git/remote.py
index e430abf..4275397 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -20,13 +20,12 @@ from .refs import (
     SymbolicReference,
     TagReference
 )
-
-
 from git.util import (
     LazyMixin,
     Iterable,
     IterableList,
-    RemoteProgress
+    RemoteProgress,
+    CallableRemoteProgress
 )
 from git.util import (
     join_path,
@@ -34,7 +33,10 @@ from git.util import (
 )
 from git.cmd import handle_process_output
 from gitdb.util import join
-from git.compat import defenc
+from git.compat import (defenc, force_text)
+import logging
+
+log = logging.getLogger('git.remote')
 
 
 __all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote')
@@ -48,8 +50,8 @@ def add_progress(kwargs, git, progress):
     given, we do not request any progress
     :return: possibly altered kwargs"""
     if progress is not None:
-        v = git.version_info
-        if v[0] > 1 or v[1] > 7 or v[2] > 0 or v[3] > 3:
+        v = git.version_info[:2]
+        if v >= (1, 7):
             kwargs['progress'] = True
         # END handle --progress
     # END handle progress
@@ -58,6 +60,23 @@ def add_progress(kwargs, git, progress):
 #} END utilities
 
 
+def to_progress_instance(progress):
+    """Given the 'progress' return a suitable object derived from
+    RemoteProgress().
+    """
+    # new API only needs progress as a function
+    if callable(progress):
+        return CallableRemoteProgress(progress)
+
+    # where None is passed create a parser that eats the progress
+    elif progress is None:
+        return RemoteProgress()
+
+    # assume its the old API with an instance of RemoteProgress.
+    else:
+        return progress
+
+
 class PushInfo(object):
 
     """
@@ -185,7 +204,7 @@ class FetchInfo(object):
     NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \
         FAST_FORWARD, ERROR = [1 << x for x in range(8)]
 
-    re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([/\w_\+\.\-$@#]+)(    \(.*\)?$)?")
+    re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([/\w_\+\.\-$@#()]+)(    \(.*\)?$)?")
 
     _flag_map = {'!': ERROR,
                  '+': FORCED_UPDATE,
@@ -536,6 +555,8 @@ class Remote(LazyMixin, Iterable):
         return self
 
     def _get_fetch_info_from_stderr(self, proc, progress):
+        progress = to_progress_instance(progress)
+
         # skip first line as it is some remote info we are not interested in
         output = IterableList('name')
 
@@ -549,8 +570,8 @@ class Remote(LazyMixin, Iterable):
 
         progress_handler = progress.new_message_handler()
 
-        for line in proc.stderr.readlines():
-            line = line.decode(defenc)
+        for line in proc.stderr:
+            line = force_text(line)
             for pline in progress_handler(line):
                 if line.startswith('fatal:') or line.startswith('error:'):
                     raise GitCommandError(("Error when fetching: %s" % line,), 2)
@@ -570,16 +591,28 @@ class Remote(LazyMixin, Iterable):
         fetch_head_info = [l.decode(defenc) for l in fp.readlines()]
         fp.close()
 
-        # NOTE: We assume to fetch at least enough progress lines to allow matching each fetch head line with it.
         l_fil = len(fetch_info_lines)
         l_fhi = len(fetch_head_info)
-        assert l_fil >= l_fhi, "len(%s) <= len(%s)" % (l_fil, l_fhi)
-
+        if l_fil != l_fhi:
+            msg = "Fetch head lines do not match lines provided via progress information\n"
+            msg += "length of progress lines %i should be equal to lines in FETCH_HEAD file %i\n"
+            msg += "Will ignore extra progress lines or fetch head lines."
+            msg %= (l_fil, l_fhi)
+            log.debug(msg)
+            if l_fil < l_fhi:
+                fetch_head_info = fetch_head_info[:l_fil]
+            else:
+                fetch_info_lines = fetch_info_lines[:l_fhi]
+            # end truncate correct list
+        # end sanity check + sanitization
+        
         output.extend(FetchInfo._from_line(self.repo, err_line, fetch_line)
                       for err_line, fetch_line in zip(fetch_info_lines, fetch_head_info))
         return output
 
     def _get_push_info(self, proc, progress):
+        progress = to_progress_instance(progress)
+
         # read progress information from stderr
         # we hope stdout can hold all the data, it should ...
         # read the lines manually as it will use carriage returns between the messages
@@ -652,9 +685,9 @@ class Remote(LazyMixin, Iterable):
         else:
             args = [refspec]
 
-        proc = self.repo.git.fetch(self, *args, as_process=True, with_stdout=False, v=True,
-                                   **kwargs)
-        res = self._get_fetch_info_from_stderr(proc, progress or RemoteProgress())
+        proc = self.repo.git.fetch(self, *args, as_process=True, with_stdout=False,
+                                   universal_newlines=True, v=True, **kwargs)
+        res = self._get_fetch_info_from_stderr(proc, progress)
         if hasattr(self.repo.odb, 'update_cache'):
             self.repo.odb.update_cache()
         return res
@@ -671,8 +704,9 @@ class Remote(LazyMixin, Iterable):
             # No argument refspec, then ensure the repo's config has a fetch refspec.
             self._assert_refspec()
         kwargs = add_progress(kwargs, self.repo.git, progress)
-        proc = self.repo.git.pull(self, refspec, with_stdout=False, as_process=True, v=True, **kwargs)
-        res = self._get_fetch_info_from_stderr(proc, progress or RemoteProgress())
+        proc = self.repo.git.pull(self, refspec, with_stdout=False, as_process=True,
+                                  universal_newlines=True, v=True, **kwargs)
+        res = self._get_fetch_info_from_stderr(proc, progress)
         if hasattr(self.repo.odb, 'update_cache'):
             self.repo.odb.update_cache()
         return res
@@ -682,10 +716,19 @@ class Remote(LazyMixin, Iterable):
 
         :param refspec: see 'fetch' method
         :param progress:
-            Instance of type RemoteProgress allowing the caller to receive
-            progress information until the method returns.
-            If None, progress information will be discarded
-
+            Can take one of many value types:
+            
+            * None to discard progress information
+            * A function (callable) that is called with the progress infomation.
+            
+              Signature: ``progress(op_code, cur_count, max_count=None, message='')``.
+              
+             `Click here <http://goo.gl/NPa7st>`_ for a description of all arguments
+              given to the function.
+            * An instance of a class derived from ``git.RemoteProgress`` that
+              overrides the ``update()`` function.
+              
+        :note: No further progress information is returned after push returns.
         :param kwargs: Additional arguments to be passed to git-push
         :return:
             IterableList(PushInfo, ...) iterable list of PushInfo instances, each
@@ -696,8 +739,9 @@ class Remote(LazyMixin, Iterable):
             If the operation fails completely, the length of the returned IterableList will
             be null."""
         kwargs = add_progress(kwargs, self.repo.git, progress)
-        proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True, **kwargs)
-        return self._get_push_info(proc, progress or RemoteProgress())
+        proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True,
+                                  universal_newlines=True, **kwargs)
+        return self._get_push_info(proc, progress)
 
     @property
     def config_reader(self):
diff --git a/git/repo/base.py b/git/repo/base.py
index 0274c0a..f43cc46 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -32,7 +32,8 @@ from git.index import IndexFile
 from git.config import GitConfigParser
 from git.remote import (
     Remote,
-    add_progress
+    add_progress,
+    to_progress_instance
 )
 
 from git.db import GitCmdObjectDB
@@ -254,7 +255,9 @@ class Repo(object):
 
     @property
     def index(self):
-        """:return: IndexFile representing this repository's index."""
+        """:return: IndexFile representing this repository's index.
+        :note: This property can be expensive, as the returned ``IndexFile`` will be
+         reinitialized. It's recommended to re-use the object."""
         return IndexFile(self)
 
     @property
@@ -624,7 +627,10 @@ class Repo(object):
             are relative to the current working directory of the git command.
 
         :note:
-            ignored files will not appear here, i.e. files mentioned in .gitignore"""
+            ignored files will not appear here, i.e. files mentioned in .gitignore
+        :note:
+            This property is expensive, as no cache is involved. To process the result, please
+            consider caching it yourself."""
         return self._get_untracked_files()
 
     def _get_untracked_files(self, **kwargs):
@@ -677,10 +683,9 @@ class Repo(object):
         data = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs)
         commits = dict()
 
-        stream = iter(data.splitlines())
+        stream = (line for line in data.split(b'\n') if line)
         while True:
             line = next(stream)  # when exhausted, casues a StopIteration, terminating this function
-
             hexsha, orig_lineno, lineno, num_lines = line.split()
             lineno = int(lineno)
             num_lines = int(num_lines)
@@ -868,6 +873,8 @@ class Repo(object):
 
     @classmethod
     def _clone(cls, git, url, path, odb_default_type, progress, **kwargs):
+        progress = to_progress_instance(progress)
+
         # special handling for windows for path at which the clone should be
         # created.
         # tilde '~' will be expanded to the HOME no matter where the ~ occours. Hence
diff --git a/git/test/fixtures/blame_incremental b/git/test/fixtures/blame_incremental
index 9a0d9e3..67310ae 100644
--- a/git/test/fixtures/blame_incremental
+++ b/git/test/fixtures/blame_incremental
@@ -7,7 +7,7 @@ committer Sebastian Thiel
 committer-mail <byronimo at gmail.com>
 committer-time 1270634931
 committer-tz +0200
-summary Used this release for a first beta of the 0.2 branch of development
+summary Used this release

 for a first beta of the 0.2 branch of development
 previous 501bf602abea7d21c3dbb409b435976e92033145 AUTHORS
 filename AUTHORS
 82b8902e033430000481eb355733cd7065342037 14 14 1
diff --git a/git/test/fixtures/commit_invalid_data b/git/test/fixtures/commit_invalid_data
new file mode 100644
index 0000000..d112bf2
--- /dev/null
+++ b/git/test/fixtures/commit_invalid_data
@@ -0,0 +1,6 @@
+tree 9f1a495d7d9692d24f5caedaa89f5c2c32d59368
+parent 492ace2ffce0e426ebeb55e364e987bcf024dd3b
+author E.Azer Ko�o�o�oculu <azer at kodfabrik.com> 1306710073 +0300
+committer E.Azer Ko�o�o�oculu <azer at kodfabrik.com> 1306710073 +0300
+
+add environjs
diff --git a/git/test/fixtures/diff_patch_unsafe_paths b/git/test/fixtures/diff_patch_unsafe_paths
index 14375f7..9ee6b83 100644
--- a/git/test/fixtures/diff_patch_unsafe_paths
+++ b/git/test/fixtures/diff_patch_unsafe_paths
@@ -61,6 +61,13 @@ index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94
 +++ "b/path/¯\\_(ツ)_|¯"
 @@ -0,0 +1 @@
 +dummy content
+diff --git "a/path/\360\237\222\251.txt" "b/path/\360\237\222\251.txt"
+new file mode 100644
+index 0000000000000000000000000000000000000000..eaf5f7510320b6a327fb308379de2f94d8859a54
+--- /dev/null
++++ "b/path/\360\237\222\251.txt"
+@@ -0,0 +1 @@
++dummy content
 diff --git a/a/with spaces b/b/with some spaces
 similarity index 100%
 rename from a/with spaces
diff --git a/git/test/test_base.py b/git/test/test_base.py
index 94379ca..7b71a77 100644
--- a/git/test/test_base.py
+++ b/git/test/test_base.py
@@ -1,4 +1,4 @@
-#-*-coding:utf-8-*-
+# -*- coding: utf-8 -*-
 # test_base.py
 # Copyright (C) 2008, 2009 Michael Trier (mtrier at gmail.com) and contributors
 #
diff --git a/git/test/test_commit.py b/git/test/test_commit.py
index 23b7154..ea8cd9a 100644
--- a/git/test/test_commit.py
+++ b/git/test/test_commit.py
@@ -306,6 +306,13 @@ class TestCommit(TestBase):
         # it appears
         cmt.author.__repr__()
 
+    def test_invalid_commit(self):
+        cmt = self.rorepo.commit()
+        cmt._deserialize(open(fixture_path('commit_invalid_data'), 'rb'))
+
+        assert cmt.author.name == u'E.Azer Ko�o�o�oculu', cmt.author.name
+        assert cmt.author.email == 'azer at kodfabrik.com', cmt.author.email
+
     def test_gpgsig(self):
         cmt = self.rorepo.commit()
         cmt._deserialize(open(fixture_path('commit_with_gpgsig'), 'rb'))
diff --git a/git/test/test_diff.py b/git/test/test_diff.py
index 858b399..8966351 100644
--- a/git/test/test_diff.py
+++ b/git/test/test_diff.py
@@ -86,6 +86,7 @@ class TestDiff(TestBase):
         assert_equal(1, len(diffs))
 
         diff = diffs[0]
+        assert_true(diff.renamed_file)
         assert_true(diff.renamed)
         assert_equal(diff.rename_from, u'Jérôme')
         assert_equal(diff.rename_to, u'müller')
@@ -95,6 +96,7 @@ class TestDiff(TestBase):
         diffs = Diff._index_from_raw_format(self.rorepo, output.stdout)
         assert len(diffs) == 1
         diff = diffs[0]
+        assert diff.renamed_file
         assert diff.renamed
         assert diff.rename_from == 'this'
         assert diff.rename_to == 'that'
@@ -159,16 +161,17 @@ class TestDiff(TestBase):
         self.assertEqual(res[6].b_path, u'path/with spaces')
         self.assertEqual(res[7].b_path, u'path/with-question-mark?')
         self.assertEqual(res[8].b_path, u'path/¯\\_(ツ)_|¯')
+        self.assertEqual(res[9].b_path, u'path/💩.txt')
 
         # The "Moves"
         # NOTE: The path prefixes a/ and b/ here are legit!  We're actually
         # verifying that it's not "a/a/" that shows up, see the fixture data.
-        self.assertEqual(res[9].a_path, u'a/with spaces')       # NOTE: path a/ here legit!
-        self.assertEqual(res[9].b_path, u'b/with some spaces')  # NOTE: path b/ here legit!
-        self.assertEqual(res[10].a_path, u'a/ending in a space ')
-        self.assertEqual(res[10].b_path, u'b/ending with space ')
-        self.assertEqual(res[11].a_path, u'a/"with-quotes"')
-        self.assertEqual(res[11].b_path, u'b/"with even more quotes"')
+        self.assertEqual(res[10].a_path, u'a/with spaces')       # NOTE: path a/ here legit!
+        self.assertEqual(res[10].b_path, u'b/with some spaces')  # NOTE: path b/ here legit!
+        self.assertEqual(res[11].a_path, u'a/ending in a space ')
+        self.assertEqual(res[11].b_path, u'b/ending with space ')
+        self.assertEqual(res[12].a_path, u'a/"with-quotes"')
+        self.assertEqual(res[12].b_path, u'b/"with even more quotes"')
 
     def test_diff_patch_format(self):
         # test all of the 'old' format diffs for completness - it should at least
diff --git a/git/test/test_docs.py b/git/test/test_docs.py
index 189cdc4..2747074 100644
--- a/git/test/test_docs.py
+++ b/git/test/test_docs.py
@@ -1,4 +1,4 @@
-#-*-coding:utf-8-*-
+# -*- coding: utf-8 -*-
 # test_git.py
 # Copyright (C) 2008, 2009 Michael Trier (mtrier at gmail.com) and contributors
 #
@@ -7,11 +7,12 @@
 import os
 
 from git.test.lib import TestBase
-from gitdb.test.lib import with_rw_directory
+from gitdb.test.lib import skip_on_travis_ci, with_rw_directory
 
 
 class Tutorials(TestBase):
 
+    @skip_on_travis_ci
     @with_rw_directory
     def test_init_repo_object(self, rw_dir):
         # [1-test_init_repo_object]
@@ -165,7 +166,7 @@ class Tutorials(TestBase):
         for sm in cloned_repo.submodules:
             assert not sm.remove().exists()                   # after removal, the sm doesn't exist anymore
         sm = cloned_repo.create_submodule('mysubrepo', 'path/to/subrepo', url=bare_repo.git_dir, branch='master')
-        
+
         # .gitmodules was written and added to the index, which is now being committed
         cloned_repo.index.commit("Added submodule")
         assert sm.exists() and sm.module_exists()             # this submodule is defintely available
@@ -395,7 +396,7 @@ class Tutorials(TestBase):
         hcommit.diff()                  # diff tree against index
         hcommit.diff('HEAD~1')          # diff tree against previous tree
         hcommit.diff(None)              # diff tree against working tree
-        
+
         index = repo.index
         index.diff()                    # diff index against itself yielding empty diff
         index.diff(None)                # diff index against working copy
@@ -446,7 +447,7 @@ class Tutorials(TestBase):
         sm = sms[0]
         assert sm.name == 'gitdb'                         # git-python has gitdb as single submodule ...
         assert sm.children()[0].name == 'smmap'           # ... which has smmap as single submodule
-        
+
         # The module is the repository referenced by the submodule
         assert sm.module_exists()                         # the module is available, which doesn't have to be the case.
         assert sm.module().working_tree_dir.endswith('gitdb')
@@ -458,7 +459,7 @@ class Tutorials(TestBase):
         assert sm.config_reader().get_value('path') == sm.path
         assert len(sm.children()) == 1                    # query the submodule hierarchy
         # ![1-test_submodules]
-        
+
     @with_rw_directory
     def test_add_file_and_commit(self, rw_dir):
         import git
diff --git a/git/test/test_git.py b/git/test/test_git.py
index 00592b8..2d6ca8b 100644
--- a/git/test/test_git.py
+++ b/git/test/test_git.py
@@ -1,4 +1,4 @@
-#-*-coding:utf-8-*-
+# -*- coding: utf-8 -*-
 # test_git.py
 # Copyright (C) 2008, 2009 Michael Trier (mtrier at gmail.com) and contributors
 #
diff --git a/git/test/test_index.py b/git/test/test_index.py
index f5f9d70..ca87783 100644
--- a/git/test/test_index.py
+++ b/git/test/test_index.py
@@ -1,4 +1,4 @@
-#-*-coding:utf-8-*-
+# -*- coding: utf-8 -*-
 # test_index.py
 # Copyright (C) 2008, 2009 Michael Trier (mtrier at gmail.com) and contributors
 #
diff --git a/git/test/test_remote.py b/git/test/test_remote.py
index 6c37614..9ca2f20 100644
--- a/git/test/test_remote.py
+++ b/git/test/test_remote.py
@@ -62,6 +62,14 @@ class TestRemoteProgress(RemoteProgress):
         # check each stage only comes once
         op_id = op_code & self.OP_MASK
         assert op_id in (self.COUNTING, self.COMPRESSING, self.WRITING)
+        
+        if op_code & self.WRITING > 0:
+            if op_code & self.BEGIN > 0:
+                assert not message, 'should not have message when remote begins writing'
+            elif op_code & self.END > 0:
+                assert message
+                assert not message.startswith(', '), "Sanitize progress messages: '%s'" % message
+                assert not message.endswith(', '), "Sanitize progress messages: '%s'" % message
 
         self._stages_per_op.setdefault(op_id, 0)
         self._stages_per_op[op_id] = self._stages_per_op[op_id] | (op_code & self.STAGE_MASK)
diff --git a/git/test/test_repo.py b/git/test/test_repo.py
index d7437d3..fc8125f 100644
--- a/git/test/test_repo.py
+++ b/git/test/test_repo.py
@@ -1,4 +1,4 @@
-#-*-coding:utf-8-*-
+# -*- coding: utf-8 -*-
 # test_repo.py
 # Copyright (C) 2008, 2009 Michael Trier (mtrier at gmail.com) and contributors
 #
diff --git a/git/util.py b/git/util.py
index bfcb894..5ed014f 100644
--- a/git/util.py
+++ b/git/util.py
@@ -39,7 +39,7 @@ from gitdb.util import (  # NOQA
 __all__ = ("stream_copy", "join_path", "to_native_path_windows", "to_native_path_linux",
            "join_path_native", "Stats", "IndexFileSHA1Writer", "Iterable", "IterableList",
            "BlockingLockFile", "LockFile", 'Actor', 'get_user_id', 'assure_directory_exists',
-           'RemoteProgress', 'rmtree', 'WaitGroup', 'unbare_repo')
+           'RemoteProgress', 'CallableRemoteProgress', 'rmtree', 'WaitGroup', 'unbare_repo')
 
 #{ Utility Methods
 
@@ -160,7 +160,6 @@ def finalize_process(proc, **kwargs):
 
 
 class RemoteProgress(object):
-
     """
     Handler providing an interface to parse progress information emitted by git-push
     and git-fetch and to dispatch callbacks allowing subclasses to react to the progress.
@@ -171,12 +170,16 @@ class RemoteProgress(object):
     STAGE_MASK = BEGIN | END
     OP_MASK = ~STAGE_MASK
 
+    DONE_TOKEN = 'done.'
+    TOKEN_SEPARATOR = ', '
+
     __slots__ = ("_cur_line", "_seen_ops")
-    re_op_absolute = re.compile("(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
-    re_op_relative = re.compile("(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
+    re_op_absolute = re.compile(r"(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
+    re_op_relative = re.compile(r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
 
     def __init__(self):
         self._seen_ops = list()
+        self._cur_line = None
 
     def _parse_progress_line(self, line):
         """Parse progress information from the given line as retrieved by git-push
@@ -257,11 +260,11 @@ class RemoteProgress(object):
             # END message handling
 
             message = message.strip()
-            done_token = ', done.'
-            if message.endswith(done_token):
+            if message.endswith(self.DONE_TOKEN):
                 op_code |= self.END
-                message = message[:-len(done_token)]
+                message = message[:-len(self.DONE_TOKEN)]
             # END end message handling
+            message = message.strip(self.TOKEN_SEPARATOR)
 
             self.update(op_code,
                         cur_count and float(cur_count),
@@ -309,10 +312,21 @@ class RemoteProgress(object):
 
         You may read the contents of the current line in self._cur_line"""
         pass
+        
 
+class CallableRemoteProgress(RemoteProgress):
+    """An implementation forwarding updates to any callable"""
+    __slots__ = ('_callable')
+    
+    def __init__(self, fn):
+        self._callable = fn
+        super(CallableRemoteProgress, self).__init__()
 
-class Actor(object):
+    def update(self, *args, **kwargs):
+        self._callable(*args, **kwargs)
 
+
+class Actor(object):
     """Actors hold information about a person acting on the repository. They
     can be committers and authors or anything with a name and an email as
     mentioned in the git log entries."""
diff --git a/requirements.txt b/requirements.txt
index d2d0da9..2316b96 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1 @@
-GitPython
 gitdb>=0.6.4

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-git.git



More information about the Python-modules-commits mailing list