summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2014-07-25 11:26:09 +0200
committerSebastian Thiel <byronimo@gmail.com>2014-07-25 11:26:09 +0200
commita66cfe99c1af3d745e929da6a61e1257e3a376b1 (patch)
treed8bc5213dd7e7c7f0befdf65afecb13d5435f873
parent75194159abce545bfa38c3172efb42da9b0017dc (diff)
parenta23d0d8617ba3119069e610fc7b0850a17322726 (diff)
downloadgitpython-a66cfe99c1af3d745e929da6a61e1257e3a376b1.tar.gz
Merge pull request #173 from craigez/feature/pep8
Autopep8 style whitespace cleanups & pre-commit hook
-rw-r--r--git/__init__.py23
-rw-r--r--git/base.py215
-rw-r--r--git/cmd.py246
-rw-r--r--git/config.py205
-rw-r--r--git/db/cmd/base.py395
-rw-r--r--git/db/cmd/complex.py6
-rw-r--r--git/db/compat.py17
-rw-r--r--git/db/complex.py8
-rw-r--r--git/db/dulwich/__init__.py4
-rw-r--r--git/db/dulwich/complex.py57
-rw-r--r--git/db/interface.py328
-rw-r--r--git/db/py/base.py278
-rw-r--r--git/db/py/complex.py78
-rw-r--r--git/db/py/loose.py128
-rw-r--r--git/db/py/mem.py65
-rw-r--r--git/db/py/pack.py88
-rw-r--r--git/db/py/ref.py20
-rw-r--r--git/db/py/resolve.py162
-rw-r--r--git/db/py/submodule.py13
-rw-r--r--git/db/py/transport.py35
-rw-r--r--git/db/pygit2/__init__.py3
-rw-r--r--git/db/pygit2/complex.py59
-rw-r--r--git/diff.py141
-rw-r--r--git/exc.py28
-rw-r--r--git/fun.py276
-rw-r--r--git/index/__init__.py2
-rw-r--r--git/index/base.py222
-rw-r--r--git/index/fun.py131
-rw-r--r--git/index/typ.py34
-rw-r--r--git/index/util.py19
-rw-r--r--git/objects/__init__.py6
-rw-r--r--git/objects/base.py87
-rw-r--r--git/objects/blob.py4
-rw-r--r--git/objects/commit.py189
-rw-r--r--git/objects/fun.py73
-rw-r--r--git/objects/submodule/base.py380
-rw-r--r--git/objects/submodule/root.py215
-rw-r--r--git/objects/submodule/util.py33
-rw-r--r--git/objects/tag.py41
-rw-r--r--git/objects/tree.py126
-rw-r--r--git/objects/util.py161
-rw-r--r--git/odict.py419
-rw-r--r--git/pack.py497
-rw-r--r--git/refs/__init__.py2
-rw-r--r--git/refs/head.py42
-rw-r--r--git/refs/headref.py70
-rw-r--r--git/refs/log.py159
-rw-r--r--git/refs/reference.py56
-rw-r--r--git/refs/remote.py24
-rw-r--r--git/refs/symbolic.py305
-rw-r--r--git/refs/tag.py38
-rw-r--r--git/remote.py140
-rw-r--r--git/repo.py15
-rw-r--r--git/stream.py348
-rw-r--r--git/test/__init__.py2
-rw-r--r--git/test/db/base.py285
-rw-r--r--git/test/db/cmd/test_base.py66
-rw-r--r--git/test/db/dulwich/lib.py8
-rw-r--r--git/test/db/dulwich/test_base.py10
-rw-r--r--git/test/db/lib.py105
-rw-r--r--git/test/db/py/test_base.py6
-rw-r--r--git/test/db/py/test_git.py22
-rw-r--r--git/test/db/py/test_loose.py18
-rw-r--r--git/test/db/py/test_mem.py13
-rw-r--r--git/test/db/py/test_pack.py36
-rw-r--r--git/test/db/py/test_ref.py32
-rw-r--r--git/test/db/pygit2/lib.py8
-rw-r--r--git/test/db/pygit2/test_base.py10
-rw-r--r--git/test/db/test_base.py2
-rw-r--r--git/test/lib/__init__.py6
-rw-r--r--git/test/lib/asserts.py18
-rw-r--r--git/test/lib/base.py76
-rw-r--r--git/test/lib/helper.py139
-rw-r--r--git/test/objects/__init__.py1
-rw-r--r--git/test/objects/lib.py16
-rw-r--r--git/test/objects/test_blob.py10
-rw-r--r--git/test/objects/test_commit.py132
-rw-r--r--git/test/objects/test_submodule.py253
-rw-r--r--git/test/objects/test_tree.py75
-rw-r--r--git/test/performance/db/__init__.py1
-rw-r--r--git/test/performance/db/looseodb_impl.py65
-rw-r--r--git/test/performance/db/odb_impl.py34
-rw-r--r--git/test/performance/db/packedodb_impl.py38
-rw-r--r--git/test/performance/db/test_looseodb_cmd.py7
-rw-r--r--git/test/performance/db/test_looseodb_dulwich.py4
-rw-r--r--git/test/performance/db/test_looseodb_pure.py2
-rw-r--r--git/test/performance/db/test_looseodb_pygit2.py4
-rw-r--r--git/test/performance/db/test_odb_cmd.py2
-rw-r--r--git/test/performance/db/test_odb_dulwich.py4
-rw-r--r--git/test/performance/db/test_odb_pure.py2
-rw-r--r--git/test/performance/db/test_odb_pygit2.py4
-rw-r--r--git/test/performance/db/test_packedodb_pure.py37
-rw-r--r--git/test/performance/lib.py32
-rw-r--r--git/test/performance/objects/__init__.py1
-rw-r--r--git/test/performance/objects/test_commit.py47
-rw-r--r--git/test/performance/test_utils.py87
-rw-r--r--git/test/refs/__init__.py1
-rw-r--r--git/test/refs/test_reflog.py50
-rw-r--r--git/test/refs/test_refs.py220
-rw-r--r--git/test/test_base.py123
-rw-r--r--git/test/test_cmd.py37
-rw-r--r--git/test/test_config.py49
-rw-r--r--git/test/test_diff.py57
-rw-r--r--git/test/test_example.py28
-rw-r--r--git/test/test_fun.py114
-rw-r--r--git/test/test_import.py27
-rw-r--r--git/test/test_index.py314
-rw-r--r--git/test/test_pack.py134
-rw-r--r--git/test/test_remote.py228
-rw-r--r--git/test/test_stats.py17
-rw-r--r--git/test/test_stream.py78
-rw-r--r--git/test/test_util.py111
-rw-r--r--git/typ.py4
-rw-r--r--git/util.py341
-rw-r--r--setup.py46
115 files changed, 5387 insertions, 5028 deletions
diff --git a/git/__init__.py b/git/__init__.py
index 91a10bb3..18c84bdd 100644
--- a/git/__init__.py
+++ b/git/__init__.py
@@ -14,16 +14,16 @@ __version__ = 'git'
#{ Initialization
def _init_externals():
"""Initialize external projects by putting them into the path"""
- ext_base = os.path.join(os.path.dirname(__file__), 'ext')
+ ext_base = os.path.join(os.path.dirname(__file__), 'ext')
for package in ('async', 'smmap'):
sys.path.append(os.path.join(ext_base, package))
try:
__import__(package)
except ImportError:
raise ImportError("%r could not be found in your PYTHONPATH" % package)
- #END verify import
- #END handle external import
-
+ # END verify import
+ # END handle external import
+
#} END initialization
#################
@@ -43,14 +43,13 @@ from git.remote import *
from git.index import *
from git.repo import Repo
from git.util import (
- LockFile,
- BlockingLockFile,
- Stats,
- Actor
- )
+ LockFile,
+ BlockingLockFile,
+ Stats,
+ Actor
+)
#} END imports
-__all__ = [ name for name, obj in locals().items()
- if not (name.startswith('_') or inspect.ismodule(obj)) ]
-
+__all__ = [name for name, obj in locals().items()
+ if not (name.startswith('_') or inspect.ismodule(obj))]
diff --git a/git/base.py b/git/base.py
index bad5f747..a3971ce4 100644
--- a/git/base.py
+++ b/git/base.py
@@ -4,192 +4,198 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Module with basic data structures - they are designed to be lightweight and fast"""
from util import (
- bin_to_hex,
- zlib
- )
+ bin_to_hex,
+ zlib
+)
from fun import (
- type_id_to_type_map,
- type_to_type_id_map
- )
+ type_id_to_type_map,
+ type_to_type_id_map
+)
-__all__ = ('OInfo', 'OPackInfo', 'ODeltaPackInfo',
- 'OStream', 'OPackStream', 'ODeltaPackStream',
- 'IStream', 'InvalidOInfo', 'InvalidOStream' )
+__all__ = ('OInfo', 'OPackInfo', 'ODeltaPackInfo',
+ 'OStream', 'OPackStream', 'ODeltaPackStream',
+ 'IStream', 'InvalidOInfo', 'InvalidOStream')
#{ ODB Bases
+
class OInfo(tuple):
+
"""Carries information about an object in an ODB, provding information
about the binary sha of the object, the type_string as well as the uncompressed size
in bytes.
-
+
It can be accessed using tuple notation and using attribute access notation::
-
+
assert dbi[0] == dbi.binsha
assert dbi[1] == dbi.type
assert dbi[2] == dbi.size
-
+
The type is designed to be as lighteight as possible."""
__slots__ = tuple()
-
+
def __new__(cls, sha, type, size):
return tuple.__new__(cls, (sha, type, size))
-
+
def __init__(self, *args):
tuple.__init__(self)
-
- #{ Interface
+
+ #{ Interface
@property
def binsha(self):
""":return: our sha as binary, 20 bytes"""
return self[0]
-
+
@property
def hexsha(self):
""":return: our sha, hex encoded, 40 bytes"""
return bin_to_hex(self[0])
-
+
@property
def type(self):
return self[1]
-
+
@property
def type_id(self):
return type_to_type_id_map[self[1]]
-
+
@property
def size(self):
return self[2]
#} END interface
-
-
+
+
class OPackInfo(tuple):
+
"""As OInfo, but provides a type_id property to retrieve the numerical type id, and
does not include a sha.
-
+
Additionally, the pack_offset is the absolute offset into the packfile at which
all object information is located. The data_offset property points to the abosolute
location in the pack at which that actual data stream can be found."""
__slots__ = tuple()
-
+
def __new__(cls, packoffset, type, size):
- return tuple.__new__(cls, (packoffset,type, size))
-
+ return tuple.__new__(cls, (packoffset, type, size))
+
def __init__(self, *args):
tuple.__init__(self)
-
- #{ Interface
-
+
+ #{ Interface
+
@property
def pack_offset(self):
return self[0]
-
+
@property
def type(self):
return type_id_to_type_map[self[1]]
-
+
@property
def type_id(self):
return self[1]
-
+
@property
def size(self):
return self[2]
-
+
#} END interface
-
-
+
+
class ODeltaPackInfo(OPackInfo):
+
"""Adds delta specific information,
Either the 20 byte sha which points to some object in the database,
or the negative offset from the pack_offset, so that pack_offset - delta_info yields
the pack offset of the base object"""
__slots__ = tuple()
-
+
def __new__(cls, packoffset, type, size, delta_info):
return tuple.__new__(cls, (packoffset, type, size, delta_info))
-
- #{ Interface
+
+ #{ Interface
@property
def delta_info(self):
return self[3]
- #} END interface
-
-
+ #} END interface
+
+
class OStream(OInfo):
+
"""Base for object streams retrieved from the database, providing additional
information about the stream.
Generally, ODB streams are read-only as objects are immutable"""
__slots__ = tuple()
-
+
def __new__(cls, sha, type, size, stream, *args, **kwargs):
"""Helps with the initialization of subclasses"""
return tuple.__new__(cls, (sha, type, size, stream))
-
-
+
def __init__(self, *args, **kwargs):
tuple.__init__(self)
-
- #{ Stream Reader Interface
-
+
+ #{ Stream Reader Interface
+
def read(self, size=-1):
return self[3].read(size)
-
+
@property
def stream(self):
return self[3]
-
+
#} END stream reader interface
-
-
+
+
class ODeltaStream(OStream):
+
"""Uses size info of its stream, delaying reads"""
-
+
def __new__(cls, sha, type, size, stream, *args, **kwargs):
"""Helps with the initialization of subclasses"""
return tuple.__new__(cls, (sha, type, size, stream))
-
+
#{ Stream Reader Interface
-
+
@property
def size(self):
return self[3].size
-
+
#} END stream reader interface
-
-
+
+
class OPackStream(OPackInfo):
+
"""Next to pack object information, a stream outputting an undeltified base object
is provided"""
__slots__ = tuple()
-
+
def __new__(cls, packoffset, type, size, stream, *args):
"""Helps with the initialization of subclasses"""
return tuple.__new__(cls, (packoffset, type, size, stream))
-
- #{ Stream Reader Interface
+
+ #{ Stream Reader Interface
def read(self, size=-1):
return self[3].read(size)
-
+
@property
def stream(self):
return self[3]
#} END stream reader interface
-
+
class ODeltaPackStream(ODeltaPackInfo):
+
"""Provides a stream outputting the uncompressed offset delta information"""
__slots__ = tuple()
-
+
def __new__(cls, packoffset, type, size, delta_info, stream):
return tuple.__new__(cls, (packoffset, type, size, delta_info, stream))
-
- #{ Stream Reader Interface
+ #{ Stream Reader Interface
def read(self, size=-1):
return self[4].read(size)
-
+
@property
def stream(self):
return self[4]
@@ -197,106 +203,107 @@ class ODeltaPackStream(ODeltaPackInfo):
class IStream(list):
+
"""Represents an input content stream to be fed into the ODB. It is mutable to allow
the ODB to record information about the operations outcome right in this instance.
-
+
It provides interfaces for the OStream and a StreamReader to allow the instance
to blend in without prior conversion.
-
+
The only method your content stream must support is 'read'"""
__slots__ = tuple()
-
+
def __new__(cls, type, size, stream, sha=None):
return list.__new__(cls, (sha, type, size, stream, None))
-
+
def __init__(self, type, size, stream, sha=None):
list.__init__(self, (sha, type, size, stream, None))
-
- #{ Interface
+
+ #{ Interface
@property
def hexsha(self):
""":return: our sha, hex encoded, 40 bytes"""
return bin_to_hex(self[0])
-
+
def _error(self):
""":return: the error that occurred when processing the stream, or None"""
return self[4]
-
+
def _set_error(self, exc):
"""Set this input stream to the given exc, may be None to reset the error"""
self[4] = exc
-
+
error = property(_error, _set_error)
-
+
#} END interface
-
+
#{ Stream Reader Interface
-
+
def read(self, size=-1):
"""Implements a simple stream reader interface, passing the read call on
to our internal stream"""
return self[3].read(size)
-
- #} END stream reader interface
-
+
+ #} END stream reader interface
+
#{ interface
-
+
def _set_binsha(self, binsha):
self[0] = binsha
-
+
def _binsha(self):
return self[0]
-
+
binsha = property(_binsha, _set_binsha)
-
-
+
def _type(self):
return self[1]
-
+
def _set_type(self, type):
self[1] = type
-
+
type = property(_type, _set_type)
-
+
def _size(self):
return self[2]
-
+
def _set_size(self, size):
self[2] = size
-
+
size = property(_size, _set_size)
-
+
def _stream(self):
return self[3]
-
+
def _set_stream(self, stream):
self[3] = stream
-
+
stream = property(_stream, _set_stream)
-
- #} END odb info interface
-
+
+ #} END odb info interface
+
class InvalidOInfo(tuple):
+
"""Carries information about a sha identifying an object which is invalid in
the queried database. The exception attribute provides more information about
the cause of the issue"""
__slots__ = tuple()
-
+
def __new__(cls, sha, exc):
return tuple.__new__(cls, (sha, exc))
-
+
def __init__(self, sha, exc):
tuple.__init__(self, (sha, exc))
-
+
@property
def binsha(self):
return self[0]
-
+
@property
def hexsha(self):
return bin_to_hex(self[0])
-
+
@property
def error(self):
""":return: exception instance explaining the failure"""
@@ -304,8 +311,8 @@ class InvalidOInfo(tuple):
class InvalidOStream(InvalidOInfo):
+
"""Carries information about an invalid ODB stream"""
__slots__ = tuple()
-
-#} END ODB Bases
+#} END ODB Bases
diff --git a/git/cmd.py b/git/cmd.py
index a81919e1..b0e6b0f4 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -4,74 +4,77 @@
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
-import os, sys
+import os
+import sys
from util import (
- LazyMixin,
- stream_copy
- )
+ LazyMixin,
+ stream_copy
+)
from exc import GitCommandError
from subprocess import (
- call,
- Popen,
- PIPE
- )
+ call,
+ Popen,
+ PIPE
+)
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
- 'with_exceptions', 'as_process',
- 'output_stream', 'output_strip' )
+ 'with_exceptions', 'as_process',
+ 'output_stream', 'output_strip')
__all__ = ('Git', )
+
def dashify(string):
return string.replace('_', '-')
class Git(LazyMixin):
+
"""
The Git class manages communication with the Git binary.
-
+
It provides a convenient interface to calling the Git binary, such as in::
-
+
g = Git( git_dir )
g.init() # calls 'git init' program
rval = g.ls_files() # calls 'git ls-files' program
-
+
``Debugging``
Set the GIT_PYTHON_TRACE environment variable print each invocation
of the command to stdout.
Set its value to 'full' to see details about the returned values.
"""
__slots__ = ("_working_dir", "cat_file_all", "cat_file_header", "_version_info")
-
+
# CONFIGURATION
# The size in bytes read from stdout when copying git's output to another stream
- max_chunk_size = 1024*64
-
+ max_chunk_size = 1024 * 64
+
git_exec_name = "git" # default that should work on linux and windows
git_exec_name_win = "git.cmd" # alternate command name, windows only
-
+
# Enables debugging of GitPython's git commands
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
-
+
# Provide the full path to the git executable. Otherwise it assumes git is in the path
_git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE"
GIT_PYTHON_GIT_EXECUTABLE = os.environ.get(_git_exec_env_var, git_exec_name)
-
-
+
class AutoInterrupt(object):
+
"""Kill/Interrupt the stored process instance once this instance goes out of scope. It is
used to prevent processes piling up in case iterators stop reading.
Besides all attributes are wired through to the contained process object.
-
+
The wait method was overridden to perform automatic status code checking
and possibly raise."""
- __slots__= ("proc", "args")
-
- def __init__(self, proc, args ):
+ __slots__ = ("proc", "args")
+
+ def __init__(self, proc, args):
self.proc = proc
self.args = args
-
+
def __del__(self):
self.proc.stdout.close()
self.proc.stderr.close()
@@ -79,61 +82,62 @@ class Git(LazyMixin):
# did the process finish already so we have a return code ?
if self.proc.poll() is not None:
return
-
- # can be that nothing really exists anymore ...
+
+ # can be that nothing really exists anymore ...
if os is None:
return
-
+
# try to kill it
try:
os.kill(self.proc.pid, 2) # interrupt signal
except OSError:
- pass # ignore error when process already died
+ pass # ignore error when process already died
except AttributeError:
- # try windows
- # for some reason, providing None for stdout/stderr still prints something. This is why
- # we simply use the shell and redirect to nul. Its slower than CreateProcess, question
+ # try windows
+ # for some reason, providing None for stdout/stderr still prints something. This is why
+ # we simply use the shell and redirect to nul. Its slower than CreateProcess, question
# is whether we really want to see all these messages. Its annoying no matter what.
call(("TASKKILL /F /T /PID %s 2>nul 1>nul" % str(self.proc.pid)), shell=True)
- # END exception handling
-
+ # END exception handling
+
def __getattr__(self, attr):
return getattr(self.proc, attr)
-
+
def wait(self):
"""Wait for the process and return its status code.
-
+
:raise GitCommandError: if the return status is not 0"""
status = self.proc.wait()
self.proc.stdout.close()
self.proc.stderr.close()
if status != 0:
raise GitCommandError(self.args, status, self.proc.stderr.read())
- # END status handling
+ # END status handling
return status
# END auto interrupt
-
+
class CatFileContentStream(object):
+
"""Object representing a sized read-only stream returning the contents of
an object.
It behaves like a stream, but counts the data read and simulates an empty
stream once our sized content region is empty.
If not all data is read to the end of the objects's lifetime, we read the
rest to assure the underlying stream continues to work"""
-
+
__slots__ = ('_stream', '_nbr', '_size')
-
+
def __init__(self, size, stream):
self._stream = stream
self._size = size
self._nbr = 0 # num bytes read
-
- # special case: if the object is empty, has null bytes, get the
+
+ # special case: if the object is empty, has null bytes, get the
# final newline right away.
if size == 0:
stream.read(1)
# END handle empty streams
-
+
def read(self, size=-1):
bytes_left = self._size - self._nbr
if bytes_left == 0:
@@ -147,17 +151,17 @@ class Git(LazyMixin):
# END check early depletion
data = self._stream.read(size)
self._nbr += len(data)
-
+
# check for depletion, read our final byte to make the stream usable by others
if self._size - self._nbr == 0:
self._stream.read(1) # final newline
# END finish reading
return data
-
+
def readline(self, size=-1):
if self._nbr == self._size:
return ''
-
+
# clamp size to lowest allowed value
bytes_left = self._size - self._nbr
if size > -1:
@@ -165,21 +169,21 @@ class Git(LazyMixin):
else:
size = bytes_left
# END handle size
-
+
data = self._stream.readline(size)
self._nbr += len(data)
-
+
# handle final byte
if self._size - self._nbr == 0:
self._stream.read(1)
# END finish reading
-
+
return data
-
+
def readlines(self, size=-1):
if self._nbr == self._size:
return list()
-
+
# leave all additional logic to our readline method, we just check the size
out = list()
nbr = 0
@@ -195,16 +199,16 @@ class Git(LazyMixin):
# END handle size constraint
# END readline loop
return out
-
+
def __iter__(self):
return self
-
+
def next(self):
line = self.readline()
if not line:
raise StopIteration
return line
-
+
def __del__(self):
bytes_left = self._size - self._nbr
if bytes_left:
@@ -212,11 +216,10 @@ class Git(LazyMixin):
# includes terminating newline
self._stream.read(bytes_left + 1)
# END handle incomplete read
-
-
+
def __init__(self, working_dir=None):
"""Initialize this instance with:
-
+
:param working_dir:
Git directory we should work in. If None, we always work in the current
directory as returned by os.getcwd().
@@ -224,7 +227,7 @@ class Git(LazyMixin):
.git directory in case of bare repositories."""
super(Git, self).__init__()
self._working_dir = working_dir
-
+
# cached command slots
self.cat_file_header = None
self.cat_file_all = None
@@ -244,14 +247,13 @@ class Git(LazyMixin):
self._version_info = tuple(int(n) for n in version_numbers.split('.')[:4])
else:
super(Git, self)._set_cache_(attr)
- #END handle version info
-
+ # END handle version info
@property
def working_dir(self):
""":return: Git directory we are working on"""
return self._working_dir
-
+
@property
def version_info(self):
"""
@@ -265,8 +267,8 @@ class Git(LazyMixin):
with_keep_cwd=False,
with_extended_output=False,
with_exceptions=True,
- as_process=False,
- output_stream=None,
+ as_process=False,
+ output_stream=None,
output_strip=True,
**subprocess_kwargs
):
@@ -301,7 +303,7 @@ class Git(LazyMixin):
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
-
+
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
@@ -309,27 +311,27 @@ class Git(LazyMixin):
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
-
+
:param output_strip:
Strip the last line of the output if it is empty (default). Stripping should
be disabled whenever it is important that the output is not modified in any
way. For example when retrieving patch files using git-diff.
-
+
:param subprocess_kwargs:
Keyword arguments to be passed to subprocess.Popen. Please note that
some of the valid kwargs are already set by this method, the ones you
specify may not be the same ones.
-
+
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
-
+
if ouput_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
-
+
:raise GitCommandError:
-
+
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
@@ -338,32 +340,32 @@ class Git(LazyMixin):
# Allow the user to have the command executed in their working dir.
if with_keep_cwd or self._working_dir is None:
- cwd = os.getcwd()
+ cwd = os.getcwd()
else:
- cwd=self._working_dir
-
+ cwd = self._working_dir
+
# Start the process
env = os.environ.copy()
env['LANG'] = 'C'
proc = Popen(command,
- cwd=cwd,
- stdin=istream,
- stderr=PIPE,
- stdout=PIPE,
- close_fds=(os.name=='posix'),# unsupported on linux
- env=env,
- **subprocess_kwargs
- )
+ cwd=cwd,
+ stdin=istream,
+ stderr=PIPE,
+ stdout=PIPE,
+ close_fds=(os.name == 'posix'), # unsupported on linux
+ env=env,
+ **subprocess_kwargs
+ )
if as_process:
return self.AutoInterrupt(proc, command)
-
+
# Wait for the process to return
status = 0
stdout_value = ''
stderr_value = ''
try:
if output_stream is None:
- stdout_value, stderr_value = proc.communicate()
+ stdout_value, stderr_value = proc.communicate()
# strip trailing "\n"
if stdout_value.endswith("\n") and output_strip:
stdout_value = stdout_value[:-1]
@@ -425,18 +427,18 @@ class Git(LazyMixin):
@classmethod
def __unpack_args(cls, arg_list):
- if not isinstance(arg_list, (list,tuple)):
+ if not isinstance(arg_list, (list, tuple)):
if isinstance(arg_list, unicode):
return [arg_list.encode('utf-8')]
- return [ str(arg_list) ]
-
+ return [str(arg_list)]
+
outlist = list()
for arg in arg_list:
if isinstance(arg_list, (list, tuple)):
- outlist.extend(cls.__unpack_args( arg ))
+ outlist.extend(cls.__unpack_args(arg))
elif isinstance(arg_list, unicode):
outlist.append(arg_list.encode('utf-8'))
- # END recursion
+ # END recursion
else:
outlist.append(str(arg))
# END for each arg
@@ -475,16 +477,16 @@ class Git(LazyMixin):
# Prepare the argument list
opt_args = self.transform_kwargs(**kwargs)
-
+
ext_args = self.__unpack_args([a for a in args if a is not None])
args = opt_args + ext_args
-
+
def make_call():
call = [self.GIT_PYTHON_GIT_EXECUTABLE, dashify(method)]
call.extend(args)
return call
- #END utility to recreate call after changes
-
+ # END utility to recreate call after changes
+
if sys.platform == 'win32':
try:
try:
@@ -493,33 +495,33 @@ class Git(LazyMixin):
# did we switch to git.cmd already, or was it changed from default ? permanently fail
if self.GIT_PYTHON_GIT_EXECUTABLE != self.git_exec_name:
raise
- #END handle overridden variable
+ # END handle overridden variable
type(self).GIT_PYTHON_GIT_EXECUTABLE = self.git_exec_name_win
call = [self.GIT_PYTHON_GIT_EXECUTABLE] + list(args)
-
+
try:
return self.execute(make_call(), **_kwargs)
finally:
import warnings
msg = "WARNING: Automatically switched to use git.cmd as git executable, which reduces performance by ~70%."
- msg += "Its recommended to put git.exe into the PATH or to set the %s environment variable to the executable's location" % self._git_exec_env_var
+ msg += "Its recommended to put git.exe into the PATH or to set the %s environment variable to the executable's location" % self._git_exec_env_var
warnings.warn(msg)
- #END print of warning
- #END catch first failure
+ # END print of warning
+ # END catch first failure
except WindowsError:
raise WindowsError("The system cannot find or execute the file at %r" % self.GIT_PYTHON_GIT_EXECUTABLE)
- #END provide better error message
+ # END provide better error message
else:
return self.execute(make_call(), **_kwargs)
- #END handle windows default installation
-
+ # END handle windows default installation
+
def _parse_object_header(self, header_line):
"""
:param header_line:
<hex_sha> type_string size_as_int
-
+
:return: (hex_sha, type_string, size_as_int)
-
+
:raise ValueError: if the header contains indication for an error due to
incorrect input sha"""
tokens = header_line.split()
@@ -530,46 +532,46 @@ class Git(LazyMixin):
raise ValueError("SHA %s could not be resolved, git returned: %r" % (tokens[0], header_line.strip()))
# END handle actual return value
# END error handling
-
+
if len(tokens[0]) != 40:
- raise ValueError("Failed to parse header: %r" % header_line)
+ raise ValueError("Failed to parse header: %r" % header_line)
return (tokens[0], tokens[1], int(tokens[2]))
-
+
def __prepare_ref(self, ref):
# required for command to separate refs on stdin
refstr = str(ref) # could be ref-object
if refstr.endswith("\n"):
return refstr
return refstr + "\n"
-
- def __get_persistent_cmd(self, attr_name, cmd_name, *args,**kwargs):
+
+ def __get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs):
cur_val = getattr(self, attr_name)
if cur_val is not None:
return cur_val
-
- options = { "istream" : PIPE, "as_process" : True }
- options.update( kwargs )
-
- cmd = self._call_process( cmd_name, *args, **options )
- setattr(self, attr_name, cmd )
+
+ options = {"istream": PIPE, "as_process": True}
+ options.update(kwargs)
+
+ cmd = self._call_process(cmd_name, *args, **options)
+ setattr(self, attr_name, cmd)
return cmd
-
+
def __get_object_header(self, cmd, ref):
cmd.stdin.write(self.__prepare_ref(ref))
cmd.stdin.flush()
return self._parse_object_header(cmd.stdout.readline())
-
+
def get_object_header(self, ref):
""" Use this method to quickly examine the type and size of the object behind
the given ref.
-
+
:note: The method will only suffer from the costs of command invocation
once and reuses the command in subsequent calls.
-
+
:return: (hexsha, type_string, size_as_int)"""
cmd = self.__get_persistent_cmd("cat_file_header", "cat_file", batch_check=True)
return self.__get_object_header(cmd, ref)
-
+
def get_object_data(self, ref):
""" As get_object_header, but returns object data as well
:return: (hexsha, type_string, size_as_int,data_string)
@@ -578,7 +580,7 @@ class Git(LazyMixin):
data = stream.read(size)
del(stream)
return (hexsha, typename, size, data)
-
+
def stream_object_data(self, ref):
"""As get_object_header, but returns the data as a stream
:return: (hexsha, type_string, size_as_int, stream)
@@ -587,12 +589,12 @@ class Git(LazyMixin):
cmd = self.__get_persistent_cmd("cat_file_all", "cat_file", batch=True)
hexsha, typename, size = self.__get_object_header(cmd, ref)
return (hexsha, typename, size, self.CatFileContentStream(size, cmd.stdout))
-
+
def clear_cache(self):
"""Clear all kinds of internal caches to release resources.
-
+
Currently persistent commands will be interrupted.
-
+
:return: self"""
self.cat_file_all = None
self.cat_file_header = None
diff --git a/git/config.py b/git/config.py
index e5cba936..c66d0cc4 100644
--- a/git/config.py
+++ b/git/config.py
@@ -17,7 +17,9 @@ from git.util import LockFile
__all__ = ('GitConfigParser', 'SectionConstraint')
+
class MetaParserBuilder(type):
+
"""Utlity class wrapping base-class methods into decorators that assure read-only properties"""
def __new__(metacls, name, bases, clsdict):
"""
@@ -27,7 +29,7 @@ class MetaParserBuilder(type):
if kmm in clsdict:
mutating_methods = clsdict[kmm]
for base in bases:
- methods = ( t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_") )
+ methods = (t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_"))
for name, method in methods:
if name in clsdict:
continue
@@ -35,30 +37,32 @@ class MetaParserBuilder(type):
if name in mutating_methods:
method_with_values = set_dirty_and_flush_changes(method_with_values)
# END mutating methods handling
-
+
clsdict[name] = method_with_values
# END for each name/method pair
# END for each base
# END if mutating methods configuration is set
-
+
new_type = super(MetaParserBuilder, metacls).__new__(metacls, name, bases, clsdict)
return new_type
-
-
+
def needs_values(func):
"""Returns method assuring we read values (on demand) before we try to access them"""
+
def assure_data_present(self, *args, **kwargs):
self.read()
return func(self, *args, **kwargs)
# END wrapper method
assure_data_present.__name__ = func.__name__
return assure_data_present
-
+
+
def set_dirty_and_flush_changes(non_const_func):
"""Return method that checks whether given non constant function may be called.
If so, the instance will be set dirty.
Additionally, we flush the changes right to disk"""
+
def flush_changes(self, *args, **kwargs):
rval = non_const_func(self, *args, **kwargs)
self.write()
@@ -66,64 +70,65 @@ def set_dirty_and_flush_changes(non_const_func):
# END wrapper method
flush_changes.__name__ = non_const_func.__name__
return flush_changes
-
+
class SectionConstraint(object):
+
"""Constrains a ConfigParser to only option commands which are constrained to
always use the section we have been initialized with.
-
+
It supports all ConfigParser methods that operate on an option"""
__slots__ = ("_config", "_section_name")
- _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option",
- "remove_section", "remove_option", "options")
-
+ _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option",
+ "remove_section", "remove_option", "options")
+
def __init__(self, config, section):
self._config = config
self._section_name = section
-
+
def __getattr__(self, attr):
if attr in self._valid_attrs_:
return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs)
- return super(SectionConstraint,self).__getattribute__(attr)
-
+ return super(SectionConstraint, self).__getattribute__(attr)
+
def _call_config(self, method, *args, **kwargs):
"""Call the configuration at the given method which must take a section name
as first argument"""
return getattr(self._config, method)(self._section_name, *args, **kwargs)
-
+
@property
def config(self):
"""return: Configparser instance we constrain"""
return self._config
-
+
class GitConfigParser(cp.RawConfigParser, object):
+
"""Implements specifics required to read git style configuration files.
-
+
This variation behaves much like the git.config command such that the configuration
will be read on demand based on the filepath given during initialization.
-
+
The changes will automatically be written once the instance goes out of scope, but
can be triggered manually as well.
-
+
The configuration file will be locked if you intend to change values preventing other
instances to write concurrently.
-
+
:note:
The config is case-sensitive even when queried, hence section and option names
must match perfectly."""
__metaclass__ = MetaParserBuilder
-
-
+
#{ Configuration
# The lock type determines the type of lock to use in new configuration readers.
# They must be compatible to the LockFile interface.
# A suitable alternative would be the BlockingLockFile
t_lock = LockFile
re_comment = re.compile('^\s*[#;]')
-
- #} END configuration
-
+
+ #} END configuration
+
OPTCRE = re.compile(
r'\s*(?P<option>[^:=\s][^:=]*)' # very permissive, incuding leading whitespace
r'\s*(?P<vi>[:=])\s*' # any number of space/tab,
@@ -131,76 +136,76 @@ class GitConfigParser(cp.RawConfigParser, object):
# (either : or =), followed
# by any # space/tab
r'(?P<value>.*)$' # everything up to eol
- )
-
+ )
+
# list of RawConfigParser methods able to change the instance
_mutating_methods_ = ("add_section", "remove_section", "remove_option", "set")
- __slots__ = ("_sections", "_defaults", "_file_or_files", "_read_only","_is_initialized", '_lock')
-
+ __slots__ = ("_sections", "_defaults", "_file_or_files", "_read_only", "_is_initialized", '_lock')
+
def __init__(self, file_or_files, read_only=True):
"""Initialize a configuration reader to read the given file_or_files and to
possibly allow changes to it by setting read_only False
-
+
:param file_or_files:
A single file path or file objects or multiple of these
-
+
:param read_only:
If True, the ConfigParser may only read the data , but not change it.
If False, only a single file path or file object may be given."""
super(GitConfigParser, self).__init__()
- # initialize base with ordered dictionaries to be sure we write the same
- # file back
+ # initialize base with ordered dictionaries to be sure we write the same
+ # file back
self._sections = OrderedDict()
self._defaults = OrderedDict()
-
+
self._file_or_files = file_or_files
self._read_only = read_only
self._is_initialized = False
self._lock = None
-
+
if not read_only:
if isinstance(file_or_files, (tuple, list)):
- raise ValueError("Write-ConfigParsers can operate on a single file only, multiple files have been passed")
+ raise ValueError(
+ "Write-ConfigParsers can operate on a single file only, multiple files have been passed")
# END single file check
-
+
if not isinstance(file_or_files, basestring):
file_or_files = file_or_files.name
# END get filename from handle/stream
# initialize lock base - we want to write
self._lock = self.t_lock(file_or_files)
-
+
self._lock._obtain_lock()
# END read-only check
-
-
+
def __del__(self):
"""Write pending changes if required and release locks"""
# checking for the lock here makes sure we do not raise during write()
# in case an invalid parser was created who could not get a lock
if self.read_only or not self._lock._has_lock():
return
-
+
try:
try:
self.write()
- except IOError,e:
+ except IOError, e:
print "Exception during destruction of GitConfigParser: %s" % str(e)
finally:
self._lock._release_lock()
-
+
def optionxform(self, optionstr):
"""Do not transform options in any way when writing"""
return optionstr
-
+
def _read(self, fp, fpname):
"""A direct copy of the py2.4 version of the super class's _read method
to assure it uses ordered dicts. Had to change one line to make it work.
-
+
Future versions have this fixed, but in fact its quite embarassing for the
guys not to have done it right in the first place !
-
+
Removed big comments to make it more compact.
-
+
Made sure it ignores initial whitespace as git uses tabs"""
cursect = None # None, or a dictionary
optname = None
@@ -242,10 +247,10 @@ class GitConfigParser(cp.RawConfigParser, object):
optname, vi, optval = mo.group('option', 'vi', 'value')
if vi in ('=', ':') and ';' in optval:
pos = optval.find(';')
- if pos != -1 and optval[pos-1].isspace():
+ if pos != -1 and optval[pos - 1].isspace():
optval = optval[:pos]
optval = optval.strip()
-
+
# Remove paired unescaped-quotes
unquoted_optval = ''
escaped = False
@@ -256,45 +261,44 @@ class GitConfigParser(cp.RawConfigParser, object):
else:
escaped = (c == '\\') and not escaped
unquoted_optval += c
-
+
if in_quote:
if not e:
e = cp.ParsingError(fpname)
e.append(lineno, repr(line))
-
+
optval = unquoted_optval
-
- optval = optval.replace('\\\\', '\\') # Unescape backslashes
- optval = optval.replace(r'\"', '"') # Unescape quotes
-
+
+ optval = optval.replace('\\\\', '\\') # Unescape backslashes
+ optval = optval.replace(r'\"', '"') # Unescape quotes
+
optname = self.optionxform(optname.rstrip())
cursect[optname] = optval
else:
if not e:
e = cp.ParsingError(fpname)
e.append(lineno, repr(line))
- # END
- # END ?
+ # END
+ # END ?
# END ?
- # END while reading
+ # END while reading
# if any parsing errors occurred, raise an exception
if e:
raise e
-
-
+
def read(self):
"""Reads the data stored in the files we have been initialized with. It will
ignore files that cannot be read, possibly leaving an empty configuration
-
+
:return: Nothing
:raise IOError: if a file cannot be handled"""
if self._is_initialized:
return
-
+
files_to_read = self._file_or_files
if not isinstance(files_to_read, (tuple, list)):
- files_to_read = [ files_to_read ]
-
+ files_to_read = [files_to_read]
+
for file_object in files_to_read:
fp = file_object
close_fp = False
@@ -303,10 +307,10 @@ class GitConfigParser(cp.RawConfigParser, object):
try:
fp = open(file_object)
close_fp = True
- except IOError,e:
+ except IOError, e:
continue
# END fp handling
-
+
try:
self._read(fp, fp.name)
finally:
@@ -315,7 +319,7 @@ class GitConfigParser(cp.RawConfigParser, object):
# END read-handling
# END for each file object to read
self._is_initialized = True
-
+
def _write(self, fp):
"""Write an .ini-format representation of the configuration state in
git compatible format"""
@@ -324,34 +328,33 @@ class GitConfigParser(cp.RawConfigParser, object):
for (key, value) in section_dict.items():
if key != "__name__":
value = str(value)
- value = value.replace('\\', '\\\\') # Escape backslashes
- value = value.replace('"', r'\"') # Escape quotes
+ value = value.replace('\\', '\\\\') # Escape backslashes
+ value = value.replace('"', r'\"') # Escape quotes
value = value.replace('\n', '\n\t')
fp.write("\t%s = %s\n" % (key, value))
# END if key is not __name__
- # END section writing
-
+ # END section writing
+
if self._defaults:
write_section(cp.DEFAULTSECT, self._defaults)
- map(lambda t: write_section(t[0],t[1]), self._sections.items())
+ map(lambda t: write_section(t[0], t[1]), self._sections.items())
-
@needs_values
def write(self):
"""Write changes to our file, if there are changes at all
-
+
:raise IOError: if this is a read-only writer instance or if we could not obtain
a file lock"""
self._assure_writable("write")
-
+
fp = self._file_or_files
close_fp = False
-
+
# we have a physical file on disk, so get a lock
if isinstance(fp, (basestring, file)):
self._lock._obtain_lock()
# END get lock for physical files
-
+
if not hasattr(fp, "seek"):
fp = open(self._file_or_files, "w")
close_fp = True
@@ -360,9 +363,9 @@ class GitConfigParser(cp.RawConfigParser, object):
# make sure we do not overwrite into an existing file
if hasattr(fp, 'truncate'):
fp.truncate()
- #END
+ # END
# END handle stream or file
-
+
# WRITE DATA
try:
self._write(fp)
@@ -370,33 +373,33 @@ class GitConfigParser(cp.RawConfigParser, object):
if close_fp:
fp.close()
# END data writing
-
- # we do not release the lock - it will be done automatically once the
+
+ # we do not release the lock - it will be done automatically once the
# instance vanishes
-
+
def _assure_writable(self, method_name):
if self.read_only:
raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))
-
+
@needs_values
@set_dirty_and_flush_changes
def add_section(self, section):
"""Assures added options will stay in order"""
super(GitConfigParser, self).add_section(section)
self._sections[section] = OrderedDict()
-
+
@property
def read_only(self):
""":return: True if this instance may change the configuration file"""
return self._read_only
-
- def get_value(self, section, option, default = None):
+
+ def get_value(self, section, option, default=None):
"""
:param default:
If not None, the given default value will be returned in case
the option did not exist
:return: a properly typed value, either int, float or string
-
+
:raise TypeError: in case the value could not be understood
Otherwise the exceptions known to the ConfigParser will be raised."""
try:
@@ -405,43 +408,43 @@ class GitConfigParser(cp.RawConfigParser, object):
if default is not None:
return default
raise
-
- types = ( long, float )
+
+ types = (long, float)
for numtype in types:
try:
- val = numtype( valuestr )
+ val = numtype(valuestr)
# truncated value ?
- if val != float( valuestr ):
+ if val != float(valuestr):
continue
return val
- except (ValueError,TypeError):
+ except (ValueError, TypeError):
continue
# END for each numeric type
-
+
# try boolean values as git uses them
- vl = valuestr.lower()
+ vl = valuestr.lower()
if vl == 'false':
return False
if vl == 'true':
return True
-
- if not isinstance( valuestr, basestring ):
- raise TypeError( "Invalid value type: only int, long, float and str are allowed", valuestr )
-
+
+ if not isinstance(valuestr, basestring):
+ raise TypeError("Invalid value type: only int, long, float and str are allowed", valuestr)
+
return valuestr
-
+
@needs_values
@set_dirty_and_flush_changes
def set_value(self, section, option, value):
"""Sets the given option in section to the given value.
It will create the section if required, and will not throw as opposed to the default
ConfigParser 'set' method.
-
+
:param section: Name of the section in which the option resides or should reside
:param option: Name of the options whose value to set
-
+
:param value: Value to set the option to. It must be a string or convertible
to a string"""
if not self.has_section(section):
diff --git a/git/db/cmd/base.py b/git/db/cmd/base.py
index 96320a8a..9d9ad583 100644
--- a/git/db/cmd/base.py
+++ b/git/db/cmd/base.py
@@ -2,38 +2,38 @@
:note: we could add all implementations of the basic interfaces, its more efficient though
to obtain them from the pure implementation"""
from git.exc import (
- GitCommandError,
- BadObject
- )
+ GitCommandError,
+ BadObject
+)
from git.base import (
- OInfo,
- OStream
- )
+ OInfo,
+ OStream
+)
from git.util import (
- bin_to_hex,
- hex_to_bin,
- isfile,
- join_path,
- join,
- Actor,
- IterableList,
- )
+ bin_to_hex,
+ hex_to_bin,
+ isfile,
+ join_path,
+ join,
+ Actor,
+ IterableList,
+)
from git.db.interface import (
- FetchInfo,
- PushInfo,
- HighLevelRepository,
- TransportDB,
- RemoteProgress
- )
+ FetchInfo,
+ PushInfo,
+ HighLevelRepository,
+ TransportDB,
+ RemoteProgress
+)
from git.cmd import Git
from git.refs import (
- Reference,
- RemoteReference,
- SymbolicReference,
- TagReference
- )
+ Reference,
+ RemoteReference,
+ SymbolicReference,
+ TagReference
+)
from git.objects.commit import Commit
from cStringIO import StringIO
import re
@@ -41,8 +41,8 @@ import os
import sys
-__all__ = ('CmdTransportMixin', 'GitCommandMixin', 'CmdPushInfo', 'CmdFetchInfo',
- 'CmdRemoteProgress', 'CmdObjectDBRMixin', 'CmdHighLevelRepository')
+__all__ = ('CmdTransportMixin', 'GitCommandMixin', 'CmdPushInfo', 'CmdFetchInfo',
+ 'CmdRemoteProgress', 'CmdObjectDBRMixin', 'CmdHighLevelRepository')
#{ Utilities
@@ -50,12 +50,12 @@ __all__ = ('CmdTransportMixin', 'GitCommandMixin', 'CmdPushInfo', 'CmdFetchInfo'
def touch(filename):
fp = open(filename, "a")
fp.close()
-
-
+
+
def digest_process_messages(fh, progress):
"""Read progress messages from file-like object fh, supplying the respective
progress messages to the progress instance.
-
+
:return: list(line, ...) list of lines without linebreaks that did
not contain progress information"""
line_so_far = ''
@@ -64,7 +64,7 @@ def digest_process_messages(fh, progress):
char = fh.read(1)
if not char:
break
-
+
if char in ('\r', '\n'):
dropped_lines.extend(progress._parse_progress_line(line_so_far))
line_so_far = ''
@@ -73,25 +73,25 @@ def digest_process_messages(fh, progress):
# END process parsed line
# END while file is not done reading
return dropped_lines
-
+
+
def finalize_process(proc):
"""Wait for the process (fetch, pull or push) and handle its errors accordingly"""
try:
proc.wait()
- except GitCommandError,e:
+ except GitCommandError, e:
# if a push has rejected items, the command has non-zero return status
# a return status of 128 indicates a connection error - reraise the previous one
if proc.poll() == 128:
raise
pass
# END exception handling
-
+
def get_fetch_info_from_stderr(repo, proc, progress):
# skip first line as it is some remote info we are not interested in
output = IterableList('name')
-
-
+
# lines which are no progress are fetch info lines
# this also waits for the command to finish
# Skip some progress lines that don't provide relevant information
@@ -107,27 +107,28 @@ def get_fetch_info_from_stderr(repo, proc, progress):
# END handle special messages
fetch_info_lines.append(line)
# END for each line
-
- # read head information
- fp = open(join(repo.git_dir, 'FETCH_HEAD'),'r')
+
+ # read head information
+ fp = open(join(repo.git_dir, 'FETCH_HEAD'), 'r')
fetch_head_info = fp.readlines()
fp.close()
-
+
assert len(fetch_info_lines) == len(fetch_head_info)
-
- output.extend(CmdFetchInfo._from_line(repo, err_line, fetch_line)
- for err_line,fetch_line in zip(fetch_info_lines, fetch_head_info))
-
+
+ output.extend(CmdFetchInfo._from_line(repo, err_line, fetch_line)
+ for err_line, fetch_line in zip(fetch_info_lines, fetch_head_info))
+
finalize_process(proc)
return output
+
def get_push_info(repo, remotename_or_url, proc, 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
# to override the previous one. This is why we read the bytes manually
digest_process_messages(proc.stderr, progress)
-
+
output = IterableList('name')
for line in proc.stdout.readlines():
try:
@@ -135,12 +136,13 @@ def get_push_info(repo, remotename_or_url, proc, progress):
except ValueError:
# if an error happens, additional info is given which we cannot parse
pass
- # END exception handling
+ # END exception handling
# END for each line
-
+
finalize_process(proc)
return output
+
def add_progress(kwargs, git, progress):
"""Add the --progress flag to the given kwargs dict if supported by the
git command. If the actual progress in the given progress instance is not
@@ -150,13 +152,15 @@ def add_progress(kwargs, git, progress):
v = git.version_info
if v[0] > 1 or v[1] > 7 or v[2] > 0 or v[3] > 3:
kwargs['progress'] = True
- #END handle --progress
- #END handle progress
+ # END handle --progress
+ # END handle progress
return kwargs
#} END utilities
+
class CmdRemoteProgress(RemoteProgress):
+
"""
A Remote progress implementation taking a user derived progress to call the
respective methods on.
@@ -164,56 +168,56 @@ class CmdRemoteProgress(RemoteProgress):
__slots__ = ("_seen_ops", '_progress')
re_op_absolute = re.compile("(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
re_op_relative = re.compile("(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
-
- def __init__(self, progress_instance = None):
+
+ def __init__(self, progress_instance=None):
self._seen_ops = list()
if progress_instance is None:
progress_instance = RemoteProgress()
- #END assure proper instance
+ # END assure proper instance
self._progress = progress_instance
-
+
def _parse_progress_line(self, line):
"""Parse progress information from the given line as retrieved by git-push
or git-fetch
-
+
Call the own update(), __call__() and line_dropped() methods according
to the parsed result.
-
+
:return: list(line, ...) list of lines that could not be processed"""
# handle
- # Counting objects: 4, done.
+ # Counting objects: 4, done.
# Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
sub_lines = line.split('\r')
failed_lines = list()
for sline in sub_lines:
- # find esacpe characters and cut them away - regex will not work with
+ # find esacpe characters and cut them away - regex will not work with
# them as they are non-ascii. As git might expect a tty, it will send them
last_valid_index = None
- for i,c in enumerate(reversed(sline)):
+ for i, c in enumerate(reversed(sline)):
if ord(c) < 32:
# its a slice index
- last_valid_index = -i-1
+ last_valid_index = -i - 1
# END character was non-ascii
# END for each character in sline
if last_valid_index is not None:
sline = sline[:last_valid_index]
# END cut away invalid part
sline = sline.rstrip()
-
+
cur_count, max_count = None, None
match = self.re_op_relative.match(sline)
if match is None:
match = self.re_op_absolute.match(sline)
-
+
if not match:
self._progress.line_dropped(sline)
failed_lines.append(sline)
continue
# END could not get match
-
+
op_code = 0
remote, op_name, percent, cur_count, max_count, message = match.groups()
-
+
# get operation id
if op_name == "Counting objects":
op_code |= self.COUNTING
@@ -227,7 +231,7 @@ class CmdRemoteProgress(RemoteProgress):
op_code |= self.RESOLVING
else:
# Note: On windows it can happen that partial lines are sent
- # Hence we get something like "CompreReceiving objects", which is
+ # Hence we get something like "CompreReceiving objects", which is
# a blend of "Compressing objects" and "Receiving objects".
# This can't really be prevented, so we drop the line verbosely
# to make sure we get informed in case the process spits out new
@@ -237,25 +241,25 @@ class CmdRemoteProgress(RemoteProgress):
# Note: Don't add this line to the failed lines, as we have to silently
# drop it
return failed_lines
- #END handle opcode
-
+ # END handle opcode
+
# figure out stage
if op_code not in self._seen_ops:
self._seen_ops.append(op_code)
op_code |= self.BEGIN
# END begin opcode
-
+
if message is None:
message = ''
# END message handling
-
+
message = message.strip()
done_token = ', done.'
if message.endswith(done_token):
op_code |= self.END
message = message[:-len(done_token)]
# END end message handling
-
+
self._progress.update(op_code, cur_count, max_count, message, line)
self._progress(message, line)
# END for each sub line
@@ -263,21 +267,22 @@ class CmdRemoteProgress(RemoteProgress):
class CmdPushInfo(PushInfo):
+
"""
Pure Python implementation of a PushInfo interface
"""
- __slots__ = ('local_ref', 'remote_ref_string', 'flags', 'old_commit_binsha',
- '_remotename_or_url', 'repo', 'summary')
-
- _flag_map = { 'X' : PushInfo.NO_MATCH,
- '-' : PushInfo.DELETED, '*' : 0,
- '+' : PushInfo.FORCED_UPDATE,
- ' ' : PushInfo.FAST_FORWARD,
- '=' : PushInfo.UP_TO_DATE,
- '!' : PushInfo.ERROR }
-
- def __init__(self, flags, local_ref, remote_ref_string, repo, remotename_or_url, old_commit_binsha=None,
- summary=''):
+ __slots__ = ('local_ref', 'remote_ref_string', 'flags', 'old_commit_binsha',
+ '_remotename_or_url', 'repo', 'summary')
+
+ _flag_map = {'X': PushInfo.NO_MATCH,
+ '-': PushInfo.DELETED, '*': 0,
+ '+': PushInfo.FORCED_UPDATE,
+ ' ': PushInfo.FAST_FORWARD,
+ '=': PushInfo.UP_TO_DATE,
+ '!': PushInfo.ERROR}
+
+ def __init__(self, flags, local_ref, remote_ref_string, repo, remotename_or_url, old_commit_binsha=None,
+ summary=''):
""" Initialize a new instance """
self.flags = flags
self.local_ref = local_ref
@@ -286,7 +291,7 @@ class CmdPushInfo(PushInfo):
self._remotename_or_url = remotename_or_url
self.old_commit_binsha = old_commit_binsha
self.summary = summary
-
+
@property
def remote_ref(self):
"""
@@ -299,35 +304,36 @@ class CmdPushInfo(PushInfo):
elif self.remote_ref_string.startswith("refs/heads"):
remote_ref = Reference(self.repo, self.remote_ref_string)
if '/' in self._remotename_or_url:
- sys.stderr.write("Cannot provide RemoteReference instance if it was created from a url instead of of a remote name: %s. Returning Reference instance instead" % sefl._remotename_or_url)
+ sys.stderr.write(
+ "Cannot provide RemoteReference instance if it was created from a url instead of of a remote name: %s. Returning Reference instance instead" % sefl._remotename_or_url)
return remote_ref
- #END assert correct input
+ # END assert correct input
return RemoteReference(self.repo, "refs/remotes/%s/%s" % (str(self._remotename_or_url), remote_ref.name))
else:
raise ValueError("Could not handle remote ref: %r" % self.remote_ref_string)
- # END
-
+ # END
+
@classmethod
def _from_line(cls, repo, remotename_or_url, line):
"""Create a new PushInfo instance as parsed from line which is expected to be like
refs/heads/master:refs/heads/master 05d2687..1d0568e"""
control_character, from_to, summary = line.split('\t', 3)
flags = 0
-
+
# control character handling
try:
- flags |= cls._flag_map[ control_character ]
+ flags |= cls._flag_map[control_character]
except KeyError:
- raise ValueError("Control Character %r unknown as parsed from line %r" % (control_character, line))
+ raise ValueError("Control Character %r unknown as parsed from line %r" % (control_character, line))
# END handle control character
-
+
# from_to handling
from_ref_string, to_ref_string = from_to.split(':')
if flags & cls.DELETED:
from_ref = None
else:
from_ref = Reference.from_path(repo, from_ref_string)
-
+
# commit handling, could be message or commit info
old_commit_binsha = None
if summary.startswith('['):
@@ -345,7 +351,7 @@ class CmdPushInfo(PushInfo):
flags |= cls.NEW_HEAD
# uptodate encoded in control character
else:
- # fast-forward or forced update - was encoded in control character,
+ # fast-forward or forced update - was encoded in control character,
# but we parse the old and new commit
split_token = "..."
if control_character == " ":
@@ -353,27 +359,28 @@ class CmdPushInfo(PushInfo):
old_sha, new_sha = summary.split(' ')[0].split(split_token)
old_commit_binsha = repo.resolve(old_sha)
# END message handling
-
+
return cls(flags, from_ref, to_ref_string, repo, remotename_or_url, old_commit_binsha, summary)
-
+
class CmdFetchInfo(FetchInfo):
+
"""
Pure python implementation of a FetchInfo interface
"""
- __slots__ = ('ref','old_commit_binsha', 'flags', 'note')
-
+ __slots__ = ('ref', 'old_commit_binsha', 'flags', 'note')
+
# %c %-*s %-*s -> %s (%s)
re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.]+\]?)\s+(.+) -> ([/\w_\+\.-]+)( \(.*\)?$)?")
-
- _flag_map = { '!' : FetchInfo.ERROR,
- '+' : FetchInfo.FORCED_UPDATE,
- '-' : FetchInfo.TAG_UPDATE,
- '*' : 0,
- '=' : FetchInfo.HEAD_UPTODATE,
- ' ' : FetchInfo.FAST_FORWARD }
-
- def __init__(self, ref, flags, note = '', old_commit_binsha = None):
+
+ _flag_map = {'!': FetchInfo.ERROR,
+ '+': FetchInfo.FORCED_UPDATE,
+ '-': FetchInfo.TAG_UPDATE,
+ '*': 0,
+ '=': FetchInfo.HEAD_UPTODATE,
+ ' ': FetchInfo.FAST_FORWARD}
+
+ def __init__(self, ref, flags, note='', old_commit_binsha=None):
"""
Initialize a new instance
"""
@@ -381,28 +388,28 @@ class CmdFetchInfo(FetchInfo):
self.flags = flags
self.note = note
self.old_commit_binsha = old_commit_binsha
-
+
def __str__(self):
return self.name
-
+
@property
def name(self):
""":return: Name of our remote ref"""
return self.ref.name
-
+
@property
def commit(self):
""":return: Commit of our remote ref"""
return self.ref.commit
-
+
@classmethod
def _from_line(cls, repo, line, fetch_line):
"""Parse information from the given line as returned by git-fetch -v
and return a new CmdFetchInfo object representing this information.
-
+
We can handle a line as follows
"%c %-*s %-*s -> %s%s"
-
+
Where c is either ' ', !, +, -, *, or =
! means error
+ means success forcing update
@@ -410,13 +417,13 @@ class CmdFetchInfo(FetchInfo):
* means birth of new branch or tag
= means the head was up to date ( and not moved )
' ' means a fast-forward
-
+
fetch line is the corresponding line from FETCH_HEAD, like
acb0fa8b94ef421ad60c8507b634759a472cd56c not-for-merge branch '0.1.7RC' of /tmp/tmpya0vairemote_repo"""
match = cls.re_fetch_result.match(line)
if match is None:
raise ValueError("Failed to parse line: %r" % line)
-
+
# parse lines
control_character, operation, local_remote_ref, remote_local_ref, note = match.groups()
try:
@@ -424,11 +431,11 @@ class CmdFetchInfo(FetchInfo):
ref_type_name, fetch_note = fetch_note.split(' ', 1)
except ValueError: # unpack error
raise ValueError("Failed to parse FETCH__HEAD line: %r" % fetch_line)
-
+
# handle FETCH_HEAD and figure out ref type
- # If we do not specify a target branch like master:refs/remotes/origin/master,
+ # If we do not specify a target branch like master:refs/remotes/origin/master,
# the fetch result is stored in FETCH_HEAD which destroys the rule we usually
- # have. In that case we use a symbolic reference which is detached
+ # have. In that case we use a symbolic reference which is detached
ref_type = None
if remote_local_ref == "FETCH_HEAD":
ref_type = SymbolicReference
@@ -440,11 +447,11 @@ class CmdFetchInfo(FetchInfo):
ref_type = TagReference
else:
raise TypeError("Cannot handle reference type: %r" % ref_type_name)
- #END handle ref type
-
+ # END handle ref type
+
# create ref instance
if ref_type is SymbolicReference:
- remote_local_ref = ref_type(repo, "FETCH_HEAD")
+ remote_local_ref = ref_type(repo, "FETCH_HEAD")
else:
# determine prefix. Tags are usually pulled into refs/tags, they may have subdirectories.
# It is not clear sometimes where exactly the item is, unless we have an absolute path as indicated
@@ -459,30 +466,29 @@ class CmdFetchInfo(FetchInfo):
ref_path = remote_local_ref
if ref_type is not TagReference and not remote_local_ref.startswith(RemoteReference._common_path_default + "/"):
ref_type = Reference
- #END downgrade remote reference
+ # END downgrade remote reference
elif ref_type is TagReference and 'tags/' in remote_local_ref:
# even though its a tag, it is located in refs/remotes
ref_path = join_path(RemoteReference._common_path_default, remote_local_ref)
else:
ref_path = join_path(ref_type._common_path_default, remote_local_ref)
- #END obtain refpath
-
- # even though the path could be within the git conventions, we make
+ # END obtain refpath
+
+ # even though the path could be within the git conventions, we make
# sure we respect whatever the user wanted, and disabled path checking
remote_local_ref = ref_type(repo, ref_path, check_path=False)
- # END create ref instance
-
-
- note = ( note and note.strip() ) or ''
-
+ # END create ref instance
+
+ note = (note and note.strip()) or ''
+
# parse flags from control_character
flags = 0
try:
flags |= cls._flag_map[control_character]
except KeyError:
raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line))
- # END control char exception hanlding
-
+ # END control char exception hanlding
+
# parse operation string for more info - makes no sense for symbolic refs
old_commit_binsha = None
if isinstance(remote_local_ref, Reference):
@@ -499,24 +505,26 @@ class CmdFetchInfo(FetchInfo):
old_commit_binsha = repo.resolve(operation.split(split_token)[0])
# END handle refspec
# END reference flag handling
-
+
return cls(remote_local_ref, flags, note, old_commit_binsha)
-
+
class GitCommandMixin(object):
+
"""A mixin to provide the git command object through the git property"""
-
+
def __init__(self, *args, **kwargs):
"""Initialize this instance with the root and a git command"""
super(GitCommandMixin, self).__init__(*args, **kwargs)
self._git = Git(self.working_dir)
-
+
@property
def git(self):
return self._git
-
+
class CmdObjectDBRMixin(object):
+
"""A mixing implementing object reading through a git command
It will create objects only in the loose object database.
:note: for now, we use the git command to do all the lookup, just until he
@@ -525,10 +533,11 @@ class CmdObjectDBRMixin(object):
#{ ODB Interface
# overrides from PureOdb Implementation, which is responsible only for writing
# objects
+
def info(self, sha):
hexsha, typename, size = self._git.get_object_header(bin_to_hex(sha))
return OInfo(hex_to_bin(hexsha), typename, size)
-
+
def stream(self, sha):
"""For now, all lookup is done by git itself
:note: As we don't know when the stream is actually read (and if it is
@@ -536,10 +545,10 @@ class CmdObjectDBRMixin(object):
This has HUGE performance implication, both for memory as for
reading/deserializing objects, but we have no other choice in order
to make the database behaviour consistent with other implementations !"""
-
+
hexsha, typename, size, data = self._git.get_object_data(bin_to_hex(sha))
return OStream(hex_to_bin(hexsha), typename, size, StringIO(data))
-
+
def partial_to_complete_sha_hex(self, partial_hexsha):
""":return: Full binary 20 byte sha from the given partial hexsha
:raise AmbiguousObjectName:
@@ -552,20 +561,21 @@ class CmdObjectDBRMixin(object):
except (GitCommandError, ValueError):
raise BadObject(partial_hexsha)
# END handle exceptions
-
+
#} END odb interface
-
+
class CmdTransportMixin(TransportDB):
+
"""A mixin requiring the .git property as well as repository paths
-
+
It will create objects only in the loose object database.
:note: for now, we use the git command to do all the lookup, just until he
have packs and the other implementations
"""
-
+
#{ Transport DB interface
-
+
def push(self, url, refspecs=None, progress=None, **kwargs):
"""Push given refspecs using the git default implementation
:param url: may be a remote name or a url
@@ -573,9 +583,10 @@ class CmdTransportMixin(TransportDB):
:param progress: RemoteProgress derived instance or None
:param **kwargs: Additional arguments to be passed to the git-push process"""
progress = CmdRemoteProgress(progress)
- proc = self._git.push(url, refspecs, porcelain=True, as_process=True, **add_progress(kwargs, self.git, progress))
+ proc = self._git.push(
+ url, refspecs, porcelain=True, as_process=True, **add_progress(kwargs, self.git, progress))
return get_push_info(self, url, proc, progress)
-
+
def pull(self, url, refspecs=None, progress=None, **kwargs):
"""Fetch and merge the given refspecs.
If not refspecs are given, the merge will only work properly if you
@@ -584,47 +595,50 @@ class CmdTransportMixin(TransportDB):
:param refspecs: see push()
:param progress: see push()"""
progress = CmdRemoteProgress(progress)
- proc = self._git.pull(url, refspecs, with_extended_output=True, as_process=True, v=True, **add_progress(kwargs, self.git, progress))
+ proc = self._git.pull(url, refspecs, with_extended_output=True, as_process=True,
+ v=True, **add_progress(kwargs, self.git, progress))
return get_fetch_info_from_stderr(self, proc, progress)
-
+
def fetch(self, url, refspecs=None, progress=None, **kwargs):
"""Fetch the latest changes
:param url: may be a remote name or a url
:param refspecs: see push()
:param progress: see push()"""
progress = CmdRemoteProgress(progress)
- proc = self._git.fetch(url, refspecs, with_extended_output=True, as_process=True, v=True, **add_progress(kwargs, self.git, progress))
+ proc = self._git.fetch(url, refspecs, with_extended_output=True, as_process=True,
+ v=True, **add_progress(kwargs, self.git, progress))
return get_fetch_info_from_stderr(self, proc, progress)
-
+
#} end transport db interface
-
-
+
+
class CmdHighLevelRepository(HighLevelRepository):
+
"""An intermediate interface carrying advanced git functionality that can be used
in other comound repositories which do not implement this functionality themselves.
-
+
The mixin must be used with repositories compatible to the GitCommandMixin.
-
+
:note: at some point, methods provided here are supposed to be provided by custom interfaces"""
DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
-
+
# precompiled regex
re_whitespace = re.compile(r'\s+')
re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{4,40}$')
re_author_committer_start = re.compile(r'^(author|committer)')
re_tab_full_line = re.compile(r'^\t(.*)$')
-
+
#{ Configuration
CommitCls = Commit
GitCls = Git
#} END configuration
-
+
def daemon_export():
def _get_daemon_export(self):
filename = join(self.git_dir, self.DAEMON_EXPORT_FILE)
return os.path.exists(filename)
-
+
def _set_daemon_export(self, value):
filename = join(self.git_dir, self.DAEMON_EXPORT_FILE)
fileexists = os.path.exists(filename)
@@ -635,7 +649,7 @@ class CmdHighLevelRepository(HighLevelRepository):
return property(_get_daemon_export, _set_daemon_export,
doc="If True, git-daemon may export this repository")
-
+
daemon_export = daemon_export()
def is_dirty(self, index=True, working_tree=True, untracked_files=False):
@@ -643,13 +657,13 @@ class CmdHighLevelRepository(HighLevelRepository):
# Bare repositories with no associated working directory are
# always consired to be clean.
return False
-
+
# start from the one which is fastest to evaluate
default_args = ('--abbrev=40', '--full-index', '--raw')
- if index:
+ if index:
# diff index against HEAD
if isfile(self.index.path) and self.head.is_valid() and \
- len(self.git.diff('HEAD', '--cached', *default_args)):
+ len(self.git.diff('HEAD', '--cached', *default_args)):
return True
# END index handling
if working_tree:
@@ -693,7 +707,7 @@ class CmdHighLevelRepository(HighLevelRepository):
parts = self.re_whitespace.split(line, 1)
firstpart = parts[0]
if self.re_hexsha_only.search(firstpart):
- # handles
+ # handles
# 634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7 - indicates blame-data start
# 634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
digits = parts[-1].split(" ")
@@ -707,7 +721,7 @@ class CmdHighLevelRepository(HighLevelRepository):
else:
m = self.re_author_committer_start.search(firstpart)
if m:
- # handles:
+ # handles:
# author Tom Preston-Werner
# author-mail <tom@mojombo.com>
# author-time 1192271832
@@ -738,19 +752,21 @@ class CmdHighLevelRepository(HighLevelRepository):
sha = info['id']
c = commits.get(sha)
if c is None:
- c = self.CommitCls( self, hex_to_bin(sha),
- author=Actor._from_string(info['author'] + ' ' + info['author_email']),
- authored_date=info['author_date'],
- committer=Actor._from_string(info['committer'] + ' ' + info['committer_email']),
- committed_date=info['committer_date'],
- message=info['summary'])
+ c = self.CommitCls(self, hex_to_bin(sha),
+ author=Actor._from_string(
+ info['author'] + ' ' + info['author_email']),
+ authored_date=info['author_date'],
+ committer=Actor._from_string(
+ info['committer'] + ' ' + info['committer_email']),
+ committed_date=info['committer_date'],
+ message=info['summary'])
commits[sha] = c
# END if commit objects needs initial creation
m = self.re_tab_full_line.search(line)
text, = m.groups()
blames[-1][0] = c
blames[-1][1].append(text)
- info = { 'id' : sha }
+ info = {'id': sha}
# END if we collected commit info
# END distinguish filename,summary,rest
# END distinguish author|committer vs filename,summary,rest
@@ -775,7 +791,7 @@ class CmdHighLevelRepository(HighLevelRepository):
@classmethod
def _clone(cls, git, url, path, progress, **kwargs):
- # special handling for windows for path at which the clone should be
+ # 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
# we at least give a proper error instead of letting git fail
@@ -784,9 +800,9 @@ class CmdHighLevelRepository(HighLevelRepository):
if os.name == 'nt':
if '~' in path:
raise OSError("Git cannot handle the ~ character in path %r correctly" % path)
-
- # on windows, git will think paths like c: are relative and prepend the
- # current working dir ( before it fails ). We temporarily adjust the working
+
+ # on windows, git will think paths like c: are relative and prepend the
+ # current working dir ( before it fails ). We temporarily adjust the working
# dir to make this actually work
match = re.match("(\w:[/\\\])(.*)", path)
if match:
@@ -796,14 +812,15 @@ class CmdHighLevelRepository(HighLevelRepository):
os.chdir(drive)
path = rest_of_path
kwargs['with_keep_cwd'] = True
- # END cwd preparation
- # END windows handling
-
+ # END cwd preparation
+ # END windows handling
+
try:
- proc = git.clone(url, path, with_extended_output=True, as_process=True, v=True, **add_progress(kwargs, git, progress))
+ proc = git.clone(url, path, with_extended_output=True, as_process=True,
+ v=True, **add_progress(kwargs, git, progress))
if progress is not None:
digest_process_messages(proc.stderr, progress)
- #END digest progress messages
+ # END digest progress messages
finalize_process(proc)
finally:
if prev_cwd is not None:
@@ -811,16 +828,16 @@ class CmdHighLevelRepository(HighLevelRepository):
path = prev_path
# END reset previous working dir
# END bad windows handling
-
- # our git command could have a different working dir than our actual
+
+ # our git command could have a different working dir than our actual
# environment, hence we prepend its working dir if required
if not os.path.isabs(path) and git.working_dir:
path = join(git._working_dir, path)
-
- # adjust remotes - there may be operating systems which use backslashes,
+
+ # adjust remotes - there may be operating systems which use backslashes,
# These might be given as initial paths, but when handling the config file
# that contains the remote from which we were clones, git stops liking it
- # as it will escape the backslashes. Hence we undo the escaping just to be
+ # as it will escape the backslashes. Hence we undo the escaping just to be
# sure
repo = cls(os.path.abspath(path))
if repo.remotes:
@@ -828,16 +845,16 @@ class CmdHighLevelRepository(HighLevelRepository):
# END handle remote repo
return repo
- def clone(self, path, progress = None, **kwargs):
+ def clone(self, path, progress=None, **kwargs):
"""
:param kwargs:
All remaining keyword arguments are given to the git-clone command
-
+
For more information, see the respective method in HighLevelRepository"""
return self._clone(self.git, self.git_dir, path, CmdRemoteProgress(progress), **kwargs)
@classmethod
- def clone_from(cls, url, to_path, progress = None, **kwargs):
+ def clone_from(cls, url, to_path, progress=None, **kwargs):
"""
:param kwargs: see the ``clone`` method
For more information, see the respective method in the HighLevelRepository"""
@@ -854,8 +871,8 @@ class CmdHighLevelRepository(HighLevelRepository):
if treeish is None:
treeish = self.head.commit
if prefix and 'prefix' not in kwargs:
- kwargs['prefix'] = prefix
+ kwargs['prefix'] = prefix
kwargs['output_stream'] = ostream
-
+
self.git.archive(treeish, **kwargs)
return self
diff --git a/git/db/cmd/complex.py b/git/db/cmd/complex.py
index ad792826..ff5e71ce 100644
--- a/git/db/cmd/complex.py
+++ b/git/db/cmd/complex.py
@@ -7,10 +7,10 @@ from base import *
__all__ = ['CmdPartialGitDB']
-class CmdPartialGitDB( GitCommandMixin, CmdObjectDBRMixin, CmdTransportMixin,
- CmdHighLevelRepository ):
+class CmdPartialGitDB(GitCommandMixin, CmdObjectDBRMixin, CmdTransportMixin,
+ CmdHighLevelRepository):
+
"""Utility repository which only partially implements all required methods.
It cannot be reliably used alone, but is provided to allow mixing it with other
implementations"""
pass
-
diff --git a/git/db/compat.py b/git/db/compat.py
index ce686196..5e833081 100644
--- a/git/db/compat.py
+++ b/git/db/compat.py
@@ -4,42 +4,45 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Module providing adaptors to maintain backwards compatability"""
+
class RepoCompatibilityInterfaceNoBare(object):
+
"""Interface to install backwards compatability of the new complex repository
types with the previous, all in one, repository."""
-
+
def rev_parse(self, *args, **kwargs):
return self.resolve_object(*args, **kwargs)
-
+
@property
def odb(self):
"""The odb is now an integrated part of each repository"""
return self
-
+
@property
def active_branch(self):
"""The name of the currently active branch.
:return: Head to the active branch"""
return self.head.reference
-
+
def __repr__(self):
"""Return the representation of the repository, the way it used to be"""
return '<git.Repo "%s">' % self.git_dir
-
+
@property
def branches(self):
return self.heads
class RepoCompatibilityInterface(RepoCompatibilityInterfaceNoBare):
+
"""Interface to install backwards compatability of the new complex repository
types with the previous, all in one, repository."""
-
+
@property
def bare(self):
return self.is_bare
-
+
@property
def refs(self):
return self.references
diff --git a/git/db/complex.py b/git/db/complex.py
index e8ad8a62..e3442dee 100644
--- a/git/db/complex.py
+++ b/git/db/complex.py
@@ -6,23 +6,31 @@ from compat import RepoCompatibilityInterface
__all__ = ['CmdGitDB', 'PureGitDB', 'CmdCompatibilityGitDB', 'PureCompatibilityGitDB']
+
class CmdGitDB(CmdPartialGitDB, PurePartialGitDB):
+
"""A database which uses primarily the git command implementation, but falls back
to pure python where it is more feasible
:note: To assure consistent behaviour across implementations, when calling the
``stream()`` method a cache is created. This makes this implementation a bad
choice when reading big files as these are streamed from memory in all cases."""
+
class CmdCompatibilityGitDB(RepoCompatibilityInterface, CmdGitDB):
+
"""A database which fills in its missing implementation using the pure python
implementation"""
pass
+
class PureGitDB(PurePartialGitDB, CmdPartialGitDB):
+
"""A repository which uses the pure implementation primarily, but falls back
on using the git command for high-level functionality"""
+
class PureCompatibilityGitDB(RepoCompatibilityInterface, PureGitDB):
+
"""Repository which uses the pure implementation primarily, but falls back
to the git command implementation. Please note that the CmdGitDB does it
the opposite way around."""
diff --git a/git/db/dulwich/__init__.py b/git/db/dulwich/__init__.py
index 26f63652..94aa8660 100644
--- a/git/db/dulwich/__init__.py
+++ b/git/db/dulwich/__init__.py
@@ -1,13 +1,13 @@
"""Dulwich module initialization"""
+
def init_dulwich():
""":raise ImportError: if dulwich is not present"""
try:
import dulwich
except ImportError:
raise ImportError("Could not find 'dulwich' in the PYTHONPATH - dulwich functionality is not available")
- #END handle dulwich import
-
+ # END handle dulwich import
init_dulwich()
diff --git a/git/db/dulwich/complex.py b/git/db/dulwich/complex.py
index 1428361a..1c073fc0 100644
--- a/git/db/dulwich/complex.py
+++ b/git/db/dulwich/complex.py
@@ -3,10 +3,10 @@ __all__ = ['DulwichGitODB', 'DulwichGitDB', 'DulwichCompatibilityGitDB']
from git.db.py.complex import PureGitODB
from git.db.py.base import (
- PureRepositoryPathsMixin,
- PureConfigurationMixin,
- PureIndexDB,
- )
+ PureRepositoryPathsMixin,
+ PureConfigurationMixin,
+ PureIndexDB,
+)
from git.db.py.resolve import PureReferencesMixin
from git.db.py.transport import PureTransportDB
from git.db.py.submodule import PureSubmoduleDB
@@ -19,15 +19,16 @@ from dulwich.repo import Repo as DulwichRepo
from dulwich.objects import ShaFile
from git.base import OInfo, OStream
-from git.fun import type_id_to_type_map, type_to_type_id_map
+from git.fun import type_id_to_type_map, type_to_type_id_map
-from cStringIO import StringIO
+from cStringIO import StringIO
import os
class DulwichGitODB(PureGitODB):
+
"""A full fledged database to read and write object files from all kinds of sources."""
-
+
def __init__(self, objects_root):
"""Initalize this instance"""
PureGitODB.__init__(self, objects_root)
@@ -35,9 +36,9 @@ class DulwichGitODB(PureGitODB):
wd = self.working_dir
else:
wd = os.path.dirname(os.path.dirname(objects_root))
- #END try to figure out good entry for dulwich, which doesn't do an extensive search
+ # END try to figure out good entry for dulwich, which doesn't do an extensive search
self._dw_repo = DulwichRepo(wd)
-
+
def __getattr__(self, attr):
try:
# supply LazyMixin with this call first
@@ -45,46 +46,46 @@ class DulwichGitODB(PureGitODB):
except AttributeError:
# now assume its on the dulwich repository ... for now
return getattr(self._dw_repo, attr)
- #END handle attr
-
+ # END handle attr
+
#{ Object DBR
-
+
def info(self, binsha):
- type_id, uncomp_data = self._dw_repo.object_store.get_raw(binsha)
+ type_id, uncomp_data = self._dw_repo.object_store.get_raw(binsha)
return OInfo(binsha, type_id_to_type_map[type_id], len(uncomp_data))
-
+
def stream(self, binsha):
type_id, uncomp_data = self._dw_repo.object_store.get_raw(binsha)
return OStream(binsha, type_id_to_type_map[type_id], len(uncomp_data), StringIO(uncomp_data))
-
+
#}END object dbr
-
+
#{ Object DBW
-
+
def store(self, istream):
obj = ShaFile.from_raw_string(type_to_type_id_map[istream.type], istream.read())
self._dw_repo.object_store.add_object(obj)
istream.binsha = obj.sha().digest()
return istream
-
+
#}END object dbw
-
-class DulwichGitDB( PureRepositoryPathsMixin, PureConfigurationMixin,
- PureReferencesMixin, PureSubmoduleDB,
- PureIndexDB,
- PureTransportDB, # not fully implemented
- GitCommandMixin,
- CmdHighLevelRepository,
- DulwichGitODB): # must come last, as it doesn't pass on __init__ with super
+class DulwichGitDB(PureRepositoryPathsMixin, PureConfigurationMixin,
+ PureReferencesMixin, PureSubmoduleDB,
+ PureIndexDB,
+ PureTransportDB, # not fully implemented
+ GitCommandMixin,
+ CmdHighLevelRepository,
+ DulwichGitODB): # must come last, as it doesn't pass on __init__ with super
+
def __init__(self, root_path):
"""Initialize ourselves on the .git directory, or the .git/objects directory."""
PureRepositoryPathsMixin._initialize(self, root_path)
super(DulwichGitDB, self).__init__(self.objects_dir)
-
+
class DulwichCompatibilityGitDB(RepoCompatibilityInterfaceNoBare, DulwichGitDB):
+
"""Basic dulwich compatibility database"""
pass
-
diff --git a/git/db/interface.py b/git/db/interface.py
index 07d8ca70..0e3b44fc 100644
--- a/git/db/interface.py
+++ b/git/db/interface.py
@@ -4,26 +4,27 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Contains interfaces for basic database building blocks"""
-__all__ = ( 'ObjectDBR', 'ObjectDBW', 'RootPathDB', 'CompoundDB', 'CachingDB',
- 'TransportDB', 'ConfigurationMixin', 'RepositoryPathsMixin',
- 'RefSpec', 'FetchInfo', 'PushInfo', 'ReferencesMixin', 'SubmoduleDB',
- 'IndexDB', 'HighLevelRepository')
+__all__ = ('ObjectDBR', 'ObjectDBW', 'RootPathDB', 'CompoundDB', 'CachingDB',
+ 'TransportDB', 'ConfigurationMixin', 'RepositoryPathsMixin',
+ 'RefSpec', 'FetchInfo', 'PushInfo', 'ReferencesMixin', 'SubmoduleDB',
+ 'IndexDB', 'HighLevelRepository')
class ObjectDBR(object):
+
"""Defines an interface for object database lookup.
Objects are identified either by their 20 byte bin sha"""
-
+
def __contains__(self, sha):
return self.has_obj(sha)
-
- #{ Query Interface
+
+ #{ Query Interface
def has_object(self, sha):
"""
:return: True if the object identified by the given 20 bytes
binary sha is contained in the database"""
raise NotImplementedError("To be implemented in subclass")
-
+
def has_object_async(self, reader):
"""Return a reader yielding information about the membership of objects
as identified by shas
@@ -31,25 +32,25 @@ class ObjectDBR(object):
:return: async.Reader yielding tuples of (sha, bool) pairs which indicate
whether the given sha exists in the database or not"""
raise NotImplementedError("To be implemented in subclass")
-
+
def info(self, sha):
""" :return: OInfo instance
:param sha: bytes binary sha
:raise BadObject:"""
raise NotImplementedError("To be implemented in subclass")
-
+
def info_async(self, reader):
"""Retrieve information of a multitude of objects asynchronously
:param reader: Channel yielding the sha's of the objects of interest
:return: async.Reader yielding OInfo|InvalidOInfo, in any order"""
raise NotImplementedError("To be implemented in subclass")
-
+
def stream(self, sha):
""":return: OStream instance
:param sha: 20 bytes binary sha
:raise BadObject:"""
raise NotImplementedError("To be implemented in subclass")
-
+
def stream_async(self, reader):
"""Retrieve the OStream of multiple objects
:param reader: see ``info``
@@ -59,15 +60,15 @@ class ObjectDBR(object):
read all OStreams at once. Instead, read them individually using reader.read(x)
where x is small enough."""
raise NotImplementedError("To be implemented in subclass")
-
+
def size(self):
""":return: amount of objects in this database"""
raise NotImplementedError()
-
+
def sha_iter(self):
"""Return iterator yielding 20 byte shas for all objects in this data base"""
raise NotImplementedError()
-
+
def partial_to_complete_sha_hex(self, partial_hexsha):
"""
:return: 20 byte binary sha1 from the given less-than-40 byte hexsha
@@ -75,7 +76,7 @@ class ObjectDBR(object):
:raise AmbiguousObjectName: If multiple objects would match the given sha
:raies BadObject: If object was not found"""
raise NotImplementedError()
-
+
def partial_to_complete_sha(self, partial_binsha, canonical_length):
""":return: 20 byte sha as inferred by the given partial binary sha
:param partial_binsha: binary sha with less than 20 bytes
@@ -85,64 +86,67 @@ class ObjectDBR(object):
:raise AmbiguousObjectName:
:raise BadObject: """
#} END query interface
-
-
+
+
class ObjectDBW(object):
+
"""Defines an interface to create objects in the database"""
-
+
#{ Edit Interface
+
def set_ostream(self, stream):
"""
Adjusts the stream to which all data should be sent when storing new objects
-
+
:param stream: if not None, the stream to use, if None the default stream
will be used.
:return: previously installed stream, or None if there was no override
:raise TypeError: if the stream doesn't have the supported functionality"""
raise NotImplementedError("To be implemented in subclass")
-
+
def ostream(self):
"""
:return: overridden output stream this instance will write to, or None
if it will write to the default stream"""
raise NotImplementedError("To be implemented in subclass")
-
+
def store(self, istream):
"""
Create a new object in the database
:return: the input istream object with its sha set to its corresponding value
-
+
:param istream: IStream compatible instance. If its sha is already set
to a value, the object will just be stored in the our database format,
in which case the input stream is expected to be in object format ( header + contents ).
:raise IOError: if data could not be written"""
raise NotImplementedError("To be implemented in subclass")
-
+
def store_async(self, reader):
"""
Create multiple new objects in the database asynchronously. The method will
return right away, returning an output channel which receives the results as
they are computed.
-
+
:return: Channel yielding your IStream which served as input, in any order.
The IStreams sha will be set to the sha it received during the process,
or its error attribute will be set to the exception informing about the error.
-
+
:param reader: async.Reader yielding IStream instances.
The same instances will be used in the output channel as were received
in by the Reader.
-
+
:note:As some ODB implementations implement this operation atomic, they might
abort the whole operation if one item could not be processed. Hence check how
many items have actually been produced."""
raise NotImplementedError("To be implemented in subclass")
-
+
#} END edit interface
-
+
class RootPathDB(object):
+
"""Provides basic facilities to retrieve files of interest"""
-
+
def __init__(self, root_path):
"""Initialize this instance to look for its files at the given root path
All subsequent operations will be relative to this path
@@ -155,12 +159,12 @@ class RootPathDB(object):
except TypeError:
pass
# END handle py 2.6
-
+
#{ Interface
def root_path(self):
""":return: path at which this db operates"""
raise NotImplementedError()
-
+
def db_path(self, rela_path):
"""
:return: the given relative path relative to our database root, allowing
@@ -169,54 +173,58 @@ class RootPathDB(object):
to the database root path. Otherwise you will obtain the database root path itself"""
raise NotImplementedError()
#} END interface
-
+
class CachingDB(object):
+
"""A database which uses caches to speed-up access"""
-
- #{ Interface
-
+
+ #{ Interface
+
def update_cache(self, force=False):
"""
Call this method if the underlying data changed to trigger an update
of the internal caching structures.
-
+
:param force: if True, the update must be performed. Otherwise the implementation
may decide not to perform an update if it thinks nothing has changed.
:return: True if an update was performed as something change indeed"""
-
+
# END interface
class CompoundDB(object):
+
"""A database which delegates calls to sub-databases.
They should usually be cached and lazy-loaded"""
-
+
#{ Interface
-
+
def databases(self):
""":return: tuple of database instances we use for lookups"""
raise NotImplementedError()
#} END interface
-
-
+
+
class IndexDB(object):
+
"""A database which provides a flattened index to all objects in its currently
active tree."""
@property
def index(self):
""":return: IndexFile compatible instance"""
raise NotImplementedError()
-
+
class RefSpec(object):
+
"""A refspec is a simple container which provides information about the way
something should be fetched or pushed. It requires to use symbols to describe
the actual objects which is done using reference names (or respective instances
which resolve to actual reference names)."""
__slots__ = ('source', 'destination', 'force')
-
+
def __init__(self, source, destination, force=False):
"""initalize the instance with the required values
:param source: reference name or instance. If None, the Destination
@@ -226,73 +234,74 @@ class RefSpec(object):
self.force = force
if self.destination is None:
raise ValueError("Destination must be set")
-
+
def __str__(self):
""":return: a git-style refspec"""
s = str(self.source)
if self.source is None:
s = ''
- #END handle source
+ # END handle source
d = str(self.destination)
p = ''
if self.force:
p = '+'
- #END handle force
+ # END handle force
res = "%s%s:%s" % (p, s, d)
-
+
def delete_destination(self):
return self.source is None
-
-
+
+
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.
-
+
Subclasses should derive from this type.
"""
_num_op_codes = 7
- BEGIN, END, COUNTING, COMPRESSING, WRITING, RECEIVING, RESOLVING = [1 << x for x in range(_num_op_codes)]
- STAGE_MASK = BEGIN|END
+ BEGIN, END, COUNTING, COMPRESSING, WRITING, RECEIVING, RESOLVING = [1 << x for x in range(_num_op_codes)]
+ STAGE_MASK = BEGIN | END
OP_MASK = ~STAGE_MASK
-
+
#{ Subclass Interface
-
+
def line_dropped(self, line):
"""Called whenever a line could not be understood and was therefore dropped."""
pass
-
+
def update(self, op_code, cur_count, max_count=None, message='', input=''):
"""Called whenever the progress changes
-
+
:param op_code:
Integer allowing to be compared against Operation IDs and stage IDs.
-
+
Stage IDs are BEGIN and END. BEGIN will only be set once for each Operation
ID as well as END. It may be that BEGIN and END are set at once in case only
one progress message was emitted due to the speed of the operation.
Between BEGIN and END, none of these flags will be set
-
+
Operation IDs are all held within the OP_MASK. Only one Operation ID will
be active per call.
:param cur_count: Current absolute count of items
-
+
:param max_count:
The maximum count of items we expect. It may be None in case there is
no maximum number of items or if it is (yet) unknown.
-
+
:param message:
In case of the 'WRITING' operation, it contains the amount of bytes
transferred. It may possibly be used for other purposes as well.
-
+
:param input:
The actual input string that was used to parse the information from.
This is usually a line from the output of git-fetch, but really
depends on the implementation
-
+
You may read the contents of the current line in self._cur_line"""
pass
-
+
def __call__(self, message, input=''):
"""Same as update, but with a simpler interface which only provides the
message of the operation.
@@ -300,9 +309,10 @@ class RemoteProgress(object):
up to you which one you implement"""
pass
#} END subclass interface
-
-
+
+
class PushInfo(object):
+
"""A type presenting information about the result of a push operation for exactly
one refspec
@@ -317,14 +327,15 @@ class PushInfo(object):
summary # summary line providing human readable english text about the push
"""
__slots__ = tuple()
-
+
NEW_TAG, NEW_HEAD, NO_MATCH, REJECTED, REMOTE_REJECTED, REMOTE_FAILURE, DELETED, \
- FORCED_UPDATE, FAST_FORWARD, UP_TO_DATE, ERROR = [ 1 << x for x in range(11) ]
-
-
+ FORCED_UPDATE, FAST_FORWARD, UP_TO_DATE, ERROR = [1 << x for x in range(11)]
+
+
class FetchInfo(object):
+
"""A type presenting information about the fetch operation on exactly one refspec
-
+
The following members are defined:
ref # name of the reference to the changed
# remote head or FETCH_HEAD. Implementations can provide
@@ -336,28 +347,29 @@ class FetchInfo(object):
old_commit_binsha# if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD,
# field is set to the previous location of ref as binary sha or None"""
__slots__ = tuple()
-
+
NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \
- FAST_FORWARD, ERROR = [ 1 << x for x in range(8) ]
+ FAST_FORWARD, ERROR = [1 << x for x in range(8)]
class TransportDB(object):
+
"""A database which allows to transport objects from and to different locations
which are specified by urls (location) and refspecs (what to transport,
see http://www.kernel.org/pub/software/scm/git/docs/git-fetch.html).
-
+
At the beginning of a transport operation, it will be determined which objects
have to be sent (either by this or by the other side).
-
+
Afterwards a pack with the required objects is sent (or received). If there is
nothing to send, the pack will be empty.
-
+
As refspecs involve symbolic names for references to be handled, we require
RefParse functionality. How this is done is up to the actual implementation."""
# The following variables need to be set by the derived class
-
+
#{ Interface
-
+
def fetch(self, url, refspecs, progress=None, **kwargs):
"""Fetch the objects defined by the given refspec from the given url.
:param url: url identifying the source of the objects. It may also be
@@ -377,7 +389,7 @@ class TransportDB(object):
supported by the protocol.
"""
raise NotImplementedError()
-
+
def push(self, url, refspecs, progress=None, **kwargs):
"""Transport the objects identified by the given refspec to the remote
at the given url.
@@ -391,44 +403,44 @@ class TransportDB(object):
:todo: what to return ?
:raise: if any issue arises during transport or if the url cannot be handled"""
raise NotImplementedError()
-
+
@property
def remotes(self):
""":return: An IterableList of Remote objects allowing to access and manipulate remotes
:note: Remote objects can also be used for the actual push or fetch operation"""
raise NotImplementedError()
-
+
def remote(self, name='origin'):
""":return: Remote object with the given name
:note: it does not necessarily exist, hence this is just a more convenient way
to construct Remote objects"""
raise NotImplementedError()
-
+
#}end interface
-
-
+
#{ Utility Methods
-
+
def create_remote(self, name, url, **kwargs):
"""Create a new remote with the given name pointing to the given url
:return: Remote instance, compatible to the Remote interface"""
return Remote.create(self, name, url, **kwargs)
-
+
def delete_remote(self, remote):
"""Delete the given remote.
:param remote: a Remote instance"""
return Remote.remove(self, remote)
-
+
#} END utility methods
class ReferencesMixin(object):
+
"""Database providing reference objects which in turn point to database objects
like Commits or Tag(Object)s.
-
+
The returned types are compatible to the interfaces of the pure python
reference implementation in GitDB.ref"""
-
+
def resolve(self, name):
"""Resolve the given name into a binary sha. Valid names are as defined
in the rev-parse documentation http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html
@@ -436,32 +448,32 @@ class ReferencesMixin(object):
:raise AmbiguousObjectName:
:raise BadObject: """
raise NotImplementedError()
-
+
def resolve_object(self, name):
"""As ``resolve()``, but returns the Objecft instance pointed to by the
resolved binary sha
:return: Object instance of the correct type, e.g. shas pointing to commits
will be represented by a Commit object"""
raise NotImplementedError()
-
+
@property
def references(self):
""":return: iterable list of all Reference objects representing tags, heads
and remote references. This is the most general method to obtain any
references."""
raise NotImplementedError()
-
+
@property
def heads(self):
""":return: IterableList with HeadReference objects pointing to all
heads in the repository."""
raise NotImplementedError()
-
+
@property
def head(self):
""":return: HEAD Object pointing to the current head reference"""
raise NotImplementedError()
-
+
@property
def tags(self):
""":return: An IterableList of TagReferences or compatible items that
@@ -469,20 +481,19 @@ class ReferencesMixin(object):
raise NotImplementedError()
#{ Utility Methods
-
+
def tag(self, name):
""":return: Tag with the given name
:note: It does not necessarily exist, hence this is just a more convenient
way to construct TagReference objects"""
raise NotImplementedError()
-
-
+
def commit(self, rev=None):
"""The Commit object for the specified revision
:param rev: revision specifier, see git-rev-parse for viable options.
:return: Commit compatible object"""
raise NotImplementedError()
-
+
def iter_trees(self, *args, **kwargs):
""":return: Iterator yielding Tree compatible objects
:note: Takes all arguments known to iter_commits method"""
@@ -491,12 +502,12 @@ class ReferencesMixin(object):
def tree(self, rev=None):
"""The Tree (compatible) object for the given treeish revision
Examples::
-
+
repo.tree(repo.heads[0])
:param rev: is a revision pointing to a Treeish ( being a commit or tree )
:return: ``git.Tree``
-
+
:note:
If you need a non-root level tree, find it by iterating the root tree. Otherwise
it cannot know about its path relative to the repository root and subsequent
@@ -513,7 +524,7 @@ class ReferencesMixin(object):
:parm paths:
is an optional path or a list of paths to limit the returned commits to
Commits that do not contain that path or the paths will not be returned.
-
+
:parm kwargs:
Arguments to be passed to git-rev-list - common ones are
max_count and skip
@@ -524,12 +535,11 @@ class ReferencesMixin(object):
:return: iterator yielding Commit compatible instances"""
raise NotImplementedError()
-
#} END utility methods
-
+
#{ Edit Methods
-
- def create_head(self, path, commit='HEAD', force=False, logmsg=None ):
+
+ def create_head(self, path, commit='HEAD', force=False, logmsg=None):
"""Create a new head within the repository.
:param commit: a resolvable name to the commit or a Commit or Reference instance the new head should point to
:param force: if True, a head will be created even though it already exists
@@ -538,12 +548,12 @@ class ReferencesMixin(object):
will be used
:return: newly created Head instances"""
raise NotImplementedError()
-
+
def delete_head(self, *heads):
"""Delete the given heads
:param heads: list of Head references that are to be deleted"""
raise NotImplementedError()
-
+
def create_tag(self, path, ref='HEAD', message=None, force=False):
"""Create a new tag reference.
:param path: name or path of the new tag.
@@ -556,37 +566,39 @@ class ReferencesMixin(object):
exist at the given path. Otherwise an exception will be thrown
:return: TagReference object """
raise NotImplementedError()
-
+
def delete_tag(self, *tags):
"""Delete the given tag references
:param tags: TagReferences to delete"""
raise NotImplementedError()
-
+
#}END edit methods
class RepositoryPathsMixin(object):
+
"""Represents basic functionality of a full git repository. This involves an
optional working tree, a git directory with references and an object directory.
-
+
This type collects the respective paths and verifies the provided base path
truly is a git repository.
-
+
If the underlying type provides the config_reader() method, we can properly determine
whether this is a bare repository as well. Otherwise it will make an educated guess
based on the path name."""
#{ Subclass Interface
+
def _initialize(self, path):
"""initialize this instance with the given path. It may point to
any location within the repositories own data, as well as the working tree.
-
+
The implementation will move up and search for traces of a git repository,
which is indicated by a child directory ending with .git or the
current path portion ending with .git.
-
+
The paths made available for query are suitable for full git repositories
only. Plain object databases need to be fed the "objects" directory path.
-
+
:param path: the path to initialize the repository with
It is a path to either the root git directory or the bare git repo::
@@ -594,54 +606,54 @@ class RepositoryPathsMixin(object):
repo = Repo("/Users/mtrier/Development/git-python.git")
repo = Repo("~/Development/git-python.git")
repo = Repo("$REPOSITORIES/Development/git-python.git")
-
+
:raise InvalidDBRoot:
"""
raise NotImplementedError()
#} end subclass interface
-
+
#{ Object Interface
-
+
def __eq__(self, rhs):
raise NotImplementedError()
-
+
def __ne__(self, rhs):
raise NotImplementedError()
-
+
def __hash__(self):
raise NotImplementedError()
def __repr__(self):
raise NotImplementedError()
-
+
#} END object interface
-
+
#{ Interface
-
+
@property
def is_bare(self):
""":return: True if this is a bare repository
:note: this value is cached upon initialization"""
raise NotImplementedError()
-
+
@property
def git_dir(self):
""":return: path to directory containing this actual git repository (which
in turn provides access to objects and references"""
raise NotImplementedError()
-
+
@property
def working_tree_dir(self):
""":return: path to directory containing the working tree checkout of our
git repository.
:raise AssertionError: If this is a bare repository"""
raise NotImplementedError()
-
+
@property
def objects_dir(self):
""":return: path to the repository's objects directory"""
raise NotImplementedError()
-
+
@property
def working_dir(self):
""":return: working directory of the git process or related tools, being
@@ -653,31 +665,32 @@ class RepositoryPathsMixin(object):
""":return: description text associated with this repository or set the
description."""
raise NotImplementedError()
-
+
#} END interface
-
-
+
+
class ConfigurationMixin(object):
+
"""Interface providing configuration handler instances, which provide locked access
to a single git-style configuration file (ini like format, using tabs as improve readablity).
-
+
Configuration readers can be initialized with multiple files at once, whose information is concatenated
when reading. Lower-level files overwrite values from higher level files, i.e. a repository configuration file
overwrites information coming from a system configuration file
-
+
:note: for the 'repository' config level, a git_path() compatible type is required"""
config_level = ("system", "global", "repository")
-
+
#{ Interface
-
+
def config_reader(self, config_level=None):
"""
:return:
GitConfigParser allowing to read the full git configuration, but not to write it
-
+
The configuration will include values from the system, user and repository
configuration files.
-
+
:param config_level:
For possible values, see config_writer method
If None, all applicable levels will be used. Specify a level in case
@@ -686,7 +699,7 @@ class ConfigurationMixin(object):
:note: On windows, system configuration cannot currently be read as the path is
unknown, instead the global path will be used."""
raise NotImplementedError()
-
+
def config_writer(self, config_level="repository"):
"""
:return:
@@ -694,59 +707,60 @@ class ConfigurationMixin(object):
Config writers should be retrieved, used to change the configuration ,and written
right away as they will lock the configuration file in question and prevent other's
to write it.
-
+
:param config_level:
One of the following values
system = sytem wide configuration file
global = user level configuration file
repository = configuration file for this repostory only"""
raise NotImplementedError()
-
-
+
#} END interface
-
-
+
+
class SubmoduleDB(object):
+
"""Interface providing access to git repository submodules.
The actual implementation is found in the Submodule object type, which is
currently only available in one implementation."""
-
+
@property
def submodules(self):
"""
:return: git.IterableList(Submodule, ...) of direct submodules
available from the current head"""
raise NotImplementedError()
-
+
def submodule(self, name):
""" :return: Submodule with the given name
:raise ValueError: If no such submodule exists"""
raise NotImplementedError()
-
+
def create_submodule(self, *args, **kwargs):
"""Create a new submodule
-
+
:note: See the documentation of Submodule.add for a description of the
applicable parameters
:return: created submodules"""
raise NotImplementedError()
-
+
def iter_submodules(self, *args, **kwargs):
"""An iterator yielding Submodule instances, see Traversable interface
for a description of args and kwargs
:return: Iterator"""
raise NotImplementedError()
-
+
def submodule_update(self, *args, **kwargs):
"""Update the submodules, keeping the repository consistent as it will
take the previous state into consideration. For more information, please
see the documentation of RootModule.update"""
raise NotImplementedError()
-
-
+
+
class HighLevelRepository(object):
+
"""An interface combining several high-level repository functionality and properties"""
-
+
@property
def daemon_export(self):
""":return: True if the repository may be published by the git-daemon"""
@@ -759,13 +773,13 @@ class HighLevelRepository(object):
like a git-status without untracked files, hence it is dirty if the
index or the working copy have changes."""
raise NotImplementedError()
-
+
@property
def untracked_files(self):
"""
:return:
list(str,...)
-
+
:note:
ignored files will not appear here, i.e. files mentioned in .gitignore.
Bare repositories never have untracked files"""
@@ -781,7 +795,7 @@ class HighLevelRepository(object):
changed within the given commit. The Commit objects will be given in order
of appearance."""
raise NotImplementedError()
-
+
@classmethod
def init(cls, path=None, mkdir=True):
"""Initialize a git repository at the given path if specified
@@ -800,19 +814,19 @@ class HighLevelRepository(object):
of this class"""
raise NotImplementedError()
- def clone(self, path, progress = None):
+ def clone(self, path, progress=None):
"""Create a clone from this repository.
:param path:
is the full path of the new repo (traditionally ends with ./<name>.git).
:param progress:
a RemoteProgress instance or None if no progress information is required
-
+
:return: ``git.Repo`` (the newly cloned repo)"""
raise NotImplementedError()
@classmethod
- def clone_from(cls, url, to_path, progress = None):
+ def clone_from(cls, url, to_path, progress=None):
"""Create a clone from the given URL
:param url: valid git url, see http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS
:param to_path: Path to which the repository should be cloned to
@@ -832,5 +846,3 @@ class HighLevelRepository(object):
specialized ostreams to write any format supported by python
:return: self"""
raise NotImplementedError()
-
-
diff --git a/git/db/py/base.py b/git/db/py/base.py
index 6710a0cc..127d828a 100644
--- a/git/db/py/base.py
+++ b/git/db/py/base.py
@@ -6,29 +6,29 @@
from git.db.interface import *
from git.util import (
- pool,
- join,
- isfile,
- normpath,
- abspath,
- dirname,
- LazyMixin,
- hex_to_bin,
- bin_to_hex,
- expandvars,
- expanduser,
- exists,
- is_git_dir,
- )
+ pool,
+ join,
+ isfile,
+ normpath,
+ abspath,
+ dirname,
+ LazyMixin,
+ hex_to_bin,
+ bin_to_hex,
+ expandvars,
+ expanduser,
+ exists,
+ is_git_dir,
+)
from git.index import IndexFile
from git.config import GitConfigParser
-from git.exc import (
- BadObject,
- AmbiguousObjectName,
- InvalidGitRepositoryError,
- NoSuchPathError
- )
+from git.exc import (
+ BadObject,
+ AmbiguousObjectName,
+ InvalidGitRepositoryError,
+ NoSuchPathError
+)
from async import ChannelThreadTask
@@ -37,28 +37,28 @@ import sys
import os
-__all__ = ( 'PureObjectDBR', 'PureObjectDBW', 'PureRootPathDB', 'PureCompoundDB',
- 'PureConfigurationMixin', 'PureRepositoryPathsMixin', 'PureAlternatesFileMixin',
- 'PureIndexDB')
-
+__all__ = ('PureObjectDBR', 'PureObjectDBW', 'PureRootPathDB', 'PureCompoundDB',
+ 'PureConfigurationMixin', 'PureRepositoryPathsMixin', 'PureAlternatesFileMixin',
+ 'PureIndexDB')
+
class PureObjectDBR(ObjectDBR):
-
- #{ Query Interface
-
+
+ #{ Query Interface
+
def has_object_async(self, reader):
task = ChannelThreadTask(reader, str(self.has_object_async), lambda sha: (sha, self.has_object(sha)))
- return pool.add_task(task)
-
+ return pool.add_task(task)
+
def info_async(self, reader):
task = ChannelThreadTask(reader, str(self.info_async), self.info)
return pool.add_task(task)
-
+
def stream_async(self, reader):
# base implementation just uses the stream method repeatedly
task = ChannelThreadTask(reader, str(self.stream_async), self.stream)
return pool.add_task(task)
-
+
def partial_to_complete_sha_hex(self, partial_hexsha):
len_partial_hexsha = len(partial_hexsha)
if len_partial_hexsha % 2 != 0:
@@ -67,53 +67,52 @@ class PureObjectDBR(ObjectDBR):
partial_binsha = hex_to_bin(partial_hexsha)
# END assure successful binary conversion
return self.partial_to_complete_sha(partial_binsha, len(partial_hexsha))
-
+
#} END query interface
-
-
+
+
class PureObjectDBW(ObjectDBW):
-
+
def __init__(self, *args, **kwargs):
try:
super(PureObjectDBW, self).__init__(*args, **kwargs)
except TypeError:
pass
- #END handle py 2.6
+ # END handle py 2.6
self._ostream = None
-
+
#{ Edit Interface
def set_ostream(self, stream):
cstream = self._ostream
self._ostream = stream
return cstream
-
+
def ostream(self):
return self._ostream
-
+
def store_async(self, reader):
- task = ChannelThreadTask(reader, str(self.store_async), self.store)
+ task = ChannelThreadTask(reader, str(self.store_async), self.store)
return pool.add_task(task)
-
+
#} END edit interface
-
+
class PureRootPathDB(RootPathDB):
-
+
def __init__(self, root_path):
self._root_path = root_path
super(PureRootPathDB, self).__init__(root_path)
-
-
- #{ Interface
+
+ #{ Interface
def root_path(self):
return self._root_path
-
+
def db_path(self, rela_path=None):
if not rela_path:
return self._root_path
return join(self._root_path, rela_path)
#} END interface
-
+
def _databases_recursive(database, output):
"""Fill output list with database from db, in order. Deals with Loose, Packed
@@ -127,50 +126,51 @@ def _databases_recursive(database, output):
else:
output.append(database)
# END handle database type
-
+
class PureCompoundDB(CompoundDB, PureObjectDBR, LazyMixin, CachingDB):
+
def _set_cache_(self, attr):
if attr == '_dbs':
self._dbs = list()
else:
super(PureCompoundDB, self)._set_cache_(attr)
-
- #{ PureObjectDBR interface
-
+
+ #{ PureObjectDBR interface
+
def has_object(self, sha):
for db in self._dbs:
if db.has_object(sha):
return True
- #END for each db
+ # END for each db
return False
-
+
def info(self, sha):
for db in self._dbs:
try:
return db.info(sha)
except BadObject:
pass
- #END for each db
-
+ # END for each db
+
def stream(self, sha):
for db in self._dbs:
try:
return db.stream(sha)
except BadObject:
pass
- #END for each db
+ # END for each db
def size(self):
- return reduce(lambda x,y: x+y, (db.size() for db in self._dbs), 0)
-
+ return reduce(lambda x, y: x + y, (db.size() for db in self._dbs), 0)
+
def sha_iter(self):
return chain(*(db.sha_iter() for db in self._dbs))
-
+
#} END object DBR Interface
-
+
#{ Interface
-
+
def databases(self):
return tuple(self._dbs)
@@ -183,15 +183,15 @@ class PureCompoundDB(CompoundDB, PureObjectDBR, LazyMixin, CachingDB):
# END if is caching db
# END for each database to update
return stat
-
+
def partial_to_complete_sha_hex(self, partial_hexsha):
len_partial_hexsha = len(partial_hexsha)
if len_partial_hexsha % 2 != 0:
partial_binsha = hex_to_bin(partial_hexsha + "0")
else:
partial_binsha = hex_to_bin(partial_hexsha)
- # END assure successful binary conversion
-
+ # END assure successful binary conversion
+
candidate = None
for db in self._dbs:
full_bin_sha = None
@@ -213,34 +213,34 @@ class PureCompoundDB(CompoundDB, PureObjectDBR, LazyMixin, CachingDB):
if not candidate:
raise BadObject(partial_binsha)
return candidate
-
+
def partial_to_complete_sha(self, partial_binsha, hex_len):
"""Simple adaptor to feed into our implementation"""
return self.partial_to_complete_sha_hex(bin_to_hex(partial_binsha)[:hex_len])
#} END interface
-
-
+
+
class PureRepositoryPathsMixin(RepositoryPathsMixin):
# slots has no effect here, its just to keep track of used attrs
- __slots__ = ("_git_path", '_bare', '_working_tree_dir')
-
- #{ Configuration
+ __slots__ = ("_git_path", '_bare', '_working_tree_dir')
+
+ #{ Configuration
repo_dir = '.git'
objs_dir = 'objects'
#} END configuration
-
+
#{ Subclass Interface
def _initialize(self, path):
epath = abspath(expandvars(expanduser(path or os.getcwd())))
if not exists(epath):
raise NoSuchPathError(epath)
- #END check file
+ # END check file
self._working_tree_dir = None
self._git_path = None
curpath = epath
-
+
# walk up the path to find the .git dir
while curpath:
if is_git_dir(curpath):
@@ -256,7 +256,7 @@ class PureRepositoryPathsMixin(RepositoryPathsMixin):
if not dummy:
break
# END while curpath
-
+
if self._git_path is None:
raise InvalidGitRepositoryError(epath)
# END path not found
@@ -264,167 +264,167 @@ class PureRepositoryPathsMixin(RepositoryPathsMixin):
self._bare = self._working_tree_dir is None
if hasattr(self, 'config_reader'):
try:
- self._bare = self.config_reader("repository").getboolean('core','bare')
+ self._bare = self.config_reader("repository").getboolean('core', 'bare')
except Exception:
# lets not assume the option exists, although it should
pass
- #END handle exception
- #END check bare flag
+ # END handle exception
+ # END check bare flag
self._working_tree_dir = self._bare and None or self._working_tree_dir
-
+
#} end subclass interface
-
+
#{ Object Interface
-
+
def __eq__(self, rhs):
if hasattr(rhs, 'git_dir'):
return self.git_dir == rhs.git_dir
return False
-
+
def __ne__(self, rhs):
return not self.__eq__(rhs)
-
+
def __hash__(self):
return hash(self.git_dir)
def __repr__(self):
return "%s(%r)" % (type(self).__name__, self.git_dir)
-
+
#} END object interface
-
+
#{ Interface
-
+
@property
def is_bare(self):
return self._bare
-
+
@property
def git_dir(self):
return self._git_path
-
+
@property
def working_tree_dir(self):
if self._working_tree_dir is None:
raise AssertionError("Repository at %s is bare and does not have a working tree directory" % self.git_dir)
- #END assertion
+ # END assertion
return dirname(self.git_dir)
-
+
@property
def objects_dir(self):
return join(self.git_dir, self.objs_dir)
-
+
@property
def working_dir(self):
if self.is_bare:
return self.git_dir
else:
return self.working_tree_dir
- #END handle bare state
-
+ # END handle bare state
+
def _mk_description():
def _get_description(self):
filename = join(self.git_dir, 'description')
return file(filename).read().rstrip()
-
+
def _set_description(self, descr):
filename = join(self.git_dir, 'description')
- file(filename, 'w').write(descr+'\n')
-
+ file(filename, 'w').write(descr + '\n')
+
return property(_get_description, _set_description, "Descriptive text for the content of the repository")
description = _mk_description()
del(_mk_description)
-
+
#} END interface
-
-
+
+
class PureConfigurationMixin(ConfigurationMixin):
-
+
#{ Configuration
system_config_file_name = "gitconfig"
repo_config_file_name = "config"
#} END
-
+
def __new__(cls, *args, **kwargs):
"""This is just a stupid workaround for the evil py2.6 change which makes mixins quite impossible"""
return super(PureConfigurationMixin, cls).__new__(cls, *args, **kwargs)
-
+
def __init__(self, *args, **kwargs):
"""Verify prereqs"""
try:
super(PureConfigurationMixin, self).__init__(*args, **kwargs)
except TypeError:
pass
- #END handle code-breaking change in python 2.6
+ # END handle code-breaking change in python 2.6
assert hasattr(self, 'git_dir')
-
- def _path_at_level(self, level ):
- # we do not support an absolute path of the gitconfig on windows ,
+
+ def _path_at_level(self, level):
+ # we do not support an absolute path of the gitconfig on windows ,
# use the global config instead
if sys.platform == "win32" and level == "system":
level = "global"
- #END handle windows
-
+ # END handle windows
+
if level == "system":
return "/etc/%s" % self.system_config_file_name
elif level == "global":
return normpath(expanduser("~/.%s" % self.system_config_file_name))
elif level == "repository":
return join(self.git_dir, self.repo_config_file_name)
- #END handle level
-
+ # END handle level
+
raise ValueError("Invalid configuration level: %r" % level)
-
+
#{ Interface
-
+
def config_reader(self, config_level=None):
files = None
if config_level is None:
- files = [ self._path_at_level(f) for f in self.config_level ]
+ files = [self._path_at_level(f) for f in self.config_level]
else:
- files = [ self._path_at_level(config_level) ]
- #END handle level
+ files = [self._path_at_level(config_level)]
+ # END handle level
return GitConfigParser(files, read_only=True)
-
+
def config_writer(self, config_level="repository"):
return GitConfigParser(self._path_at_level(config_level), read_only=False)
-
-
+
#} END interface
-
-
+
+
class PureIndexDB(IndexDB):
#{ Configuration
IndexCls = IndexFile
#} END configuration
-
+
@property
def index(self):
return self.IndexCls(self)
-
-
+
+
class PureAlternatesFileMixin(object):
+
"""Utility able to read and write an alternates file through the alternates property
It needs to be part of a type with the git_dir or db_path property.
-
+
The file by default is assumed to be located at the default location as imposed
by the standard git repository layout"""
-
+
#{ Configuration
alternates_filepath = os.path.join('info', 'alternates') # relative path to alternates file
-
+
#} END configuration
-
+
def __init__(self, *args, **kwargs):
try:
super(PureAlternatesFileMixin, self).__init__(*args, **kwargs)
except TypeError:
pass
- #END handle py2.6 code breaking changes
- self._alternates_path() # throws on incompatible type
-
- #{ Interface
-
+ # END handle py2.6 code breaking changes
+ self._alternates_path() # throws on incompatible type
+
+ #{ Interface
+
def _alternates_path(self):
if hasattr(self, 'git_dir'):
return join(self.git_dir, 'objects', self.alternates_filepath)
@@ -432,8 +432,8 @@ class PureAlternatesFileMixin(object):
return self.db_path(self.alternates_filepath)
else:
raise AssertionError("This mixin requires a parent type with either the git_dir property or db_path method")
- #END handle path
-
+ # END handle path
+
def _get_alternates(self):
"""The list of alternates for this repo from which objects can be retrieved
@@ -462,7 +462,7 @@ class PureAlternatesFileMixin(object):
:note:
The method does not check for the existance of the paths in alts
as the caller is responsible."""
- alternates_path = self._alternates_path()
+ alternates_path = self._alternates_path()
if not alts:
if isfile(alternates_path):
os.remove(alternates_path)
@@ -472,10 +472,10 @@ class PureAlternatesFileMixin(object):
f.write("\n".join(alts))
finally:
f.close()
- # END file handling
+ # END file handling
# END alts handling
- alternates = property(_get_alternates, _set_alternates, doc="Retrieve a list of alternates paths or set a list paths to be used as alternates")
-
+ alternates = property(_get_alternates, _set_alternates,
+ doc="Retrieve a list of alternates paths or set a list paths to be used as alternates")
+
#} END interface
-
diff --git a/git/db/py/complex.py b/git/db/py/complex.py
index 9d06f74a..1ef40ac2 100644
--- a/git/db/py/complex.py
+++ b/git/db/py/complex.py
@@ -4,14 +4,14 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
from git.db.interface import HighLevelRepository
from base import (
- PureCompoundDB,
- PureObjectDBW,
- PureRootPathDB,
- PureRepositoryPathsMixin,
- PureConfigurationMixin,
- PureAlternatesFileMixin,
- PureIndexDB,
- )
+ PureCompoundDB,
+ PureObjectDBW,
+ PureRootPathDB,
+ PureRepositoryPathsMixin,
+ PureConfigurationMixin,
+ PureAlternatesFileMixin,
+ PureIndexDB,
+)
from transport import PureTransportDB
from resolve import PureReferencesMixin
@@ -29,6 +29,7 @@ __all__ = ('PureGitODB', 'PurePartialGitDB', 'PureCompatibilityGitDB')
class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureAlternatesFileMixin):
+
"""A git-style object-only database, which contains all objects in the 'objects'
subdirectory.
:note: The type needs to be initialized on the ./objects directory to function,
@@ -38,23 +39,22 @@ class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureAlternatesFi
PackDBCls = PurePackedODB
LooseDBCls = PureLooseObjectODB
PureReferenceDBCls = PureReferenceDB
-
+
# Directories
packs_dir = 'pack'
loose_dir = ''
-
-
+
def __init__(self, root_path):
"""Initialize ourselves on a git ./objects directory"""
super(PureGitODB, self).__init__(root_path)
-
+
def _set_cache_(self, attr):
if attr == '_dbs' or attr == '_loose_db':
self._dbs = list()
loose_db = None
- for subpath, dbcls in ((self.packs_dir, self.PackDBCls),
- (self.loose_dir, self.LooseDBCls),
- (self.alternates_filepath, self.PureReferenceDBCls)):
+ for subpath, dbcls in ((self.packs_dir, self.PackDBCls),
+ (self.loose_dir, self.LooseDBCls),
+ (self.alternates_filepath, self.PureReferenceDBCls)):
path = self.db_path(subpath)
if os.path.exists(path):
self._dbs.append(dbcls(path))
@@ -63,56 +63,56 @@ class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureAlternatesFi
# END remember loose db
# END check path exists
# END for each db type
-
+
# should have at least one subdb
if not self._dbs:
raise InvalidDBRoot(self.root_path())
# END handle error
-
+
# we the first one should have the store method
assert loose_db is not None and hasattr(loose_db, 'store'), "One database needs store functionality"
-
+
# finally set the value
self._loose_db = loose_db
else:
super(PureGitODB, self)._set_cache_(attr)
# END handle attrs
-
+
#{ PureObjectDBW interface
-
+
def store(self, istream):
return self._loose_db.store(istream)
-
+
def ostream(self):
return self._loose_db.ostream()
-
+
def set_ostream(self, ostream):
return self._loose_db.set_ostream(ostream)
-
+
#} END objectdbw interface
-
-
-
-class PurePartialGitDB(PureGitODB,
- PureRepositoryPathsMixin, PureConfigurationMixin,
- PureReferencesMixin, PureSubmoduleDB,
- PureIndexDB,
- PureTransportDB # not fully implemented
- # HighLevelRepository Currently not implemented !
- ):
+
+
+class PurePartialGitDB(PureGitODB,
+ PureRepositoryPathsMixin, PureConfigurationMixin,
+ PureReferencesMixin, PureSubmoduleDB,
+ PureIndexDB,
+ PureTransportDB # not fully implemented
+ # HighLevelRepository Currently not implemented !
+ ):
+
"""Git like database with support for object lookup as well as reference resolution.
Our rootpath is set to the actual .git directory (bare on unbare).
-
+
The root_path will be the git objects directory. Use git_path() to obtain the actual top-level
git directory."""
- #directories
-
+ # directories
+
def __init__(self, root_path):
"""Initialize ourselves on the .git directory, or the .git/objects directory."""
PureRepositoryPathsMixin._initialize(self, root_path)
super(PurePartialGitDB, self).__init__(self.objects_dir)
-
-
+
+
class PureCompatibilityGitDB(PurePartialGitDB, RepoCompatibilityInterface):
+
"""Pure git database with a compatability layer required by 0.3x code"""
-
diff --git a/git/db/py/loose.py b/git/db/py/loose.py
index 8267be98..40639e4e 100644
--- a/git/db/py/loose.py
+++ b/git/db/py/loose.py
@@ -3,53 +3,53 @@
# This module is part of GitDB and is released under
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
from base import (
- PureRootPathDB,
- PureObjectDBR,
- PureObjectDBW
- )
+ PureRootPathDB,
+ PureObjectDBR,
+ PureObjectDBW
+)
from git.exc import (
- InvalidDBRoot,
+ InvalidDBRoot,
BadObject,
AmbiguousObjectName
- )
+)
from git.stream import (
- DecompressMemMapReader,
- FDCompressedSha1Writer,
- FDStream,
- Sha1Writer
- )
+ DecompressMemMapReader,
+ FDCompressedSha1Writer,
+ FDStream,
+ Sha1Writer
+)
from git.base import (
- OStream,
- OInfo
- )
+ OStream,
+ OInfo
+)
from git.util import (
- file_contents_ro_filepath,
- ENOENT,
- hex_to_bin,
- bin_to_hex,
- exists,
- chmod,
- isdir,
- isfile,
- remove,
- mkdir,
- rename,
- dirname,
- basename,
- join
- )
-
-from git.fun import (
+ file_contents_ro_filepath,
+ ENOENT,
+ hex_to_bin,
+ bin_to_hex,
+ exists,
+ chmod,
+ isdir,
+ isfile,
+ remove,
+ mkdir,
+ rename,
+ dirname,
+ basename,
+ join
+)
+
+from git.fun import (
chunk_size,
- loose_object_header_info,
+ loose_object_header_info,
write_object,
stream_copy
- )
+)
import tempfile
import mmap
@@ -57,23 +57,23 @@ import sys
import os
-__all__ = ( 'PureLooseObjectODB', )
+__all__ = ('PureLooseObjectODB', )
class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
+
"""A database which operates on loose object files"""
-
+
# CONFIGURATION
# chunks in which data will be copied between streams
stream_chunk_size = chunk_size
-
+
# On windows we need to keep it writable, otherwise it cannot be removed
# either
new_objects_mode = 0444
if os.name == 'nt':
new_objects_mode = 0644
-
-
+
def __init__(self, root_path):
super(PureLooseObjectODB, self).__init__(root_path)
self._hexsha_to_file = dict()
@@ -81,14 +81,14 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
# Depending on the root, this might work for some mounts, for others not, which
# is why it is per instance
self._fd_open_flags = getattr(os, 'O_NOATIME', 0)
-
- #{ Interface
+
+ #{ Interface
def object_path(self, hexsha):
"""
:return: path at which the object with the given hexsha would be stored,
relative to the database root"""
return join(hexsha[:2], hexsha[2:])
-
+
def readable_db_object_path(self, hexsha):
"""
:return: readable object path to the object identified by hexsha
@@ -97,8 +97,8 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
return self._hexsha_to_file[hexsha]
except KeyError:
pass
- # END ignore cache misses
-
+ # END ignore cache misses
+
# try filesystem
path = self.db_path(self.object_path(hexsha))
if exists(path):
@@ -106,10 +106,9 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
return path
# END handle cache
raise BadObject(hexsha)
-
-
+
#} END interface
-
+
def _map_loose_object(self, sha):
"""
:return: memory map of that file to allow random read access
@@ -117,7 +116,7 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
db_path = self.db_path(self.object_path(bin_to_hex(sha)))
try:
return file_contents_ro_filepath(db_path, flags=self._fd_open_flags)
- except OSError,e:
+ except OSError, e:
if e.errno != ENOENT:
# try again without noatime
try:
@@ -135,13 +134,13 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
finally:
os.close(fd)
# END assure file is closed
-
+
def set_ostream(self, stream):
""":raise TypeError: if the stream does not support the Sha1Writer interface"""
if stream is not None and not isinstance(stream, Sha1Writer):
raise TypeError("Output stream musst support the %s interface" % Sha1Writer.__name__)
return super(PureLooseObjectODB, self).set_ostream(stream)
-
+
def info(self, sha):
m = self._map_loose_object(sha)
try:
@@ -150,12 +149,12 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
finally:
m.close()
# END assure release of system resources
-
+
def stream(self, sha):
m = self._map_loose_object(sha)
- type, size, stream = DecompressMemMapReader.new(m, close_on_deletion = True)
+ type, size, stream = DecompressMemMapReader.new(m, close_on_deletion=True)
return OStream(sha, type, size, stream)
-
+
def has_object(self, sha):
try:
self.readable_db_object_path(bin_to_hex(sha))
@@ -163,7 +162,7 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
except BadObject:
return False
# END check existance
-
+
def partial_to_complete_sha_hex(self, partial_hexsha):
""":return: 20 byte binary sha1 string which matches the given name uniquely
:param name: hexadecimal partial name
@@ -180,7 +179,7 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
if candidate is None:
raise BadObject(partial_hexsha)
return candidate
-
+
def store(self, istream):
"""note: The sha we produce will be hex by nature"""
tmp_path = None
@@ -188,14 +187,14 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
if writer is None:
# open a tmp file to write the data to
fd, tmp_path = tempfile.mkstemp(prefix='obj', dir=self._root_path)
-
+
if istream.binsha is None:
writer = FDCompressedSha1Writer(fd)
else:
writer = FDStream(fd)
# END handle direct stream copies
# END handle custom writer
-
+
try:
try:
if istream.binsha is not None:
@@ -205,7 +204,7 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
else:
# write object with header, we have to make a new one
write_object(istream.type, istream.size, istream.read, writer.write,
- chunk_size=self.stream_chunk_size)
+ chunk_size=self.stream_chunk_size)
# END handle direct stream copies
finally:
if tmp_path:
@@ -216,14 +215,14 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
os.remove(tmp_path)
raise
# END assure tmpfile removal on error
-
+
hexsha = None
if istream.binsha:
hexsha = istream.hexsha
else:
hexsha = writer.sha(as_hex=True)
# END handle sha
-
+
if tmp_path:
obj_path = self.db_path(self.object_path(hexsha))
obj_dir = dirname(obj_path)
@@ -235,29 +234,28 @@ class PureLooseObjectODB(PureRootPathDB, PureObjectDBR, PureObjectDBW):
remove(obj_path)
# END handle win322
rename(tmp_path, obj_path)
-
+
# make sure its readable for all ! It started out as rw-- tmp file
# but needs to be rwrr
chmod(obj_path, self.new_objects_mode)
# END handle dry_run
-
+
istream.binsha = hex_to_bin(hexsha)
return istream
-
+
def sha_iter(self):
# find all files which look like an object, extract sha from there
for root, dirs, files in os.walk(self.root_path()):
root_base = basename(root)
if len(root_base) != 2:
continue
-
+
for f in files:
if len(f) != 38:
continue
yield hex_to_bin(root_base + f)
# END for each file
# END for each walk iteration
-
+
def size(self):
return len(tuple(self.sha_iter()))
-
diff --git a/git/db/py/mem.py b/git/db/py/mem.py
index 63ceb756..65a457fe 100644
--- a/git/db/py/mem.py
+++ b/git/db/py/mem.py
@@ -4,72 +4,74 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Contains the MemoryDatabase implementation"""
from base import (
- PureObjectDBR,
- PureObjectDBW
- )
+ PureObjectDBR,
+ PureObjectDBW
+)
from loose import PureLooseObjectODB
from git.base import (
- OStream,
- IStream,
- )
+ OStream,
+ IStream,
+)
from git.exc import (
- BadObject,
- UnsupportedOperation
- )
+ BadObject,
+ UnsupportedOperation
+)
from git.stream import (
- ZippedStoreShaWriter,
- DecompressMemMapReader,
- )
+ ZippedStoreShaWriter,
+ DecompressMemMapReader,
+)
from cStringIO import StringIO
__all__ = ("PureMemoryDB", )
+
class PureMemoryDB(PureObjectDBR, PureObjectDBW):
+
"""A memory database stores everything to memory, providing fast IO and object
retrieval. It should be used to buffer results and obtain SHAs before writing
it to the actual physical storage, as it allows to query whether object already
exists in the target storage before introducing actual IO
-
+
:note: memory is currently not threadsafe, hence the async methods cannot be used
for storing"""
-
+
def __init__(self):
super(PureMemoryDB, self).__init__()
self._db = PureLooseObjectODB("path/doesnt/matter")
-
+
# maps 20 byte shas to their OStream objects
self._cache = dict()
-
+
def set_ostream(self, stream):
raise UnsupportedOperation("PureMemoryDB's always stream into memory")
-
+
def store(self, istream):
zstream = ZippedStoreShaWriter()
self._db.set_ostream(zstream)
-
+
istream = self._db.store(istream)
zstream.close() # close to flush
zstream.seek(0)
-
- # don't provide a size, the stream is written in object format, hence the
+
+ # don't provide a size, the stream is written in object format, hence the
# header needs decompression
- decomp_stream = DecompressMemMapReader(zstream.getvalue(), close_on_deletion=False)
+ decomp_stream = DecompressMemMapReader(zstream.getvalue(), close_on_deletion=False)
self._cache[istream.binsha] = OStream(istream.binsha, istream.type, istream.size, decomp_stream)
-
+
return istream
-
+
def store_async(self, reader):
raise UnsupportedOperation("PureMemoryDBs cannot currently be used for async write access")
-
+
def has_object(self, sha):
return sha in self._cache
def info(self, sha):
# we always return streams, which are infos as well
return self.stream(sha)
-
+
def stream(self, sha):
try:
ostream = self._cache[sha]
@@ -79,15 +81,14 @@ class PureMemoryDB(PureObjectDBR, PureObjectDBW):
except KeyError:
raise BadObject(sha)
# END exception handling
-
+
def size(self):
return len(self._cache)
-
+
def sha_iter(self):
return self._cache.iterkeys()
-
-
- #{ Interface
+
+ #{ Interface
def stream_copy(self, sha_iter, odb):
"""Copy the streams as identified by sha's yielded by sha_iter into the given odb
The streams will be copied directly
@@ -99,12 +100,12 @@ class PureMemoryDB(PureObjectDBR, PureObjectDBW):
if odb.has_object(sha):
continue
# END check object existance
-
+
ostream = self.stream(sha)
# compressed data including header
sio = StringIO(ostream.stream.data())
istream = IStream(ostream.type, ostream.size, sio, sha)
-
+
odb.store(istream)
count += 1
# END for each sha
diff --git a/git/db/py/pack.py b/git/db/py/pack.py
index 0d4c533a..e107aba2 100644
--- a/git/db/py/pack.py
+++ b/git/db/py/pack.py
@@ -5,17 +5,17 @@
"""Module containing a database to deal with packs"""
from git.db import CachingDB
from base import (
- PureRootPathDB,
- PureObjectDBR
- )
+ PureRootPathDB,
+ PureObjectDBR
+)
from git.util import LazyMixin
from git.exc import (
- BadObject,
- UnsupportedOperation,
- AmbiguousObjectName
- )
+ BadObject,
+ UnsupportedOperation,
+ AmbiguousObjectName
+)
from git.pack import PackEntity
@@ -28,16 +28,17 @@ __all__ = ('PurePackedODB', )
class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
+
"""A database operating on a set of object packs"""
-
+
# the type to use when instantiating a pack entity
PackEntityCls = PackEntity
-
+
# sort the priority list every N queries
- # Higher values are better, performance tests don't show this has
+ # Higher values are better, performance tests don't show this has
# any effect, but it should have one
_sort_interval = 500
-
+
def __init__(self, root_path):
super(PurePackedODB, self).__init__(root_path)
# list of lists with three items:
@@ -47,16 +48,16 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
# self._entities = list() # lazy loaded list
self._hit_count = 0 # amount of hits
self._st_mtime = 0 # last modification data of our root path
-
+
def _set_cache_(self, attr):
if attr == '_entities':
self._entities = list()
self.update_cache(force=True)
# END handle entities initialization
-
+
def _sort_entities(self):
self._entities.sort(key=lambda l: l[0], reverse=True)
-
+
def _pack_info(self, sha):
""":return: tuple(entity, index) for an item at the given sha
:param sha: 20 or 40 byte sha
@@ -69,7 +70,7 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
if self._hit_count % self._sort_interval == 0:
self._sort_entities()
# END update sorting
-
+
for item in self._entities:
index = item[2](sha)
if index is not None:
@@ -78,14 +79,14 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
return (item[1], index)
# END index found in pack
# END for each item
-
+
# no hit, see whether we have to update packs
# NOTE: considering packs don't change very often, we safe this call
# and leave it to the super-caller to trigger that
raise BadObject(sha)
-
- #{ Object DB Read
-
+
+ #{ Object DB Read
+
def has_object(self, sha):
try:
self._pack_info(sha)
@@ -93,15 +94,15 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
except BadObject:
return False
# END exception handling
-
+
def info(self, sha):
entity, index = self._pack_info(sha)
return entity.info_at_index(index)
-
+
def stream(self, sha):
entity, index = self._pack_info(sha)
return entity.stream_at_index(index)
-
+
def sha_iter(self):
sha_list = list()
for entity in self.entities():
@@ -111,35 +112,34 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
yield sha_by_index(index)
# END for each index
# END for each entity
-
+
def size(self):
sizes = [item[1].index().size() for item in self._entities]
- return reduce(lambda x,y: x+y, sizes, 0)
-
+ return reduce(lambda x, y: x + y, sizes, 0)
+
#} END object db read
-
+
#{ object db write
-
+
def store(self, istream):
"""Storing individual objects is not feasible as a pack is designed to
hold multiple objects. Writing or rewriting packs for single objects is
inefficient"""
raise UnsupportedOperation()
-
+
def store_async(self, reader):
# TODO: add PureObjectDBRW before implementing this
raise NotImplementedError()
-
+
#} END object db write
-
-
- #{ Interface
-
+
+ #{ Interface
+
def update_cache(self, force=False):
"""
Update our cache with the acutally existing packs on disk. Add new ones,
and remove deleted ones. We keep the unchanged ones
-
+
:param force: If True, the cache will be updated even though the directory
does not appear to have changed according to its modification timestamp.
:return: True if the packs have been updated so there is new information,
@@ -149,12 +149,12 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
return False
# END abort early on no change
self._st_mtime = stat.st_mtime
-
+
# packs are supposed to be prefixed with pack- by git-convention
# get all pack files, figure out what changed
pack_files = set(glob.glob(os.path.join(self.root_path(), "pack-*.pack")))
our_pack_files = set(item[1].pack().path() for item in self._entities)
-
+
# new packs
for pack_file in (pack_files - our_pack_files):
# init the hit-counter/priority with the size, a good measure for hit-
@@ -162,7 +162,7 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
entity = self.PackEntityCls(pack_file)
self._entities.append([entity.pack().size(), entity, entity.index().sha_to_index])
# END for each new packfile
-
+
# removed packs
for pack_file in (our_pack_files - pack_files):
del_index = -1
@@ -175,15 +175,15 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
assert del_index != -1
del(self._entities[del_index])
# END for each removed pack
-
+
# reinitialize prioritiess
self._sort_entities()
return True
-
+
def entities(self):
""":return: list of pack entities operated upon by this database"""
- return [ item[1] for item in self._entities ]
-
+ return [item[1] for item in self._entities]
+
def partial_to_complete_sha(self, partial_binsha, canonical_length):
""":return: 20 byte sha as inferred by the given partial binary sha
:param partial_binsha: binary sha with less than 20 bytes
@@ -202,11 +202,11 @@ class PurePackedODB(PureRootPathDB, PureObjectDBR, CachingDB, LazyMixin):
candidate = sha
# END handle full sha could be found
# END for each entity
-
+
if candidate:
return candidate
-
+
# still not found ?
raise BadObject(partial_binsha)
-
+
#} END interface
diff --git a/git/db/py/ref.py b/git/db/py/ref.py
index 75bc4fd1..3552f2a3 100644
--- a/git/db/py/ref.py
+++ b/git/db/py/ref.py
@@ -7,18 +7,20 @@ from base import PureCompoundDB
import os
__all__ = ('PureReferenceDB', )
+
class PureReferenceDB(PureCompoundDB):
+
"""A database consisting of database referred to in a file"""
-
+
# Configuration
# Specifies the object database to use for the paths found in the alternates
# file. If None, it defaults to the PureGitODB
ObjectDBCls = None
-
+
def __init__(self, ref_file):
super(PureReferenceDB, self).__init__()
self._ref_file = ref_file
-
+
def _set_cache_(self, attr):
if attr == '_dbs':
self._dbs = list()
@@ -26,7 +28,7 @@ class PureReferenceDB(PureCompoundDB):
else:
super(PureReferenceDB, self)._set_cache_(attr)
# END handle attrs
-
+
def _update_dbs_from_ref_file(self):
dbcls = self.ObjectDBCls
if dbcls is None:
@@ -34,7 +36,7 @@ class PureReferenceDB(PureCompoundDB):
import complex
dbcls = complex.PureGitODB
# END get db type
-
+
# try to get as many as possible, don't fail if some are unavailable
ref_paths = list()
try:
@@ -42,10 +44,10 @@ class PureReferenceDB(PureCompoundDB):
except (OSError, IOError):
pass
# END handle alternates
-
+
ref_paths_set = set(ref_paths)
cur_ref_paths_set = set(db.root_path() for db in self._dbs)
-
+
# remove existing
for path in (cur_ref_paths_set - ref_paths_set):
for i, db in enumerate(self._dbs[:]):
@@ -54,7 +56,7 @@ class PureReferenceDB(PureCompoundDB):
continue
# END del matching db
# END for each path to remove
-
+
# add new
# sort them to maintain order
added_paths = sorted(ref_paths_set - cur_ref_paths_set, key=lambda p: ref_paths.index(p))
@@ -70,7 +72,7 @@ class PureReferenceDB(PureCompoundDB):
# ignore invalid paths or issues
pass
# END for each path to add
-
+
def update_cache(self, force=False):
# re-read alternates and update databases
self._update_dbs_from_ref_file()
diff --git a/git/db/py/resolve.py b/git/db/py/resolve.py
index 8a64d76b..4301c2ad 100644
--- a/git/db/py/resolve.py
+++ b/git/db/py/resolve.py
@@ -4,12 +4,12 @@ version assuming compatible interface for reference and object types"""
from git.db.interface import ReferencesMixin
from git.exc import BadObject
from git.refs import (
- SymbolicReference,
- Reference,
- HEAD,
- Head,
- TagReference
- )
+ SymbolicReference,
+ Reference,
+ HEAD,
+ Head,
+ TagReference
+)
from git.refs.head import HEAD
from git.refs.headref import Head
from git.refs.tag import TagReference
@@ -17,13 +17,13 @@ from git.refs.tag import TagReference
from git.objects.base import Object
from git.objects.commit import Commit
from git.util import (
- join,
- isdir,
- isfile,
- hex_to_bin,
- bin_to_hex,
- is_git_dir
- )
+ join,
+ isdir,
+ isfile,
+ hex_to_bin,
+ bin_to_hex,
+ is_git_dir
+)
from string import digits
import os
import re
@@ -32,6 +32,7 @@ __all__ = ["PureReferencesMixin"]
#{ Utilities
+
def short_to_long(odb, hexsha):
""":return: long hexadecimal sha1 from the given less-than-40 byte hexsha
or None if no candidate could be found.
@@ -41,8 +42,8 @@ def short_to_long(odb, hexsha):
except BadObject:
return None
# END exception handling
-
-
+
+
def name_to_object(repo, name, return_ref=False):
"""
:return: object specified by the given name, hexshas ( short and long )
@@ -51,7 +52,7 @@ def name_to_object(repo, name, return_ref=False):
instead of the object. Otherwise it will raise BadObject
"""
hexsha = None
-
+
# is it a hexsha ? Try the most common ones, which is 7 to 40
if repo.re_hexsha_shortened.match(name):
if len(name) != 40:
@@ -60,9 +61,9 @@ def name_to_object(repo, name, return_ref=False):
else:
hexsha = name
# END handle short shas
- #END find sha if it matches
-
- # if we couldn't find an object for what seemed to be a short hexsha
+ # END find sha if it matches
+
+ # if we couldn't find an object for what seemed to be a short hexsha
# try to find it as reference anyway, it could be named 'aaa' for instance
if hexsha is None:
for base in ('%s', 'refs/%s', 'refs/tags/%s', 'refs/heads/%s', 'refs/remotes/%s', 'refs/remotes/%s/HEAD'):
@@ -70,7 +71,7 @@ def name_to_object(repo, name, return_ref=False):
hexsha = SymbolicReference.dereference_recursive(repo, base % name)
if return_ref:
return SymbolicReference(repo, base % name)
- #END handle symbolic ref
+ # END handle symbolic ref
break
except ValueError:
pass
@@ -80,15 +81,16 @@ def name_to_object(repo, name, return_ref=False):
# didn't find any ref, this is an error
if return_ref:
raise BadObject("Couldn't find reference named %r" % name)
- #END handle return ref
+ # END handle return ref
# tried everything ? fail
if hexsha is None:
raise BadObject(name)
# END assert hexsha was found
-
+
return Object.new_from_sha(repo, hex_to_bin(hexsha))
+
def deref_tag(tag):
"""Recursively dereference a tag and return the resulting object"""
while True:
@@ -99,16 +101,18 @@ def deref_tag(tag):
# END dereference tag
return tag
+
def to_commit(obj):
"""Convert the given object to a commit if possible and return it"""
if obj.type == 'tag':
obj = deref_tag(obj)
-
+
if obj.type != "commit":
raise ValueError("Cannot convert object %r to type commit" % obj)
# END verify type
return obj
+
def rev_parse(repo, rev):
"""
:return: Object at the given revision, either Commit, Tag, Tree or Blob
@@ -120,13 +124,13 @@ def rev_parse(repo, rev):
:raise BadObject: if the given revision could not be found
:raise ValueError: If rev couldn't be parsed
:raise IndexError: If invalid reflog index is specified"""
-
+
# colon search mode ?
if rev.startswith(':/'):
# colon search mode
raise NotImplementedError("commit by message search ( regex )")
# END handle search
-
+
obj = None
ref = None
output_type = "commit"
@@ -138,9 +142,9 @@ def rev_parse(repo, rev):
start += 1
continue
# END handle start
-
+
token = rev[start]
-
+
if obj is None:
# token is a rev name
if start == 0:
@@ -150,27 +154,26 @@ def rev_parse(repo, rev):
ref = name_to_object(repo, rev[:start], return_ref=True)
else:
obj = name_to_object(repo, rev[:start])
- #END handle token
- #END handle refname
-
+ # END handle token
+ # END handle refname
+
if ref is not None:
obj = ref.commit
- #END handle ref
+ # END handle ref
# END initialize obj on first token
-
-
+
start += 1
-
+
# try to parse {type}
if start < lr and rev[start] == '{':
end = rev.find('}', start)
if end == -1:
raise ValueError("Missing closing brace to define type in %s" % rev)
- output_type = rev[start+1:end] # exclude brace
-
- # handle type
+ output_type = rev[start + 1:end] # exclude brace
+
+ # handle type
if output_type == 'commit':
- pass # default
+ pass # default
elif output_type == 'tree':
try:
obj = to_commit(obj).tree
@@ -190,37 +193,37 @@ def rev_parse(repo, rev):
revlog_index = None
try:
# transform reversed index into the format of our revlog
- revlog_index = -(int(output_type)+1)
+ revlog_index = -(int(output_type) + 1)
except ValueError:
# TODO: Try to parse the other date options, using parse_date
# maybe
raise NotImplementedError("Support for additional @{...} modes not implemented")
- #END handle revlog index
-
+ # END handle revlog index
+
try:
entry = ref.log_entry(revlog_index)
except IndexError:
raise IndexError("Invalid revlog index: %i" % revlog_index)
- #END handle index out of bound
-
+ # END handle index out of bound
+
obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha))
-
+
# make it pass the following checks
output_type = None
else:
- raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
+ raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
# END handle output type
-
+
# empty output types don't require any specific type, its just about dereferencing tags
if output_type and obj.type != output_type:
raise ValueError("Could not accomodate requested object type %r, got %s" % (output_type, obj.type))
# END verify ouput type
-
- start = end+1 # skip brace
+
+ start = end + 1 # skip brace
parsed_to = start
continue
# END parse type
-
+
# try to parse a number
num = 0
if token != ":":
@@ -234,15 +237,14 @@ def rev_parse(repo, rev):
break
# END handle number
# END number parse loop
-
+
# no explicit number given, 1 is the default
- # It could be 0 though
+ # It could be 0 though
if not found_digit:
num = 1
# END set default num
# END number parsing only if non-blob mode
-
-
+
parsed_to = start
# handle hiererarchy walk
try:
@@ -255,7 +257,7 @@ def rev_parse(repo, rev):
obj = to_commit(obj)
# must be n'th parent
if num:
- obj = obj.parents[num-1]
+ obj = obj.parents[num - 1]
elif token == ":":
if obj.type != "tree":
obj = obj.tree
@@ -269,29 +271,31 @@ def rev_parse(repo, rev):
raise BadObject("Invalid Revision in %s" % rev)
# END exception handling
# END parse loop
-
+
# still no obj ? Its probably a simple name
if obj is None:
obj = name_to_object(repo, rev)
parsed_to = lr
# END handle simple name
-
+
if obj is None:
raise ValueError("Revision specifier could not be parsed: %s" % rev)
if parsed_to != lr:
raise ValueError("Didn't consume complete rev spec %s, consumed part: %s" % (rev, rev[:parsed_to]))
-
+
return obj
#} END utilities
+
class PureReferencesMixin(ReferencesMixin):
+
"""Pure-Python refparse implementation"""
-
+
re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{4,40}$')
-
+
#{ Configuration
# Types to use when instatiating references
TagReferenceCls = TagReference
@@ -300,64 +304,62 @@ class PureReferencesMixin(ReferencesMixin):
HEADCls = HEAD
CommitCls = Commit
#} END configuration
-
+
def resolve(self, name):
return self.resolve_object(name).binsha
-
+
def resolve_object(self, name):
return rev_parse(self, name)
-
+
@property
def references(self):
return self.ReferenceCls.list_items(self)
-
+
@property
def heads(self):
return self.HeadCls.list_items(self)
-
+
@property
def tags(self):
return self.TagReferenceCls.list_items(self)
-
+
def tag(self, name):
return self.TagReferenceCls(self, self.TagReferenceCls.to_full_path(name))
-
+
def commit(self, rev=None):
if rev is None:
return self.head.commit
else:
- return self.resolve_object(str(rev)+"^0")
- #END handle revision
-
+ return self.resolve_object(str(rev) + "^0")
+ # END handle revision
+
def iter_trees(self, *args, **kwargs):
- return ( c.tree for c in self.iter_commits(*args, **kwargs) )
+ return (c.tree for c in self.iter_commits(*args, **kwargs))
def tree(self, rev=None):
if rev is None:
return self.head.commit.tree
else:
- return self.resolve_object(str(rev)+"^{tree}")
+ return self.resolve_object(str(rev) + "^{tree}")
def iter_commits(self, rev=None, paths='', **kwargs):
if rev is None:
rev = self.head.commit
-
+
return self.CommitCls.iter_items(self, rev, paths, **kwargs)
-
@property
def head(self):
- return self.HEADCls(self,'HEAD')
-
- def create_head(self, path, commit='HEAD', force=False, logmsg=None ):
+ return self.HEADCls(self, 'HEAD')
+
+ def create_head(self, path, commit='HEAD', force=False, logmsg=None):
return self.HeadCls.create(self, path, commit, force, logmsg)
-
+
def delete_head(self, *heads, **kwargs):
return self.HeadCls.delete(self, *heads, **kwargs)
-
+
def create_tag(self, path, ref='HEAD', message=None, force=False, **kwargs):
return self.TagReferenceCls.create(self, path, ref, message, force, **kwargs)
-
+
def delete_tag(self, *tags):
return self.TagReferenceCls.delete(self, *tags)
-
diff --git a/git/db/py/submodule.py b/git/db/py/submodule.py
index 0f2120c5..39b20961 100644
--- a/git/db/py/submodule.py
+++ b/git/db/py/submodule.py
@@ -8,26 +8,27 @@ from git.db.interface import SubmoduleDB
__all__ = ["PureSubmoduleDB"]
+
class PureSubmoduleDB(SubmoduleDB):
+
"""Pure python implementation of submodule functionality"""
-
+
@property
def submodules(self):
return Submodule.list_items(self)
-
+
def submodule(self, name):
try:
return self.submodules[name]
except IndexError:
raise ValueError("Didn't find submodule named %r" % name)
# END exception handling
-
+
def create_submodule(self, *args, **kwargs):
return Submodule.add(self, *args, **kwargs)
-
+
def iter_submodules(self, *args, **kwargs):
return RootModule(self).traverse(*args, **kwargs)
-
+
def submodule_update(self, *args, **kwargs):
return RootModule(self).update(*args, **kwargs)
-
diff --git a/git/db/py/transport.py b/git/db/py/transport.py
index 7bcaab95..809d1977 100644
--- a/git/db/py/transport.py
+++ b/git/db/py/transport.py
@@ -4,10 +4,10 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Implement a transport compatible database which sends objects using the git protocol"""
-from git.db.interface import ( TransportDB,
- PushInfo,
- FetchInfo,
- RefSpec )
+from git.db.interface import (TransportDB,
+ PushInfo,
+ FetchInfo,
+ RefSpec)
from git.refs.remote import RemoteReference
from git.remote import Remote
@@ -15,16 +15,18 @@ from git.remote import Remote
__all__ = ["PureTransportDB"]
+
class PurePushInfo(PushInfo):
+
"""TODO: Implementation"""
__slots__ = tuple()
-
-
-
+
+
class PureFetchInfo(FetchInfo):
+
"""TODO"""
__slots__ = tuple()
-
+
class PureTransportDB(TransportDB):
# The following variables need to be set by the derived class
@@ -32,27 +34,26 @@ class PureTransportDB(TransportDB):
protocol = None
RemoteCls = Remote
#}end configuraiton
-
+
#{ Interface
-
+
def fetch(self, url, refspecs, progress=None, **kwargs):
raise NotImplementedError()
-
+
def push(self, url, refspecs, progress=None, **kwargs):
raise NotImplementedError()
-
+
@property
def remotes(self):
return self.RemoteCls.list_items(self)
-
+
def remote(self, name='origin'):
return self.remotes[name]
-
+
def create_remote(self, name, url, **kwargs):
return self.RemoteCls.create(self, name, url, **kwargs)
-
+
def delete_remote(self, remote):
return self.RemoteCls.remove(self, remote)
-
- #}end interface
+ #}end interface
diff --git a/git/db/pygit2/__init__.py b/git/db/pygit2/__init__.py
index f600bf2b..686ebf07 100644
--- a/git/db/pygit2/__init__.py
+++ b/git/db/pygit2/__init__.py
@@ -1,11 +1,12 @@
"""Pygit2 module initialization"""
+
def init_pygit2():
""":raise ImportError: if pygit2 is not present"""
try:
import pygit2
except ImportError:
raise ImportError("Could not find 'pygit2' in the PYTHONPATH - pygit2 functionality is not available")
- #END handle pygit2 import
+ # END handle pygit2 import
init_pygit2()
diff --git a/git/db/pygit2/complex.py b/git/db/pygit2/complex.py
index cf845ff6..78734124 100644
--- a/git/db/pygit2/complex.py
+++ b/git/db/pygit2/complex.py
@@ -3,10 +3,10 @@ __all__ = ['Pygit2GitODB', 'Pygit2GitDB', 'Pygit2CompatibilityGitDB']
from git.db.py.complex import PureGitODB
from git.db.py.base import (
- PureRepositoryPathsMixin,
- PureConfigurationMixin,
- PureIndexDB,
- )
+ PureRepositoryPathsMixin,
+ PureConfigurationMixin,
+ PureIndexDB,
+)
from git.db.py.resolve import PureReferencesMixin
from git.db.py.transport import PureTransportDB
from git.db.py.submodule import PureSubmoduleDB
@@ -20,13 +20,14 @@ from git.base import OInfo, OStream
from git.fun import type_id_to_type_map, type_to_type_id_map
from git.util import hex_to_bin
-from cStringIO import StringIO
+from cStringIO import StringIO
import os
class Pygit2GitODB(PureGitODB):
+
"""A full fledged database to read and write object files from all kinds of sources."""
-
+
def __init__(self, objects_root):
"""Initalize this instance"""
PureGitODB.__init__(self, objects_root)
@@ -34,11 +35,11 @@ class Pygit2GitODB(PureGitODB):
wd = self.git_dir
else:
wd = os.path.dirname(objects_root)
- #END try to figure out good entry for pygit2 - it needs the .gitdir
+ # END try to figure out good entry for pygit2 - it needs the .gitdir
print objects_root
print wd
self._py2_repo = Pygit2Repo(wd)
-
+
def __getattr__(self, attr):
try:
# supply LazyMixin with this call first
@@ -46,21 +47,21 @@ class Pygit2GitODB(PureGitODB):
except AttributeError:
# now assume its on the pygit2 repository ... for now
return getattr(self._py2_repo, attr)
- #END handle attr
-
+ # END handle attr
+
#{ Object DBR
-
+
def info(self, binsha):
- type_id, uncomp_data = self._py2_repo.read(binsha)
+ type_id, uncomp_data = self._py2_repo.read(binsha)
return OInfo(binsha, type_id_to_type_map[type_id], len(uncomp_data))
-
+
def stream(self, binsha):
type_id, uncomp_data = self._py2_repo.read(binsha)
return OStream(binsha, type_id_to_type_map[type_id], len(uncomp_data), StringIO(uncomp_data))
-
- # #}END object dbr
- #
- # #{ Object DBW
+
+ # }END object dbr
+ #
+ # { Object DBW
def store(self, istream):
# TODO: remove this check once the required functionality was merged in pygit2
if hasattr(self._py2_repo, 'write'):
@@ -68,26 +69,26 @@ class Pygit2GitODB(PureGitODB):
return istream
else:
return super(Pygit2GitODB, self).store(istream)
- #END handle write support
-
+ # END handle write support
+
#}END object dbw
-
-class Pygit2GitDB( PureRepositoryPathsMixin, PureConfigurationMixin,
- PureReferencesMixin, PureSubmoduleDB,
- PureIndexDB,
- PureTransportDB, # not fully implemented
- GitCommandMixin,
- CmdHighLevelRepository,
- Pygit2GitODB): # must come last, as it doesn't pass on __init__ with super
+class Pygit2GitDB(PureRepositoryPathsMixin, PureConfigurationMixin,
+ PureReferencesMixin, PureSubmoduleDB,
+ PureIndexDB,
+ PureTransportDB, # not fully implemented
+ GitCommandMixin,
+ CmdHighLevelRepository,
+ Pygit2GitODB): # must come last, as it doesn't pass on __init__ with super
+
def __init__(self, root_path):
"""Initialize ourselves on the .git directory, or the .git/objects directory."""
PureRepositoryPathsMixin._initialize(self, root_path)
super(Pygit2GitDB, self).__init__(self.objects_dir)
-
+
class Pygit2CompatibilityGitDB(RepoCompatibilityInterface, Pygit2GitDB):
+
"""Basic pygit2 compatibility database"""
pass
-
diff --git a/git/diff.py b/git/diff.py
index f892861e..b9b0bf1b 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -6,26 +6,28 @@
import re
from objects.blob import Blob
-from objects.util import mode_str_to_int
+from objects.util import mode_str_to_int
from exc import GitCommandError
from git.util import hex_to_bin
-
+
__all__ = ('Diffable', 'DiffIndex', 'Diff')
-
+
+
class Diffable(object):
+
"""Common interface for all object that can be diffed against another object of compatible type.
-
+
:note:
Subclasses require a repo member as it is the case for Object instances, for practical
reasons we do not derive from Object."""
__slots__ = tuple()
-
+
# standin indicating you want to diff against the index
class Index(object):
- pass
-
+ pass
+
def _process_diff_args(self, args):
"""
:return:
@@ -33,7 +35,7 @@ class Diffable(object):
Method is called right before git command execution.
Subclasses can use it to alter the behaviour of the superclass"""
return args
-
+
def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
"""Creates diffs between two items being trees, trees and index or an
index and the working tree.
@@ -60,54 +62,55 @@ class Diffable(object):
R=True to swap both sides of the diff.
:return: git.DiffIndex
-
+
:note:
Rename detection will only work if create_patch is True.
-
+
On a bare repository, 'other' needs to be provided as Index or as
as Tree/Commit, or a git command error will occour"""
args = list()
- args.append( "--abbrev=40" ) # we need full shas
- args.append( "--full-index" ) # get full index paths, not only filenames
-
+ args.append("--abbrev=40") # we need full shas
+ args.append("--full-index") # get full index paths, not only filenames
+
if create_patch:
args.append("-p")
args.append("-M") # check for renames
else:
args.append("--raw")
-
- if paths is not None and not isinstance(paths, (tuple,list)):
- paths = [ paths ]
+
+ if paths is not None and not isinstance(paths, (tuple, list)):
+ paths = [paths]
if other is not None and other is not self.Index:
args.insert(0, other)
if other is self.Index:
args.insert(0, "--cached")
-
- args.insert(0,self)
-
+
+ args.insert(0, self)
+
# paths is list here or None
if paths:
args.append("--")
args.extend(paths)
# END paths handling
-
+
kwargs['as_process'] = True
proc = self.repo.git.diff(*self._process_diff_args(args), **kwargs)
-
+
diff_method = Diff._index_from_raw_format
if create_patch:
diff_method = Diff._index_from_patch_format
index = diff_method(self.repo, proc.stdout)
-
+
status = proc.wait()
return index
class DiffIndex(list):
+
"""Implements an Index for diffs, allowing a list of Diffs to be queried by
the diff properties.
-
+
The class improves the diff handling convenience"""
# change type invariant identifying possible ways a blob can have changed
# A = Added
@@ -115,23 +118,22 @@ class DiffIndex(list):
# R = Renamed
# M = modified
change_type = ("A", "D", "R", "M")
-
-
+
def iter_change_type(self, change_type):
"""
:return:
iterator yieling Diff instances that match the given change_type
-
+
:param change_type:
Member of DiffIndex.change_type, namely:
-
+
* 'A' for added paths
* 'D' for deleted paths
* 'R' for renamed paths
* 'M' for paths with modified data"""
if change_type not in self.change_type:
- raise ValueError( "Invalid change type: %s" % change_type )
-
+ raise ValueError("Invalid change type: %s" % change_type)
+
for diff in self:
if change_type == "A" and diff.new_file:
yield diff
@@ -142,37 +144,38 @@ class DiffIndex(list):
elif change_type == "M" and diff.a_blob and diff.b_blob and diff.a_blob != diff.b_blob:
yield diff
# END for each diff
-
+
class Diff(object):
+
"""A Diff contains diff information between two Trees.
-
+
It contains two sides a and b of the diff, members are prefixed with
"a" and "b" respectively to inidcate that.
-
+
Diffs keep information about the changed blob objects, the file mode, renames,
deletions and new files.
-
+
There are a few cases where None has to be expected as member variable value:
-
+
``New File``::
-
+
a_mode is None
a_blob is None
-
+
``Deleted File``::
-
+
b_mode is None
b_blob is None
-
+
``Working Tree Blobs``
-
+
When comparing to working trees, the working tree blob will have a null hexsha
as a corresponding object does not yet exist. The mode will be null as well.
But the path will be available though.
If it is listed in a diff the working tree version of the file must
be different to the version in the index or tree, and hence has been modified."""
-
+
# precompiled regex
re_header = re.compile(r"""
#^diff[ ]--git
@@ -188,24 +191,24 @@ class Diff(object):
\.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
""", re.VERBOSE | re.MULTILINE)
# can be used for comparisons
- NULL_HEX_SHA = "0"*40
- NULL_BIN_SHA = "\0"*20
-
- __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "new_file", "deleted_file",
+ NULL_HEX_SHA = "0" * 40
+ NULL_BIN_SHA = "\0" * 20
+
+ __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "new_file", "deleted_file",
"rename_from", "rename_to", "diff")
def __init__(self, repo, a_path, b_path, a_blob_id, b_blob_id, a_mode,
b_mode, new_file, deleted_file, rename_from,
rename_to, diff):
-
+
self.a_mode = a_mode
self.b_mode = b_mode
-
+
if self.a_mode:
self.a_mode = mode_str_to_int(self.a_mode)
if self.b_mode:
self.b_mode = mode_str_to_int(self.b_mode)
-
+
if a_blob_id is None:
self.a_blob = None
else:
@@ -214,16 +217,15 @@ class Diff(object):
self.b_blob = None
else:
self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=b_path)
-
+
self.new_file = new_file
self.deleted_file = deleted_file
-
+
# be clear and use None instead of empty strings
self.rename_from = rename_from or None
self.rename_to = rename_to or None
-
- self.diff = diff
+ self.diff = diff
def __eq__(self, other):
for name in self.__slots__:
@@ -231,24 +233,24 @@ class Diff(object):
return False
# END for each name
return True
-
+
def __ne__(self, other):
- return not ( self == other )
-
+ return not (self == other)
+
def __hash__(self):
- return hash(tuple(getattr(self,n) for n in self.__slots__))
+ return hash(tuple(getattr(self, n) for n in self.__slots__))
def __str__(self):
h = "%s"
if self.a_blob:
h %= self.a_blob.path
- elif self.b_blob:
+ elif self.b_blob:
h %= self.b_blob.path
-
+
msg = ''
l = None # temp line
ll = 0 # line length
- for b,n in zip((self.a_blob, self.b_blob), ('lhs', 'rhs')):
+ for b, n in zip((self.a_blob, self.b_blob), ('lhs', 'rhs')):
if b:
l = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
else:
@@ -257,10 +259,10 @@ class Diff(object):
ll = max(len(l), ll)
msg += l
# END for each blob
-
+
# add headline
- h += '\n' + '='*ll
-
+ h += '\n' + '=' * ll
+
if self.deleted_file:
msg += '\nfile deleted in rhs'
if self.new_file:
@@ -274,7 +276,7 @@ class Diff(object):
msg += self.diff
msg += '\n---'
# END diff info
-
+
return h + msg
@property
@@ -302,16 +304,16 @@ class Diff(object):
new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode)
index.append(Diff(repo, a_path, b_path, a_blob_id, b_blob_id,
- old_mode or deleted_file_mode, new_mode or new_file_mode or b_mode,
- new_file, deleted_file, rename_from, rename_to, diff[header.end():]))
+ old_mode or deleted_file_mode, new_mode or new_file_mode or b_mode,
+ new_file, deleted_file, rename_from, rename_to, diff[header.end():]))
return index
-
+
@classmethod
def _index_from_raw_format(cls, repo, stream):
"""Create a new DiffIndex from the given stream which must be in raw format.
:return: git.DiffIndex"""
- # handles
+ # handles
# :100644 100644 6870991011cc8d9853a7a8a6f02061512c6a8190 37c5e30c879213e9ae83b21e9d11e55fc20c54b7 M .gitignore
# or
# :100644 100644 4aab7ea753e2867dd464f2a50dd266d426ddc8c8 4aab7ea753e2867dd464f2a50dd266d426ddc8c8 R100 src/bootstrap/package.json package.json
@@ -330,7 +332,7 @@ class Diff(object):
rename_from, rename_to = a_path, b_path
deleted_file = False
new_file = False
-
+
# NOTE: We cannot conclude from the existance of a blob to change type
# as diffs with the working do not have blobs yet
if change_type == 'D':
@@ -340,11 +342,10 @@ class Diff(object):
a_blob_id = None
new_file = True
# END add/remove handling
-
+
diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode,
new_file, deleted_file, rename_from, rename_to, '')
index.append(diff)
# END for each line
-
- return index
+ return index
diff --git a/git/exc.py b/git/exc.py
index a68486f7..8667e4b4 100644
--- a/git/exc.py
+++ b/git/exc.py
@@ -7,63 +7,78 @@
from util import to_hex_sha
+
class GitPythonError(Exception):
+
"""Base exception for all git-python related errors"""
+
class ODBError(GitPythonError):
+
"""All errors thrown by the object database"""
class InvalidDBRoot(ODBError):
+
"""Thrown if an object database cannot be initialized at the given path"""
class BadObject(ODBError):
+
"""The object with the given SHA does not exist. Instantiate with the
failed sha"""
-
+
def __str__(self):
return "BadObject: %s" % to_hex_sha(self.args[0])
class ParseError(ODBError):
+
"""Thrown if the parsing of a file failed due to an invalid format"""
class AmbiguousObjectName(ODBError):
+
"""Thrown if a possibly shortened name does not uniquely represent a single object
in the database"""
class BadObjectType(ODBError):
+
"""The object had an unsupported type"""
class UnsupportedOperation(ODBError):
+
"""Thrown if the given operation cannot be supported by the object database"""
class InvalidGitRepositoryError(InvalidDBRoot):
+
""" Thrown if the given repository appears to have an invalid format. """
class NoSuchPathError(InvalidDBRoot):
+
""" Thrown if a path could not be access by the system. """
class GitCommandError(GitPythonError):
+
""" Thrown if execution of the git command fails with non-zero status code. """
+
def __init__(self, command, status, stderr=None):
self.stderr = stderr
self.status = status
self.command = command
-
+
def __str__(self):
return ("'%s' returned exit status %i: %s" %
- (' '.join(str(i) for i in self.command), self.status, self.stderr))
+ (' '.join(str(i) for i in self.command), self.status, self.stderr))
class CheckoutError(GitPythonError):
+
"""Thrown if a file could not be checked out from the index as it contained
changes.
@@ -76,6 +91,7 @@ class CheckoutError(GitPythonError):
The .valid_files attribute contains a list of relative paths to files that
were checked out successfully and hence match the version stored in the
index"""
+
def __init__(self, message, failed_files, valid_files, failed_reasons):
Exception.__init__(self, message)
self.failed_files = failed_files
@@ -84,12 +100,14 @@ class CheckoutError(GitPythonError):
def __str__(self):
return Exception.__str__(self) + ":%s" % self.failed_files
-
-
+
+
class CacheError(GitPythonError):
+
"""Base for all errors related to the git index, which is called cache internally"""
class UnmergedEntriesError(CacheError):
+
"""Thrown if an operation cannot proceed as there are still unmerged
entries in the cache"""
diff --git a/git/fun.py b/git/fun.py
index 7749ee8a..5db36f2c 100644
--- a/git/fun.py
+++ b/git/fun.py
@@ -8,7 +8,7 @@ it into c later, if required"""
from exc import (
BadObjectType
- )
+)
from util import zlib
decompressobj = zlib.decompressobj
@@ -23,32 +23,32 @@ OFS_DELTA = 6
REF_DELTA = 7
delta_types = (OFS_DELTA, REF_DELTA)
-type_id_to_type_map = {
- 0 : "", # EXT 1
- 1 : "commit",
- 2 : "tree",
- 3 : "blob",
- 4 : "tag",
- 5 : "", # EXT 2
- OFS_DELTA : "OFS_DELTA", # OFFSET DELTA
- REF_DELTA : "REF_DELTA" # REFERENCE DELTA
- }
+type_id_to_type_map = {
+ 0: "", # EXT 1
+ 1: "commit",
+ 2: "tree",
+ 3: "blob",
+ 4: "tag",
+ 5: "", # EXT 2
+ OFS_DELTA: "OFS_DELTA", # OFFSET DELTA
+ REF_DELTA: "REF_DELTA" # REFERENCE DELTA
+}
type_to_type_id_map = dict(
- commit=1,
- tree=2,
- blob=3,
- tag=4,
- OFS_DELTA=OFS_DELTA,
- REF_DELTA=REF_DELTA
- )
+ commit=1,
+ tree=2,
+ blob=3,
+ tag=4,
+ OFS_DELTA=OFS_DELTA,
+ REF_DELTA=REF_DELTA
+)
# used when dealing with larger streams
-chunk_size = 1000*mmap.PAGESIZE
+chunk_size = 1000 * mmap.PAGESIZE
-__all__ = ('is_loose_object', 'loose_object_header_info', 'msb_size', 'pack_object_header_info',
- 'write_object', 'loose_object_header', 'stream_copy', 'apply_delta_data',
- 'is_equal_canonical_sha', 'connect_deltas', 'DeltaChunkList', 'create_pack_object_header')
+__all__ = ('is_loose_object', 'loose_object_header_info', 'msb_size', 'pack_object_header_info',
+ 'write_object', 'loose_object_header', 'stream_copy', 'apply_delta_data',
+ 'is_equal_canonical_sha', 'connect_deltas', 'DeltaChunkList', 'create_pack_object_header')
#{ Structures
@@ -59,11 +59,12 @@ def _set_delta_rbound(d, size):
to our size
:return: d"""
d.ts = size
-
+
# NOTE: data is truncated automatically when applying the delta
# MUST NOT DO THIS HERE
return d
-
+
+
def _move_delta_lbound(d, bytes):
"""Move the delta by the given amount of bytes, reducing its size so that its
right bound stays static
@@ -71,19 +72,21 @@ def _move_delta_lbound(d, bytes):
:return: d"""
if bytes == 0:
return
-
+
d.to += bytes
d.so += bytes
d.ts -= bytes
if d.data is not None:
d.data = d.data[bytes:]
# END handle data
-
+
return d
-
+
+
def delta_duplicate(src):
return DeltaChunk(src.to, src.ts, src.so, src.data)
-
+
+
def delta_chunk_apply(dc, bbuf, write):
"""Apply own data to the target buffer
:param bbuf: buffer providing source bytes for copy operations
@@ -104,16 +107,17 @@ def delta_chunk_apply(dc, bbuf, write):
class DeltaChunk(object):
+
"""Represents a piece of a delta, it can either add new data, or copy existing
one from a source buffer"""
__slots__ = (
- 'to', # start offset in the target buffer in bytes
+ 'to', # start offset in the target buffer in bytes
'ts', # size of this chunk in the target buffer in bytes
'so', # start offset in the source buffer in bytes or None
'data', # chunk of bytes to be added to the target buffer,
# DeltaChunkList to use as base, or None
- )
-
+ )
+
def __init__(self, to, ts, so, data):
self.to = to
self.ts = ts
@@ -122,18 +126,19 @@ class DeltaChunk(object):
def __repr__(self):
return "DeltaChunk(%i, %i, %s, %s)" % (self.to, self.ts, self.so, self.data or "")
-
+
#{ Interface
-
+
def rbound(self):
return self.to + self.ts
-
+
def has_data(self):
""":return: True if the instance has data to add to the target stream"""
return self.data is not None
-
+
#} END interface
+
def _closest_index(dcl, absofs):
""":return: index at which the given absofs should be inserted. The index points
to the DeltaChunk with a target buffer absofs that equals or is greater than
@@ -152,8 +157,9 @@ def _closest_index(dcl, absofs):
lo = mid + 1
# END handle bound
# END for each delta absofs
- return len(dcl)-1
-
+ return len(dcl) - 1
+
+
def delta_list_apply(dcl, bbuf, write):
"""Apply the chain's changes and write the final result using the passed
write function.
@@ -165,6 +171,7 @@ def delta_list_apply(dcl, bbuf, write):
delta_chunk_apply(dc, bbuf, write)
# END for each dc
+
def delta_list_slice(dcl, absofs, size, ndcl):
""":return: Subsection of this list at the given absolute offset, with the given
size in bytes.
@@ -172,8 +179,8 @@ def delta_list_slice(dcl, absofs, size, ndcl):
cdi = _closest_index(dcl, absofs) # delta start index
cd = dcl[cdi]
slen = len(dcl)
- lappend = ndcl.append
-
+ lappend = ndcl.append
+
if cd.to != absofs:
tcd = DeltaChunk(cd.to, cd.ts, cd.so, cd.data)
_move_delta_lbound(tcd, absofs - cd.to)
@@ -182,7 +189,7 @@ def delta_list_slice(dcl, absofs, size, ndcl):
size -= tcd.ts
cdi += 1
# END lbound overlap handling
-
+
while cdi < slen and size:
# are we larger than the current block
cd = dcl[cdi]
@@ -198,38 +205,39 @@ def delta_list_slice(dcl, absofs, size, ndcl):
# END hadle size
cdi += 1
# END for each chunk
-
-
+
+
class DeltaChunkList(list):
+
"""List with special functionality to deal with DeltaChunks.
There are two types of lists we represent. The one was created bottom-up, working
towards the latest delta, the other kind was created top-down, working from the
latest delta down to the earliest ancestor. This attribute is queryable
after all processing with is_reversed."""
-
+
__slots__ = tuple()
-
+
def rbound(self):
""":return: rightmost extend in bytes, absolute"""
if len(self) == 0:
return 0
return self[-1].rbound()
-
+
def lbound(self):
""":return: leftmost byte at which this chunklist starts"""
if len(self) == 0:
return 0
return self[0].to
-
+
def size(self):
""":return: size of bytes as measured by our delta chunks"""
return self.rbound() - self.lbound()
-
+
def apply(self, bbuf, write):
"""Only used by public clients, internally we only use the global routines
for performance"""
return delta_list_apply(self, bbuf, write)
-
+
def compress(self):
"""Alter the list to reduce the amount of nodes. Currently we concatenate
add-chunks
@@ -239,41 +247,41 @@ class DeltaChunkList(list):
return self
i = 0
slen_orig = slen
-
+
first_data_index = None
while i < slen:
dc = self[i]
i += 1
if dc.data is None:
- if first_data_index is not None and i-2-first_data_index > 1:
- #if first_data_index is not None:
+ if first_data_index is not None and i - 2 - first_data_index > 1:
+ # if first_data_index is not None:
nd = StringIO() # new data
so = self[first_data_index].to # start offset in target buffer
- for x in xrange(first_data_index, i-1):
+ for x in xrange(first_data_index, i - 1):
xdc = self[x]
nd.write(xdc.data[:xdc.ts])
# END collect data
-
- del(self[first_data_index:i-1])
+
+ del(self[first_data_index:i - 1])
buf = nd.getvalue()
- self.insert(first_data_index, DeltaChunk(so, len(buf), 0, buf))
-
+ self.insert(first_data_index, DeltaChunk(so, len(buf), 0, buf))
+
slen = len(self)
i = first_data_index + 1
-
+
# END concatenate data
first_data_index = None
continue
# END skip non-data chunks
-
+
if first_data_index is None:
- first_data_index = i-1
+ first_data_index = i - 1
# END iterate list
-
- #if slen_orig != len(self):
+
+ # if slen_orig != len(self):
# print "INFO: Reduced delta list len to %f %% of former size" % ((float(len(self)) / slen_orig) * 100)
return self
-
+
def check_integrity(self, target_size=-1):
"""Verify the list has non-overlapping chunks only, and the total size matches
target_size
@@ -281,35 +289,36 @@ class DeltaChunkList(list):
:raise AssertionError: if the size doen't match"""
if target_size > -1:
assert self[-1].rbound() == target_size
- assert reduce(lambda x,y: x+y, (d.ts for d in self), 0) == target_size
+ assert reduce(lambda x, y: x + y, (d.ts for d in self), 0) == target_size
# END target size verification
-
+
if len(self) < 2:
return
-
+
# check data
for dc in self:
assert dc.ts > 0
if dc.has_data():
assert len(dc.data) >= dc.ts
# END for each dc
-
- left = islice(self, 0, len(self)-1)
+
+ left = islice(self, 0, len(self) - 1)
right = iter(self)
right.next()
- # this is very pythonic - we might have just use index based access here,
+ # this is very pythonic - we might have just use index based access here,
# but this could actually be faster
- for lft,rgt in izip(left, right):
+ for lft, rgt in izip(left, right):
assert lft.rbound() == rgt.to
assert lft.to + lft.ts == rgt.to
# END for each pair
-
+
class TopdownDeltaChunkList(DeltaChunkList):
+
"""Represents a list which is generated by feeding its ancestor streams one by
one"""
- __slots__ = tuple()
-
+ __slots__ = tuple()
+
def connect_with_next_base(self, bdcl):
"""Connect this chain with the next level of our base delta chunklist.
The goal in this game is to mark as many of our chunks rigid, hence they
@@ -326,13 +335,13 @@ class TopdownDeltaChunkList(DeltaChunkList):
while dci < slen:
dc = self[dci]
dci += 1
-
+
# all add-chunks which are already topmost don't need additional processing
if dc.data is not None:
nfc += 1
continue
# END skip add chunks
-
+
# copy chunks
# integrate the portion of the base list into ourselves. Lists
# dont support efficient insertion ( just one at a time ), but for now
@@ -341,37 +350,37 @@ class TopdownDeltaChunkList(DeltaChunkList):
# ourselves in order to reduce the amount of insertions ...
del(ccl[:])
delta_list_slice(bdcl, dc.so, dc.ts, ccl)
-
+
# move the target bounds into place to match with our chunk
ofs = dc.to - dc.so
for cdc in ccl:
cdc.to += ofs
# END update target bounds
-
+
if len(ccl) == 1:
- self[dci-1] = ccl[0]
+ self[dci - 1] = ccl[0]
else:
# maybe try to compute the expenses here, and pick the right algorithm
# It would normally be faster than copying everything physically though
# TODO: Use a deque here, and decide by the index whether to extend
# or extend left !
post_dci = self[dci:]
- del(self[dci-1:]) # include deletion of dc
+ del(self[dci - 1:]) # include deletion of dc
self.extend(ccl)
self.extend(post_dci)
-
+
slen = len(self)
- dci += len(ccl)-1 # deleted dc, added rest
-
+ dci += len(ccl) - 1 # deleted dc, added rest
+
# END handle chunk replacement
# END for each chunk
-
+
if nfc == slen:
return False
# END handle completeness
return True
-
-
+
+
#} END structures
#{ Routines
@@ -384,6 +393,7 @@ def is_loose_object(m):
word = (b0 << 8) + b1
return b0 == 0x78 and (word % 31) == 0
+
def loose_object_header_info(m):
"""
:return: tuple(type_string, uncompressed_size_in_bytes) the type string of the
@@ -393,7 +403,8 @@ def loose_object_header_info(m):
hdr = decompressobj().decompress(m, decompress_size)
type_name, size = hdr[:hdr.find("\0")].split(" ")
return type_name, int(size)
-
+
+
def pack_object_header_info(data):
"""
:return: tuple(type_id, uncompressed_size_in_bytes, byte_offset)
@@ -413,13 +424,14 @@ def pack_object_header_info(data):
# END character loop
return (type_id, size, i)
+
def create_pack_object_header(obj_type, obj_size):
""":return: string defining the pack header comprised of the object type
and its incompressed size in bytes
:parmam obj_type: pack type_id of the object
:param obj_size: uncompressed size in bytes of the following object stream"""
c = 0 # 1 byte
- hdr = str() # output string
+ hdr = str() # output string
c = (obj_type << 4) | (obj_size & 0xf)
obj_size >>= 4
@@ -427,10 +439,11 @@ def create_pack_object_header(obj_type, obj_size):
hdr += chr(c | 0x80)
c = obj_size & 0x7f
obj_size >>= 7
- #END until size is consumed
+ # END until size is consumed
hdr += chr(c)
return hdr
-
+
+
def msb_size(data, offset=0):
"""
:return: tuple(read_bytes, size) read the msb size from the given random
@@ -440,8 +453,8 @@ def msb_size(data, offset=0):
l = len(data)
hit_msb = False
while i < l:
- c = ord(data[i+offset])
- size |= (c & 0x7f) << i*7
+ c = ord(data[i + offset])
+ size |= (c & 0x7f) << i * 7
i += 1
if not c & 0x80:
hit_msb = True
@@ -450,19 +463,21 @@ def msb_size(data, offset=0):
# END while in range
if not hit_msb:
raise AssertionError("Could not find terminating MSB byte in data stream")
- return i+offset, size
-
+ return i + offset, size
+
+
def loose_object_header(type, size):
"""
:return: string representing the loose object header, which is immediately
followed by the content stream of size 'size'"""
return "%s %i\0" % (type, size)
-
+
+
def write_object(type, size, read, write, chunk_size=chunk_size):
"""
Write the object as identified by type, size and source_stream into the
target_stream
-
+
:param type: type string of the object
:param size: amount of bytes to write from source_stream
:param read: read method of a stream providing the content data
@@ -471,26 +486,27 @@ def write_object(type, size, read, write, chunk_size=chunk_size):
the routine exits, even if an error is thrown
:return: The actual amount of bytes written to stream, which includes the header and a trailing newline"""
tbw = 0 # total num bytes written
-
+
# WRITE HEADER: type SP size NULL
tbw += write(loose_object_header(type, size))
tbw += stream_copy(read, write, size, chunk_size)
-
+
return tbw
+
def stream_copy(read, write, size, chunk_size):
"""
Copy a stream up to size bytes using the provided read and write methods,
in chunks of chunk_size
-
+
:note: its much like stream_copy utility, but operates just using methods"""
dbw = 0 # num data bytes written
-
+
# WRITE ALL DATA UP TO SIZE
while True:
- cs = min(chunk_size, size-dbw)
+ cs = min(chunk_size, size - dbw)
# NOTE: not all write methods return the amount of written bytes, like
- # mmap.write. Its bad, but we just deal with it ... perhaps its not
+ # mmap.write. Its bad, but we just deal with it ... perhaps its not
# even less efficient
# data_len = write(read(cs))
# dbw += data_len
@@ -503,27 +519,28 @@ def stream_copy(read, write, size, chunk_size):
# END check for stream end
# END duplicate data
return dbw
-
+
+
def connect_deltas(dstreams):
"""
Read the condensed delta chunk information from dstream and merge its information
into a list of existing delta chunks
-
+
:param dstreams: iterable of delta stream objects, the delta to be applied last
comes first, then all its ancestors in order
:return: DeltaChunkList, containing all operations to apply"""
tdcl = None # topmost dcl
-
+
dcl = tdcl = TopdownDeltaChunkList()
for dsi, ds in enumerate(dstreams):
# print "Stream", dsi
db = ds.read()
delta_buf_size = ds.size
-
+
# read header
i, base_size = msb_size(db)
i, target_size = msb_size(db, i)
-
+
# interpret opcodes
tbw = 0 # amount of target bytes written
while i < delta_buf_size:
@@ -552,46 +569,47 @@ def connect_deltas(dstreams):
if (c & 0x40):
cp_size |= (ord(db[i]) << 16)
i += 1
-
- if not cp_size:
+
+ if not cp_size:
cp_size = 0x10000
-
+
rbound = cp_off + cp_size
if (rbound < cp_size or
- rbound > base_size):
+ rbound > base_size):
break
-
+
dcl.append(DeltaChunk(tbw, cp_size, cp_off, None))
tbw += cp_size
elif c:
# NOTE: in C, the data chunks should probably be concatenated here.
# In python, we do it as a post-process
- dcl.append(DeltaChunk(tbw, c, 0, db[i:i+c]))
+ dcl.append(DeltaChunk(tbw, c, 0, db[i:i + c]))
i += c
tbw += c
else:
raise ValueError("unexpected delta opcode 0")
# END handle command byte
# END while processing delta data
-
+
dcl.compress()
-
+
# merge the lists !
if dsi > 0:
if not tdcl.connect_with_next_base(dcl):
break
# END handle merge
-
+
# prepare next base
dcl = DeltaChunkList()
# END for each delta stream
-
+
return tdcl
-
+
+
def apply_delta_data(src_buf, src_buf_size, delta_buf, delta_buf_size, write):
"""
Apply data from a delta buffer using a source buffer to the target file
-
+
:param src_buf: random access data from which the delta was created
:param src_buf_size: size of the source buffer in bytes
:param delta_buf_size: size fo the delta buffer in bytes
@@ -626,27 +644,27 @@ def apply_delta_data(src_buf, src_buf_size, delta_buf, delta_buf_size, write):
if (c & 0x40):
cp_size |= (ord(db[i]) << 16)
i += 1
-
- if not cp_size:
+
+ if not cp_size:
cp_size = 0x10000
-
+
rbound = cp_off + cp_size
if (rbound < cp_size or
- rbound > src_buf_size):
+ rbound > src_buf_size):
break
write(buffer(src_buf, cp_off, cp_size))
elif c:
- write(db[i:i+c])
+ write(db[i:i + c])
i += c
else:
raise ValueError("unexpected delta opcode 0")
# END handle command byte
# END while processing delta data
-
+
# yes, lets use the exact same error message that git uses :)
assert i == delta_buf_size, "delta replay has gone wild"
-
-
+
+
def is_equal_canonical_sha(canonical_length, match, sha1):
"""
:return: True if the given lhs and rhs 20 byte binary shas
@@ -654,16 +672,16 @@ def is_equal_canonical_sha(canonical_length, match, sha1):
hence the comparison will only use the last 4 bytes for uneven canonical representations
:param match: less than 20 byte sha
:param sha1: 20 byte sha"""
- binary_length = canonical_length/2
+ binary_length = canonical_length / 2
if match[:binary_length] != sha1[:binary_length]:
return False
-
+
if canonical_length - binary_length and \
- (ord(match[-1]) ^ ord(sha1[len(match)-1])) & 0xf0:
+ (ord(match[-1]) ^ ord(sha1[len(match) - 1])) & 0xf0:
return False
# END handle uneven canonnical length
return True
-
+
#} END routines
diff --git a/git/index/__init__.py b/git/index/__init__.py
index fe4a7f59..4a495c33 100644
--- a/git/index/__init__.py
+++ b/git/index/__init__.py
@@ -1,4 +1,4 @@
"""Initialize the index package"""
from base import *
-from typ import * \ No newline at end of file
+from typ import *
diff --git a/git/index/base.py b/git/index/base.py
index c2b90218..c200f05f 100644
--- a/git/index/base.py
+++ b/git/index/base.py
@@ -13,86 +13,87 @@ from cStringIO import StringIO
from stat import S_ISLNK
from typ import (
- BaseIndexEntry,
- IndexEntry,
- )
+ BaseIndexEntry,
+ IndexEntry,
+)
from util import (
- TemporaryFileSwap,
- post_clear_cache,
- default_index,
- git_working_dir
- )
+ TemporaryFileSwap,
+ post_clear_cache,
+ default_index,
+ git_working_dir
+)
import git.objects
import git.diff as diff
from git.exc import (
- GitCommandError,
- CheckoutError
- )
+ GitCommandError,
+ CheckoutError
+)
from git.objects import (
- Blob,
- Submodule,
- Tree,
- Object,
- Commit,
- )
+ Blob,
+ Submodule,
+ Tree,
+ Object,
+ Commit,
+)
from git.objects.util import Serializable
from git.util import (
- IndexFileSHA1Writer,
- LazyMixin,
- LockedFD,
- join_path_native,
- file_contents_ro,
- to_native_path_linux,
- to_native_path
- )
+ IndexFileSHA1Writer,
+ LazyMixin,
+ LockedFD,
+ join_path_native,
+ file_contents_ro,
+ to_native_path_linux,
+ to_native_path
+)
from fun import (
- entry_key,
- write_cache,
- read_cache,
- aggressive_tree_merge,
- write_tree_from_cache,
- stat_mode_to_index_mode,
- S_IFGITLINK
- )
+ entry_key,
+ write_cache,
+ read_cache,
+ aggressive_tree_merge,
+ write_tree_from_cache,
+ stat_mode_to_index_mode,
+ S_IFGITLINK
+)
from git.base import IStream
from git.util import to_bin_sha
from itertools import izip
-__all__ = ( 'IndexFile', 'CheckoutError' )
+__all__ = ('IndexFile', 'CheckoutError')
class IndexFile(LazyMixin, diff.Diffable, Serializable):
+
"""
Implements an Index that can be manipulated using a native implementation in
order to save git command function calls wherever possible.
-
+
It provides custom merging facilities allowing to merge without actually changing
your index or your working tree. This way you can perform own test-merges based
on the index only without having to deal with the working copy. This is useful
in case of partial working trees.
``Entries``
-
+
The index contains an entries dict whose keys are tuples of type IndexEntry
to facilitate access.
You may read the entries dict or manipulate it using IndexEntry instance, i.e.::
-
+
index.entries[index.entry_key(index_entry_instance)] = index_entry_instance
-
+
Make sure you use index.write() once you are done manipulating the index directly
before operating on it using the git command"""
__slots__ = ("repo", "version", "entries", "_extension_data", "_file_path")
_VERSION = 2 # latest version we support
- S_IFGITLINK = S_IFGITLINK # a submodule
+ S_IFGITLINK = S_IFGITLINK # a submodule
def __init__(self, repo, file_path=None):
"""Initialize this Index instance, optionally from the given ``file_path``.
@@ -119,14 +120,14 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
return
# END exception handling
- # Here it comes: on windows in python 2.5, memory maps aren't closed properly
- # Hence we are in trouble if we try to delete a file that is memory mapped,
+ # Here it comes: on windows in python 2.5, memory maps aren't closed properly
+ # Hence we are in trouble if we try to delete a file that is memory mapped,
# which happens during read-tree.
# In this case, we will just read the memory in directly.
# Its insanely bad ... I am disappointed !
- allow_mmap = (os.name != 'nt' or sys.version_info[1] > 5)
+ allow_mmap = (os.name != 'nt' or sys.version_info[1] > 5)
stream = file_contents_ro(fd, stream=True, allow_mmap=allow_mmap)
-
+
try:
self._deserialize(stream)
finally:
@@ -153,30 +154,29 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
pass
# END exception handling
- #{ Serializable Interface
+ #{ Serializable Interface
def _deserialize(self, stream):
"""Initialize this instance with index values read from the given stream"""
self.version, self.entries, self._extension_data, conten_sha = read_cache(stream)
return self
-
+
def _entries_sorted(self):
""":return: list of entries, in a sorted fashion, first by path, then by stage"""
entries_sorted = self.entries.values()
entries_sorted.sort(key=lambda e: (e.path, e.stage)) # use path/stage as sort key
return entries_sorted
-
+
def _serialize(self, stream, ignore_tree_extension_data=False):
entries = self._entries_sorted()
write_cache(entries,
stream,
- (ignore_tree_extension_data and None) or self._extension_data)
+ (ignore_tree_extension_data and None) or self._extension_data)
return self
-
-
+
#} END serializable interface
- def write(self, file_path = None, ignore_tree_extension_data=False):
+ def write(self, file_path=None, ignore_tree_extension_data=False):
"""Write the current state to our file path or to the given one
:param file_path:
@@ -197,14 +197,14 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
:return: self"""
# make sure we have our entries read before getting a write lock
- # else it would be done when streaming. This can happen
+ # else it would be done when streaming. This can happen
# if one doesn't change the index, but writes it right away
self.entries
lfd = LockedFD(file_path or self._file_path)
stream = lfd.open(write=True, stream=True)
-
+
self._serialize(stream, ignore_tree_extension_data)
-
+
lfd.commit()
# make sure we represent what we have written
@@ -263,16 +263,15 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
If you intend to write such a merged Index, supply an alternate file_path
to its 'write' method."""
base_entries = aggressive_tree_merge(repo.odb, [to_bin_sha(str(t)) for t in tree_sha])
-
+
inst = cls(repo)
# convert to entries dict
- entries = dict(izip(((e.path, e.stage) for e in base_entries),
+ entries = dict(izip(((e.path, e.stage) for e in base_entries),
(IndexEntry.from_base(e) for e in base_entries)))
-
+
inst.entries = entries
return inst
-
@classmethod
def from_tree(cls, repo, *treeish, **kwargs):
"""Merge the given treeish revisions into a new index which is returned.
@@ -313,7 +312,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
arg_list = list()
# ignore that working tree and index possibly are out of date
- if len(treeish)>1:
+ if len(treeish) > 1:
# drop unmerged entries when reading our index and merging
arg_list.append("--reset")
# handle non-trivial cases the way a real merge does
@@ -322,7 +321,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# tmp file created in git home directory to be sure renaming
# works - /tmp/ dirs could be on another device
- tmp_index = tempfile.mktemp('','',repo.git_dir)
+ tmp_index = tempfile.mktemp('', '', repo.git_dir)
arg_list.append("--index-output=%s" % tmp_index)
arg_list.extend(treeish)
@@ -379,8 +378,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# END path exception handling
# END for each path
- def _write_path_to_stdin(self, proc, filepath, item, fmakeexc, fprogress,
- read_from_stdout=True):
+ def _write_path_to_stdin(self, proc, filepath, item, fmakeexc, fprogress,
+ read_from_stdout=True):
"""Write path to proc.stdin and make sure it processes the item, including progress.
:return: stdout string
@@ -409,7 +408,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
fprogress(filepath, True, item)
return rval
- def iter_blobs(self, predicate = lambda t: True):
+ def iter_blobs(self, predicate=lambda t: True):
"""
:return: Iterator yielding tuples of Blob objects and stages, tuple(stage, Blob)
@@ -418,7 +417,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
iterator. A default filter, the BlobFilter, allows you to yield blobs
only if they match a given list of paths. """
for entry in self.entries.itervalues():
- # TODO: is it necessary to convert the mode ? We did that when adding
+ # TODO: is it necessary to convert the mode ? We did that when adding
# it to the index, right ?
mode = stat_mode_to_index_mode(entry.mode)
blob = entry.to_blob(self.repo)
@@ -471,13 +470,13 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
for blob in iter_blobs:
stage_null_key = (blob.path, 0)
if stage_null_key in self.entries:
- raise ValueError( "Path %r already exists at stage 0" % blob.path )
+ raise ValueError("Path %r already exists at stage 0" % blob.path)
# END assert blob is not stage 0 already
# delete all possible stages
for stage in (1, 2, 3):
try:
- del( self.entries[(blob.path, stage)])
+ del(self.entries[(blob.path, stage)])
except KeyError:
pass
# END ignore key errors
@@ -502,7 +501,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
def write_tree(self):
"""Writes this index to a corresponding Tree object into the repository's
object database and return it.
-
+
:return: Tree object representing this index
:note: The tree will be written even if one or more objects the tree refers to
does not yet exist in the object database. This could happen if you added
@@ -516,17 +515,16 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
mdb = git.db.py.mem.PureMemoryDB()
entries = self._entries_sorted()
binsha, tree_items = write_tree_from_cache(entries, mdb, slice(0, len(entries)))
-
+
# copy changed trees only
mdb.stream_copy(mdb.sha_iter(), self.repo.odb)
-
-
+
# note: additional deserialization could be saved if write_tree_from_cache
# would return sorted tree entries
root_tree = Tree(self.repo, binsha, path='')
root_tree._cache = tree_items
return root_tree
-
+
def _process_diff_args(self, args):
try:
args.pop(args.index(self))
@@ -540,9 +538,9 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
if it is not within our git direcotory"""
if not os.path.isabs(path):
return path
- relative_path = path.replace(self.repo.working_tree_dir+os.sep, "")
+ relative_path = path.replace(self.repo.working_tree_dir + os.sep, "")
if relative_path == path:
- raise ValueError("Absolute path %r is not in git repository at %r" % (path,self.repo.working_tree_dir))
+ raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
return relative_path
def _preprocess_add_items(self, items):
@@ -563,8 +561,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
return (paths, entries)
@git_working_dir
- def add(self, items, force=True, fprogress=lambda *args: None, path_rewriter=None,
- write=True):
+ def add(self, items, force=True, fprogress=lambda *args: None, path_rewriter=None,
+ write=True):
"""Add files from the working tree, specific blobs or BaseIndexEntries
to the index.
@@ -639,7 +637,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
:param write:
If True, the index will be written once it was altered. Otherwise
the changes only exist in memory and are not available to git commands.
-
+
:return:
List(BaseIndexEntries) representing the entries just actually added.
@@ -656,16 +654,15 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
if paths and path_rewriter:
for path in paths:
abspath = os.path.abspath(path)
- gitrelative_path = abspath[len(self.repo.working_tree_dir)+1:]
- blob = Blob(self.repo, Blob.NULL_BIN_SHA,
- stat_mode_to_index_mode(os.stat(abspath).st_mode),
+ gitrelative_path = abspath[len(self.repo.working_tree_dir) + 1:]
+ blob = Blob(self.repo, Blob.NULL_BIN_SHA,
+ stat_mode_to_index_mode(os.stat(abspath).st_mode),
to_native_path_linux(gitrelative_path))
entries.append(BaseIndexEntry.from_blob(blob))
# END for each path
del(paths[:])
# END rewrite paths
-
def store_path(filepath):
"""Store file at filepath in the database and return the base index entry"""
st = os.lstat(filepath) # handles non-symlinks as well
@@ -678,11 +675,10 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
fprogress(filepath, False, filepath)
istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream))
fprogress(filepath, True, filepath)
- return BaseIndexEntry((stat_mode_to_index_mode(st.st_mode),
- istream.binsha, 0, to_native_path_linux(filepath)))
+ return BaseIndexEntry((stat_mode_to_index_mode(st.st_mode),
+ istream.binsha, 0, to_native_path_linux(filepath)))
# END utility method
-
# HANDLE PATHS
if paths:
assert len(entries_added) == 0
@@ -692,22 +688,22 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# END for each filepath
# END path handling
-
# HANDLE ENTRIES
if entries:
- null_mode_entries = [ e for e in entries if e.mode == 0 ]
+ null_mode_entries = [e for e in entries if e.mode == 0]
if null_mode_entries:
- raise ValueError("At least one Entry has a null-mode - please use index.remove to remove files for clarity")
+ raise ValueError(
+ "At least one Entry has a null-mode - please use index.remove to remove files for clarity")
# END null mode should be remove
# HANLDE ENTRY OBJECT CREATION
# create objects if required, otherwise go with the existing shas
- null_entries_indices = [ i for i,e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA ]
+ null_entries_indices = [i for i, e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA]
if null_entries_indices:
for ei in null_entries_indices:
null_entry = entries[ei]
new_entry = store_path(null_entry.path)
-
+
# update null entry
entries[ei] = BaseIndexEntry((null_entry.mode, new_entry.binsha, null_entry.stage, null_entry.path))
# END for each entry index
@@ -717,7 +713,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# If we have to rewrite the entries, do so now, after we have generated
# all object sha's
if path_rewriter:
- for i,e in enumerate(entries):
+ for i, e in enumerate(entries):
entries[i] = BaseIndexEntry((e.mode, e.binsha, e.stage, path_rewriter(e)))
# END for each entry
# END handle path rewriting
@@ -737,11 +733,11 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# add the new entries to this instance
for entry in entries_added:
self.entries[(entry.path, 0)] = IndexEntry.from_base(entry)
-
+
if write:
self.write()
# END handle write
-
+
return entries_added
def _items_to_rela_paths(self, items):
@@ -749,7 +745,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
may be absolute or relative paths, entries or blobs"""
paths = list()
for item in items:
- if isinstance(item, (BaseIndexEntry,(Blob, Submodule))):
+ if isinstance(item, (BaseIndexEntry, (Blob, Submodule))):
paths.append(self._to_relative_path(item.path))
elif isinstance(item, basestring):
paths.append(self._to_relative_path(item))
@@ -807,7 +803,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# process output to gain proper paths
# rm 'path'
- return [ p[4:-1] for p in removed_paths ]
+ return [p[4:-1] for p in removed_paths]
@post_clear_cache
@default_index
@@ -853,7 +849,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# parse result - first 0:n/2 lines are 'checking ', the remaining ones
# are the 'renaming' ones which we parse
- for ln in xrange(len(mvlines)/2, len(mvlines)):
+ for ln in xrange(len(mvlines) / 2, len(mvlines)):
tokens = mvlines[ln].split(' to ')
assert len(tokens) == 2, "Too many tokens in %s" % mvlines[ln]
@@ -867,7 +863,6 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
return out
# END handle dryrun
-
# now apply the actual operation
kwargs.pop('dry_run')
self.repo.git.mv(args, paths, **kwargs)
@@ -888,7 +883,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
return Commit.create_from_tree(self.repo, tree, message, parent_commits, head)
@classmethod
- def _flush_stdin_and_wait(cls, proc, ignore_stdout = False):
+ def _flush_stdin_and_wait(cls, proc, ignore_stdout=False):
proc.stdin.flush()
proc.stdin.close()
stdout = ''
@@ -902,7 +897,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
def checkout(self, paths=None, force=False, fprogress=lambda *args: None, **kwargs):
"""Checkout the given paths or all files from the version known to the index into
the working tree.
-
+
:note: Be sure you have written pending changes using the ``write`` method
in case you have altered the enties dictionary directly
@@ -935,7 +930,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
( as opposed to the original git command who ignores them ).
Raise GitCommandError if error lines could not be parsed - this truly is
an exceptional state
-
+
.. note:: The checkout is limited to checking out the files in the
index. Files which are not in the index anymore and exist in
the working tree will not be deleted. This behaviour is fundamentally
@@ -987,10 +982,10 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
raise GitCommandError(("git-checkout-index", ), 128, stderr)
if failed_files:
valid_files = list(set(iter_checked_out_files) - set(failed_files))
- raise CheckoutError("Some files could not be checked out from the index due to local modifications", failed_files, valid_files, failed_reasons)
+ raise CheckoutError(
+ "Some files could not be checked out from the index due to local modifications", failed_files, valid_files, failed_reasons)
# END stderr handler
-
if paths is None:
args.append("--all")
kwargs['as_process'] = 1
@@ -998,7 +993,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
proc = self.repo.git.checkout_index(*args, **kwargs)
proc.wait()
fprogress(None, True, None)
- rval_iter = ( e.path for e in self.entries.itervalues() )
+ rval_iter = (e.path for e in self.entries.itervalues())
handle_stderr(proc, rval_iter)
return rval_iter
else:
@@ -1006,15 +1001,15 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
paths = [paths]
# make sure we have our entries loaded before we start checkout_index
- # which will hold a lock on it. We try to get the lock as well during
+ # which will hold a lock on it. We try to get the lock as well during
# our entries initialization
self.entries
-
+
args.append("--stdin")
kwargs['as_process'] = True
kwargs['istream'] = subprocess.PIPE
proc = self.repo.git.checkout_index(args, **kwargs)
- make_exc = lambda : GitCommandError(("git-checkout-index",)+tuple(args), 128, proc.stderr.read())
+ make_exc = lambda: GitCommandError(("git-checkout-index",) + tuple(args), 128, proc.stderr.read())
checked_out_files = list()
for path in paths:
@@ -1031,8 +1026,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
for entry in self.entries.itervalues():
if entry.path.startswith(dir):
p = entry.path
- self._write_path_to_stdin(proc, p, p, make_exc,
- fprogress, read_from_stdout=False)
+ self._write_path_to_stdin(proc, p, p, make_exc,
+ fprogress, read_from_stdout=False)
checked_out_files.append(p)
path_is_directory = True
# END if entry is in directory
@@ -1040,8 +1035,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# END path exception handlnig
if not path_is_directory:
- self._write_path_to_stdin(proc, co_path, path, make_exc,
- fprogress, read_from_stdout=False)
+ self._write_path_to_stdin(proc, co_path, path, make_exc,
+ fprogress, read_from_stdout=False)
checked_out_files.append(co_path)
# END path is a file
# END for each path
@@ -1067,11 +1062,11 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
If False, the working tree will not be touched
Please note that changes to the working copy will be discarded without
warning !
-
+
:param head:
If True, the head will be set to the given commit. This is False by default,
but if True, this method behaves like HEAD.reset.
-
+
:param paths: if given as an iterable of absolute or repository-relative paths,
only these will be reset to their state at the given commit'ish.
The paths need to exist at the commit, otherwise an exception will be
@@ -1079,7 +1074,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
:param kwargs:
Additional keyword arguments passed to git-reset
-
+
.. note:: IndexFile.reset, as opposed to HEAD.reset, will not delete anyfiles
in order to maintain a consistent working tree. Instead, it will just
checkout the files according to their state in the index.
@@ -1109,11 +1104,11 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# END for each path
# END handle paths
self.write()
-
+
if working_tree:
self.checkout(paths=paths, force=True)
# END handle working tree
-
+
if head:
self.repo.head.set_commit(self.repo.commit(commit), logmsg="%s: Updating HEAD" % commit)
# END handle head change
@@ -1151,8 +1146,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# if other is not None here, something is wrong
if other is not None:
- raise ValueError( "other must be None, Diffable.Index, a Tree or Commit, was %r" % other )
+ raise ValueError("other must be None, Diffable.Index, a Tree or Commit, was %r" % other)
# diff against working copy - can be handled by superclass natively
return super(IndexFile, self).diff(other, paths, create_patch, **kwargs)
-
diff --git a/git/index/fun.py b/git/index/fun.py
index 390bb269..b3ad98a4 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -2,14 +2,14 @@
# more versatile
# NOTE: Autodoc hates it if this is a docstring
from stat import (
- S_IFDIR,
- S_IFLNK,
- S_ISLNK,
- S_IFDIR,
- S_ISDIR,
- S_IFMT,
- S_IFREG,
- )
+ S_IFDIR,
+ S_IFLNK,
+ S_ISLNK,
+ S_IFDIR,
+ S_ISDIR,
+ S_IFMT,
+ S_IFREG,
+)
S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule
@@ -18,29 +18,29 @@ from cStringIO import StringIO
from git.util import IndexFileSHA1Writer
from git.exc import UnmergedEntriesError
from git.objects.fun import (
- tree_to_stream,
- traverse_tree_recursive,
- traverse_trees_recursive
- )
+ tree_to_stream,
+ traverse_tree_recursive,
+ traverse_trees_recursive
+)
from typ import (
- BaseIndexEntry,
- IndexEntry,
- CE_NAMEMASK,
- CE_STAGESHIFT
- )
+ BaseIndexEntry,
+ IndexEntry,
+ CE_NAMEMASK,
+ CE_STAGESHIFT
+)
CE_NAMEMASK_INV = ~CE_NAMEMASK
-from util import (
- pack,
- unpack
- )
+from util import (
+ pack,
+ unpack
+)
from git.base import IStream
from git.typ import str_tree_type
-__all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key',
- 'stat_mode_to_index_mode', 'S_IFGITLINK')
+__all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key',
+ 'stat_mode_to_index_mode', 'S_IFGITLINK')
def stat_mode_to_index_mode(mode):
@@ -55,19 +55,19 @@ def stat_mode_to_index_mode(mode):
def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1Writer):
"""Write the cache represented by entries to a stream
-
+
:param entries: **sorted** list of entries
:param stream: stream to wrap into the AdapterStreamCls - it is used for
final output.
-
+
:param ShaStreamCls: Type to use when writing to the stream. It produces a sha
while writing to it, before the data is passed on to the wrapped stream
-
+
:param extension_data: any kind of data to write as a trailer, it must begin
a 4 byte identifier, followed by its size ( 4 bytes )"""
# wrap the stream into a compatible writer
stream = ShaStreamCls(stream)
-
+
tell = stream.tell
write = stream.write
@@ -86,7 +86,7 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1
assert plen == len(path), "Path %s too long to fit into index" % entry[3]
flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values
write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0],
- entry[8], entry[9], entry[10], entry[1], flags))
+ entry[8], entry[9], entry[10], entry[1], flags))
write(path)
real_size = ((tell() - beginoffset + 8) & ~7)
write("\0" * ((beginoffset + real_size) - tell()))
@@ -98,17 +98,19 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1
# write the sha over the content
stream.write_sha()
-
+
+
def read_header(stream):
- """Return tuple(version_long, num_entries) from the given stream"""
- type_id = stream.read(4)
- if type_id != "DIRC":
- raise AssertionError("Invalid index file header: %r" % type_id)
- version, num_entries = unpack(">LL", stream.read(4 * 2))
-
- # TODO: handle version 3: extended data, see read-cache.c
- assert version in (1, 2)
- return version, num_entries
+ """Return tuple(version_long, num_entries) from the given stream"""
+ type_id = stream.read(4)
+ if type_id != "DIRC":
+ raise AssertionError("Invalid index file header: %r" % type_id)
+ version, num_entries = unpack(">LL", stream.read(4 * 2))
+
+ # TODO: handle version 3: extended data, see read-cache.c
+ assert version in (1, 2)
+ return version, num_entries
+
def entry_key(*entry):
""":return: Key suitable to be used for the index.entries dictionary
@@ -119,6 +121,7 @@ def entry_key(*entry):
return tuple(entry)
# END handle entry
+
def read_cache(stream):
"""Read a cache file from the given stream
:return: tuple(version, entries_dict, extension_data, content_sha)
@@ -130,7 +133,7 @@ def read_cache(stream):
version, num_entries = read_header(stream)
count = 0
entries = dict()
-
+
read = stream.read
tell = stream.tell
while count < num_entries:
@@ -141,7 +144,7 @@ def read_cache(stream):
unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
path_size = flags & CE_NAMEMASK
path = read(path_size)
-
+
real_size = ((tell() - beginoffset + 8) & ~7)
data = read((beginoffset + real_size) - tell())
entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
@@ -157,19 +160,21 @@ def read_cache(stream):
# 4 bytes length of chunk
# repeated 0 - N times
extension_data = stream.read(~0)
- assert len(extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(extension_data)
+ assert len(extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(
+ extension_data)
content_sha = extension_data[-20:]
# truncate the sha in the end as we will dynamically create it anyway
extension_data = extension_data[:-20]
-
+
return (version, entries, extension_data, content_sha)
-
+
+
def write_tree_from_cache(entries, odb, sl, si=0):
"""Create a tree from the given sorted list of entries and put the respective
trees into the given object database
-
+
:param entries: **sorted** list of IndexEntries
:param odb: object database to store the trees in
:param si: start index at which we should start creating subtrees
@@ -202,28 +207,30 @@ def write_tree_from_cache(entries, odb, sl, si=0):
# END abort on base mismatch
xi += 1
# END find common base
-
+
# enter recursion
# ci - 1 as we want to count our current item as well
- sha, tree_entry_list = write_tree_from_cache(entries, odb, slice(ci-1, xi), rbound+1)
+ sha, tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1)
tree_items_append((sha, S_IFDIR, base))
-
+
# skip ahead
ci = xi
- # END handle bounds
+ # END handle bounds
# END for each entry
-
+
# finally create the tree
sio = StringIO()
tree_to_stream(tree_items, sio.write)
sio.seek(0)
-
+
istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
return (istream.binsha, tree_items)
-
+
+
def _tree_entry_to_baseindexentry(tree_entry, stage):
- return BaseIndexEntry((tree_entry[1], tree_entry[0], stage <<CE_STAGESHIFT, tree_entry[2]))
-
+ return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2]))
+
+
def aggressive_tree_merge(odb, tree_shas):
"""
:return: list of BaseIndexEntries representing the aggressive merge of the given
@@ -235,16 +242,16 @@ def aggressive_tree_merge(odb, tree_shas):
If 3 are given, a 3 way merge is performed"""
out = list()
out_append = out.append
-
+
# one and two way is the same for us, as we don't have to handle an existing
# index, instrea
- if len(tree_shas) in (1,2):
+ if len(tree_shas) in (1, 2):
for entry in traverse_tree_recursive(odb, tree_shas[-1], ''):
out_append(_tree_entry_to_baseindexentry(entry, 0))
# END for each entry
return out
- # END handle single tree
-
+ # END handle single tree
+
if len(tree_shas) > 3:
raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
@@ -259,7 +266,7 @@ def aggressive_tree_merge(odb, tree_shas):
# its a conflict, otherwise we take the changed version
# This should be the most common branch, so it comes first
if( base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0] ) or \
- ( base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1] ):
+ (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]):
# changed by both
out_append(_tree_entry_to_baseindexentry(base, 1))
out_append(_tree_entry_to_baseindexentry(ours, 2))
@@ -271,11 +278,11 @@ def aggressive_tree_merge(odb, tree_shas):
# either nobody changed it, or they did. In either
# case, use theirs
out_append(_tree_entry_to_baseindexentry(theirs, 0))
- # END handle modification
+ # END handle modification
else:
-
+
if ours[0] != base[0] or ours[1] != base[1]:
- # they deleted it, we changed it, conflict
+ # they deleted it, we changed it, conflict
out_append(_tree_entry_to_baseindexentry(base, 1))
out_append(_tree_entry_to_baseindexentry(ours, 2))
# else:
@@ -293,7 +300,7 @@ def aggressive_tree_merge(odb, tree_shas):
out_append(_tree_entry_to_baseindexentry(base, 1))
out_append(_tree_entry_to_baseindexentry(theirs, 3))
# END theirs changed
- #else:
+ # else:
# theirs didnt change
# pass
# END handle theirs
diff --git a/git/index/typ.py b/git/index/typ.py
index 7f27d869..0e64d28c 100644
--- a/git/index/typ.py
+++ b/git/index/typ.py
@@ -1,13 +1,13 @@
"""Module with additional types used by the index"""
from util import (
- pack,
- unpack
- )
+ pack,
+ unpack
+)
from binascii import (
- b2a_hex,
- )
+ b2a_hex,
+)
from git.objects import Blob
__all__ = ('BlobFilter', 'BaseIndexEntry', 'IndexEntry')
@@ -21,7 +21,9 @@ CE_STAGESHIFT = 12
#} END invariants
+
class BlobFilter(object):
+
"""
Predicate to be used by iter_blobs allowing to filter only return blobs which
match the given list of directories or files.
@@ -47,6 +49,7 @@ class BlobFilter(object):
class BaseIndexEntry(tuple):
+
"""Small Brother of an index entry which can be created to describe changes
done to the index in which case plenty of additional information is not requried.
@@ -56,7 +59,7 @@ class BaseIndexEntry(tuple):
def __str__(self):
return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path)
-
+
def __repr__(self):
return "(%o, %s, %i, %s)" % (self.mode, self.hexsha, self.stage, self.path)
@@ -69,7 +72,7 @@ class BaseIndexEntry(tuple):
def binsha(self):
"""binary sha of the blob """
return self[1]
-
+
@property
def hexsha(self):
"""hex version of our sha"""
@@ -78,12 +81,12 @@ class BaseIndexEntry(tuple):
@property
def stage(self):
"""Stage of the entry, either:
-
+
* 0 = default stage
* 1 = stage before a merge or common ancestor entry in case of a 3 way merge
* 2 = stage of entries from the 'left' side of the merge
* 3 = stage of entries from the right side of the merge
-
+
:note: For more information, see http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html
"""
return (self[2] & CE_STAGEMASK) >> CE_STAGESHIFT
@@ -99,16 +102,17 @@ class BaseIndexEntry(tuple):
return self[2]
@classmethod
- def from_blob(cls, blob, stage = 0):
+ def from_blob(cls, blob, stage=0):
""":return: Fully equipped BaseIndexEntry at the given stage"""
return cls((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path))
-
+
def to_blob(self, repo):
""":return: Blob using the information of this index entry"""
- return Blob(repo, self.binsha, self.mode, self.path)
+ return Blob(repo, self.binsha, self.mode, self.path)
class IndexEntry(BaseIndexEntry):
+
"""Allows convenient access to IndexEntry data without completely unpacking it.
Attributes usully accessed often are cached in the tuple whereas others are
@@ -152,7 +156,7 @@ class IndexEntry(BaseIndexEntry):
def size(self):
""":return: Uncompressed size of the blob """
return self[10]
-
+
@classmethod
def from_base(cls, base):
"""
@@ -165,9 +169,7 @@ class IndexEntry(BaseIndexEntry):
return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))
@classmethod
- def from_blob(cls, blob, stage = 0):
+ def from_blob(cls, blob, stage=0):
""":return: Minimal entry resembling the given blob object"""
time = pack(">LL", 0, 0)
return IndexEntry((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path, time, time, 0, 0, 0, 0, blob.size))
-
-
diff --git a/git/index/util.py b/git/index/util.py
index 59f8d591..97f4c5e5 100644
--- a/git/index/util.py
+++ b/git/index/util.py
@@ -3,9 +3,9 @@ import struct
import tempfile
import os
-__all__ = ( 'TemporaryFileSwap', 'post_clear_cache', 'default_index', 'git_working_dir' )
+__all__ = ('TemporaryFileSwap', 'post_clear_cache', 'default_index', 'git_working_dir')
-#{ Aliases
+#{ Aliases
pack = struct.pack
unpack = struct.unpack
@@ -13,13 +13,14 @@ unpack = struct.unpack
#} END aliases
class TemporaryFileSwap(object):
+
"""Utility class moving a file to a temporary location within the same directory
and moving it back on to where on object deletion."""
__slots__ = ("file_path", "tmp_file_path")
def __init__(self, file_path):
self.file_path = file_path
- self.tmp_file_path = self.file_path + tempfile.mktemp('','','')
+ self.tmp_file_path = self.file_path + tempfile.mktemp('', '', '')
# it may be that the source does not exist
try:
os.rename(self.file_path, self.tmp_file_path)
@@ -34,7 +35,7 @@ class TemporaryFileSwap(object):
# END temp file exists
-#{ Decorators
+#{ Decorators
def post_clear_cache(func):
"""Decorator for functions that alter the index using the git command. This would
@@ -45,6 +46,7 @@ def post_clear_cache(func):
This decorator will not be required once all functions are implemented
natively which in fact is possible, but probably not feasible performance wise.
"""
+
def post_clear_cache_if_not_raised(self, *args, **kwargs):
rval = func(self, *args, **kwargs)
self._delete_entries_cache()
@@ -54,22 +56,27 @@ def post_clear_cache(func):
post_clear_cache_if_not_raised.__name__ = func.__name__
return post_clear_cache_if_not_raised
+
def default_index(func):
"""Decorator assuring the wrapped method may only run if we are the default
repository index. This is as we rely on git commands that operate
on that index only. """
+
def check_default_index(self, *args, **kwargs):
if self._file_path != self._index_path():
- raise AssertionError( "Cannot call %r on indices that do not represent the default git index" % func.__name__ )
+ raise AssertionError(
+ "Cannot call %r on indices that do not represent the default git index" % func.__name__)
return func(self, *args, **kwargs)
# END wrpaper method
check_default_index.__name__ = func.__name__
return check_default_index
+
def git_working_dir(func):
"""Decorator which changes the current working dir to the one of the git
repository in order to assure relative paths are handled correctly"""
+
def set_git_working_dir(self, *args, **kwargs):
cur_wd = os.getcwd()
os.chdir(self.repo.working_tree_dir)
@@ -79,7 +86,7 @@ def git_working_dir(func):
os.chdir(cur_wd)
# END handle working dir
# END wrapper
-
+
set_git_working_dir.__name__ = func.__name__
return set_git_working_dir
diff --git a/git/objects/__init__.py b/git/objects/__init__.py
index 77f69d29..0b40934c 100644
--- a/git/objects/__init__.py
+++ b/git/objects/__init__.py
@@ -3,7 +3,7 @@ Import all submodules main classes into the package space
"""
import inspect
from base import *
-# Fix import dependency - add IndexObject to the util module, so that it can be
+# Fix import dependency - add IndexObject to the util module, so that it can be
# imported by the submodule.base
import submodule.util
submodule.util.IndexObject = IndexObject
@@ -17,5 +17,5 @@ from blob import *
from commit import *
from tree import *
-__all__ = [ name for name, obj in locals().items()
- if not (name.startswith('_') or inspect.ismodule(obj)) ] \ No newline at end of file
+__all__ = [name for name, obj in locals().items()
+ if not (name.startswith('_') or inspect.ismodule(obj))]
diff --git a/git/objects/base.py b/git/objects/base.py
index cb6f9761..584845f8 100644
--- a/git/objects/base.py
+++ b/git/objects/base.py
@@ -6,41 +6,43 @@
from util import get_object_type_by_name
from git.util import (
- hex_to_bin,
- bin_to_hex,
- dirname,
- basename,
- LazyMixin,
- join_path_native,
- stream_copy
- )
+ hex_to_bin,
+ bin_to_hex,
+ dirname,
+ basename,
+ LazyMixin,
+ join_path_native,
+ stream_copy
+)
from git.db.interface import RepositoryPathsMixin
from git.exc import UnsupportedOperation
from git.typ import ObjectType
-
+
_assertion_msg_format = "Created object %r whose python type %r disagrees with the acutal git object type %r"
__all__ = ("Object", "IndexObject")
+
class Object(LazyMixin):
+
"""Implements an Object which may be Blobs, Trees, Commits and Tags"""
- NULL_HEX_SHA = '0'*40
- NULL_BIN_SHA = '\0'*20
-
+ NULL_HEX_SHA = '0' * 40
+ NULL_BIN_SHA = '\0' * 20
+
TYPES = (ObjectType.blob, ObjectType.tree, ObjectType.commit, ObjectType.tag)
- __slots__ = ("odb", "binsha", "size" )
-
+ __slots__ = ("odb", "binsha", "size")
+
type = None # to be set by subclass
type_id = None # to be set by subclass
-
+
def __init__(self, odb, binsha):
"""Initialize an object by identifying it by its binary sha.
All keyword arguments will be set on demand if None.
-
+
:param odb: repository this object is located in
-
+
:param binsha: 20 byte SHA1"""
- super(Object,self).__init__()
+ super(Object, self).__init__()
self.odb = odb
self.binsha = binsha
assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (binsha, len(binsha))
@@ -51,13 +53,13 @@ class Object(LazyMixin):
:return: New Object instance of a type appropriate to the object type behind
id. The id of the newly created object will be a binsha even though
the input id may have been a Reference or Rev-Spec
-
+
:param id: reference, rev-spec, or hexsha
-
+
:note: This cannot be a __new__ method as it would always call __init__
with the input id which is not necessarily a binsha."""
return odb.rev_parse(str(id))
-
+
@classmethod
def new_from_sha(cls, odb, sha1):
"""
@@ -67,41 +69,41 @@ class Object(LazyMixin):
if sha1 == cls.NULL_BIN_SHA:
# the NULL binsha is always the root commit
return get_object_type_by_name('commit')(odb, sha1)
- #END handle special case
+ # END handle special case
oinfo = odb.info(sha1)
inst = get_object_type_by_name(oinfo.type)(odb, oinfo.binsha)
inst.size = oinfo.size
- return inst
-
+ return inst
+
def _set_cache_(self, attr):
"""Retrieve object information"""
- if attr == "size":
+ if attr == "size":
oinfo = self.odb.info(self.binsha)
self.size = oinfo.size
# assert oinfo.type == self.type, _assertion_msg_format % (self.binsha, oinfo.type, self.type)
else:
- super(Object,self)._set_cache_(attr)
-
+ super(Object, self)._set_cache_(attr)
+
def __eq__(self, other):
""":return: True if the objects have the same SHA1"""
if not hasattr(other, 'binsha'):
return False
return self.binsha == other.binsha
-
+
def __ne__(self, other):
""":return: True if the objects do not have the same SHA1 """
if not hasattr(other, 'binsha'):
return True
return self.binsha != other.binsha
-
+
def __hash__(self):
""":return: Hash of our id allowing objects to be used in dicts and sets"""
return hash(self.binsha)
-
+
def __str__(self):
""":return: string of our SHA1 as understood by all git commands"""
return bin_to_hex(self.binsha)
-
+
def __repr__(self):
""":return: string with pythonic representation of our object"""
return '<git.%s "%s">' % (self.__class__.__name__, self.hexsha)
@@ -124,16 +126,17 @@ class Object(LazyMixin):
istream = self.odb.stream(self.binsha)
stream_copy(istream, ostream)
return self
-
+
class IndexObject(Object):
+
"""Base for all objects that can be part of the index file , namely Tree, Blob and
SubModule objects"""
__slots__ = ("path", "mode")
-
+
# for compatability with iterable lists
_id_attribute_ = 'path'
-
+
def __init__(self, odb, binsha, mode=None, path=None):
"""Initialize a newly instanced IndexObject
:param odb: is the object database we are located in
@@ -151,33 +154,34 @@ class IndexObject(Object):
self.mode = mode
if path is not None:
self.path = path
-
+
def __hash__(self):
""":return:
Hash of our path as index items are uniquely identifyable by path, not
by their data !"""
return hash(self.path)
-
+
def _set_cache_(self, attr):
if attr in IndexObject.__slots__:
# they cannot be retrieved lateron ( not without searching for them )
- raise AttributeError( "path and mode attributes must have been set during %s object creation" % type(self).__name__ )
+ raise AttributeError(
+ "path and mode attributes must have been set during %s object creation" % type(self).__name__)
else:
super(IndexObject, self)._set_cache_(attr)
# END hanlde slot attribute
-
+
@property
def name(self):
""":return: Name portion of the path, effectively being the basename"""
return basename(self.path)
-
+
@property
def abspath(self):
"""
:return:
Absolute path to this index object in the file system ( as opposed to the
.path field which is a path relative to the git repository ).
-
+
The returned path will be native to the system and contains '\' on windows.
:raise UnsupportedOperation: if underlying odb does not support the required method to obtain a working dir"""
# TODO: Here we suddenly need something better than a plain object database
@@ -187,6 +191,5 @@ class IndexObject(Object):
root = self.odb.working_tree_dir
else:
raise UnsupportedOperation("Cannot provide absolute path from a database without Repository path support")
- #END handle odb type
+ # END handle odb type
return join_path_native(root, self.path)
-
diff --git a/git/objects/blob.py b/git/objects/blob.py
index 7217101f..a2b70fb7 100644
--- a/git/objects/blob.py
+++ b/git/objects/blob.py
@@ -12,12 +12,14 @@ import base
__all__ = ('Blob', )
+
class Blob(base.IndexObject, RepoAliasMixin):
+
"""A Blob encapsulates a git blob object"""
DEFAULT_MIME_TYPE = "text/plain"
type = ObjectType.blob
type_id = ObjectType.blob_id
-
+
# valid blob modes
executable_mode = 0100755
file_mode = 0100644
diff --git a/git/objects/commit.py b/git/objects/commit.py
index e64d4da3..225f64d0 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -10,20 +10,20 @@ from tree import Tree
from cStringIO import StringIO
from git.util import (
- hex_to_bin,
- Actor,
- RepoAliasMixin,
- Iterable,
- Actor,
- Stats
- )
+ hex_to_bin,
+ Actor,
+ RepoAliasMixin,
+ Iterable,
+ Actor,
+ Stats
+)
from util import (
- Traversable,
- Serializable,
- altz_to_utctz_str,
- parse_actor_and_date
- )
+ Traversable,
+ Serializable,
+ altz_to_utctz_str,
+ parse_actor_and_date
+)
from git.diff import Diffable
from git.base import IStream
from cStringIO import StringIO
@@ -36,36 +36,36 @@ import sys
__all__ = ('Commit', )
+
class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Serializable):
+
"""Wraps a git Commit object.
-
+
This class will act lazily on some of its attributes and will query the
value on demand only if it involves calling the git binary."""
__slots__ = tuple()
-
+
# ENVIRONMENT VARIABLES
# read when creating new commits
env_author_date = "GIT_AUTHOR_DATE"
env_committer_date = "GIT_COMMITTER_DATE"
-
+
# CONFIGURATION KEYS
conf_encoding = 'i18n.commitencoding'
-
+
# INVARIANTS
default_encoding = "UTF-8"
-
-
- # object configuration
+
+ # object configuration
type = ObjectType.commit
type_id = ObjectType.commit_id
-
+
__slots__ = ("tree",
"author", "authored_date", "author_tz_offset",
"committer", "committed_date", "committer_tz_offset",
"message", "parents", "encoding")
_id_attribute_ = "binsha"
-
-
+
def count(self, paths='', **kwargs):
"""Count the number of commits reachable from this commit
@@ -83,7 +83,6 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
return len(self.repo.git.rev_list(self.hexsha, '--', paths, **kwargs).splitlines())
else:
return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines())
-
@property
def name_rev(self):
@@ -118,10 +117,10 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
proc = repo.git.rev_list(rev, args, as_process=True, **kwargs)
return cls._iter_from_process_or_stream(repo, proc)
-
+
def iter_parents(self, paths='', **kwargs):
"""Iterate _all_ parents of this commit.
-
+
:param paths:
Optional path or list of paths limiting the Commits to those that
contain at least one of the paths
@@ -129,17 +128,17 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
:return: Iterator yielding Commit objects which are parents of self """
# skip ourselves
skip = kwargs.get("skip", 1)
- if skip == 0: # skip ourselves
+ if skip == 0: # skip ourselves
skip = 1
kwargs['skip'] = skip
-
+
return self.iter_items(self.repo, self, paths, **kwargs)
@property
def stats(self):
"""Create a git stat from changes between this commit and its first parent
or from all changes done if this is the very first commit.
-
+
:return: git.Stats"""
if not self.parents:
text = self.repo.git.diff_tree(self.hexsha, '--', numstat=True, root=True)
@@ -152,11 +151,10 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
text = self.repo.git.diff(self.parents[0].hexsha, self.hexsha, '--', numstat=True)
return Stats._list_from_string(self.repo, text)
-
@classmethod
def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False):
"""Commit the given tree, creating a commit object.
-
+
:param repo: Repo object the commit should be part of
:param tree: Tree object or hex or bin sha
the tree of the new commit
@@ -172,9 +170,9 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
If True, the HEAD will be advanced to the new commit automatically.
Else the HEAD will remain pointing on the previous commit. This could
lead to undesired results when diffing files.
-
+
:return: Commit object representing the new commit
-
+
:note:
Additional information about the committer and Author are taken from the
environment or from the git configuration, see git-commit-tree for
@@ -182,72 +180,71 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
parents = parent_commits
if parent_commits is None:
try:
- parent_commits = [ repo.head.commit ]
+ parent_commits = [repo.head.commit]
except ValueError:
# empty repositories have no head commit
parent_commits = list()
# END handle parent commits
# END if parent commits are unset
-
- # retrieve all additional information, create a commit object, and
+
+ # retrieve all additional information, create a commit object, and
# serialize it
- # Generally:
+ # Generally:
# * Environment variables override configuration values
# * Sensible defaults are set according to the git documentation
-
+
# COMMITER AND AUTHOR INFO
cr = repo.config_reader()
env = os.environ
-
+
committer = Actor.committer(cr)
author = Actor.author(cr)
-
+
# PARSE THE DATES
unix_time = int(time())
offset = altzone
-
+
author_date_str = env.get(cls.env_author_date, '')
if author_date_str:
author_time, author_offset = parse_date(author_date_str)
else:
author_time, author_offset = unix_time, offset
# END set author time
-
+
committer_date_str = env.get(cls.env_committer_date, '')
- if committer_date_str:
+ if committer_date_str:
committer_time, committer_offset = parse_date(committer_date_str)
else:
committer_time, committer_offset = unix_time, offset
# END set committer time
-
+
# assume utf8 encoding
enc_section, enc_option = cls.conf_encoding.split('.')
conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)
-
-
+
# if the tree is no object, make sure we create one - otherwise
# the created commit object is invalid
if isinstance(tree, str):
tree = repo.tree(tree)
# END tree conversion
-
+
# CREATE NEW COMMIT
- new_commit = cls(repo, cls.NULL_BIN_SHA, tree,
- author, author_time, author_offset,
- committer, committer_time, committer_offset,
- message, parent_commits, conf_encoding)
-
+ new_commit = cls(repo, cls.NULL_BIN_SHA, tree,
+ author, author_time, author_offset,
+ committer, committer_time, committer_offset,
+ message, parent_commits, conf_encoding)
+
stream = StringIO()
new_commit._serialize(stream)
streamlen = stream.tell()
stream.seek(0)
-
+
istream = repo.odb.store(IStream(cls.type, streamlen, stream))
new_commit.binsha = istream.binsha
-
+
if head:
# need late import here, importing git at the very beginning throws
- # as well ...
+ # as well ...
import git.refs
try:
repo.head.set_commit(new_commit, logmsg="commit: %s" % message)
@@ -258,16 +255,16 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
master = git.refs.Head.create(repo, repo.head.ref, new_commit, logmsg="commit (initial): %s" % message)
repo.head.set_reference(master, logmsg='commit: Switching to %s' % master)
# END handle empty repositories
- # END advance head handling
-
+ # END advance head handling
+
return new_commit
-
+
def __init__(self, odb, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None,
- committer=None, committed_date=None, committer_tz_offset=None,
+ committer=None, committed_date=None, committer_tz_offset=None,
message=None, parents=None, encoding=None):
"""Instantiate a new Commit. All keyword arguments taking None as default will
be implicitly set on first query.
-
+
:param binsha: 20 byte sha1
:param parents: tuple( Commit, ... )
is a tuple of commit ids or actual Commits
@@ -295,11 +292,11 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
List or tuple of Commit objects which are our parent(s) in the commit
dependency graph
:return: git.Commit
-
+
:note: Timezone information is in the same format and in the same sign
as what time.altzone returns. The sign is inverted compared to git's
UTC timezone."""
- super(Commit,self).__init__(odb, binsha)
+ super(Commit, self).__init__(odb, binsha)
if tree is not None:
assert isinstance(tree, Tree), "Tree needs to be a Tree instance, was %s" % type(tree)
if tree is not None:
@@ -322,7 +319,7 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
self.parents = parents
if encoding is not None:
self.encoding = encoding
-
+
@classmethod
def _get_intermediate_items(cls, commit):
return commit.parents
@@ -340,7 +337,7 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
def summary(self):
""":return: First line of the commit message"""
return self.message.split('\n', 1)[0]
-
+
@classmethod
def _iter_from_process_or_stream(cls, odb, proc_or_stream):
"""Parse out commit information into a list of Commit objects
@@ -351,10 +348,10 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
:return: iterator returning Commit objects"""
stream = proc_or_stream
close_std_err = False
- if not hasattr(stream,'readline'):
+ if not hasattr(stream, 'readline'):
stream = proc_or_stream.stdout
close_std_err = True
-
+
readline = stream.readline
try:
while True:
@@ -366,7 +363,7 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
# split additional information, as returned by bisect for instance
hexsha, rest = line.split(None, 1)
# END handle extra info
-
+
assert len(hexsha) == 40, "Invalid line: %s" % hexsha
yield cls(odb, hex_to_bin(hexsha))
# END for each line in stream
@@ -376,39 +373,39 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
proc_or_stream.stderr.close()
#{ Serializable Implementation
-
+
def _serialize(self, stream):
write = stream.write
write("tree %s\n" % self.tree)
for p in self.parents:
write("parent %s\n" % p)
-
+
a = self.author
aname = a.name
if isinstance(aname, unicode):
aname = aname.encode(self.encoding)
# END handle unicode in name
-
+
c = self.committer
fmt = "%s %s <%s> %s %s\n"
- write(fmt % ("author", aname, a.email,
- self.authored_date,
- altz_to_utctz_str(self.author_tz_offset)))
-
+ write(fmt % ("author", aname, a.email,
+ self.authored_date,
+ altz_to_utctz_str(self.author_tz_offset)))
+
# encode committer
aname = c.name
if isinstance(aname, unicode):
aname = aname.encode(self.encoding)
# END handle unicode in name
- write(fmt % ("committer", aname, c.email,
- self.committed_date,
- altz_to_utctz_str(self.committer_tz_offset)))
-
+ write(fmt % ("committer", aname, c.email,
+ self.committed_date,
+ altz_to_utctz_str(self.committer_tz_offset)))
+
if self.encoding != self.default_encoding:
write("encoding %s\n" % self.encoding)
-
+
write("\n")
-
+
# write plain bytes, be sure its encoded according to our encoding
if isinstance(self.message, unicode):
write(self.message.encode(self.encoding))
@@ -416,12 +413,12 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
write(self.message)
# END handle encoding
return self
-
+
def _deserialize(self, stream):
""":param from_rev_list: if true, the stream format is coming from the rev-list command
Otherwise it is assumed to be a plain data stream from our object"""
readline = stream.readline
- self.tree = Tree(self.odb, hex_to_bin(readline().split()[1]), Tree.tree_id<<12, '')
+ self.tree = Tree(self.odb, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '')
self.parents = list()
next_line = None
@@ -434,11 +431,10 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
self.parents.append(type(self)(self.odb, hex_to_bin(parent_line.split()[-1])))
# END for each parent line
self.parents = tuple(self.parents)
-
+
self.author, self.authored_date, self.author_tz_offset = parse_actor_and_date(next_line)
self.committer, self.committed_date, self.committer_tz_offset = parse_actor_and_date(readline())
-
-
+
# now we can have the encoding line, or an empty line followed by the optional
# message.
self.encoding = self.default_encoding
@@ -446,25 +442,27 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
enc = readline()
enc = enc.strip()
if enc:
- self.encoding = enc[enc.find(' ')+1:]
- # now comes the message separator
+ self.encoding = enc[enc.find(' ') + 1:]
+ # now comes the message separator
readline()
# END handle encoding
-
+
# decode the authors name
try:
- self.author.name = self.author.name.decode(self.encoding)
+ self.author.name = self.author.name.decode(self.encoding)
except UnicodeDecodeError:
- print >> sys.stderr, "Failed to decode author name '%s' using encoding %s" % (self.author.name, self.encoding)
+ print >> sys.stderr, "Failed to decode author name '%s' using encoding %s" % (
+ self.author.name, self.encoding)
# END handle author's encoding
-
+
# decode committer name
try:
- self.committer.name = self.committer.name.decode(self.encoding)
+ self.committer.name = self.committer.name.decode(self.encoding)
except UnicodeDecodeError:
- print >> sys.stderr, "Failed to decode committer name '%s' using encoding %s" % (self.committer.name, self.encoding)
+ print >> sys.stderr, "Failed to decode committer name '%s' using encoding %s" % (
+ self.committer.name, self.encoding)
# END handle author's encoding
-
+
# a stream from our data simply gives us the plain message
# The end of our message stream is marked with a newline that we strip
self.message = stream.read()
@@ -472,8 +470,7 @@ class Commit(Diffable, Iterable, RepoAliasMixin, base.Object, Traversable, Seria
self.message = self.message.decode(self.encoding)
except UnicodeDecodeError:
print >> sys.stderr, "Failed to decode message '%s' using encoding %s" % (self.message, self.encoding)
- # END exception handling
+ # END exception handling
return self
-
- #} END serializable implementation
+ #} END serializable implementation
diff --git a/git/objects/fun.py b/git/objects/fun.py
index 600dee85..04ece928 100644
--- a/git/objects/fun.py
+++ b/git/objects/fun.py
@@ -3,37 +3,35 @@
from stat import S_ISDIR
__all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive',
- 'traverse_tree_recursive')
+ 'traverse_tree_recursive')
-
-
def tree_to_stream(entries, write):
"""Write the give list of entries into a stream using its write method
:param entries: **sorted** list of tuples with (binsha, mode, name)
:param write: write method which takes a data string"""
ord_zero = ord('0')
bit_mask = 7 # 3 bits set
-
+
for binsha, mode, name in entries:
mode_str = ''
for i in xrange(6):
- mode_str = chr(((mode >> (i*3)) & bit_mask) + ord_zero) + mode_str
+ mode_str = chr(((mode >> (i * 3)) & bit_mask) + ord_zero) + mode_str
# END for each 8 octal value
-
+
# git slices away the first octal if its zero
if mode_str[0] == '0':
mode_str = mode_str[1:]
# END save a byte
# here it comes: if the name is actually unicode, the replacement below
- # will not work as the binsha is not part of the ascii unicode encoding -
+ # will not work as the binsha is not part of the ascii unicode encoding -
# hence we must convert to an utf8 string for it to work properly.
# According to my tests, this is exactly what git does, that is it just
# takes the input literally, which appears to be utf8 on linux.
if isinstance(name, unicode):
name = name.encode("utf8")
- write("%s %s\0%s" % (mode_str, name, binsha))
+ write("%s %s\0%s" % (mode_str, name, binsha))
# END for each item
@@ -47,7 +45,7 @@ def tree_entries_from_data(data):
out = list()
while i < len_data:
mode = 0
-
+
# read mode
# Some git versions truncate the leading 0, some don't
# The type will be extracted from the mode later
@@ -57,17 +55,17 @@ def tree_entries_from_data(data):
mode = (mode << 3) + (ord(data[i]) - ord_zero)
i += 1
# END while reading mode
-
+
# byte is space now, skip it
i += 1
-
+
# parse name, it is NULL separated
-
+
ns = i
while data[i] != '\0':
i += 1
# END while not reached NULL
-
+
# default encoding for strings in git is utf8
# Only use the respective unicode object if the byte stream was encoded
name = data[ns:i]
@@ -75,16 +73,16 @@ def tree_entries_from_data(data):
if len(name) > len(name_enc):
name = name_enc
# END handle encoding
-
+
# byte is NULL, get next 20
i += 1
- sha = data[i:i+20]
+ sha = data[i:i + 20]
i = i + 20
out.append((sha, mode, name))
# END for each byte in data stream
return out
-
-
+
+
def _find_by_name(tree_data, name, is_dir, start_at):
"""return data entry matching the given name and tree mode
or None.
@@ -92,7 +90,7 @@ def _find_by_name(tree_data, name, is_dir, start_at):
None in the tree_data list to mark it done"""
try:
item = tree_data[start_at]
- if item and item[2] == name and S_ISDIR(item[1]) == is_dir:
+ if item and item[2] == name and S_ISDIR(item[1]) == is_dir:
tree_data[start_at] = None
return item
except IndexError:
@@ -106,12 +104,14 @@ def _find_by_name(tree_data, name, is_dir, start_at):
# END for each item
return None
+
def _to_full_path(item, path_prefix):
"""Rebuild entry with given path prefix"""
if not item:
return item
- return (item[0], item[1], path_prefix+item[2])
-
+ return (item[0], item[1], path_prefix + item[2])
+
+
def traverse_trees_recursive(odb, tree_shas, path_prefix):
"""
:return: list with entries according to the given binary tree-shas.
@@ -137,10 +137,10 @@ def traverse_trees_recursive(odb, tree_shas, path_prefix):
# END handle muted trees
trees_data.append(data)
# END for each sha to get data for
-
+
out = list()
out_append = out.append
-
+
# find all matching entries and recursively process them together if the match
# is a tree. If the match is a non-tree item, put it into the result.
# Processed items will be set None
@@ -149,35 +149,37 @@ def traverse_trees_recursive(odb, tree_shas, path_prefix):
if not item:
continue
# END skip already done items
- entries = [ None for n in range(nt) ]
+ entries = [None for n in range(nt)]
entries[ti] = item
sha, mode, name = item # its faster to unpack
is_dir = S_ISDIR(mode) # type mode bits
-
+
# find this item in all other tree data items
- # wrap around, but stop one before our current index, hence
+ # wrap around, but stop one before our current index, hence
# ti+nt, not ti+1+nt
- for tio in range(ti+1, ti+nt):
+ for tio in range(ti + 1, ti + nt):
tio = tio % nt
entries[tio] = _find_by_name(trees_data[tio], name, is_dir, ii)
# END for each other item data
-
+
# if we are a directory, enter recursion
if is_dir:
- out.extend(traverse_trees_recursive(odb, [((ei and ei[0]) or None) for ei in entries], path_prefix+name+'/'))
+ out.extend(traverse_trees_recursive(
+ odb, [((ei and ei[0]) or None) for ei in entries], path_prefix + name + '/'))
else:
out_append(tuple(_to_full_path(e, path_prefix) for e in entries))
# END handle recursion
-
+
# finally mark it done
tree_data[ii] = None
# END for each item
-
+
# we are done with one tree, set all its data empty
del(tree_data[:])
# END for each tree_data chunk
return out
-
+
+
def traverse_tree_recursive(odb, tree_sha, path_prefix):
"""
:return: list of entries of the tree pointed to by the binary tree_sha. An entry
@@ -188,14 +190,13 @@ def traverse_tree_recursive(odb, tree_sha, path_prefix):
:param path_prefix: prefix to prepend to the front of all returned paths"""
entries = list()
data = tree_entries_from_data(odb.stream(tree_sha).read())
-
+
# unpacking/packing is faster than accessing individual items
for sha, mode, name in data:
if S_ISDIR(mode):
- entries.extend(traverse_tree_recursive(odb, sha, path_prefix+name+'/'))
+ entries.extend(traverse_tree_recursive(odb, sha, path_prefix + name + '/'))
else:
- entries.append((sha, mode, path_prefix+name))
+ entries.append((sha, mode, path_prefix + name))
# END for each item
-
- return entries
+ return entries
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index ff908d4a..2608f5db 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -5,29 +5,29 @@
from git.util import RepoAliasMixin
import util
from util import (
- mkhead,
- sm_name,
- sm_section,
- unbare_repo,
- SubmoduleConfigParser,
- find_first_remote_branch
- )
+ mkhead,
+ sm_name,
+ sm_section,
+ unbare_repo,
+ SubmoduleConfigParser,
+ find_first_remote_branch
+)
from git.objects.util import Traversable
from StringIO import StringIO # need a dict to set bloody .name field
from git.util import (
- Iterable,
- join_path_native,
- to_native_path_linux,
- rmtree
- )
+ Iterable,
+ join_path_native,
+ to_native_path_linux,
+ rmtree
+)
from git.db.interface import RemoteProgress
from git.config import SectionConstraint
from git.exc import (
- InvalidGitRepositoryError,
- NoSuchPathError
- )
+ InvalidGitRepositoryError,
+ NoSuchPathError
+)
import stat
import git # we use some types indirectly to prevent cyclic imports !
@@ -40,14 +40,15 @@ __all__ = ["Submodule", "UpdateProgress"]
class UpdateProgress(RemoteProgress):
+
"""Class providing detailed progress information to the caller who should
derive from it and implement the ``update(...)`` message"""
- CLONE, FETCH, UPDWKTREE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes+3)]
+ CLONE, FETCH, UPDWKTREE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes + 3)]
_num_op_codes = RemoteProgress._num_op_codes + 3
-
+
__slots__ = tuple()
-
-
+
+
BEGIN = UpdateProgress.BEGIN
END = UpdateProgress.END
CLONE = UpdateProgress.CLONE
@@ -55,37 +56,38 @@ FETCH = UpdateProgress.FETCH
UPDWKTREE = UpdateProgress.UPDWKTREE
-# IndexObject comes via util module, its a 'hacky' fix thanks to pythons import
+# IndexObject comes via util module, its a 'hacky' fix thanks to pythons import
# mechanism which cause plenty of trouble of the only reason for packages and
# modules is refactoring - subpackages shoudn't depend on parent packages
class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
+
"""Implements access to a git submodule. They are special in that their sha
represents a commit in the submodule's repository which is to be checked out
at the path of this instance.
The submodule type does not have a string type associated with it, as it exists
solely as a marker in the tree and index.
-
+
All methods work in bare and non-bare repositories."""
-
+
_id_attribute_ = "name"
k_modules_file = '.gitmodules'
k_head_option = 'branch'
k_head_default = 'master'
k_default_mode = stat.S_IFDIR | stat.S_IFLNK # submodules are directories with link-status
-
+
# this is a bogus type for base class compatability
type = 'submodule'
-
+
# this type doesn't really have a type id
type_id = 0
-
+
__slots__ = ('_parent_commit', '_url', '_branch_path', '_name', '__weakref__')
_cache_attrs = ('path', '_url', '_branch_path')
-
- def __init__(self, repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, branch_path=None):
+
+ def __init__(self, repo, binsha, mode=None, path=None, name=None, parent_commit=None, url=None, branch_path=None):
"""Initialize this instance with its attributes. We only document the ones
that differ from ``IndexObject``
-
+
:param repo: Our parent repository
:param binsha: binary sha referring to a commit in the remote repository, see url parameter
:param parent_commit: a Commit object instance, see set_parent_commit() for more information
@@ -102,7 +104,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
self._branch_path = branch_path
if name is not None:
self._name = name
-
+
def _set_cache_(self, attr):
if attr == '_parent_commit':
# set a default value, which is the root tree of the current head
@@ -119,7 +121,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
else:
super(Submodule, self)._set_cache_(attr)
# END handle attribute name
-
+
def _get_intermediate_items(self, item):
""":return: all the submodules of our module repository"""
try:
@@ -127,28 +129,28 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
except InvalidGitRepositoryError:
return list()
# END handle intermeditate items
-
+
def __eq__(self, other):
"""Compare with another submodule"""
# we may only compare by name as this should be the ID they are hashed with
# Otherwise this type wouldn't be hashable
# return self.path == other.path and self.url == other.url and super(Submodule, self).__eq__(other)
return self._name == other._name
-
+
def __ne__(self, other):
"""Compare with another submodule for inequality"""
return not (self == other)
-
+
def __hash__(self):
"""Hash this instance using its logical id, not the sha"""
return hash(self._name)
-
+
def __str__(self):
return self._name
-
+
def __repr__(self):
- return "git.%s(name=%s, path=%s, url=%s, branch_path=%s)" % (type(self).__name__, self._name, self.path, self.url, self.branch_path)
-
+ return "git.%s(name=%s, path=%s, url=%s, branch_path=%s)" % (type(self).__name__, self._name, self.path, self.url, self.branch_path)
+
@classmethod
def _config_parser(cls, repo, parent_commit, read_only):
""":return: Config Parser constrained to our submodule in read or write mode
@@ -167,15 +169,16 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
try:
fp_module = cls._sio_modules(parent_commit)
except KeyError:
- raise IOError("Could not find %s file in the tree of parent commit %s" % (cls.k_modules_file, parent_commit))
+ raise IOError("Could not find %s file in the tree of parent commit %s" %
+ (cls.k_modules_file, parent_commit))
# END handle exceptions
# END handle non-bare working tree
-
+
if not read_only and (repo.bare or not parent_matches_head):
raise ValueError("Cannot write blobs of 'historical' submodule configurations")
# END handle writes of historical submodules
-
- return SubmoduleConfigParser(fp_module, read_only = read_only)
+
+ return SubmoduleConfigParser(fp_module, read_only=read_only)
def _clear_cache(self):
# clear the possibly changed values
@@ -186,29 +189,29 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
pass
# END try attr deletion
# END for each name to delete
-
+
@classmethod
def _sio_modules(cls, parent_commit):
""":return: Configuration file as StringIO - we only access it through the respective blob's data"""
sio = StringIO(parent_commit.tree[cls.k_modules_file].data_stream.read())
sio.name = cls.k_modules_file
return sio
-
+
def _config_parser_constrained(self, read_only):
""":return: Config Parser constrained to our submodule in read or write mode"""
parser = self._config_parser(self.repo, self._parent_commit, read_only)
parser.set_submodule(self)
return SectionConstraint(parser, sm_section(self.name))
-
+
#{ Edit Interface
-
+
@classmethod
def add(cls, repo, name, path, url=None, branch=None, no_checkout=False, repoType=None):
"""Add a new submodule to the given repository. This will alter the index
as well as the .gitmodules file, but will not create a new commit.
If the submodule already exists, no matter if the configuration differs
from the one provided, the existing submodule will be returned.
-
+
:param repo: Repository instance which should receive the submodule
:param name: The name/identifier for the submodule
:param path: repository-relative or absolute path at which the submodule
@@ -236,20 +239,20 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
if repo.bare:
raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
# END handle bare repos
-
+
repoType = repoType or git.Repo
-
+
path = to_native_path_linux(path)
if path.endswith('/'):
path = path[:-1]
# END handle trailing slash
-
- # assure we never put backslashes into the url, as some operating systems
- # like it ...
+
+ # assure we never put backslashes into the url, as some operating systems
+ # like it ...
if url != None:
url = to_native_path_linux(url)
- #END assure url correctness
-
+ # END assure url correctness
+
# INSTANTIATE INTERMEDIATE SM
sm = cls(repo, cls.NULL_BIN_SHA, cls.k_default_mode, path, name)
if sm.exists():
@@ -264,17 +267,18 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
return sm
# END handle exceptions
# END handle existing
-
+
# fake-repo - we only need the functionality on the branch instance
br = git.Head(repo, git.Head.to_full_path(str(branch) or cls.k_head_default))
has_module = sm.module_exists()
branch_is_default = branch is None
if has_module and url is not None:
if url not in [r.url for r in sm.module().remotes]:
- raise ValueError("Specified URL '%s' does not match any remote url of the repository at '%s'" % (url, sm.abspath))
+ raise ValueError(
+ "Specified URL '%s' does not match any remote url of the repository at '%s'" % (url, sm.abspath))
# END check url
# END verify urls match
-
+
mrepo = None
if url is None:
if not has_module:
@@ -288,19 +292,19 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
url = urls[0]
else:
# clone new repo
- kwargs = {'n' : no_checkout}
+ kwargs = {'n': no_checkout}
if not branch_is_default:
kwargs['b'] = br.name
# END setup checkout-branch
mrepo = repoType.clone_from(url, path, **kwargs)
# END verify url
-
+
# update configuration and index
index = sm.repo.index
writer = sm.config_writer(index=index, write=False)
writer.set_value('url', url)
writer.set_value('path', path)
-
+
sm._url = url
if not branch_is_default:
# store full path
@@ -308,20 +312,20 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
sm._branch_path = br.path
# END handle path
del(writer)
-
+
# we deliberatly assume that our head matches our index !
pcommit = repo.head.commit
sm._parent_commit = pcommit
sm.binsha = mrepo.head.commit.binsha
index.add([sm], write=True)
-
+
return sm
-
- def update(self, recursive=False, init=True, to_latest_revision=False, progress=None,
- dry_run=False, ):
+
+ def update(self, recursive=False, init=True, to_latest_revision=False, progress=None,
+ dry_run=False, ):
"""Update the repository of this submodule to point to the checkout
we point at with the binsha of this instance.
-
+
:param recursive: if True, we will operate recursively and update child-
modules as well.
:param init: if True, the module repository will be cloned into place if necessary
@@ -338,21 +342,21 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
:return: self"""
if self.repo.bare:
return self
- #END pass in bare mode
-
+ # END pass in bare mode
+
if progress is None:
progress = UpdateProgress()
- #END handle progress
+ # END handle progress
prefix = ''
if dry_run:
prefix = "DRY-RUN: "
- #END handle prefix
-
+ # END handle prefix
+
# to keep things plausible in dry-run mode
if dry_run:
mrepo = None
- #END init mrepo
-
+ # END init mrepo
+
# ASSURE REPO IS PRESENT AND UPTODATE
#####################################
try:
@@ -363,24 +367,24 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
op = FETCH
if i == 0:
op |= BEGIN
- #END handle start
-
- progress.update(op, i, len_rmts, prefix+"Fetching remote %s of submodule %r" % (remote, self.name))
+ # END handle start
+
+ progress.update(op, i, len_rmts, prefix + "Fetching remote %s of submodule %r" % (remote, self.name))
#===============================
if not dry_run:
remote.fetch(progress=progress)
- #END handle dry-run
+ # END handle dry-run
#===============================
- if i == len_rmts-1:
+ if i == len_rmts - 1:
op |= END
- #END handle end
- progress.update(op, i, len_rmts, prefix+"Done fetching remote of submodule %r" % self.name)
- #END fetch new data
+ # END handle end
+ progress.update(op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name)
+ # END fetch new data
except InvalidGitRepositoryError:
if not init:
return self
# END early abort if init is not allowed
-
+
# there is no git-repository yet - but delete empty paths
module_path = join_path_native(self.repo.working_tree_dir, self.path)
if not dry_run and os.path.isdir(module_path):
@@ -390,44 +394,43 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
raise OSError("Module directory at %r does already exist and is non-empty" % module_path)
# END handle OSError
# END handle directory removal
-
+
# don't check it out at first - nonetheless it will create a local
# branch according to the remote-HEAD if possible
- progress.update(BEGIN|CLONE, 0, 1, prefix+"Cloning %s to %s in submodule %r" % (self.url, module_path, self.name))
+ progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning %s to %s in submodule %r" %
+ (self.url, module_path, self.name))
if not dry_run:
mrepo = type(self.repo).clone_from(self.url, module_path, n=True)
- #END handle dry-run
- progress.update(END|CLONE, 0, 1, prefix+"Done cloning to %s" % module_path)
-
-
+ # END handle dry-run
+ progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % module_path)
+
if not dry_run:
# see whether we have a valid branch to checkout
try:
# find a remote which has our branch - we try to be flexible
remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
local_branch = mkhead(mrepo, self.branch_path)
-
+
# have a valid branch, but no checkout - make sure we can figure
# that out by marking the commit with a null_sha
local_branch.set_object(util.Object(mrepo, self.NULL_BIN_SHA))
# END initial checkout + branch creation
-
+
# make sure HEAD is not detached
mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch)
mrepo.head.ref.set_tracking_branch(remote_branch)
except IndexError:
- print >> sys.stderr, "Warning: Failed to checkout tracking branch %s" % self.branch_path
- #END handle tracking branch
-
+ print >> sys.stderr, "Warning: Failed to checkout tracking branch %s" % self.branch_path
+ # END handle tracking branch
+
# NOTE: Have to write the repo config file as well, otherwise
# the default implementation will be offended and not update the repository
- # Maybe this is a good way to assure it doesn't get into our way, but
+ # Maybe this is a good way to assure it doesn't get into our way, but
# we want to stay backwards compatible too ... . Its so redundant !
self.repo.config_writer().set_value(sm_section(self.name), 'url', self.url)
- #END handle dry_run
- #END handle initalization
-
-
+ # END handle dry_run
+ # END handle initalization
+
# DETERMINE SHAS TO CHECKOUT
############################
binsha = self.binsha
@@ -435,8 +438,8 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
if mrepo is not None:
# mrepo is only set if we are not in dry-run mode or if the module existed
is_detached = mrepo.head.is_detached
- #END handle dry_run
-
+ # END handle dry_run
+
if mrepo is not None and to_latest_revision:
msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir
if not is_detached:
@@ -446,21 +449,23 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
binsha = rcommit.binsha
hexsha = rcommit.hexsha
else:
- print >> sys.stderr, "%s a tracking branch was not set for local branch '%s'" % (msg_base, mrepo.head.ref)
+ print >> sys.stderr, "%s a tracking branch was not set for local branch '%s'" % (
+ msg_base, mrepo.head.ref)
# END handle remote ref
else:
print >> sys.stderr, "%s there was no local tracking branch" % msg_base
# END handle detached head
# END handle to_latest_revision option
-
+
# update the working tree
# handles dry_run
if mrepo is not None and mrepo.head.commit.binsha != binsha:
- progress.update(BEGIN|UPDWKTREE, 0, 1, prefix+"Updating working tree at %s for submodule %r to revision %s" % (self.path, self.name, hexsha))
+ progress.update(BEGIN | UPDWKTREE, 0, 1, prefix +
+ "Updating working tree at %s for submodule %r to revision %s" % (self.path, self.name, hexsha))
if not dry_run:
if is_detached:
# NOTE: for now we force, the user is no supposed to change detached
- # submodules anyway. Maybe at some point this becomes an option, to
+ # submodules anyway. Maybe at some point this becomes an option, to
# properly handle user modifications - see below for future options
# regarding rebase and merge.
mrepo.git.checkout(hexsha, force=True)
@@ -470,10 +475,10 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
# branch - this should be prevented when setting the branch option
mrepo.head.reset(hexsha, index=True, working_tree=True)
# END handle checkout
- #END handle dry_run
- progress.update(END|UPDWKTREE, 0, 1, prefix+"Done updating working tree for submodule %r" % self.name)
+ # END handle dry_run
+ progress.update(END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r" % self.name)
# END update to new commit only if needed
-
+
# HANDLE RECURSION
##################
if recursive:
@@ -482,17 +487,17 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
for submodule in self.iter_items(self.module()):
submodule.update(recursive, init, to_latest_revision, progress=progress, dry_run=dry_run)
# END handle recursive update
- #END handle dry run
+ # END handle dry run
# END for each submodule
-
+
return self
-
+
@unbare_repo
def move(self, module_path, configuration=True, module=True):
"""Move the submodule to a another module path. This involves physically moving
the repository at our current path, changing the configuration, as well as
adjusting our index entry accordingly.
-
+
:param module_path: the path to which to move our module, given as
repository-relative path. Intermediate directories will be created
accordingly. If the path already exists, it must be empty.
@@ -510,59 +515,58 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
"""
if module + configuration < 1:
raise ValueError("You must specify to move at least the module or the configuration of the submodule")
- #END handle input
-
+ # END handle input
+
module_path = to_native_path_linux(module_path)
if module_path.endswith('/'):
module_path = module_path[:-1]
# END handle trailing slash
-
+
# VERIFY DESTINATION
if module_path == self.path:
return self
- #END handle no change
-
+ # END handle no change
+
dest_path = join_path_native(self.repo.working_tree_dir, module_path)
if os.path.isfile(dest_path):
raise ValueError("Cannot move repository onto a file: %s" % dest_path)
# END handle target files
-
+
index = self.repo.index
tekey = index.entry_key(module_path, 0)
# if the target item already exists, fail
if configuration and tekey in index.entries:
raise ValueError("Index entry for target path did alredy exist")
- #END handle index key already there
-
+ # END handle index key already there
+
# remove existing destination
if module:
if os.path.exists(dest_path):
if len(os.listdir(dest_path)):
raise ValueError("Destination module directory was not empty")
- #END handle non-emptyness
-
+ # END handle non-emptyness
+
if os.path.islink(dest_path):
os.remove(dest_path)
else:
os.rmdir(dest_path)
- #END handle link
+ # END handle link
else:
# recreate parent directories
# NOTE: renames() does that now
pass
- #END handle existance
+ # END handle existance
# END handle module
-
+
# move the module into place if possible
cur_path = self.abspath
renamed_module = False
if module and os.path.exists(cur_path):
os.renames(cur_path, dest_path)
renamed_module = True
- #END move physical module
-
-
- # rename the index entry - have to manipulate the index directly as
+ # END move physical module
+
+ # rename the index entry - have to manipulate the index directly as
# git-mv cannot be used on submodules ... yeah
try:
if configuration:
@@ -570,12 +574,12 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
ekey = index.entry_key(self.path, 0)
entry = index.entries[ekey]
del(index.entries[ekey])
- nentry = git.IndexEntry(entry[:3]+(module_path,)+entry[4:])
+ nentry = git.IndexEntry(entry[:3] + (module_path,) + entry[4:])
index.entries[tekey] = nentry
except KeyError:
raise InvalidGitRepositoryError("Submodule's entry at %r did not exist" % (self.path))
- #END handle submodule doesn't exist
-
+ # END handle submodule doesn't exist
+
# update configuration
writer = self.config_writer(index=index) # auto-write
writer.set_value('path', module_path)
@@ -587,15 +591,15 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
os.renames(dest_path, cur_path)
# END undo module renaming
raise
- #END handle undo rename
-
+ # END handle undo rename
+
return self
-
+
@unbare_repo
def remove(self, module=True, force=False, configuration=True, dry_run=False):
"""Remove this submodule from the repository. This will remove our entry
from the .gitmodules file and the entry in the .git/config file.
-
+
:param module: If True, the module we point to will be deleted
as well. If the module is currently on a commit which is not part
of any branch in the remote, if the currently checked out branch
@@ -620,7 +624,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
if not (module + configuration):
raise ValueError("Need to specify to delete at least the module, or the configuration")
# END handle params
-
+
# DELETE MODULE REPOSITORY
##########################
if module and self.module_exists():
@@ -636,20 +640,21 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
method = rmtree
elif os.path.exists(mp):
raise AssertionError("Cannot forcibly delete repository as it was neither a link, nor a directory")
- #END handle brutal deletion
+ # END handle brutal deletion
if not dry_run:
assert method
method(mp)
- #END apply deletion method
+ # END apply deletion method
else:
# verify we may delete our module
mod = self.module()
if mod.is_dirty(untracked_files=True):
- raise InvalidGitRepositoryError("Cannot delete module at %s with any modifications, unless force is specified" % mod.working_tree_dir)
+ raise InvalidGitRepositoryError(
+ "Cannot delete module at %s with any modifications, unless force is specified" % mod.working_tree_dir)
# END check for dirt
-
+
# figure out whether we have new commits compared to the remotes
- # NOTE: If the user pulled all the time, the remote heads might
+ # NOTE: If the user pulled all the time, the remote heads might
# not have been updated, so commits coming from the remote look
# as if they come from us. But we stay strictly read-only and
# don't fetch beforhand.
@@ -661,23 +666,24 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
# END for each remote ref
# not a single remote branch contained all our commits
if num_branches_with_new_commits == len(rrefs):
- raise InvalidGitRepositoryError("Cannot delete module at %s as there are new commits" % mod.working_tree_dir)
+ raise InvalidGitRepositoryError(
+ "Cannot delete module at %s as there are new commits" % mod.working_tree_dir)
# END handle new commits
- # have to manually delete references as python's scoping is
+ # have to manually delete references as python's scoping is
# not existing, they could keep handles open ( on windows this is a problem )
if len(rrefs):
del(rref)
- #END handle remotes
+ # END handle remotes
del(rrefs)
del(remote)
# END for each remote
-
+
# gently remove all submodule repositories
for sm in self.children():
sm.remove(module=True, force=False, configuration=False, dry_run=dry_run)
del(sm)
# END for each child-submodule
-
+
# finally delete our own submodule
if not dry_run:
wtd = mod.working_tree_dir
@@ -686,7 +692,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
# END delete tree if possible
# END handle force
# END handle module deletion
-
+
# DELETE CONFIGURATION
######################
if configuration and not dry_run:
@@ -696,10 +702,10 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
del(index.entries[index.entry_key(self.path, 0)])
except KeyError:
pass
- #END delete entry
+ # END delete entry
index.write()
-
- # now git config - need the config intact, otherwise we can't query
+
+ # now git config - need the config intact, otherwise we can't query
# inforamtion anymore
self.repo.config_writer().remove_section(sm_section(self.name))
self.config_writer().remove_section()
@@ -707,13 +713,13 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
# void our data not to delay invalid access
self._clear_cache()
-
+
return self
-
+
def set_parent_commit(self, commit, check=True):
"""Set this instance to use the given commit whose tree is supposed to
contain the .gitmodules blob.
-
+
:param commit: Commit'ish reference pointing at the root_tree
:param check: if True, relatively expensive checks will be performed to verify
validity of the submodule.
@@ -726,30 +732,30 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
if self.k_modules_file not in pctree:
raise ValueError("Tree of commit %s did not contain the %s file" % (commit, self.k_modules_file))
# END handle exceptions
-
+
prev_pc = self._parent_commit
self._parent_commit = pcommit
-
+
if check:
parser = self._config_parser(self.repo, self._parent_commit, read_only=True)
if not parser.has_section(sm_section(self.name)):
self._parent_commit = prev_pc
- raise ValueError("Submodule at path %r did not exist in parent commit %s" % (self.path, commit))
+ raise ValueError("Submodule at path %r did not exist in parent commit %s" % (self.path, commit))
# END handle submodule did not exist
# END handle checking mode
-
+
# update our sha, it could have changed
self.binsha = pctree[self.path].binsha
-
+
self._clear_cache()
-
+
return self
-
+
@unbare_repo
def config_writer(self, index=None, write=True):
""":return: a config writer instance allowing you to read and write the data
belonging to this submodule into the .gitmodules file.
-
+
:param index: if not None, an IndexFile instance which should be written.
defaults to the index of the Submodule's parent repository.
:param write: if True, the index will be written each time a configuration
@@ -765,11 +771,11 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
writer.config._index = index
writer.config._auto_write = write
return writer
-
+
#} END edit interface
-
+
#{ Query Interface
-
+
@unbare_repo
def module(self, repoType=None):
""":return: Repository instance initialized from the repository at our submodule path
@@ -781,7 +787,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
# late import to workaround circular dependencies
module_path = self.abspath
repoType = repoType or git.Repo
-
+
try:
repo = repoType(module_path)
if repo != self.repo:
@@ -792,7 +798,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
else:
raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_path)
# END handle exceptions
-
+
def module_exists(self):
""":return: True if our module exists and is a valid git repository. See module() method"""
try:
@@ -801,7 +807,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
except Exception:
return False
# END handle exception
-
+
def exists(self):
"""
:return: True if the submodule exists, False otherwise. Please note that
@@ -814,9 +820,9 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
if hasattr(self, attr):
loc[attr] = getattr(self, attr)
# END if we have the attribute cache
- #END for each attr
+ # END for each attr
self._clear_cache()
-
+
try:
try:
self.path
@@ -831,38 +837,38 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
# END if we have a cache
# END reapply each attribute
# END handle object state consistency
-
+
@property
def branch(self):
""":return: The branch instance that we are to checkout
:raise InvalidGitRepositoryError: if our module is not yet checked out"""
return mkhead(self.module(), self._branch_path)
-
+
@property
def branch_path(self):
"""
:return: full (relative) path as string to the branch we would checkout
from the remote and track"""
return self._branch_path
-
+
@property
def branch_name(self):
""":return: the name of the branch, which is the shortest possible branch name"""
# use an instance method, for this we create a temporary Head instance
# which uses a repository that is available at least ( it makes no difference )
return git.Head(self.repo, self._branch_path).name
-
+
@property
def url(self):
""":return: The url to the repository which our module-repository refers to"""
return self._url
-
+
@property
def parent_commit(self):
""":return: Commit instance with the tree containing the .gitmodules file
:note: will always point to the current head's commit if it was not set explicitly"""
return self._parent_commit
-
+
@property
def name(self):
""":return: The name of this submodule. It is used to identify it within the
@@ -873,7 +879,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
easily
"""
return self._name
-
+
def config_reader(self):
"""
:return: ConfigReader instance which allows you to qurey the configuration values
@@ -883,17 +889,17 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
:note: Should be cached by the caller and only kept as long as needed
:raise IOError: If the .gitmodules file/blob could not be read"""
return self._config_parser_constrained(read_only=True)
-
+
def children(self):
"""
:return: IterableList(Submodule, ...) an iterable list of submodules instances
which are children of this submodule or 0 if the submodule is not checked out"""
return self._get_intermediate_items(self)
-
+
#} END query interface
-
+
#{ Iterable Interface
-
+
@classmethod
def iter_items(cls, repo, parent_commit='HEAD'):
""":return: iterator yielding Submodule instances available in the given repository"""
@@ -903,9 +909,9 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
except IOError:
raise StopIteration
# END handle empty iterator
-
+
rt = pc.tree # root tree
-
+
for sms in parser.sections():
n = sm_name(sms)
p = parser.get_value(sms, 'path')
@@ -914,7 +920,7 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
if parser.has_option(sms, cls.k_head_option):
b = parser.get_value(sms, cls.k_head_option)
# END handle optional information
-
+
# get the binsha
index = repo.index
try:
@@ -925,18 +931,18 @@ class Submodule(util.IndexObject, Iterable, Traversable, RepoAliasMixin):
entry = index.entries[index.entry_key(p, 0)]
sm = Submodule(repo, entry.binsha, entry.mode, entry.path)
except KeyError:
- raise InvalidGitRepositoryError("Gitmodule path %r did not exist in revision of parent commit %s" % (p, parent_commit))
+ raise InvalidGitRepositoryError(
+ "Gitmodule path %r did not exist in revision of parent commit %s" % (p, parent_commit))
# END handle keyerror
# END handle critical error
-
+
# fill in remaining info - saves time as it doesn't have to be parsed again
sm._name = n
sm._parent_commit = pc
sm._branch_path = git.Head.to_full_path(b)
sm._url = u
-
+
yield sm
# END for each section
-
- #} END iterable interface
+ #} END iterable interface
diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py
index 07fb793b..c6eee8af 100644
--- a/git/objects/submodule/root.py
+++ b/git/objects/submodule/root.py
@@ -4,8 +4,8 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
from base import Submodule, UpdateProgress
from util import (
- find_first_remote_branch
- )
+ find_first_remote_branch
+)
from git.exc import InvalidGitRepositoryError
import git
@@ -15,10 +15,12 @@ __all__ = ["RootModule", "RootUpdateProgress"]
class RootUpdateProgress(UpdateProgress):
+
"""Utility class which adds more opcodes to the UpdateProgress"""
- REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes+4)]
- _num_op_codes = UpdateProgress._num_op_codes+4
-
+ REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [
+ 1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)]
+ _num_op_codes = UpdateProgress._num_op_codes + 4
+
__slots__ = tuple()
BEGIN = RootUpdateProgress.BEGIN
@@ -30,41 +32,41 @@ PATHCHANGE = RootUpdateProgress.PATHCHANGE
class RootModule(Submodule):
+
"""A (virtual) Root of all submodules in the given repository. It can be used
to more easily traverse all submodules of the master repository"""
-
+
__slots__ = tuple()
-
+
k_root_name = '__ROOT__'
-
- def __init__(self, repo, parent_commit = None):
+
+ def __init__(self, repo, parent_commit=None):
super(RootModule, self).__init__(
- repo,
- binsha = self.NULL_BIN_SHA,
- mode = self.k_default_mode,
- path = '',
- name = self.k_root_name,
- parent_commit = parent_commit or repo.head.commit,
- url = '',
- branch_path = git.Head.to_full_path(self.k_head_default)
- )
-
-
+ repo,
+ binsha=self.NULL_BIN_SHA,
+ mode=self.k_default_mode,
+ path='',
+ name=self.k_root_name,
+ parent_commit=parent_commit or repo.head.commit,
+ url='',
+ branch_path=git.Head.to_full_path(self.k_head_default)
+ )
+
def _clear_cache(self):
"""May not do anything"""
pass
-
- #{ Interface
-
- def update(self, previous_commit=None, recursive=True, force_remove=False, init=True,
- to_latest_revision=False, progress=None, dry_run=False):
+
+ #{ Interface
+
+ def update(self, previous_commit=None, recursive=True, force_remove=False, init=True,
+ to_latest_revision=False, progress=None, dry_run=False):
"""Update the submodules of this repository to the current HEAD commit.
This method behaves smartly by determining changes of the path of a submodules
repository, next to changes to the to-be-checked-out commit or the branch to be
checked out. This works if the submodules ID does not change.
Additionally it will detect addition and removal of submodules, which will be handled
gracefully.
-
+
:param previous_commit: If set to a commit'ish, the commit we should use
as the previous commit the HEAD pointed to before it was set to the commit it points to now.
If None, it defaults to HEAD@{1} otherwise
@@ -83,17 +85,17 @@ class RootModule(Submodule):
if self.repo.bare:
raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
# END handle bare
-
+
if progress is None:
progress = RootUpdateProgress()
- #END assure progress is set
-
+ # END assure progress is set
+
prefix = ''
if dry_run:
prefix = 'DRY-RUN: '
-
+
repo = self.repo
-
+
# SETUP BASE COMMIT
###################
cur_commit = repo.head.commit
@@ -102,21 +104,20 @@ class RootModule(Submodule):
previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha)
if previous_commit.binsha == previous_commit.NULL_BIN_SHA:
raise IndexError
- #END handle initial commit
+ # END handle initial commit
except IndexError:
# in new repositories, there is no previous commit
previous_commit = cur_commit
- #END exception handling
+ # END exception handling
else:
- previous_commit = repo.commit(previous_commit) # obtain commit object
+ previous_commit = repo.commit(previous_commit) # obtain commit object
# END handle previous commit
-
-
+
psms = self.list_items(repo, parent_commit=previous_commit)
sms = self.list_items(repo)
spsms = set(psms)
ssms = set(sms)
-
+
# HANDLE REMOVALS
###################
rrsm = (spsms - ssms)
@@ -125,22 +126,22 @@ class RootModule(Submodule):
op = REMOVE
if i == 0:
op |= BEGIN
- #END handle begin
-
+ # END handle begin
+
# fake it into thinking its at the current commit to allow deletion
# of previous module. Trigger the cache to be updated before that
- progress.update(op, i, len_rrsm, prefix+"Removing submodule %r at %s" % (rsm.name, rsm.abspath))
+ progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath))
rsm._parent_commit = repo.head.commit
if not dry_run:
rsm.remove(configuration=False, module=True, force=force_remove)
- #END handle dry-run
-
- if i == len_rrsm-1:
+ # END handle dry-run
+
+ if i == len_rrsm - 1:
op |= END
- #END handle end
- progress.update(op, i, len_rrsm, prefix+"Done removing submodule %r" % rsm.name)
+ # END handle end
+ progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
# END for each removed submodule
-
+
# HANDLE PATH RENAMES
#####################
# url changes + branch changes
@@ -149,44 +150,48 @@ class RootModule(Submodule):
for i, csm in enumerate(csms):
psm = psms[csm.name]
sm = sms[csm.name]
-
- #PATH CHANGES
+
+ # PATH CHANGES
##############
if sm.path != psm.path and psm.module_exists():
- progress.update(BEGIN|PATHCHANGE, i, len_csms, prefix+"Moving repository of submodule %r from %s to %s" % (sm.name, psm.abspath, sm.abspath))
+ progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix +
+ "Moving repository of submodule %r from %s to %s" % (sm.name, psm.abspath, sm.abspath))
# move the module to the new path
if not dry_run:
psm.move(sm.path, module=True, configuration=False)
- #END handle dry_run
- progress.update(END|PATHCHANGE, i, len_csms, prefix+"Done moving repository of submodule %r" % sm.name)
+ # END handle dry_run
+ progress.update(
+ END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name)
# END handle path changes
-
+
if sm.module_exists():
# HANDLE URL CHANGE
###################
if sm.url != psm.url:
# Add the new remote, remove the old one
- # This way, if the url just changes, the commits will not
+ # This way, if the url just changes, the commits will not
# have to be re-retrieved
nn = '__new_origin__'
smm = sm.module()
rmts = smm.remotes
-
+
# don't do anything if we already have the url we search in place
if len([r for r in rmts if r.url == sm.url]) == 0:
- progress.update(BEGIN|URLCHANGE, i, len_csms, prefix+"Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
-
+ progress.update(BEGIN | URLCHANGE, i, len_csms, prefix +
+ "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
+
if not dry_run:
assert nn not in [r.name for r in rmts]
smr = smm.create_remote(nn, sm.url)
smr.fetch(progress=progress)
-
+
# If we have a tracking branch, it should be available
# in the new remote as well.
if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0:
- raise ValueError("Submodule branch named %r was not available in new submodule remote at %r" % (sm.branch_name, sm.url))
+ raise ValueError(
+ "Submodule branch named %r was not available in new submodule remote at %r" % (sm.branch_name, sm.url))
# END head is not detached
-
+
# now delete the changed one
rmt_for_deletion = None
for remote in rmts:
@@ -195,36 +200,37 @@ class RootModule(Submodule):
break
# END if urls match
# END for each remote
-
- # if we didn't find a matching remote, but have exactly one,
+
+ # if we didn't find a matching remote, but have exactly one,
# we can safely use this one
if rmt_for_deletion is None:
if len(rmts) == 1:
rmt_for_deletion = rmts[0]
else:
# if we have not found any remote with the original url
- # we may not have a name. This is a special case,
+ # we may not have a name. This is a special case,
# and its okay to fail here
# Alternatively we could just generate a unique name and leave all
# existing ones in place
- raise InvalidGitRepositoryError("Couldn't find original remote-repo at url %r" % psm.url)
- #END handle one single remote
+ raise InvalidGitRepositoryError(
+ "Couldn't find original remote-repo at url %r" % psm.url)
+ # END handle one single remote
# END handle check we found a remote
-
+
orig_name = rmt_for_deletion.name
smm.delete_remote(rmt_for_deletion)
# NOTE: Currently we leave tags from the deleted remotes
- # as well as separate tracking branches in the possibly totally
- # changed repository ( someone could have changed the url to
+ # as well as separate tracking branches in the possibly totally
+ # changed repository ( someone could have changed the url to
# another project ). At some point, one might want to clean
# it up, but the danger is high to remove stuff the user
# has added explicitly
-
+
# rename the new remote back to what it was
smr.rename(orig_name)
-
+
# early on, we verified that the our current tracking branch
- # exists in the remote. Now we have to assure that the
+ # exists in the remote. Now we have to assure that the
# sha we point to is still contained in the new remote
# tracking branch.
smsha = sm.binsha
@@ -236,28 +242,30 @@ class RootModule(Submodule):
break
# END traverse all commits in search for sha
# END for each commit
-
+
if not found:
# adjust our internal binsha to use the one of the remote
# this way, it will be checked out in the next step
- # This will change the submodule relative to us, so
+ # This will change the submodule relative to us, so
# the user will be able to commit the change easily
print >> sys.stderr, "WARNING: Current sha %s was not contained in the tracking branch at the new remote, setting it the the remote's tracking branch" % sm.hexsha
sm.binsha = rref.commit.binsha
- #END reset binsha
-
- #NOTE: All checkout is performed by the base implementation of update
- #END handle dry_run
- progress.update(END|URLCHANGE, i, len_csms, prefix+"Done adjusting url of submodule %r" % (sm.name))
+ # END reset binsha
+
+ # NOTE: All checkout is performed by the base implementation of update
+ # END handle dry_run
+ progress.update(
+ END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name))
# END skip remote handling if new url already exists in module
# END handle url
-
+
# HANDLE PATH CHANGES
#####################
if sm.branch_path != psm.branch_path:
- # finally, create a new tracking branch which tracks the
+ # finally, create a new tracking branch which tracks the
# new remote branch
- progress.update(BEGIN|BRANCHCHANGE, i, len_csms, prefix+"Changing branch of submodule %r from %s to %s" % (sm.name, psm.branch_path, sm.branch_path))
+ progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix +
+ "Changing branch of submodule %r from %s to %s" % (sm.name, psm.branch_path, sm.branch_path))
if not dry_run:
smm = sm.module()
smmr = smm.remotes
@@ -266,50 +274,51 @@ class RootModule(Submodule):
except OSError:
# ... or reuse the existing one
tbr = git.Head(smm, sm.branch_path)
- #END assure tracking branch exists
-
+ # END assure tracking branch exists
+
tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
# figure out whether the previous tracking branch contains
- # new commits compared to the other one, if not we can
+ # new commits compared to the other one, if not we can
# delete it.
try:
tbr = find_first_remote_branch(smmr, psm.branch_name)
if len(smm.git.cherry(tbr, psm.branch)) == 0:
psm.branch.delete(smm, psm.branch)
- #END delete original tracking branch if there are no changes
+ # END delete original tracking branch if there are no changes
except InvalidGitRepositoryError:
# ignore it if the previous branch couldn't be found in the
# current remotes, this just means we can't handle it
pass
# END exception handling
-
- #NOTE: All checkout is done in the base implementation of update
- #END handle dry_run
-
- progress.update(END|BRANCHCHANGE, i, len_csms, prefix+"Done changing branch of submodule %r" % sm.name)
- #END handle branch
- #END handle
- # END for each common submodule
-
+
+ # NOTE: All checkout is done in the base implementation of update
+ # END handle dry_run
+
+ progress.update(
+ END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name)
+ # END handle branch
+ # END handle
+ # END for each common submodule
+
# FINALLY UPDATE ALL ACTUAL SUBMODULES
######################################
for sm in sms:
# update the submodule using the default method
- sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision,
- progress=progress, dry_run=dry_run)
-
- # update recursively depth first - question is which inconsitent
+ sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision,
+ progress=progress, dry_run=dry_run)
+
+ # update recursively depth first - question is which inconsitent
# state will be better in case it fails somewhere. Defective branch
- # or defective depth. The RootSubmodule type will never process itself,
+ # or defective depth. The RootSubmodule type will never process itself,
# which was done in the previous expression
if recursive:
# the module would exist by now if we are not in dry_run mode
if sm.module_exists():
- type(self)(sm.module()).update( recursive=True, force_remove=force_remove,
- init=init, to_latest_revision=to_latest_revision,
- progress=progress, dry_run=dry_run)
- #END handle dry_run
- #END handle recursive
+ type(self)(sm.module()).update(recursive=True, force_remove=force_remove,
+ init=init, to_latest_revision=to_latest_revision,
+ progress=progress, dry_run=dry_run)
+ # END handle dry_run
+ # END handle recursive
# END for each submodule to update
def module(self):
diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py
index 1c6ab483..552f353d 100644
--- a/git/objects/submodule/util.py
+++ b/git/objects/submodule/util.py
@@ -8,36 +8,42 @@ from git.config import GitConfigParser
from StringIO import StringIO
import weakref
-__all__ = ( 'sm_section', 'sm_name', 'mkhead', 'unbare_repo', 'find_first_remote_branch',
- 'SubmoduleConfigParser')
+__all__ = ('sm_section', 'sm_name', 'mkhead', 'unbare_repo', 'find_first_remote_branch',
+ 'SubmoduleConfigParser')
#{ Utilities
+
def sm_section(name):
""":return: section title used in .gitmodules configuration file"""
return 'submodule "%s"' % name
+
def sm_name(section):
""":return: name of the submodule as parsed from the section name"""
section = section.strip()
return section[11:-1]
-
+
+
def mkhead(repo, path):
""":return: New branch/head instance"""
return git.Head(repo, git.Head.to_full_path(path))
-
+
+
def unbare_repo(func):
"""Methods with this decorator raise InvalidGitRepositoryError if they
encounter a bare repository"""
+
def wrapper(self, *args, **kwargs):
if self.repo.bare:
raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__)
- #END bare method
+ # END bare method
return func(self, *args, **kwargs)
# END wrapper
wrapper.__name__ = func.__name__
return wrapper
-
+
+
def find_first_remote_branch(remotes, branch_name):
"""Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError"""
for remote in remotes:
@@ -46,30 +52,31 @@ def find_first_remote_branch(remotes, branch_name):
except IndexError:
continue
# END exception handling
- #END for remote
+ # END for remote
raise InvalidGitRepositoryError("Didn't find remote branch %r in any of the given remotes", branch_name)
-
+
#} END utilities
#{ Classes
class SubmoduleConfigParser(GitConfigParser):
+
"""
Catches calls to _write, and updates the .gitmodules blob in the index
with the new data, if we have written into a stream. Otherwise it will
add the local file to the index to make it correspond with the working tree.
Additionally, the cache must be cleared
-
+
Please note that no mutating method will work in bare mode
"""
-
+
def __init__(self, *args, **kwargs):
self._smref = None
self._index = None
self._auto_write = True
super(SubmoduleConfigParser, self).__init__(*args, **kwargs)
-
+
#{ Interface
def set_submodule(self, submodule):
"""Set this instance's submodule. It must be called before
@@ -81,7 +88,7 @@ class SubmoduleConfigParser(GitConfigParser):
assert self._smref is not None
# should always have a file here
assert not isinstance(self._file_or_files, StringIO)
-
+
sm = self._smref()
if sm is not None:
index = self._index
@@ -93,7 +100,7 @@ class SubmoduleConfigParser(GitConfigParser):
# END handle weakref
#} END interface
-
+
#{ Overridden Methods
def write(self):
rval = super(SubmoduleConfigParser, self).write()
diff --git a/git/objects/tag.py b/git/objects/tag.py
index 2937f470..630a6499 100644
--- a/git/objects/tag.py
+++ b/git/objects/tag.py
@@ -8,24 +8,26 @@ import base
from git.util import RepoAliasMixin
from git.util import hex_to_bin
from util import (
- get_object_type_by_name,
- parse_actor_and_date
- )
+ get_object_type_by_name,
+ parse_actor_and_date
+)
from git.typ import ObjectType
__all__ = ("TagObject", )
+
class TagObject(base.Object, RepoAliasMixin):
+
"""Non-Lightweight tag carrying additional information about an object we are pointing to."""
type = ObjectType.tag
type_id = ObjectType.tag_id
-
- __slots__ = ( "object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message" )
-
- def __init__(self, odb, binsha, object=None, tag=None,
- tagger=None, tagged_date=None, tagger_tz_offset=None, message=None):
+
+ __slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message")
+
+ def __init__(self, odb, binsha, object=None, tag=None,
+ tagger=None, tagged_date=None, tagger_tz_offset=None, message=None):
"""Initialize a tag object with additional data
-
+
:param odb: repository this object is located in
:param binsha: 20 byte SHA1
:param object: Object instance of object we are pointing to
@@ -36,7 +38,7 @@ class TagObject(base.Object, RepoAliasMixin):
it into a different format
:param tagged_tz_offset: int_seconds_west_of_utc is the timezone that the
authored_date is in, in a format similar to time.altzone"""
- super(TagObject, self).__init__(odb, binsha )
+ super(TagObject, self).__init__(odb, binsha)
if object is not None:
self.object = object
if tag is not None:
@@ -49,24 +51,24 @@ class TagObject(base.Object, RepoAliasMixin):
self.tagger_tz_offset = tagger_tz_offset
if message is not None:
self.message = message
-
+
def _set_cache_(self, attr):
"""Cache all our attributes at once"""
if attr in TagObject.__slots__:
ostream = self.odb.stream(self.binsha)
lines = ostream.read().splitlines()
-
+
obj, hexsha = lines[0].split(" ") # object <hexsha>
- type_token, type_name = lines[1].split(" ") # type <type_name>
+ type_token, type_name = lines[1].split(" ") # type <type_name>
self.object = get_object_type_by_name(type_name)(self.odb, hex_to_bin(hexsha))
-
+
self.tag = lines[2][4:] # tag <tag name>
-
- tagger_info = lines[3][7:]# tagger <actor> <date>
+
+ tagger_info = lines[3][7:] # tagger <actor> <date>
self.tagger, self.tagged_date, self.tagger_tz_offset = parse_actor_and_date(tagger_info)
-
+
# line 4 empty - it could mark the beginning of the next header
- # in case there really is no message, it would not exist. Otherwise
+ # in case there really is no message, it would not exist. Otherwise
# a newline separates header from message
if len(lines) > 5:
self.message = "\n".join(lines[5:])
@@ -75,6 +77,3 @@ class TagObject(base.Object, RepoAliasMixin):
# END check our attributes
else:
super(TagObject, self)._set_cache_(attr)
-
-
-
diff --git a/git/objects/tree.py b/git/objects/tree.py
index e0765c87..1986e9fa 100644
--- a/git/objects/tree.py
+++ b/git/objects/tree.py
@@ -11,28 +11,30 @@ from blob import Blob
from submodule.base import Submodule
from fun import (
- tree_entries_from_data,
- tree_to_stream
- )
+ tree_entries_from_data,
+ tree_to_stream
+)
from git.util import (
- to_bin_sha,
- join_path
- )
+ to_bin_sha,
+ join_path
+)
import util
__all__ = ("TreeModifier", "Tree")
+
class TreeModifier(object):
+
"""A utility class providing methods to alter the underlying cache in a list-like fashion.
-
+
Once all adjustments are complete, the _cache, which really is a refernce to
the cache of a tree, will be sorted. Assuring it will be in a serializable state"""
__slots__ = '_cache'
-
+
def __init__(self, cache):
self._cache = cache
-
+
def _index_by_name(self, name):
""":return: index of an item with name, or -1 if not found"""
for i, t in enumerate(self._cache):
@@ -41,8 +43,8 @@ class TreeModifier(object):
# END found item
# END for each item in cache
return -1
-
- #{ Interface
+
+ #{ Interface
def set_done(self):
"""Call this method once you are done modifying the tree information.
It may be called several times, but be aware that each call will cause
@@ -51,14 +53,14 @@ class TreeModifier(object):
self._cache.sort(key=lambda t: t[2]) # sort by name
return self
#} END interface
-
+
#{ Mutators
def add(self, sha, mode, name, force=False):
"""Add the given item to the tree. If an item with the given name already
exists, nothing will be done, but a ValueError will be raised if the
sha and mode of the existing item do not match the one you add, unless
force is True
-
+
:param sha: The 20 or 40 byte sha of the item to add
:param mode: int representing the stat compatible mode of the item
:param force: If True, an item with your name and information will overwrite
@@ -68,7 +70,7 @@ class TreeModifier(object):
raise ValueError("Name must not contain '/' characters")
if (mode >> 12) not in Tree._map_id_to_type:
raise ValueError("Invalid object type according to mode %o" % mode)
-
+
sha = to_bin_sha(sha)
index = self._index_by_name(name)
item = (sha, mode, name)
@@ -85,60 +87,60 @@ class TreeModifier(object):
# END handle force
# END handle name exists
return self
-
+
def add_unchecked(self, binsha, mode, name):
"""Add the given item to the tree, its correctness is assumed, which
puts the caller into responsibility to assure the input is correct.
For more information on the parameters, see ``add``
:param binsha: 20 byte binary sha"""
self._cache.append((binsha, mode, name))
-
+
def __delitem__(self, name):
"""Deletes an item with the given name if it exists"""
index = self._index_by_name(name)
if index > -1:
del(self._cache[index])
-
+
#} END mutators
class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, RepoAliasMixin):
+
"""Tree objects represent an ordered list of Blobs and other Trees.
-
+
``Tree as a list``::
-
+
Access a specific blob using the
tree['filename'] notation.
-
+
You may as well access by index
blob = tree[0]
"""
-
+
type = ObjectType.tree
type_id = ObjectType.tree_id
-
+
__slots__ = "_cache"
-
- # actual integer ids for comparison
+
+ # actual integer ids for comparison
commit_id = 016 # equals stat.S_IFDIR | stat.S_IFLNK - a directory link
blob_id = 010
symlink_id = 012
tree_id = 004
-
+
#{ Configuration
-
+
# override in subclass if you would like your own types to be instantiated instead
_map_id_to_type = {
- commit_id : Submodule,
- blob_id : Blob,
- symlink_id : Blob
- # tree id added once Tree is defined
- }
-
+ commit_id: Submodule,
+ blob_id: Blob,
+ symlink_id: Blob
+ # tree id added once Tree is defined
+ }
+
#} end configuration
-
-
- def __init__(self, repo, binsha, mode=tree_id<<12, path=None):
+
+ def __init__(self, repo, binsha, mode=tree_id << 12, path=None):
super(Tree, self).__init__(repo, binsha, mode, path)
@classmethod
@@ -154,7 +156,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
self._cache = tree_entries_from_data(ostream.read())
else:
super(Tree, self)._set_cache_(attr)
- # END handle attribute
+ # END handle attribute
def _iter_convert_to_object(self, iterable):
"""Iterable yields tuples of (binsha, mode, name), which will be converted
@@ -165,25 +167,25 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
yield self._map_id_to_type[mode >> 12](self.repo, binsha, mode, path)
except KeyError:
raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path))
- # END for each item
+ # END for each item
def __div__(self, file):
"""Find the named object in this tree's contents
:return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule``
-
+
:raise KeyError: if given file or tree does not exist in tree"""
msg = "Blob or Tree named %r not found"
if '/' in file:
tree = self
item = self
tokens = file.split('/')
- for i,token in enumerate(tokens):
+ for i, token in enumerate(tokens):
item = tree[token]
if item.type == 'tree':
tree = item
else:
# safety assertion - blobs are at the end of the path
- if i != len(tokens)-1:
+ if i != len(tokens) - 1:
raise KeyError(msg % file)
return item
# END handle item type
@@ -196,19 +198,18 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
if info[2] == file: # [2] == name
return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2]))
# END for each obj
- raise KeyError( msg % file )
+ raise KeyError(msg % file)
# END handle long paths
-
@property
def trees(self):
""":return: list(Tree, ...) list of trees directly below this tree"""
- return [ i for i in self if i.type == "tree" ]
-
+ return [i for i in self if i.type == "tree"]
+
@property
def blobs(self):
""":return: list(Blob, ...) list of blobs directly below this tree"""
- return [ i for i in self if i.type == "blob" ]
+ return [i for i in self if i.type == "blob"]
@property
def cache(self):
@@ -219,9 +220,9 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
See the ``TreeModifier`` for more information on how to alter the cache"""
return TreeModifier(self._cache)
- def traverse( self, predicate = lambda i,d: True,
- prune = lambda i,d: False, depth = -1, branch_first=True,
- visit_once = False, ignore_self=1 ):
+ def traverse(self, predicate=lambda i, d: True,
+ prune=lambda i, d: False, depth=-1, branch_first=True,
+ visit_once=False, ignore_self=1):
"""For documentation, see util.Traversable.traverse
Trees are set to visit_once = False to gain more performance in the traversal"""
return super(Tree, self).traverse(predicate, prune, depth, branch_first, visit_once, ignore_self)
@@ -229,26 +230,25 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
# List protocol
def __getslice__(self, i, j):
return list(self._iter_convert_to_object(self._cache[i:j]))
-
+
def __iter__(self):
return self._iter_convert_to_object(self._cache)
-
+
def __len__(self):
return len(self._cache)
-
+
def __getitem__(self, item):
if isinstance(item, int):
info = self._cache[item]
return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2]))
-
+
if isinstance(item, basestring):
# compatability
return self.__div__(item)
- # END index is basestring
-
- raise TypeError( "Invalid index type: %r" % item )
-
-
+ # END index is basestring
+
+ raise TypeError("Invalid index type: %r" % item)
+
def __contains__(self, item):
if isinstance(item, IndexObject):
for info in self._cache:
@@ -258,7 +258,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
# END for each entry
# END handle item is index object
# compatability
-
+
# treat item as repo-relative path
path = self.path
for info in self._cache:
@@ -266,10 +266,10 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
return True
# END for each item
return False
-
+
def __reversed__(self):
return reversed(self._iter_convert_to_object(self._cache))
-
+
def _serialize(self, stream):
"""Serialize this tree into the stream. Please note that we will assume
our tree data to be in a sorted state. If this is not the case, serialization
@@ -277,12 +277,12 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable, Repo
by algorithms"""
tree_to_stream(self._cache, stream.write)
return self
-
+
def _deserialize(self, stream):
self._cache = tree_entries_from_data(stream.read())
return self
-
-
+
+
# END tree
# finalize map definition
diff --git a/git/objects/util.py b/git/objects/util.py
index 626bd363..d2c1f201 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -5,9 +5,9 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Module for general utility functions"""
from git.util import (
- IterableList,
- Actor
- )
+ IterableList,
+ Actor
+)
import re
from collections import deque as Deque
@@ -16,9 +16,9 @@ from string import digits
import time
import os
-__all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date',
- 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz',
- 'verify_utctz', 'Actor')
+__all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date',
+ 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz',
+ 'verify_utctz', 'Actor')
#{ Functions
@@ -33,17 +33,18 @@ def mode_str_to_int(modestr):
for example."""
mode = 0
for iteration, char in enumerate(reversed(modestr[-6:])):
- mode += int(char) << iteration*3
+ mode += int(char) << iteration * 3
# END for each char
return mode
+
def get_object_type_by_name(object_type_name):
"""
:return: type suitable to handle the given object type name.
Use the type to create new instances.
-
+
:param object_type_name: Member of TYPES
-
+
:raise ValueError: In case object_type_name is unknown"""
if object_type_name == "commit":
import commit
@@ -59,23 +60,25 @@ def get_object_type_by_name(object_type_name):
return tree.Tree
else:
raise ValueError("Cannot handle unknown object type: %s" % object_type_name)
-
+
+
def utctz_to_altz(utctz):
"""we convert utctz to the timezone in seconds, it is the format time.altzone
returns. Git stores it as UTC timezone which has the opposite sign as well,
which explains the -1 * ( that was made explicit here )
:param utctz: git utc timezone string, i.e. +0200"""
- return -1 * int(float(utctz)/100*3600)
-
+ return -1 * int(float(utctz) / 100 * 3600)
+
+
def altz_to_utctz_str(altz):
"""As above, but inverses the operation, returning a string that can be used
in commit objects"""
- utci = -1 * int((altz / 3600)*100)
+ utci = -1 * int((altz / 3600) * 100)
utcs = str(abs(utci))
- utcs = "0"*(4-len(utcs)) + utcs
+ utcs = "0" * (4 - len(utcs)) + utcs
prefix = (utci < 0 and '-') or '+'
return prefix + utcs
-
+
def verify_utctz(offset):
""":raise ValueError: if offset is incorrect
@@ -86,22 +89,23 @@ def verify_utctz(offset):
if offset[0] not in "+-":
raise fmt_exc
if offset[1] not in digits or \
- offset[2] not in digits or \
- offset[3] not in digits or \
- offset[4] not in digits:
+ offset[2] not in digits or \
+ offset[3] not in digits or \
+ offset[4] not in digits:
raise fmt_exc
# END for each char
return offset
+
def parse_date(string_date):
"""
Parse the given date as one of the following
-
+
* Git internal format: timestamp offset
* RFC 2822: Thu, 07 Apr 2005 22:13:13 +0200.
* ISO 8601 2005-04-07T22:13:13
The T can be a space as well
-
+
:return: Tuple(int(timestamp), int(offset)), both in seconds since epoch
:raise ValueError: If the format could not be understood
:note: Date can also be YYYY.MM.DD, MM/DD/YYYY and DD.MM.YYYY"""
@@ -117,7 +121,7 @@ def parse_date(string_date):
offset = verify_utctz(string_date[-5:])
string_date = string_date[:-6] # skip space as well
# END split timezone info
-
+
# now figure out the date and time portion - split time
date_formats = list()
splitter = -1
@@ -130,26 +134,26 @@ def parse_date(string_date):
date_formats.append("%Y.%m.%d")
date_formats.append("%m/%d/%Y")
date_formats.append("%d.%m.%Y")
-
+
splitter = string_date.rfind('T')
if splitter == -1:
splitter = string_date.rfind(' ')
# END handle 'T' and ' '
- # END handle rfc or iso
-
+ # END handle rfc or iso
+
assert splitter > -1
-
+
# split date and time
- time_part = string_date[splitter+1:] # skip space
+ time_part = string_date[splitter + 1:] # skip space
date_part = string_date[:splitter]
-
+
# parse time
tstruct = time.strptime(time_part, "%H:%M:%S")
-
+
for fmt in date_formats:
try:
dtstruct = time.strptime(date_part, fmt)
- fstruct = time.struct_time((dtstruct.tm_year, dtstruct.tm_mon, dtstruct.tm_mday,
+ fstruct = time.struct_time((dtstruct.tm_year, dtstruct.tm_mon, dtstruct.tm_mday,
tstruct.tm_hour, tstruct.tm_min, tstruct.tm_sec,
dtstruct.tm_wday, dtstruct.tm_yday, tstruct.tm_isdst))
return int(time.mktime(fstruct)), utctz_to_altz(offset)
@@ -157,56 +161,60 @@ def parse_date(string_date):
continue
# END exception handling
# END for each fmt
-
+
# still here ? fail
raise ValueError("no format matched")
# END handle format
except Exception:
- raise ValueError("Unsupported date format: %s" % string_date)
+ raise ValueError("Unsupported date format: %s" % string_date)
# END handle exceptions
-
+
# precompiled regex
_re_actor_epoch = re.compile(r'^.+? (.*) (\d+) ([+-]\d+).*$')
+
def parse_actor_and_date(line):
"""Parse out the actor (author or committer) info from a line like::
-
+
author Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
-
+
:return: [Actor, int_seconds_since_epoch, int_timezone_offset]"""
m = _re_actor_epoch.search(line)
actor, epoch, offset = m.groups()
return (Actor._from_string(actor), int(epoch), utctz_to_altz(offset))
-
+
#} END functions
-#{ Classes
-
+#{ Classes
+
class ProcessStreamAdapter(object):
+
"""Class wireing all calls to the contained Process instance.
-
+
Use this type to hide the underlying process to provide access only to a specified
stream. The process is usually wrapped into an AutoInterrupt class to kill
it if the instance goes out of scope."""
__slots__ = ("_proc", "_stream")
+
def __init__(self, process, stream_name):
self._proc = process
self._stream = getattr(process, stream_name)
-
+
def __getattr__(self, attr):
return getattr(self._stream, attr)
-
-
+
+
class Traversable(object):
+
"""Simple interface to perforam depth-first or breadth-first traversals
into one direction.
Subclasses only need to implement one function.
Instances of the Subclass must be hashable"""
__slots__ = tuple()
-
+
@classmethod
def _get_intermediate_items(cls, item):
"""
@@ -215,7 +223,7 @@ class Traversable(object):
Must be implemented in subclass
"""
raise NotImplementedError("To be implemented in subclass")
-
+
def list_traverse(self, *args, **kwargs):
"""
:return: IterableList with the results of the traversal as produced by
@@ -223,92 +231,93 @@ class Traversable(object):
out = IterableList(self._id_attribute_)
out.extend(self.traverse(*args, **kwargs))
return out
-
- def traverse( self, predicate = lambda i,d: True,
- prune = lambda i,d: False, depth = -1, branch_first=True,
- visit_once = True, ignore_self=1, as_edge = False ):
+
+ def traverse(self, predicate=lambda i, d: True,
+ prune=lambda i, d: False, depth=-1, branch_first=True,
+ visit_once=True, ignore_self=1, as_edge=False):
""":return: iterator yieling of items found when traversing self
-
+
:param predicate: f(i,d) returns False if item i at depth d should not be included in the result
-
+
:param prune:
f(i,d) return True if the search should stop at item i at depth d.
Item i will not be returned.
-
+
:param depth:
define at which level the iteration should not go deeper
if -1, there is no limit
if 0, you would effectively only get self, the root of the iteration
i.e. if 1, you would only get the first level of predessessors/successors
-
+
:param branch_first:
if True, items will be returned branch first, otherwise depth first
-
+
:param visit_once:
if True, items will only be returned once, although they might be encountered
several times. Loops are prevented that way.
-
+
:param ignore_self:
if True, self will be ignored and automatically pruned from
the result. Otherwise it will be the first item to be returned.
If as_edge is True, the source of the first edge is None
-
+
:param as_edge:
if True, return a pair of items, first being the source, second the
destinatination, i.e. tuple(src, dest) with the edge spanning from
source to destination"""
visited = set()
stack = Deque()
- stack.append( ( 0 ,self, None ) ) # self is always depth level 0
-
- def addToStack( stack, item, branch_first, depth ):
- lst = self._get_intermediate_items( item )
+ stack.append((0, self, None)) # self is always depth level 0
+
+ def addToStack(stack, item, branch_first, depth):
+ lst = self._get_intermediate_items(item)
if not lst:
return
if branch_first:
- stack.extendleft( ( depth , i, item ) for i in lst )
+ stack.extendleft((depth, i, item) for i in lst)
else:
- reviter = ( ( depth , lst[i], item ) for i in range( len( lst )-1,-1,-1) )
- stack.extend( reviter )
+ reviter = ((depth, lst[i], item) for i in range(len(lst) - 1, -1, -1))
+ stack.extend(reviter)
# END addToStack local method
-
+
while stack:
d, item, src = stack.pop() # depth of item, item, item_source
-
+
if visit_once and item in visited:
continue
-
+
if visit_once:
visited.add(item)
-
- rval = ( as_edge and (src, item) ) or item
- if prune( rval, d ):
+
+ rval = (as_edge and (src, item)) or item
+ if prune(rval, d):
continue
-
- skipStartItem = ignore_self and ( item is self )
- if not skipStartItem and predicate( rval, d ):
+
+ skipStartItem = ignore_self and (item is self)
+ if not skipStartItem and predicate(rval, d):
yield rval
-
+
# only continue to next level if this is appropriate !
nd = d + 1
if depth > -1 and nd > depth:
continue
-
- addToStack( stack, item, branch_first, nd )
+
+ addToStack(stack, item, branch_first, nd)
# END for each item on work stack
-
+
class Serializable(object):
+
"""Defines methods to serialize and deserialize objects from and into a data stream"""
__slots__ = tuple()
-
+
def _serialize(self, stream):
"""Serialize the data of this object into the given data stream
:note: a serialized object would ``_deserialize`` into the same objet
:param stream: a file-like object
:return: self"""
raise NotImplementedError("To be implemented in subclass")
-
+
def _deserialize(self, stream):
"""Deserialize all information regarding this object from the stream
:param stream: a file-like object
diff --git a/git/odict.py b/git/odict.py
index 80f6965f..ee8afa92 100644
--- a/git/odict.py
+++ b/git/odict.py
@@ -17,7 +17,7 @@
"""A dict that keeps keys in insertion order"""
from __future__ import generators
__author__ = ('Nicola Larosa <nico-NoSp@m-tekNico.net>,'
- 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>')
+ 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>')
__docformat__ = "restructuredtext en"
__revision__ = '$Id: odict.py 129 2005-09-12 18:15:28Z teknico $'
__version__ = '0.2.2'
@@ -28,45 +28,48 @@ INTP_VER = sys.version_info[:2]
if INTP_VER < (2, 2):
raise RuntimeError("Python v.2.2 or later required")
-import types, warnings
+import types
+import warnings
+
class OrderedDict(dict):
+
"""
A class of dictionary that keeps the insertion order of keys.
-
+
All appropriate methods return keys, items, or values in an ordered way.
-
+
All normal dictionary methods are available. Update and comparison is
restricted to other OrderedDict objects.
-
+
Various sequence methods are available, including the ability to explicitly
mutate the key ordering.
-
+
__contains__ tests:
-
+
>>> d = OrderedDict(((1, 3),))
>>> 1 in d
1
>>> 4 in d
0
-
+
__getitem__ tests:
-
+
>>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2]
1
>>> OrderedDict(((1, 3), (3, 2), (2, 1)))[4]
Traceback (most recent call last):
KeyError: 4
-
+
__len__ tests:
-
+
>>> len(OrderedDict())
0
>>> len(OrderedDict(((1, 3), (3, 2), (2, 1))))
3
-
+
get tests:
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.get(1)
3
@@ -76,9 +79,9 @@ class OrderedDict(dict):
5
>>> d
OrderedDict([(1, 3), (3, 2), (2, 1)])
-
+
has_key tests:
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.has_key(1)
1
@@ -90,11 +93,11 @@ class OrderedDict(dict):
"""
Create a new ordered dictionary. Cannot init from a normal dict,
nor from kwargs, since items order is undefined in those cases.
-
+
If the ``strict`` keyword argument is ``True`` (``False`` is the
default) then when doing slice assignment - the ``OrderedDict`` you are
assigning from *must not* contain any keys in the remaining dict.
-
+
>>> OrderedDict()
OrderedDict([])
>>> OrderedDict({1: 1})
@@ -277,7 +280,7 @@ class OrderedDict(dict):
def __repr__(self):
"""
Used for __repr__ and __str__
-
+
>>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
>>> r1
"OrderedDict([('a', 'b'), ('c', 'd'), ('e', 'f')])"
@@ -315,7 +318,7 @@ class OrderedDict(dict):
>>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
>>> d
OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
-
+
>>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True)
>>> a[3] = 4
>>> a
@@ -339,12 +342,12 @@ class OrderedDict(dict):
>>> a[::-1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
>>> a
OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)])
-
+
>>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
>>> d[:1] = 3
Traceback (most recent call last):
TypeError: slice assignment requires an OrderedDict
-
+
>>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
>>> d[:1] = OrderedDict([(9, 8)])
>>> d
@@ -369,20 +372,20 @@ class OrderedDict(dict):
if k in self:
if self.strict:
raise ValueError('slice assignment must be from '
- 'unique keys')
+ 'unique keys')
else:
# NOTE: This removes duplicate keys *first*
# so start position might have changed?
del self[k]
self._sequence = (self._sequence[:pos] + newkeys +
- self._sequence[pos:])
+ self._sequence[pos:])
dict.update(self, val)
else:
# extended slice - length of new slice must be the same
# as the one being replaced
if len(keys) != len(val):
raise ValueError('attempt to assign sequence of size %s '
- 'to extended slice of size %s' % (len(val), len(keys)))
+ 'to extended slice of size %s' % (len(val), len(keys)))
# FIXME: efficiency?
del self[key]
item_list = zip(indexes, val.items())
@@ -392,7 +395,7 @@ class OrderedDict(dict):
for pos, (newkey, newval) in item_list:
if self.strict and newkey in self:
raise ValueError('slice assignment must be from unique'
- ' keys')
+ ' keys')
self.insert(pos, newkey, newval)
else:
if key not in self:
@@ -427,7 +430,7 @@ class OrderedDict(dict):
"""
if name == 'sequence':
warnings.warn('Use of the sequence attribute is deprecated.'
- ' Use the keys method instead.', DeprecationWarning)
+ ' Use the keys method instead.', DeprecationWarning)
# NOTE: doesn't return anything
self.setkeys(value)
else:
@@ -438,14 +441,14 @@ class OrderedDict(dict):
def __getattr__(self, name):
"""
Implemented so that access to ``sequence`` raises a warning.
-
+
>>> d = OrderedDict()
>>> d.sequence
[]
"""
if name == 'sequence':
warnings.warn('Use of the sequence attribute is deprecated.'
- ' Use the keys method instead.', DeprecationWarning)
+ ' Use the keys method instead.', DeprecationWarning)
# NOTE: Still (currently) returns a direct reference. Need to
# because code that uses sequence will expect to be able to
# mutate it in place.
@@ -457,7 +460,7 @@ class OrderedDict(dict):
def __deepcopy__(self, memo):
"""
To allow deepcopy to work with OrderedDict.
-
+
>>> from copy import deepcopy
>>> a = OrderedDict([(1, 1), (2, 2), (3, 3)])
>>> a['test'] = {}
@@ -486,7 +489,7 @@ class OrderedDict(dict):
"""
``items`` returns a list of tuples representing all the
``(key, value)`` pairs in the dictionary.
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.items()
[(1, 3), (3, 2), (2, 1)]
@@ -499,7 +502,7 @@ class OrderedDict(dict):
def keys(self):
"""
Return a list of keys in the ``OrderedDict``.
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.keys()
[1, 3, 2]
@@ -509,10 +512,10 @@ class OrderedDict(dict):
def values(self, values=None):
"""
Return a list of all the values in the OrderedDict.
-
+
Optionally you can pass in a list of values, which will replace the
current list. The value list must be the same len as the OrderedDict.
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.values()
[3, 2, 1]
@@ -532,6 +535,7 @@ class OrderedDict(dict):
Traceback (most recent call last):
StopIteration
"""
+
def make_iter(self=self):
keys = self.iterkeys()
while True:
@@ -569,6 +573,7 @@ class OrderedDict(dict):
Traceback (most recent call last):
StopIteration
"""
+
def make_iter(self=self):
keys = self.iterkeys()
while True:
@@ -590,7 +595,7 @@ class OrderedDict(dict):
def pop(self, key, *args):
"""
No dict.pop in Python 2.2, gotta reimplement it
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.pop(3)
2
@@ -607,7 +612,7 @@ class OrderedDict(dict):
"""
if len(args) > 1:
raise TypeError, ('pop expected at most 2 arguments, got %s' %
- (len(args) + 1))
+ (len(args) + 1))
if key in self:
val = self[key]
del self[key]
@@ -622,7 +627,7 @@ class OrderedDict(dict):
"""
Delete and return an item specified by index, not a random one as in
dict. The index is -1 by default (the last item).
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.popitem()
(2, 1)
@@ -645,7 +650,7 @@ class OrderedDict(dict):
raise IndexError('popitem(): index %s not valid' % i)
return (key, self.pop(key))
- def setdefault(self, key, defval = None):
+ def setdefault(self, key, defval=None):
"""
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.setdefault(1)
@@ -668,7 +673,7 @@ class OrderedDict(dict):
def update(self, from_od):
"""
Update from another OrderedDict or sequence of (key, value) pairs
-
+
>>> d = OrderedDict(((1, 0), (0, 1)))
>>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1))))
>>> d
@@ -694,17 +699,17 @@ class OrderedDict(dict):
key, val = item
except TypeError:
raise TypeError('cannot convert dictionary update'
- ' sequence element "%s" to a 2-item sequence' % item)
+ ' sequence element "%s" to a 2-item sequence' % item)
self[key] = val
def rename(self, old_key, new_key):
"""
Rename the key for a given value, without modifying sequence order.
-
+
For the case where new_key already exists this raise an exception,
since if new_key exists, it is ambiguous as to what happens to the
associated values, and the position of new_key in the sequence.
-
+
>>> od = OrderedDict()
>>> od['a'] = 1
>>> od['b'] = 2
@@ -726,7 +731,7 @@ class OrderedDict(dict):
if new_key in self:
raise ValueError("New key already exists: %r" % new_key)
# rename sequence entry
- value = self[old_key]
+ value = self[old_key]
old_idx = self._sequence.index(old_key)
self._sequence[old_idx] = new_key
# rename internal dict entry
@@ -736,10 +741,10 @@ class OrderedDict(dict):
def setitems(self, items):
"""
This method allows you to set the items in the dict.
-
+
It takes a list of tuples - of the same sort returned by the ``items``
method.
-
+
>>> d = OrderedDict()
>>> d.setitems(((3, 1), (2, 3), (1, 2)))
>>> d
@@ -754,10 +759,10 @@ class OrderedDict(dict):
``setkeys`` all ows you to pass in a new list of keys which will
replace the current set. This must contain the same set of keys, but
need not be in the same order.
-
+
If you pass in new keys that don't match, a ``KeyError`` will be
raised.
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.keys()
[1, 3, 2]
@@ -785,9 +790,9 @@ class OrderedDict(dict):
"""
You can pass in a list of values, which will replace the
current list. The value list must be the same len as the OrderedDict.
-
+
(Or a ``ValueError`` is raised.)
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.setvalues((1, 2, 3))
>>> d
@@ -799,7 +804,7 @@ class OrderedDict(dict):
if len(values) != len(self):
# FIXME: correct error to raise?
raise ValueError('Value list is not the same length as the '
- 'OrderedDict.')
+ 'OrderedDict.')
self.update(zip(self, values))
### Sequence Methods ###
@@ -807,7 +812,7 @@ class OrderedDict(dict):
def index(self, key):
"""
Return the position of the specified key in the OrderedDict.
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.index(3)
1
@@ -820,10 +825,10 @@ class OrderedDict(dict):
def insert(self, index, key, value):
"""
Takes ``index``, ``key``, and ``value`` as arguments.
-
+
Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in
the OrderedDict.
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.insert(0, 4, 0)
>>> d
@@ -844,7 +849,7 @@ class OrderedDict(dict):
def reverse(self):
"""
Reverse the order of the OrderedDict.
-
+
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> d.reverse()
>>> d
@@ -855,10 +860,10 @@ class OrderedDict(dict):
def sort(self, *args, **kwargs):
"""
Sort the key order in the OrderedDict.
-
+
This method takes the same arguments as the ``list.sort`` method on
your version of Python.
-
+
>>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4)))
>>> d.sort()
>>> d
@@ -866,11 +871,13 @@ class OrderedDict(dict):
"""
self._sequence.sort(*args, **kwargs)
+
class Keys(object):
# FIXME: should this object be a subclass of list?
+
"""
Custom object for accessing the keys of an OrderedDict.
-
+
Can be called like the normal ``OrderedDict.keys`` method, but also
supports indexing and sequence methods.
"""
@@ -891,7 +898,7 @@ class Keys(object):
"""
You cannot assign to keys, but you can do slice assignment to re-order
them.
-
+
You can only do slice assignment if the new set of keys is a reordering
of the original set.
"""
@@ -901,7 +908,7 @@ class Keys(object):
indexes = range(len(self._main._sequence))[index]
if len(indexes) != len(name):
raise ValueError('attempt to assign sequence of size %s '
- 'to slice of size %s' % (len(name), len(indexes)))
+ 'to slice of size %s' % (len(name), len(indexes)))
# check they are the same keys
# FIXME: Use set
old_keys = self._main._sequence[index]
@@ -917,51 +924,101 @@ class Keys(object):
for i, k, v in vals:
if self._main.strict and k in self._main:
raise ValueError('slice assignment must be from '
- 'unique keys')
+ 'unique keys')
self._main.insert(i, k, v)
else:
raise ValueError('Cannot assign to keys')
### following methods pinched from UserList and adapted ###
- def __repr__(self): return repr(self._main._sequence)
+ def __repr__(self):
+ return repr(self._main._sequence)
# FIXME: do we need to check if we are comparing with another ``Keys``
# object? (like the __cast method of UserList)
- def __lt__(self, other): return self._main._sequence < other
- def __le__(self, other): return self._main._sequence <= other
- def __eq__(self, other): return self._main._sequence == other
- def __ne__(self, other): return self._main._sequence != other
- def __gt__(self, other): return self._main._sequence > other
- def __ge__(self, other): return self._main._sequence >= other
+ def __lt__(self, other):
+ return self._main._sequence < other
+
+ def __le__(self, other):
+ return self._main._sequence <= other
+
+ def __eq__(self, other):
+ return self._main._sequence == other
+
+ def __ne__(self, other):
+ return self._main._sequence != other
+
+ def __gt__(self, other):
+ return self._main._sequence > other
+
+ def __ge__(self, other):
+ return self._main._sequence >= other
# FIXME: do we need __cmp__ as well as rich comparisons?
- def __cmp__(self, other): return cmp(self._main._sequence, other)
-
- def __contains__(self, item): return item in self._main._sequence
- def __len__(self): return len(self._main._sequence)
- def __iter__(self): return self._main.iterkeys()
- def count(self, item): return self._main._sequence.count(item)
- def index(self, item, *args): return self._main._sequence.index(item, *args)
- def reverse(self): self._main._sequence.reverse()
- def sort(self, *args, **kwds): self._main._sequence.sort(*args, **kwds)
- def __mul__(self, n): return self._main._sequence*n
+
+ def __cmp__(self, other):
+ return cmp(self._main._sequence, other)
+
+ def __contains__(self, item):
+ return item in self._main._sequence
+
+ def __len__(self):
+ return len(self._main._sequence)
+
+ def __iter__(self):
+ return self._main.iterkeys()
+
+ def count(self, item):
+ return self._main._sequence.count(item)
+
+ def index(self, item, *args):
+ return self._main._sequence.index(item, *args)
+
+ def reverse(self):
+ self._main._sequence.reverse()
+
+ def sort(self, *args, **kwds):
+ self._main._sequence.sort(*args, **kwds)
+
+ def __mul__(self, n):
+ return self._main._sequence * n
__rmul__ = __mul__
- def __add__(self, other): return self._main._sequence + other
- def __radd__(self, other): return other + self._main._sequence
+
+ def __add__(self, other):
+ return self._main._sequence + other
+
+ def __radd__(self, other):
+ return other + self._main._sequence
## following methods not implemented for keys ##
- def __delitem__(self, i): raise TypeError('Can\'t delete items from keys')
- def __iadd__(self, other): raise TypeError('Can\'t add in place to keys')
- def __imul__(self, n): raise TypeError('Can\'t multiply keys in place')
- def append(self, item): raise TypeError('Can\'t append items to keys')
- def insert(self, i, item): raise TypeError('Can\'t insert items into keys')
- def pop(self, i=-1): raise TypeError('Can\'t pop items from keys')
- def remove(self, item): raise TypeError('Can\'t remove items from keys')
- def extend(self, other): raise TypeError('Can\'t extend keys')
+ def __delitem__(self, i):
+ raise TypeError('Can\'t delete items from keys')
+
+ def __iadd__(self, other):
+ raise TypeError('Can\'t add in place to keys')
+
+ def __imul__(self, n):
+ raise TypeError('Can\'t multiply keys in place')
+
+ def append(self, item):
+ raise TypeError('Can\'t append items to keys')
+
+ def insert(self, i, item):
+ raise TypeError('Can\'t insert items into keys')
+
+ def pop(self, i=-1):
+ raise TypeError('Can\'t pop items from keys')
+
+ def remove(self, item):
+ raise TypeError('Can\'t remove items from keys')
+
+ def extend(self, other):
+ raise TypeError('Can\'t extend keys')
+
class Items(object):
+
"""
Custom object for accessing the items of an OrderedDict.
-
+
Can be called like the normal ``OrderedDict.items`` method, but also
supports indexing and sequence methods.
"""
@@ -992,7 +1049,7 @@ class Items(object):
key, value = item
if self._main.strict and key in self and (key != orig):
raise ValueError('slice assignment must be from '
- 'unique keys')
+ 'unique keys')
# delete the current one
del self._main[self._main._sequence[index]]
self._main.insert(index, key, value)
@@ -1008,29 +1065,62 @@ class Items(object):
del self._main[key]
### following methods pinched from UserList and adapted ###
- def __repr__(self): return repr(self._main.items())
+ def __repr__(self):
+ return repr(self._main.items())
# FIXME: do we need to check if we are comparing with another ``Items``
# object? (like the __cast method of UserList)
- def __lt__(self, other): return self._main.items() < other
- def __le__(self, other): return self._main.items() <= other
- def __eq__(self, other): return self._main.items() == other
- def __ne__(self, other): return self._main.items() != other
- def __gt__(self, other): return self._main.items() > other
- def __ge__(self, other): return self._main.items() >= other
- def __cmp__(self, other): return cmp(self._main.items(), other)
-
- def __contains__(self, item): return item in self._main.items()
- def __len__(self): return len(self._main._sequence) # easier :-)
- def __iter__(self): return self._main.iteritems()
- def count(self, item): return self._main.items().count(item)
- def index(self, item, *args): return self._main.items().index(item, *args)
- def reverse(self): self._main.reverse()
- def sort(self, *args, **kwds): self._main.sort(*args, **kwds)
- def __mul__(self, n): return self._main.items()*n
+ def __lt__(self, other):
+ return self._main.items() < other
+
+ def __le__(self, other):
+ return self._main.items() <= other
+
+ def __eq__(self, other):
+ return self._main.items() == other
+
+ def __ne__(self, other):
+ return self._main.items() != other
+
+ def __gt__(self, other):
+ return self._main.items() > other
+
+ def __ge__(self, other):
+ return self._main.items() >= other
+
+ def __cmp__(self, other):
+ return cmp(self._main.items(), other)
+
+ def __contains__(self, item):
+ return item in self._main.items()
+
+ def __len__(self):
+ return len(self._main._sequence) # easier :-)
+
+ def __iter__(self):
+ return self._main.iteritems()
+
+ def count(self, item):
+ return self._main.items().count(item)
+
+ def index(self, item, *args):
+ return self._main.items().index(item, *args)
+
+ def reverse(self):
+ self._main.reverse()
+
+ def sort(self, *args, **kwds):
+ self._main.sort(*args, **kwds)
+
+ def __mul__(self, n):
+ return self._main.items() * n
__rmul__ = __mul__
- def __add__(self, other): return self._main.items() + other
- def __radd__(self, other): return other + self._main.items()
+
+ def __add__(self, other):
+ return self._main.items() + other
+
+ def __radd__(self, other):
+ return other + self._main.items()
def append(self, item):
"""Add an item to the end."""
@@ -1066,12 +1156,15 @@ class Items(object):
## following methods not implemented for items ##
- def __imul__(self, n): raise TypeError('Can\'t multiply items in place')
+ def __imul__(self, n):
+ raise TypeError('Can\'t multiply items in place')
+
class Values(object):
+
"""
Custom object for accessing the values of an OrderedDict.
-
+
Can be called like the normal ``OrderedDict.values`` method, but also
supports indexing and sequence methods.
"""
@@ -1093,7 +1186,7 @@ class Values(object):
def __setitem__(self, index, value):
"""
Set the value at position i to value.
-
+
You can only do slice assignment to values if you supply a sequence of
equal length to the slice you are replacing.
"""
@@ -1101,7 +1194,7 @@ class Values(object):
keys = self._main._sequence[index]
if len(keys) != len(value):
raise ValueError('attempt to assign sequence of size %s '
- 'to slice of size %s' % (len(name), len(keys)))
+ 'to slice of size %s' % (len(name), len(keys)))
# FIXME: efficiency? Would be better to calculate the indexes
# directly from the slice object
# NOTE: the new keys can collide with existing keys (or even
@@ -1112,23 +1205,46 @@ class Values(object):
self._main[self._main._sequence[index]] = value
### following methods pinched from UserList and adapted ###
- def __repr__(self): return repr(self._main.values())
+ def __repr__(self):
+ return repr(self._main.values())
# FIXME: do we need to check if we are comparing with another ``Values``
# object? (like the __cast method of UserList)
- def __lt__(self, other): return self._main.values() < other
- def __le__(self, other): return self._main.values() <= other
- def __eq__(self, other): return self._main.values() == other
- def __ne__(self, other): return self._main.values() != other
- def __gt__(self, other): return self._main.values() > other
- def __ge__(self, other): return self._main.values() >= other
- def __cmp__(self, other): return cmp(self._main.values(), other)
-
- def __contains__(self, item): return item in self._main.values()
- def __len__(self): return len(self._main._sequence) # easier :-)
- def __iter__(self): return self._main.itervalues()
- def count(self, item): return self._main.values().count(item)
- def index(self, item, *args): return self._main.values().index(item, *args)
+ def __lt__(self, other):
+ return self._main.values() < other
+
+ def __le__(self, other):
+ return self._main.values() <= other
+
+ def __eq__(self, other):
+ return self._main.values() == other
+
+ def __ne__(self, other):
+ return self._main.values() != other
+
+ def __gt__(self, other):
+ return self._main.values() > other
+
+ def __ge__(self, other):
+ return self._main.values() >= other
+
+ def __cmp__(self, other):
+ return cmp(self._main.values(), other)
+
+ def __contains__(self, item):
+ return item in self._main.values()
+
+ def __len__(self):
+ return len(self._main._sequence) # easier :-)
+
+ def __iter__(self):
+ return self._main.itervalues()
+
+ def count(self, item):
+ return self._main.values().count(item)
+
+ def index(self, item, *args):
+ return self._main.values().index(item, *args)
def reverse(self):
"""Reverse the values"""
@@ -1143,31 +1259,53 @@ class Values(object):
vals.sort(*args, **kwds)
self[:] = vals
- def __mul__(self, n): return self._main.values()*n
+ def __mul__(self, n):
+ return self._main.values() * n
__rmul__ = __mul__
- def __add__(self, other): return self._main.values() + other
- def __radd__(self, other): return other + self._main.values()
+
+ def __add__(self, other):
+ return self._main.values() + other
+
+ def __radd__(self, other):
+ return other + self._main.values()
## following methods not implemented for values ##
- def __delitem__(self, i): raise TypeError('Can\'t delete items from values')
- def __iadd__(self, other): raise TypeError('Can\'t add in place to values')
- def __imul__(self, n): raise TypeError('Can\'t multiply values in place')
- def append(self, item): raise TypeError('Can\'t append items to values')
- def insert(self, i, item): raise TypeError('Can\'t insert items into values')
- def pop(self, i=-1): raise TypeError('Can\'t pop items from values')
- def remove(self, item): raise TypeError('Can\'t remove items from values')
- def extend(self, other): raise TypeError('Can\'t extend values')
+ def __delitem__(self, i):
+ raise TypeError('Can\'t delete items from values')
+
+ def __iadd__(self, other):
+ raise TypeError('Can\'t add in place to values')
+
+ def __imul__(self, n):
+ raise TypeError('Can\'t multiply values in place')
+
+ def append(self, item):
+ raise TypeError('Can\'t append items to values')
+
+ def insert(self, i, item):
+ raise TypeError('Can\'t insert items into values')
+
+ def pop(self, i=-1):
+ raise TypeError('Can\'t pop items from values')
+
+ def remove(self, item):
+ raise TypeError('Can\'t remove items from values')
+
+ def extend(self, other):
+ raise TypeError('Can\'t extend values')
+
class SequenceOrderedDict(OrderedDict):
+
"""
Experimental version of OrderedDict that has a custom object for ``keys``,
``values``, and ``items``.
-
+
These are callable sequence objects that work as methods, or can be
manipulated directly as sequences.
-
+
Test for ``keys``, ``items`` and ``values``.
-
+
>>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
>>> d
SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
@@ -1287,7 +1425,7 @@ class SequenceOrderedDict(OrderedDict):
>>> d.values = (1, 2, 3)
>>> d
SequenceOrderedDict([(1, 1), (2, 2), (3, 3)])
-
+
>>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
>>> d
SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
@@ -1391,4 +1529,3 @@ if __name__ == '__main__':
'INTP_VER': INTP_VER,
})
doctest.testmod(m, globs=globs)
-
diff --git a/git/pack.py b/git/pack.py
index 09a2a1e3..7b675b10 100644
--- a/git/pack.py
+++ b/git/pack.py
@@ -4,31 +4,31 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Contains PackIndexFile and PackFile implementations"""
from git.exc import (
- BadObject,
- UnsupportedOperation,
- ParseError
- )
+ BadObject,
+ UnsupportedOperation,
+ ParseError
+)
from util import (
- zlib,
- mman,
- LazyMixin,
- unpack_from,
- bin_to_hex,
- )
+ zlib,
+ mman,
+ LazyMixin,
+ unpack_from,
+ bin_to_hex,
+)
from fun import (
- create_pack_object_header,
- pack_object_header_info,
- is_equal_canonical_sha,
- type_id_to_type_map,
- write_object,
- stream_copy,
- chunk_size,
- delta_types,
- OFS_DELTA,
- REF_DELTA,
- msb_size
- )
+ create_pack_object_header,
+ pack_object_header_info,
+ is_equal_canonical_sha,
+ type_id_to_type_map,
+ write_object,
+ stream_copy,
+ chunk_size,
+ delta_types,
+ OFS_DELTA,
+ REF_DELTA,
+ msb_size
+)
try:
from _perf import PackIndexFile_sha_to_index
@@ -44,19 +44,19 @@ from base import ( # Amazing !
ODeltaStream,
ODeltaPackInfo,
ODeltaPackStream,
- )
+)
from stream import (
- DecompressMemMapReader,
- DeltaApplyReader,
- Sha1Writer,
- NullStream,
- FlexibleSha1Writer
- )
+ DecompressMemMapReader,
+ DeltaApplyReader,
+ Sha1Writer,
+ NullStream,
+ FlexibleSha1Writer
+)
from struct import (
- pack,
- unpack,
- )
+ pack,
+ unpack,
+)
from binascii import crc32
@@ -68,10 +68,8 @@ import sys
__all__ = ('PackIndexFile', 'PackFile', 'PackEntity')
-
-
-#{ Utilities
+#{ Utilities
def pack_object_at(cursor, offset, as_stream):
"""
@@ -87,7 +85,7 @@ def pack_object_at(cursor, offset, as_stream):
type_id, uncomp_size, data_rela_offset = pack_object_header_info(data)
total_rela_offset = None # set later, actual offset until data stream begins
delta_info = None
-
+
# OFFSET DELTA
if type_id == OFS_DELTA:
i = data_rela_offset
@@ -104,14 +102,14 @@ def pack_object_at(cursor, offset, as_stream):
total_rela_offset = i
# REF DELTA
elif type_id == REF_DELTA:
- total_rela_offset = data_rela_offset+20
+ total_rela_offset = data_rela_offset + 20
delta_info = data[data_rela_offset:total_rela_offset]
# BASE OBJECT
else:
# assume its a base object
total_rela_offset = data_rela_offset
# END handle type id
-
+
abs_data_offset = offset + total_rela_offset
if as_stream:
stream = DecompressMemMapReader(buffer(data, total_rela_offset), False, uncomp_size)
@@ -127,6 +125,7 @@ def pack_object_at(cursor, offset, as_stream):
# END handle info
# END handle stream
+
def write_stream_to_pack(read, write, zstream, base_crc=None):
"""Copy a stream as read from read function, zip it, and write the result.
Count the number of written bytes and return it
@@ -140,30 +139,30 @@ def write_stream_to_pack(read, write, zstream, base_crc=None):
crc = 0
if want_crc:
crc = base_crc
- #END initialize crc
-
+ # END initialize crc
+
while True:
chunk = read(chunk_size)
br += len(chunk)
compressed = zstream.compress(chunk)
bw += len(compressed)
write(compressed) # cannot assume return value
-
+
if want_crc:
crc = crc32(compressed, crc)
- #END handle crc
-
+ # END handle crc
+
if len(chunk) != chunk_size:
break
- #END copy loop
-
+ # END copy loop
+
compressed = zstream.flush()
bw += len(compressed)
write(compressed)
if want_crc:
crc = crc32(compressed, crc)
- #END handle crc
-
+ # END handle crc
+
return (br, bw, crc)
@@ -171,83 +170,84 @@ def write_stream_to_pack(read, write, zstream, base_crc=None):
class IndexWriter(object):
+
"""Utility to cache index information, allowing to write all information later
in one go to the given stream
:note: currently only writes v2 indices"""
__slots__ = '_objs'
-
+
def __init__(self):
self._objs = list()
-
+
def append(self, binsha, crc, offset):
"""Append one piece of object information"""
self._objs.append((binsha, crc, offset))
-
+
def write(self, pack_sha, write):
"""Write the index file using the given write method
:param pack_sha: binary sha over the whole pack that we index
:return: sha1 binary sha over all index file contents"""
# sort for sha1 hash
self._objs.sort(key=lambda o: o[0])
-
+
sha_writer = FlexibleSha1Writer(write)
sha_write = sha_writer.write
sha_write(PackIndexFile.index_v2_signature)
sha_write(pack(">L", PackIndexFile.index_version_default))
-
+
# fanout
- tmplist = list((0,)*256) # fanout or list with 64 bit offsets
+ tmplist = list((0,) * 256) # fanout or list with 64 bit offsets
for t in self._objs:
tmplist[ord(t[0][0])] += 1
- #END prepare fanout
+ # END prepare fanout
for i in xrange(255):
v = tmplist[i]
sha_write(pack('>L', v))
- tmplist[i+1] += v
- #END write each fanout entry
+ tmplist[i + 1] += v
+ # END write each fanout entry
sha_write(pack('>L', tmplist[255]))
-
+
# sha1 ordered
# save calls, that is push them into c
sha_write(''.join(t[0] for t in self._objs))
-
+
# crc32
for t in self._objs:
- sha_write(pack('>L', t[1]&0xffffffff))
- #END for each crc
-
+ sha_write(pack('>L', t[1] & 0xffffffff))
+ # END for each crc
+
tmplist = list()
# offset 32
for t in self._objs:
ofs = t[2]
if ofs > 0x7fffffff:
tmplist.append(ofs)
- ofs = 0x80000000 + len(tmplist)-1
- #END hande 64 bit offsets
- sha_write(pack('>L', ofs&0xffffffff))
- #END for each offset
-
+ ofs = 0x80000000 + len(tmplist) - 1
+ # END hande 64 bit offsets
+ sha_write(pack('>L', ofs & 0xffffffff))
+ # END for each offset
+
# offset 64
for ofs in tmplist:
sha_write(pack(">Q", ofs))
- #END for each offset
-
+ # END for each offset
+
# trailer
assert(len(pack_sha) == 20)
sha_write(pack_sha)
sha = sha_writer.sha(as_hex=False)
write(sha)
return sha
-
-
+
class PackIndexFile(LazyMixin):
+
"""A pack index provides offsets into the corresponding pack, allowing to find
locations for offsets faster."""
-
+
# Dont use slots as we dynamically bind functions for each version, need a dict for this
# The slots you see here are just to keep track of our instance variables
- # __slots__ = ('_indexpath', '_fanout_table', '_cursor', '_version',
+ # __slots__ = ('_indexpath', '_fanout_table', '_cursor', '_version',
# '_sha_list_offset', '_crc_list_offset', '_pack_offset', '_pack_64_offset')
# used in v2 indices
@@ -258,7 +258,7 @@ class PackIndexFile(LazyMixin):
def __init__(self, indexpath):
super(PackIndexFile, self).__init__()
self._indexpath = indexpath
-
+
def _set_cache_(self, attr):
if attr == "_packfile_checksum":
self._packfile_checksum = self._cursor.map()[-40:-20]
@@ -271,128 +271,127 @@ class PackIndexFile(LazyMixin):
self._cursor = mman.make_cursor(self._indexpath).use_region()
# We will assume that the index will always fully fit into memory !
if mman.window_size() > 0 and self._cursor.file_size() > mman.window_size():
- raise AssertionError("The index file at %s is too large to fit into a mapped window (%i > %i). This is a limitation of the implementation" % (self._indexpath, self._cursor.file_size(), mman.window_size()))
- #END assert window size
+ raise AssertionError("The index file at %s is too large to fit into a mapped window (%i > %i). This is a limitation of the implementation" % (
+ self._indexpath, self._cursor.file_size(), mman.window_size()))
+ # END assert window size
else:
# now its time to initialize everything - if we are here, someone wants
# to access the fanout table or related properties
-
+
# CHECK VERSION
mmap = self._cursor.map()
self._version = (mmap[:4] == self.index_v2_signature and 2) or 1
if self._version == 2:
- version_id = unpack_from(">L", mmap, 4)[0]
+ version_id = unpack_from(">L", mmap, 4)[0]
assert version_id == self._version, "Unsupported index version: %i" % version_id
# END assert version
-
+
# SETUP FUNCTIONS
# setup our functions according to the actual version
for fname in ('entry', 'offset', 'sha', 'crc'):
setattr(self, fname, getattr(self, "_%s_v%i" % (fname, self._version)))
# END for each function to initialize
-
-
+
# INITIALIZE DATA
# byte offset is 8 if version is 2, 0 otherwise
self._initialize()
# END handle attributes
-
#{ Access V1
-
+
def _entry_v1(self, i):
""":return: tuple(offset, binsha, 0)"""
- return unpack_from(">L20s", self._cursor.map(), 1024 + i*24) + (0, )
-
+ return unpack_from(">L20s", self._cursor.map(), 1024 + i * 24) + (0, )
+
def _offset_v1(self, i):
"""see ``_offset_v2``"""
- return unpack_from(">L", self._cursor.map(), 1024 + i*24)[0]
-
+ return unpack_from(">L", self._cursor.map(), 1024 + i * 24)[0]
+
def _sha_v1(self, i):
"""see ``_sha_v2``"""
- base = 1024 + (i*24)+4
- return self._cursor.map()[base:base+20]
-
+ base = 1024 + (i * 24) + 4
+ return self._cursor.map()[base:base + 20]
+
def _crc_v1(self, i):
"""unsupported"""
return 0
-
+
#} END access V1
-
+
#{ Access V2
def _entry_v2(self, i):
""":return: tuple(offset, binsha, crc)"""
return (self._offset_v2(i), self._sha_v2(i), self._crc_v2(i))
-
+
def _offset_v2(self, i):
""":return: 32 or 64 byte offset into pack files. 64 byte offsets will only
be returned if the pack is larger than 4 GiB, or 2^32"""
offset = unpack_from(">L", self._cursor.map(), self._pack_offset + i * 4)[0]
-
+
# if the high-bit is set, this indicates that we have to lookup the offset
# in the 64 bit region of the file. The current offset ( lower 31 bits )
# are the index into it
if offset & 0x80000000:
offset = unpack_from(">Q", self._cursor.map(), self._pack_64_offset + (offset & ~0x80000000) * 8)[0]
# END handle 64 bit offset
-
+
return offset
-
+
def _sha_v2(self, i):
""":return: sha at the given index of this file index instance"""
base = self._sha_list_offset + i * 20
- return self._cursor.map()[base:base+20]
-
+ return self._cursor.map()[base:base + 20]
+
def _crc_v2(self, i):
""":return: 4 bytes crc for the object at index i"""
- return unpack_from(">L", self._cursor.map(), self._crc_list_offset + i * 4)[0]
-
+ return unpack_from(">L", self._cursor.map(), self._crc_list_offset + i * 4)[0]
+
#} END access V2
-
+
#{ Initialization
-
+
def _initialize(self):
"""initialize base data"""
self._fanout_table = self._read_fanout((self._version == 2) * 8)
-
+
if self._version == 2:
self._crc_list_offset = self._sha_list_offset + self.size() * 20
self._pack_offset = self._crc_list_offset + self.size() * 4
self._pack_64_offset = self._pack_offset + self.size() * 4
# END setup base
-
+
def _read_fanout(self, byte_offset):
"""Generate a fanout table from our data"""
d = self._cursor.map()
out = list()
append = out.append
for i in range(256):
- append(unpack_from('>L', d, byte_offset + i*4)[0])
+ append(unpack_from('>L', d, byte_offset + i * 4)[0])
# END for each entry
return out
-
+
#} END initialization
-
+
#{ Properties
def version(self):
return self._version
-
+
def size(self):
""":return: amount of objects referred to by this index"""
return self._fanout_table[255]
-
+
def path(self):
""":return: path to the packindexfile"""
return self._indexpath
-
+
def packfile_checksum(self):
""":return: 20 byte sha representing the sha1 hash of the pack file"""
return self._cursor.map()[-40:-20]
-
+
def indexfile_checksum(self):
""":return: 20 byte sha representing the sha1 hash of this index file"""
return self._cursor.map()[-20:]
-
+
def offsets(self):
""":return: sequence of all offsets in the order in which they were written
:note: return value can be random accessed, but may be immmutable"""
@@ -400,7 +399,7 @@ class PackIndexFile(LazyMixin):
# read stream to array, convert to tuple
a = array.array('I') # 4 byte unsigned int, long are 8 byte on 64 bit it appears
a.fromstring(buffer(self._cursor.map(), self._pack_offset, self._pack_64_offset - self._pack_offset))
-
+
# networkbyteorder to something array likes more
if sys.byteorder == 'little':
a.byteswap()
@@ -408,7 +407,7 @@ class PackIndexFile(LazyMixin):
else:
return tuple(self.offset(index) for index in xrange(self.size()))
# END handle version
-
+
def sha_to_index(self, sha):
"""
:return: index usable with the ``offset`` or ``entry`` method, or None
@@ -418,9 +417,9 @@ class PackIndexFile(LazyMixin):
get_sha = self.sha
lo = 0 # lower index, the left bound of the bisection
if first_byte != 0:
- lo = self._fanout_table[first_byte-1]
+ lo = self._fanout_table[first_byte - 1]
hi = self._fanout_table[first_byte] # the upper, right bound of the bisection
-
+
# bisect until we have the sha
while lo < hi:
mid = (lo + hi) / 2
@@ -434,7 +433,7 @@ class PackIndexFile(LazyMixin):
# END handle midpoint
# END bisect
return None
-
+
def partial_sha_to_index(self, partial_bin_sha, canonical_length):
"""
:return: index as in `sha_to_index` or None if the sha was not found in this
@@ -445,18 +444,18 @@ class PackIndexFile(LazyMixin):
:raise AmbiguousObjectName:"""
if len(partial_bin_sha) < 2:
raise ValueError("Require at least 2 bytes of partial sha")
-
+
first_byte = ord(partial_bin_sha[0])
get_sha = self.sha
lo = 0 # lower index, the left bound of the bisection
if first_byte != 0:
- lo = self._fanout_table[first_byte-1]
+ lo = self._fanout_table[first_byte - 1]
hi = self._fanout_table[first_byte] # the upper, right bound of the bisection
-
+
# fill the partial to full 20 bytes
- filled_sha = partial_bin_sha + '\0'*(20 - len(partial_bin_sha))
-
- # find lowest
+ filled_sha = partial_bin_sha + '\0' * (20 - len(partial_bin_sha))
+
+ # find lowest
while lo < hi:
mid = (lo + hi) / 2
c = cmp(filled_sha, get_sha(mid))
@@ -470,99 +469,99 @@ class PackIndexFile(LazyMixin):
lo = mid + 1
# END handle midpoint
# END bisect
-
+
if lo < self.size():
cur_sha = get_sha(lo)
if is_equal_canonical_sha(canonical_length, partial_bin_sha, cur_sha):
next_sha = None
- if lo+1 < self.size():
- next_sha = get_sha(lo+1)
+ if lo + 1 < self.size():
+ next_sha = get_sha(lo + 1)
if next_sha and next_sha == cur_sha:
raise AmbiguousObjectName(partial_bin_sha)
return lo
# END if we have a match
# END if we found something
return None
-
+
if 'PackIndexFile_sha_to_index' in globals():
- # NOTE: Its just about 25% faster, the major bottleneck might be the attr
+ # NOTE: Its just about 25% faster, the major bottleneck might be the attr
# accesses
def sha_to_index(self, sha):
return PackIndexFile_sha_to_index(self, sha)
- # END redefine heavy-hitter with c version
-
+ # END redefine heavy-hitter with c version
+
#} END properties
-
-
+
+
class PackFile(LazyMixin):
+
"""A pack is a file written according to the Version 2 for git packs
-
+
As we currently use memory maps, it could be assumed that the maximum size of
packs therefor is 32 bit on 32 bit systems. On 64 bit systems, this should be
fine though.
-
+
:note: at some point, this might be implemented using streams as well, or
streams are an alternate path in the case memory maps cannot be created
for some reason - one clearly doesn't want to read 10GB at once in that
case"""
-
+
__slots__ = ('_packpath', '_cursor', '_size', '_version')
pack_signature = 0x5041434b # 'PACK'
pack_version_default = 2
-
+
# offset into our data at which the first object starts
- first_object_offset = 3*4 # header bytes
+ first_object_offset = 3 * 4 # header bytes
footer_size = 20 # final sha
-
+
def __init__(self, packpath):
self._packpath = packpath
-
+
def _set_cache_(self, attr):
# we fill the whole cache, whichever attribute gets queried first
self._cursor = mman.make_cursor(self._packpath).use_region()
-
+
# read the header information
type_id, self._version, self._size = unpack_from(">LLL", self._cursor.map(), 0)
-
+
# TODO: figure out whether we should better keep the lock, or maybe
# add a .keep file instead ?
if type_id != self.pack_signature:
raise ParseError("Invalid pack signature: %i" % type_id)
-
+
def _iter_objects(self, start_offset, as_stream=True):
"""Handle the actual iteration of objects within this pack"""
c = self._cursor
content_size = c.file_size() - self.footer_size
cur_offset = start_offset or self.first_object_offset
-
+
null = NullStream()
while cur_offset < content_size:
data_offset, ostream = pack_object_at(c, cur_offset, True)
# scrub the stream to the end - this decompresses the object, but yields
# the amount of compressed bytes we need to get to the next offset
-
+
stream_copy(ostream.read, null.write, ostream.size, chunk_size)
cur_offset += (data_offset - ostream.pack_offset) + ostream.stream.compressed_bytes_read()
-
-
+
# if a stream is requested, reset it beforehand
- # Otherwise return the Stream object directly, its derived from the
+ # Otherwise return the Stream object directly, its derived from the
# info object
if as_stream:
ostream.stream.seek(0)
yield ostream
# END until we have read everything
-
+
#{ Pack Information
-
+
def size(self):
- """:return: The amount of objects stored in this pack"""
+ """:return: The amount of objects stored in this pack"""
return self._size
-
+
def version(self):
""":return: the version of this pack"""
return self._version
-
+
def data(self):
"""
:return: read-only data of this pack. It provides random access and usually
@@ -570,18 +569,18 @@ class PackFile(LazyMixin):
:note: This method is unsafe as it returns a window into a file which might be larger than than the actual window size"""
# can use map as we are starting at offset 0. Otherwise we would have to use buffer()
return self._cursor.use_region().map()
-
+
def checksum(self):
""":return: 20 byte sha1 hash on all object sha's contained in this file"""
- return self._cursor.use_region(self._cursor.file_size()-20).buffer()[:]
-
+ return self._cursor.use_region(self._cursor.file_size() - 20).buffer()[:]
+
def path(self):
""":return: path to the packfile"""
return self._packpath
#} END pack information
-
+
#{ Pack Specific
-
+
def collect_streams(self, offset):
"""
:return: list of pack streams which are required to build the object
@@ -600,7 +599,7 @@ class PackFile(LazyMixin):
offset = ostream.pack_offset - ostream.delta_info
else:
# the only thing we can lookup are OFFSET deltas. Everything
- # else is either an object, or a ref delta, in the latter
+ # else is either an object, or a ref delta, in the latter
# case someone else has to find it
break
# END handle type
@@ -608,23 +607,23 @@ class PackFile(LazyMixin):
return out
#} END pack specific
-
+
#{ Read-Database like Interface
-
+
def info(self, offset):
"""Retrieve information about the object at the given file-absolute offset
-
+
:param offset: byte offset
:return: OPackInfo instance, the actual type differs depending on the type_id attribute"""
return pack_object_at(self._cursor, offset or self.first_object_offset, False)[1]
-
+
def stream(self, offset):
"""Retrieve an object at the given file-relative offset as stream along with its information
-
+
:param offset: byte offset
:return: OPackStream instance, the actual type differs depending on the type_id attribute"""
return pack_object_at(self._cursor, offset or self.first_object_offset, True)[1]
-
+
def stream_iter(self, start_offset=0):
"""
:return: iterator yielding OPackStream compatible instances, allowing
@@ -634,28 +633,29 @@ class PackFile(LazyMixin):
:note: Iterating a pack directly is costly as the datastream has to be decompressed
to determine the bounds between the objects"""
return self._iter_objects(start_offset, as_stream=True)
-
+
#} END Read-Database like Interface
-
-
+
+
class PackEntity(LazyMixin):
+
"""Combines the PackIndexFile and the PackFile into one, allowing the
actual objects to be resolved and iterated"""
-
- __slots__ = ( '_index', # our index file
- '_pack', # our pack file
- '_offset_map' # on demand dict mapping one offset to the next consecutive one
- )
-
+
+ __slots__ = ('_index', # our index file
+ '_pack', # our pack file
+ '_offset_map' # on demand dict mapping one offset to the next consecutive one
+ )
+
IndexFileCls = PackIndexFile
PackFileCls = PackFile
-
+
def __init__(self, pack_or_index_path):
"""Initialize ourselves with the path to the respective pack or index file"""
basename, ext = os.path.splitext(pack_or_index_path)
self._index = self.IndexFileCls("%s.idx" % basename) # PackIndexFile instance
self._pack = self.PackFileCls("%s.pack" % basename) # corresponding PackFile instance
-
+
def _set_cache_(self, attr):
# currently this can only be _offset_map
# TODO: make this a simple sorted offset array which can be bisected
@@ -664,30 +664,30 @@ class PackEntity(LazyMixin):
offsets_sorted = sorted(self._index.offsets())
last_offset = len(self._pack.data()) - self._pack.footer_size
assert offsets_sorted, "Cannot handle empty indices"
-
+
offset_map = None
if len(offsets_sorted) == 1:
- offset_map = { offsets_sorted[0] : last_offset }
+ offset_map = {offsets_sorted[0]: last_offset}
else:
iter_offsets = iter(offsets_sorted)
iter_offsets_plus_one = iter(offsets_sorted)
iter_offsets_plus_one.next()
consecutive = izip(iter_offsets, iter_offsets_plus_one)
-
+
offset_map = dict(consecutive)
-
+
# the last offset is not yet set
offset_map[offsets_sorted[-1]] = last_offset
# END handle offset amount
self._offset_map = offset_map
-
+
def _sha_to_index(self, sha):
""":return: index for the given sha, or raise"""
index = self._index.sha_to_index(sha)
if index is None:
raise BadObject(sha)
return index
-
+
def _iter_objects(self, as_stream):
"""Iterate over all objects in our index and yield their OInfo or OStream instences"""
_sha = self._index.sha
@@ -695,7 +695,7 @@ class PackEntity(LazyMixin):
for index in xrange(self._index.size()):
yield _object(_sha(index), as_stream, index)
# END for each index
-
+
def _object(self, sha, as_stream, index=-1):
""":return: OInfo or OStream object providing information about the given sha
:param index: if not -1, its assumed to be the sha's index in the IndexFile"""
@@ -712,45 +712,45 @@ class PackEntity(LazyMixin):
packstream = self._pack.stream(offset)
return OStream(sha, packstream.type, packstream.size, packstream.stream)
# END handle non-deltas
-
+
# produce a delta stream containing all info
- # To prevent it from applying the deltas when querying the size,
+ # To prevent it from applying the deltas when querying the size,
# we extract it from the delta stream ourselves
streams = self.collect_streams_at_offset(offset)
dstream = DeltaApplyReader.new(streams)
-
- return ODeltaStream(sha, dstream.type, None, dstream)
+
+ return ODeltaStream(sha, dstream.type, None, dstream)
else:
if type_id not in delta_types:
return OInfo(sha, type_id_to_type_map[type_id], uncomp_size)
# END handle non-deltas
-
+
# deltas are a little tougher - unpack the first bytes to obtain
# the actual target size, as opposed to the size of the delta data
streams = self.collect_streams_at_offset(offset)
buf = streams[0].read(512)
offset, src_size = msb_size(buf)
offset, target_size = msb_size(buf, offset)
-
+
# collect the streams to obtain the actual object type
if streams[-1].type_id in delta_types:
raise BadObject(sha, "Could not resolve delta object")
- return OInfo(sha, streams[-1].type, target_size)
+ return OInfo(sha, streams[-1].type, target_size)
# END handle stream
-
+
#{ Read-Database like Interface
-
+
def info(self, sha):
"""Retrieve information about the object identified by the given sha
-
+
:param sha: 20 byte sha1
:raise BadObject:
:return: OInfo instance, with 20 byte sha"""
return self._object(sha, False)
-
+
def stream(self, sha):
"""Retrieve an object stream along with its information as identified by the given sha
-
+
:param sha: 20 byte sha1
:raise BadObject:
:return: OStream instance, with 20 byte sha"""
@@ -759,28 +759,28 @@ class PackEntity(LazyMixin):
def info_at_index(self, index):
"""As ``info``, but uses a PackIndexFile compatible index to refer to the object"""
return self._object(None, False, index)
-
+
def stream_at_index(self, index):
"""As ``stream``, but uses a PackIndexFile compatible index to refer to the
object"""
return self._object(None, True, index)
-
+
#} END Read-Database like Interface
-
- #{ Interface
+
+ #{ Interface
def pack(self):
""":return: the underlying pack file instance"""
return self._pack
-
+
def index(self):
""":return: the underlying pack index file instance"""
return self._index
-
+
def is_valid_stream(self, sha, use_crc=False):
"""
Verify that the stream at the given sha is valid.
-
+
:param use_crc: if True, the index' crc is run over the compressed stream of
the object, which is much faster than checking the sha1. It is also
more prone to unnoticed corruption or manipulation.
@@ -791,7 +791,7 @@ class PackEntity(LazyMixin):
just this stream.
If False, the object will be decompressed and the sha generated. It must
match the given sha
-
+
:return: True if the stream is valid
:raise UnsupportedOperation: If the index is version 1 only
:raise BadObject: sha was not found"""
@@ -799,12 +799,12 @@ class PackEntity(LazyMixin):
if self._index.version() < 2:
raise UnsupportedOperation("Version 1 indices do not contain crc's, verify by sha instead")
# END handle index version
-
+
index = self._sha_to_index(sha)
offset = self._index.offset(index)
next_offset = self._offset_map[offset]
crc_value = self._index.crc(index)
-
+
# create the current crc value, on the compressed object data
# Read it in chunks, without copying the data
crc_update = zlib.crc32
@@ -817,7 +817,7 @@ class PackEntity(LazyMixin):
this_crc_value = crc_update(buffer(pack_data, cur_pos, size), this_crc_value)
cur_pos += size
# END window size loop
-
+
# crc returns signed 32 bit numbers, the AND op forces it into unsigned
# mode ... wow, sneaky, from dulwich.
return (this_crc_value & 0xffffffff) == crc_value
@@ -826,7 +826,7 @@ class PackEntity(LazyMixin):
stream = self._object(sha, as_stream=True)
# write a loose object, which is the basis for the sha
write_object(stream.type, stream.size, stream.read, shawriter.write)
-
+
assert shawriter.sha(as_hex=False) == sha
return shawriter.sha(as_hex=False) == sha
# END handle crc/sha verification
@@ -837,21 +837,21 @@ class PackEntity(LazyMixin):
:return: Iterator over all objects in this pack. The iterator yields
OInfo instances"""
return self._iter_objects(as_stream=False)
-
+
def stream_iter(self):
"""
:return: iterator over all objects in this pack. The iterator yields
OStream instances"""
return self._iter_objects(as_stream=True)
-
+
def collect_streams_at_offset(self, offset):
"""
As the version in the PackFile, but can resolve REF deltas within this pack
For more info, see ``collect_streams``
-
+
:param offset: offset into the pack file at which the object can be found"""
streams = self._pack.collect_streams(offset)
-
+
# try to resolve the last one if needed. It is assumed to be either
# a REF delta, or a base object, as OFFSET deltas are resolved by the pack
if streams[-1].type_id == REF_DELTA:
@@ -864,17 +864,17 @@ class PackEntity(LazyMixin):
stream = self._pack.stream(self._index.offset(sindex))
streams.append(stream)
else:
- # must be another OFS DELTA - this could happen if a REF
- # delta we resolve previously points to an OFS delta. Who
+ # must be another OFS DELTA - this could happen if a REF
+ # delta we resolve previously points to an OFS delta. Who
# would do that ;) ? We can handle it though
stream = self._pack.stream(stream.delta_info)
streams.append(stream)
# END handle ref delta
# END resolve ref streams
# END resolve streams
-
+
return streams
-
+
def collect_streams(self, sha):
"""
As ``PackFile.collect_streams``, but takes a sha instead of an offset.
@@ -882,22 +882,21 @@ class PackEntity(LazyMixin):
If this is not possible, the stream will be left alone, hence it is adivsed
to check for unresolved ref-deltas and resolve them before attempting to
construct a delta stream.
-
+
:param sha: 20 byte sha1 specifying the object whose related streams you want to collect
:return: list of streams, first being the actual object delta, the last being
a possibly unresolved base object.
:raise BadObject:"""
return self.collect_streams_at_offset(self._index.offset(self._sha_to_index(sha)))
-
-
+
@classmethod
- def write_pack(cls, object_iter, pack_write, index_write=None,
- object_count = None, zlib_compression = zlib.Z_BEST_SPEED):
+ def write_pack(cls, object_iter, pack_write, index_write=None,
+ object_count=None, zlib_compression=zlib.Z_BEST_SPEED):
"""
Create a new pack by putting all objects obtained by the object_iterator
into a pack which is written using the pack_write method.
The respective index is produced as well if index_write is not Non.
-
+
:param object_iter: iterator yielding odb output objects
:param pack_write: function to receive strings to write into the pack stream
:param indx_write: if not None, the function writes the index file corresponding
@@ -915,72 +914,73 @@ class PackEntity(LazyMixin):
if not object_count:
if not isinstance(object_iter, (tuple, list)):
objs = list(object_iter)
- #END handle list type
+ # END handle list type
object_count = len(objs)
- #END handle object
-
+ # END handle object
+
pack_writer = FlexibleSha1Writer(pack_write)
pwrite = pack_writer.write
ofs = 0 # current offset into the pack file
index = None
wants_index = index_write is not None
-
+
# write header
pwrite(pack('>LLL', PackFile.pack_signature, PackFile.pack_version_default, object_count))
ofs += 12
-
+
if wants_index:
index = IndexWriter()
- #END handle index header
-
+ # END handle index header
+
actual_count = 0
for obj in objs:
actual_count += 1
crc = 0
-
+
# object header
hdr = create_pack_object_header(obj.type_id, obj.size)
if index_write:
crc = crc32(hdr)
else:
crc = None
- #END handle crc
+ # END handle crc
pwrite(hdr)
-
+
# data stream
zstream = zlib.compressobj(zlib_compression)
ostream = obj.stream
- br, bw, crc = write_stream_to_pack(ostream.read, pwrite, zstream, base_crc = crc)
+ br, bw, crc = write_stream_to_pack(ostream.read, pwrite, zstream, base_crc=crc)
assert(br == obj.size)
if wants_index:
index.append(obj.binsha, crc, ofs)
- #END handle index
-
+ # END handle index
+
ofs += len(hdr) + bw
if actual_count == object_count:
break
- #END abort once we are done
- #END for each object
-
+ # END abort once we are done
+ # END for each object
+
if actual_count != object_count:
- raise ValueError("Expected to write %i objects into pack, but received only %i from iterators" % (object_count, actual_count))
- #END count assertion
-
+ raise ValueError(
+ "Expected to write %i objects into pack, but received only %i from iterators" % (object_count, actual_count))
+ # END count assertion
+
# write footer
- pack_sha = pack_writer.sha(as_hex = False)
+ pack_sha = pack_writer.sha(as_hex=False)
assert len(pack_sha) == 20
pack_write(pack_sha)
ofs += len(pack_sha) # just for completeness ;)
-
+
index_sha = None
if wants_index:
index_sha = index.write(pack_sha, index_write)
- #END handle index
-
+ # END handle index
+
return pack_sha, index_sha
-
+
@classmethod
- def create(cls, object_iter, base_dir, object_count = None, zlib_compression = zlib.Z_BEST_SPEED):
+ def create(cls, object_iter, base_dir, object_count=None, zlib_compression=zlib.Z_BEST_SPEED):
"""Create a new on-disk entity comprised of a properly named pack file and a properly named
and corresponding index file. The pack contains all OStream objects contained in object iter.
:param base_dir: directory which is to contain the files
@@ -990,18 +990,17 @@ class PackEntity(LazyMixin):
index_fd, index_path = tempfile.mkstemp('', 'index', base_dir)
pack_write = lambda d: os.write(pack_fd, d)
index_write = lambda d: os.write(index_fd, d)
-
+
pack_binsha, index_binsha = cls.write_pack(object_iter, pack_write, index_write, object_count, zlib_compression)
os.close(pack_fd)
os.close(index_fd)
-
+
fmt = "pack-%s.%s"
new_pack_path = os.path.join(base_dir, fmt % (bin_to_hex(pack_binsha), 'pack'))
new_index_path = os.path.join(base_dir, fmt % (bin_to_hex(pack_binsha), 'idx'))
os.rename(pack_path, new_pack_path)
os.rename(index_path, new_index_path)
-
+
return cls(new_pack_path)
-
-
+
#} END interface
diff --git a/git/refs/__init__.py b/git/refs/__init__.py
index 97017d5f..e5fb0868 100644
--- a/git/refs/__init__.py
+++ b/git/refs/__init__.py
@@ -15,7 +15,7 @@ del(headref)
import symbolic
for item in (HEAD, Head, RemoteReference, TagReference, Reference):
- setattr(symbolic.SymbolicReference, item.__name__+'Cls', item)
+ setattr(symbolic.SymbolicReference, item.__name__ + 'Cls', item)
del(symbolic)
diff --git a/git/refs/head.py b/git/refs/head.py
index dc44c921..686663ae 100644
--- a/git/refs/head.py
+++ b/git/refs/head.py
@@ -4,76 +4,77 @@ from git.exc import GitCommandError
__all__ = ["HEAD"]
-
+
class HEAD(SymbolicReference):
+
"""Provides additional functionality using the git command"""
__slots__ = tuple()
-
+
_HEAD_NAME = 'HEAD'
_ORIG_HEAD_NAME = 'ORIG_HEAD'
__slots__ = tuple()
-
+
def __init__(self, repo, path=_HEAD_NAME):
if path != self._HEAD_NAME:
raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
super(HEAD, self).__init__(repo, path)
-
+
def orig_head(self):
"""
:return: SymbolicReference pointing at the ORIG_HEAD, which is maintained
to contain the previous value of HEAD"""
return SymbolicReference(self.repo, self._ORIG_HEAD_NAME)
-
- def reset(self, commit='HEAD', index=True, working_tree = False,
- paths=None, **kwargs):
+
+ def reset(self, commit='HEAD', index=True, working_tree=False,
+ paths=None, **kwargs):
"""Reset our HEAD to the given commit optionally synchronizing
the index and working tree. The reference we refer to will be set to
commit as well.
-
+
:param commit:
Commit object, Reference Object or string identifying a revision we
should reset HEAD to.
-
+
:param index:
If True, the index will be set to match the given commit. Otherwise
it will not be touched.
-
+
:param working_tree:
If True, the working tree will be forcefully adjusted to match the given
commit, possibly overwriting uncommitted changes without warning.
If working_tree is True, index must be true as well
-
+
:param paths:
Single path or list of paths relative to the git root directory
that are to be reset. This allows to partially reset individual files.
-
+
:param kwargs:
Additional arguments passed to git-reset.
-
+
:return: self"""
mode = "--soft"
add_arg = None
if index:
mode = "--mixed"
-
+
# it appears, some git-versions declare mixed and paths deprecated
# see http://github.com/Byron/GitPython/issues#issue/2
if paths:
mode = None
# END special case
# END handle index
-
+
if working_tree:
mode = "--hard"
if not index:
- raise ValueError( "Cannot reset the working tree if the index is not reset as well")
-
+ raise ValueError("Cannot reset the working tree if the index is not reset as well")
+
# END working tree handling
-
+
if paths:
add_arg = "--"
# END nicely separate paths from rest
-
+
try:
self.repo.git.reset(mode, commit, add_arg, paths, **kwargs)
except GitCommandError, e:
@@ -82,6 +83,5 @@ class HEAD(SymbolicReference):
if e.status != 1:
raise
# END handle exception
-
+
return self
-
diff --git a/git/refs/headref.py b/git/refs/headref.py
index 843e897f..c3941d92 100644
--- a/git/refs/headref.py
+++ b/git/refs/headref.py
@@ -4,9 +4,11 @@ from git.util import join_path
__all__ = ["Head"]
+
class Head(Reference):
+
"""The GitPyhton Head implementation provides more git-command based features
-
+
A Head is a named reference to a Commit. Every Head instance contains a name
and a Commit object.
@@ -24,28 +26,28 @@ class Head(Reference):
>>> head.commit.hexsha
'1c09f116cbc2cb4100fb6935bb162daa4723f455'"""
__slots__ = tuple()
-
+
_common_path_default = "refs/heads"
k_config_remote = "remote"
k_config_remote_ref = "merge" # branch to merge from remote
-
+
# will be set by init method !
RemoteReferenceCls = None
-
+
#{ Configuration
-
+
def set_tracking_branch(self, remote_reference):
"""
Configure this branch to track the given remote reference. This will alter
this branch's configuration accordingly.
-
+
:param remote_reference: The remote reference to track or None to untrack
any references
:return: self"""
if remote_reference is not None and not isinstance(remote_reference, self.RemoteReferenceCls):
raise ValueError("Incorrect parameter type: %r" % remote_reference)
# END handle type
-
+
writer = self.config_writer()
if remote_reference is None:
writer.remove_option(self.k_config_remote)
@@ -57,9 +59,9 @@ class Head(Reference):
writer.set_value(self.k_config_remote, remote_reference.remote_name)
writer.set_value(self.k_config_remote_ref, Head.to_full_path(remote_reference.remote_head))
# END handle ref value
-
+
return self
-
+
def tracking_branch(self):
"""
:return: The remote_reference we are tracking, or None if we are
@@ -67,39 +69,39 @@ class Head(Reference):
reader = self.config_reader()
if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref):
ref = Head(self.repo, Head.to_full_path(reader.get_value(self.k_config_remote_ref)))
- remote_refpath = self.RemoteReferenceCls.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name))
+ remote_refpath = self.RemoteReferenceCls.to_full_path(
+ join_path(reader.get_value(self.k_config_remote), ref.name))
return self.RemoteReferenceCls(self.repo, remote_refpath)
# END handle have tracking branch
-
+
# we are not a tracking branch
return None
-
-
+
#{ Configruation
-
+
def _config_parser(self, read_only):
if read_only:
parser = self.repo.config_reader()
else:
parser = self.repo.config_writer()
# END handle parser instance
-
+
return SectionConstraint(parser, 'branch "%s"' % self.name)
-
+
def config_reader(self):
"""
:return: A configuration parser instance constrained to only read
this instance's values"""
return self._config_parser(read_only=True)
-
+
def config_writer(self):
"""
:return: A configuration writer instance with read-and write acccess
to options of this head"""
return self._config_parser(read_only=False)
-
+
#} END configuration
-
+
@classmethod
def delete(cls, repo, *heads, **kwargs):
"""Delete the given heads
@@ -112,48 +114,47 @@ class Head(Reference):
if force:
flag = "-D"
repo.git.branch(flag, *heads)
-
-
+
def rename(self, new_path, force=False):
"""Rename self to a new path
-
+
:param new_path:
Either a simple name or a path, i.e. new_name or features/new_name.
The prefix refs/heads is implied
-
+
:param force:
If True, the rename will succeed even if a head with the target name
already exists.
-
+
:return: self
:note: respects the ref log as git commands are used"""
flag = "-m"
if force:
flag = "-M"
-
+
self.repo.git.branch(flag, self, new_path)
- self.path = "%s/%s" % (self._common_path_default, new_path)
+ self.path = "%s/%s" % (self._common_path_default, new_path)
return self
-
+
def checkout(self, force=False, **kwargs):
"""Checkout this head by setting the HEAD to this reference, by updating the index
to reflect the tree we point to and by updating the working tree to reflect
the latest index.
-
+
The command will fail if changed working tree files would be overwritten.
-
+
:param force:
If True, changes to the index and the working tree will be discarded.
If False, GitCommandError will be raised in that situation.
-
+
:param kwargs:
Additional keyword arguments to be passed to git checkout, i.e.
b='new_branch' to create a new branch at the given spot.
-
+
:return:
The active branch after the checkout operation, usually self unless
a new branch has been created.
-
+
:note:
By default it is only allowed to checkout heads - everything else
will leave the HEAD detached which is allowed and possible, but remains
@@ -162,9 +163,6 @@ class Head(Reference):
kwargs['f'] = force
if kwargs['f'] == False:
kwargs.pop('f')
-
+
self.repo.git.checkout(self, **kwargs)
return self.repo.active_branch
-
-
-
diff --git a/git/refs/log.py b/git/refs/log.py
index 70f11b87..fbb3fcf8 100644
--- a/git/refs/log.py
+++ b/git/refs/log.py
@@ -1,21 +1,21 @@
from git.util import (
- join_path,
- Actor,
- LockedFD,
- LockFile,
- assure_directory_exists,
- to_native_path,
- bin_to_hex,
- join,
- file_contents_ro_filepath
- )
+ join_path,
+ Actor,
+ LockedFD,
+ LockFile,
+ assure_directory_exists,
+ to_native_path,
+ bin_to_hex,
+ join,
+ file_contents_ro_filepath
+)
from git.objects.util import (
- parse_date,
- Serializable,
- utctz_to_altz,
- altz_to_utctz_str,
- )
+ parse_date,
+ Serializable,
+ utctz_to_altz,
+ altz_to_utctz_str,
+)
import time
import os
@@ -25,54 +25,55 @@ __all__ = ["RefLog", "RefLogEntry"]
class RefLogEntry(tuple):
+
"""Named tuple allowing easy access to the revlog data fields"""
_fmt = "%s %s %s <%s> %i %s\t%s\n"
_re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
__slots__ = tuple()
-
+
def __repr__(self):
"""Representation of ourselves in git reflog format"""
act = self.actor
time = self.time
- return self._fmt % (self.oldhexsha, self.newhexsha, act.name, act.email,
+ return self._fmt % (self.oldhexsha, self.newhexsha, act.name, act.email,
time[0], altz_to_utctz_str(time[1]), self.message)
-
+
@property
def oldhexsha(self):
- """The hexsha to the commit the ref pointed to before the change"""
+ """The hexsha to the commit the ref pointed to before the change"""
return self[0]
-
+
@property
def newhexsha(self):
"""The hexsha to the commit the ref now points to, after the change"""
return self[1]
-
+
@property
def actor(self):
"""Actor instance, providing access"""
return self[2]
-
+
@property
def time(self):
"""time as tuple:
-
+
* [0] = int(time)
* [1] = int(timezone_offset) in time.altzone format """
return self[3]
-
+
@property
def message(self):
"""Message describing the operation that acted on the reference"""
return self[4]
-
+
@classmethod
def new(self, oldhexsha, newhexsha, actor, time, tz_offset, message):
""":return: New instance of a RefLogEntry"""
if not isinstance(actor, Actor):
raise ValueError("Need actor instance, got %s" % actor)
- # END check types
+ # END check types
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), message))
-
+
@classmethod
def from_line(cls, line):
""":return: New RefLogEntry instance from the given revlog line.
@@ -82,40 +83,41 @@ class RefLogEntry(tuple):
info, msg = line.split('\t', 2)
except ValueError:
raise ValueError("line is missing tab separator")
- #END handle first plit
+ # END handle first plit
oldhexsha = info[:40]
newhexsha = info[41:81]
for hexsha in (oldhexsha, newhexsha):
if not cls._re_hexsha_only.match(hexsha):
raise ValueError("Invalid hexsha: %s" % hexsha)
# END if hexsha re doesn't match
- #END for each hexsha
-
+ # END for each hexsha
+
email_end = info.find('>', 82)
if email_end == -1:
raise ValueError("Missing token: >")
- #END handle missing end brace
-
- actor = Actor._from_string(info[82:email_end+1])
- time, tz_offset = parse_date(info[email_end+2:])
-
+ # END handle missing end brace
+
+ actor = Actor._from_string(info[82:email_end + 1])
+ time, tz_offset = parse_date(info[email_end + 2:])
+
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
-
+
class RefLog(list, Serializable):
+
"""A reflog contains reflog entries, each of which defines a certain state
of the head in question. Custom query methods allow to retrieve log entries
by date or by other criteria.
-
+
Reflog entries are orded, the first added entry is first in the list, the last
entry, i.e. the last change of the head or reference, is last in the list."""
-
+
__slots__ = ('_path', )
-
+
def __new__(cls, filepath=None):
inst = super(RefLog, cls).__new__(cls)
return inst
-
+
def __init__(self, filepath=None):
"""Initialize this instance with an optional filepath, from which we will
initialize our data. The path is also used to write changes back using
@@ -124,23 +126,23 @@ class RefLog(list, Serializable):
if filepath is not None:
self._read_from_file()
# END handle filepath
-
+
def _read_from_file(self):
try:
fmap = file_contents_ro_filepath(self._path, stream=True, allow_mmap=True)
except OSError:
# it is possible and allowed that the file doesn't exist !
return
- #END handle invalid log
-
+ # END handle invalid log
+
try:
self._deserialize(fmap)
finally:
fmap.close()
- #END handle closing of handle
-
+ # END handle closing of handle
+
#{ Interface
-
+
@classmethod
def from_file(cls, filepath):
"""
@@ -149,7 +151,7 @@ class RefLog(list, Serializable):
:param filepath: path to reflog
:raise ValueError: If the file could not be read or was corrupted in some way"""
return cls(filepath)
-
+
@classmethod
def path(cls, ref):
"""
@@ -158,7 +160,7 @@ class RefLog(list, Serializable):
file though.
:param ref: SymbolicReference instance"""
return join(ref.repo.git_dir, "logs", to_native_path(ref.path))
-
+
@classmethod
def iter_entries(cls, stream):
"""
@@ -169,23 +171,23 @@ class RefLog(list, Serializable):
new_entry = RefLogEntry.from_line
if isinstance(stream, basestring):
stream = file_contents_ro_filepath(stream)
- #END handle stream type
+ # END handle stream type
while True:
line = stream.readline()
if not line:
return
yield new_entry(line.strip())
- #END endless loop
-
+ # END endless loop
+
@classmethod
def entry_at(cls, filepath, index):
""":return: RefLogEntry at the given index
:param filepath: full path to the index file from which to read the entry
:param index: python list compatible index, i.e. it may be negative to
specifiy an entry counted from the end of the list
-
+
:raise IndexError: If the entry didn't exist
-
+
.. note:: This method is faster as it only parses the entry at index, skipping
all other lines. Nonetheless, the whole file has to be read if
the index is negative
@@ -195,26 +197,26 @@ class RefLog(list, Serializable):
return RefLogEntry.from_line(fp.readlines()[index].strip())
else:
# read until index is reached
- for i in xrange(index+1):
+ for i in xrange(index + 1):
line = fp.readline()
if not line:
break
- #END abort on eof
- #END handle runup
-
+ # END abort on eof
+ # END handle runup
+
if i != index or not line:
raise IndexError
- #END handle exception
-
+ # END handle exception
+
return RefLogEntry.from_line(line.strip())
- #END handle index
-
+ # END handle index
+
def to_file(self, filepath):
"""Write the contents of the reflog instance to a file at the given filepath.
:param filepath: path to file, parent directories are assumed to exist"""
lfd = LockedFD(filepath)
assure_directory_exists(filepath, is_file=True)
-
+
fp = lfd.open(write=True, stream=True)
try:
self._serialize(fp)
@@ -223,12 +225,12 @@ class RefLog(list, Serializable):
# on failure it rolls back automatically, but we make it clear
lfd.rollback()
raise
- #END handle change
-
+ # END handle change
+
@classmethod
def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message):
"""Append a new log entry to the revlog at filepath.
-
+
:param config_reader: configuration reader of the repository - used to obtain
user information. May be None
:param filepath: full path to the log file
@@ -242,44 +244,45 @@ class RefLog(list, Serializable):
do not interfere with readers."""
if len(oldbinsha) != 20 or len(newbinsha) != 20:
raise ValueError("Shas need to be given in binary format")
- #END handle sha type
+ # END handle sha type
assure_directory_exists(filepath, is_file=True)
- entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(config_reader), (int(time.time()), time.altzone), message))
-
+ entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(
+ config_reader), (int(time.time()), time.altzone), message))
+
lf = LockFile(filepath)
lf._obtain_lock_or_raise()
-
+
fd = open(filepath, 'a')
try:
fd.write(repr(entry))
finally:
fd.close()
lf._release_lock()
- #END handle write operation
-
+ # END handle write operation
+
return entry
-
+
def write(self):
"""Write this instance's data to the file we are originating from
:return: self"""
if self._path is None:
raise ValueError("Instance was not initialized with a path, use to_file(...) instead")
- #END assert path
+ # END assert path
self.to_file(self._path)
return self
-
+
#} END interface
-
+
#{ Serializable Interface
def _serialize(self, stream):
lm1 = len(self) - 1
write = stream.write
-
+
# write all entries
for e in self:
write(repr(e))
- #END for each entry
-
+ # END for each entry
+
def _deserialize(self, stream):
self.extend(self.iter_entries(stream))
#} END serializable interface
diff --git a/git/refs/reference.py b/git/refs/reference.py
index 2aaf185f..c55070d5 100644
--- a/git/refs/reference.py
+++ b/git/refs/reference.py
@@ -3,66 +3,69 @@ import os
from symbolic import SymbolicReference
from head import HEAD
from git.util import (
- LazyMixin,
- Iterable,
- isfile,
- hex_to_bin
- )
+ LazyMixin,
+ Iterable,
+ isfile,
+ hex_to_bin
+)
__all__ = ["Reference"]
#{ Utilities
+
+
def require_remote_ref_path(func):
"""A decorator raising a TypeError if we are not a valid remote, based on the path"""
+
def wrapper(self, *args):
if not self.path.startswith(self._remote_common_path_default + "/"):
raise ValueError("ref path does not point to a remote reference: %s" % path)
return func(self, *args)
- #END wrapper
+ # END wrapper
wrapper.__name__ = func.__name__
return wrapper
#}END utilites
class Reference(SymbolicReference, LazyMixin, Iterable):
+
"""Represents a named reference to any object. Subclasses may apply restrictions though,
i.e. Heads can only point to commits."""
__slots__ = tuple()
_points_to_commits_only = False
_resolve_ref_on_create = True
_common_path_default = "refs"
-
- def __init__(self, repo, path, check_path = True):
+
+ def __init__(self, repo, path, check_path=True):
"""Initialize this instance
:param repo: Our parent repository
-
+
:param path:
Path relative to the .git/ directory pointing to the ref in question, i.e.
refs/heads/master
:param check_path: if False, you can provide any path. Otherwise the path must start with the
default path prefix of this type."""
- if check_path and not path.startswith(self._common_path_default+'/'):
+ if check_path and not path.startswith(self._common_path_default + '/'):
raise ValueError("Cannot instantiate %r from path %s" % (self.__class__.__name__, path))
super(Reference, self).__init__(repo, path)
-
def __str__(self):
return self.name
-
+
#{ Interface
- def set_object(self, object, logmsg = None):
+ def set_object(self, object, logmsg=None):
"""Special version which checks if the head-log needs an update as well"""
oldbinsha = None
head = HEAD(self.repo)
if logmsg is not None:
if not head.is_detached and head.ref == self:
oldbinsha = self.commit.binsha
- #END handle commit retrieval
- #END handle message is set
-
+ # END handle commit retrieval
+ # END handle message is set
+
super(Reference, self).set_object(object, logmsg)
-
+
if oldbinsha is not None:
# /* from refs.c in git-source
# * Special hack: If a branch is updated directly and HEAD
@@ -77,31 +80,30 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
# * scenarios (even 100% of the default ones).
# */
head.log_append(oldbinsha, logmsg)
- #END check if the head
+ # END check if the head
# NOTE: Don't have to overwrite properties as the will only work without a the log
@property
def name(self):
""":return: (shortest) Name of this reference - it may contain path components"""
- # first two path tokens are can be removed as they are
+ # first two path tokens are can be removed as they are
# refs/heads or refs/tags or refs/remotes
tokens = self.path.split('/')
if len(tokens) < 3:
return self.path # could be refs/HEAD
return '/'.join(tokens[2:])
-
+
@classmethod
- def iter_items(cls, repo, common_path = None):
+ def iter_items(cls, repo, common_path=None):
"""Equivalent to SymbolicReference.iter_items, but will return non-detached
references as well."""
return cls._iter_items(repo, common_path)
-
+
#}END interface
-
-
+
#{ Remote Interface
-
+
@property
@require_remote_ref_path
def remote_name(self):
@@ -112,7 +114,7 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
tokens = self.path.split('/')
# /refs/remotes/<remote name>/<branch_name>
return tokens[2]
-
+
@property
@require_remote_ref_path
def remote_head(self):
@@ -121,5 +123,5 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
a branch"""
tokens = self.path.split('/')
return '/'.join(tokens[3:])
-
+
#} END remote interface
diff --git a/git/refs/remote.py b/git/refs/remote.py
index b4d72f6c..b601c8b2 100644
--- a/git/refs/remote.py
+++ b/git/refs/remote.py
@@ -1,34 +1,34 @@
import os
from headref import Head
from git.util import (
- join,
- join_path
- )
+ join,
+ join_path
+)
__all__ = ["RemoteReference"]
-
+
class RemoteReference(Head):
+
"""Represents a reference pointing to a remote head."""
__slots__ = tuple()
-
+
_common_path_default = Head._remote_common_path_default
-
-
+
@classmethod
- def iter_items(cls, repo, common_path = None, remote=None):
+ def iter_items(cls, repo, common_path=None, remote=None):
"""Iterate remote references, and if given, constrain them to the given remote"""
common_path = common_path or cls._common_path_default
if remote is not None:
common_path = join_path(common_path, str(remote))
# END handle remote constraint
return super(RemoteReference, cls).iter_items(repo, common_path)
-
+
@classmethod
def create(cls, *args, **kwargs):
"""Used to disable this method"""
raise TypeError("Cannot explicitly create remote references")
-
+
@classmethod
def delete(cls, repo, *refs, **kwargs):
"""Delete the given remote references.
@@ -36,8 +36,8 @@ class RemoteReference(Head):
kwargs are given for compatability with the base class method as we
should not narrow the signature."""
repo.git.branch("-d", "-r", *refs)
- # the official deletion method will ignore remote symbolic refs - these
- # are generally ignored in the refs/ folder. We don't though
+ # the official deletion method will ignore remote symbolic refs - these
+ # are generally ignored in the refs/ folder. We don't though
# and delete remainders manually
for ref in refs:
try:
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index 450a0bd9..db429398 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -2,50 +2,52 @@ import os
import re
from git.objects import (
- Object,
- Commit
- )
+ Object,
+ Commit
+)
from git.util import (
- join_path,
- join_path_native,
- to_native_path_linux,
- assure_directory_exists,
- join,
- dirname,
- isdir,
- exists,
- isfile,
- rename,
- hex_to_bin,
- LockedFD
- )
+ join_path,
+ join_path_native,
+ to_native_path_linux,
+ assure_directory_exists,
+ join,
+ dirname,
+ isdir,
+ exists,
+ isfile,
+ rename,
+ hex_to_bin,
+ LockedFD
+)
from git.exc import BadObject
from log import RefLog
__all__ = ["SymbolicReference"]
+
class SymbolicReference(object):
+
"""Represents a special case of a reference such that this reference is symbolic.
It does not point to a specific commit, but to another Head, which itself
specifies a commit.
-
+
A typical example for a symbolic reference is HEAD."""
__slots__ = ("repo", "path")
-
+
_resolve_ref_on_create = False
_points_to_commits_only = True
_common_path_default = ""
_remote_common_path_default = "refs/remotes"
_id_attribute_ = "name"
-
+
re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
-
+
#{ Configuration
# Object class to be used when instantiating objects
ObjectCls = Object
CommitCls = Commit
-
+
# all of the following are set by the package initializer
HEADCls = None
HeadCls = None
@@ -53,28 +55,28 @@ class SymbolicReference(object):
TagReferenceCls = None
ReferenceCls = None
#}END configuration
-
+
def __init__(self, repo, path):
self.repo = repo
self.path = path
-
+
def __str__(self):
return self.path
-
+
def __repr__(self):
return '<git.%s "%s">' % (self.__class__.__name__, self.path)
-
+
def __eq__(self, other):
if hasattr(other, 'path'):
return self.path == other.path
return False
-
+
def __ne__(self, other):
- return not ( self == other )
-
+ return not (self == other)
+
def __hash__(self):
return hash(self.path)
-
+
@property
def name(self):
"""
@@ -82,15 +84,15 @@ class SymbolicReference(object):
In case of symbolic references, the shortest assumable name
is the path itself."""
return self.path
-
+
@property
def abspath(self):
return join_path_native(self.repo.git_dir, self.path)
-
+
@classmethod
def _get_packed_refs_path(cls, repo):
return join(repo.git_dir, 'packed-refs')
-
+
@classmethod
def _iter_packed_refs(cls, repo):
"""Returns an iterator yielding pairs of sha1/path pairs for the corresponding refs.
@@ -107,22 +109,22 @@ class SymbolicReference(object):
# END abort if we do not understand the packing scheme
continue
# END parse comment
-
+
# skip dereferenced tag object entries - previous line was actual
# tag reference for it
if line[0] == '^':
continue
-
+
yield tuple(line.split(' ', 1))
# END for each line
- except (OSError,IOError):
+ except (OSError, IOError):
raise StopIteration
- # END no packed-refs file handling
- # NOTE: Had try-finally block around here to close the fp,
+ # END no packed-refs file handling
+ # NOTE: Had try-finally block around here to close the fp,
# but some python version woudn't allow yields within that.
- # I believe files are closing themselves on destruction, so it is
+ # I believe files are closing themselves on destruction, so it is
# alright.
-
+
@classmethod
def dereference_recursive(cls, repo, ref_path):
"""
@@ -134,7 +136,7 @@ class SymbolicReference(object):
if hexsha is not None:
return hexsha
# END recursive dereferencing
-
+
@classmethod
def _get_ref_info(cls, repo, ref_path):
"""Return: (sha, target_ref_path) if available, the sha the file at
@@ -146,36 +148,37 @@ class SymbolicReference(object):
value = fp.read().rstrip()
fp.close()
tokens = value.split(" ")
- except (OSError,IOError):
+ except (OSError, IOError):
# Probably we are just packed, find our entry in the packed refs file
# NOTE: We are not a symbolic ref if we are in a packed file, as these
# are excluded explictly
for sha, path in cls._iter_packed_refs(repo):
- if path != ref_path: continue
+ if path != ref_path:
+ continue
tokens = (sha, path)
break
# END for each packed ref
# END handle packed refs
if tokens is None:
raise ValueError("Reference at %r does not exist" % ref_path)
-
+
# is it a reference ?
if tokens[0] == 'ref:':
return (None, tokens[1])
-
+
# its a commit
if cls.re_hexsha_only.match(tokens[0]):
return (tokens[0], None)
-
+
raise ValueError("Failed to parse reference information from %r" % ref_path)
-
+
def _get_object_sha(self):
"""
:return:
The binary sha to the object our ref currently refers to. Refs can be cached, they will
always point to the actual object as it gets re-created on each query"""
return hex_to_bin(self.dereference_recursive(self.repo, self.path))
-
+
def _get_object(self):
"""
:return:
@@ -183,11 +186,11 @@ class SymbolicReference(object):
# have to be dynamic here as we may be a tag which can point to anything
# Our path will be resolved to the hexsha which will be used accordingly
return self.ObjectCls.new_from_sha(self.repo, self._get_object_sha())
-
- def set_object(self, object_id, logmsg = None):
+
+ def set_object(self, object_id, logmsg=None):
"""Set the object we point to, possibly dereference our symbolic reference first.
If the reference does not exist, it will be created
-
+
:param object: a reference specifier string, a SymbolicReference or an object hex sha.
SymbolicReferences will be dereferenced beforehand to obtain the object they point to
:param logmsg: If not None, the message will be used in the reflog entry to be
@@ -196,21 +199,21 @@ class SymbolicReference(object):
:return: self"""
if isinstance(object_id, SymbolicReference):
object = object.object
- #END resolve references
-
+ # END resolve references
+
is_detached = True
try:
is_detached = self.is_detached
except ValueError:
pass
# END handle non-existing ones
-
+
if is_detached:
return self.set_reference(object_id, logmsg)
-
+
# set the commit on our reference
return self._get_reference().set_object(object_id, logmsg)
-
+
def _get_commit(self):
"""
:return:
@@ -219,16 +222,16 @@ class SymbolicReference(object):
obj = self._get_object()
if obj.type == 'tag':
obj = obj.object
- #END dereference tag
-
+ # END dereference tag
+
if obj.type != self.CommitCls.type:
raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
- #END handle type
+ # END handle type
return obj
-
- def set_commit(self, commit, logmsg = None):
+
+ def set_commit(self, commit, logmsg=None):
"""As set_object, but restricts the type of object to be a Commit
-
+
:raise ValueError: If commit is not a Commit object or doesn't point to
a commit
:return: self"""
@@ -243,23 +246,22 @@ class SymbolicReference(object):
is_invalid_type = self.repo.resolve_object(commit).type != self.CommitCls.type
except BadObject:
raise ValueError("Invalid object: %s" % commit)
- #END handle exception
+ # END handle exception
# END verify type
-
+
if is_invalid_type:
raise ValueError("Need commit, got %r" % commit)
- #END handle raise
-
+ # END handle raise
+
# we leave strings to the rev-parse method below
self.set_object(commit, logmsg)
-
+
return self
-
-
+
commit = property(_get_commit, set_commit, doc="Query or set commits directly")
object = property(_get_object, set_object, doc="Return the object our ref currently refers to")
object_binsha = property(_get_object_sha, set_object, doc="Return the object our ref currently refers to")
-
+
def _get_reference(self):
""":return: Reference Object we point to
:raise TypeError: If this symbolic reference is detached, hence it doesn't point
@@ -268,22 +270,22 @@ class SymbolicReference(object):
if target_ref_path is None:
raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
return self.from_path(self.repo, target_ref_path)
-
- def set_reference(self, ref, logmsg = None):
+
+ def set_reference(self, ref, logmsg=None):
"""Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
Otherwise an Object, given as Object instance or refspec, is assumed and if valid,
will be set which effectively detaches the refererence if it was a purely
symbolic one.
-
+
:param ref: SymbolicReference instance, hexadecimal sha string or refspec string
Only if the ref is a SymbolicRef instance, we will point to it. Everthiny
else is dereferenced to obtain the actual object.
:param logmsg: If set to a string, the message will be used in the reflog.
Otherwise, a reflog entry is not written for the changed reference.
The previous commit of the entry will be the commit we point to now.
-
+
See also: log_append()
-
+
:return: self
:note: This symbolic reference will not be dereferenced. For that, see
``set_object(...)``"""
@@ -296,7 +298,7 @@ class SymbolicReference(object):
write_value = ref.hexsha
elif isinstance(ref, basestring):
try:
- obj = self.repo.resolve_object(ref+"^{}") # optionally deref tags
+ obj = self.repo.resolve_object(ref + "^{}") # optionally deref tags
write_value = obj.hexsha
except BadObject:
raise ValueError("Could not extract object from %s" % ref)
@@ -304,41 +306,40 @@ class SymbolicReference(object):
else:
raise ValueError("Unrecognized Value: %r" % ref)
# END try commit attribute
-
+
# typecheck
if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
raise TypeError("Require commit, got %r" % obj)
- #END verify type
-
+ # END verify type
+
oldbinsha = None
if logmsg is not None:
try:
oldbinsha = self.commit.binsha
except ValueError:
oldbinsha = Commit.NULL_BIN_SHA
- #END handle non-existing
- #END retrieve old hexsha
-
+ # END handle non-existing
+ # END retrieve old hexsha
+
fpath = self.abspath
assure_directory_exists(fpath, is_file=True)
-
+
lfd = LockedFD(fpath)
fd = lfd.open(write=True, stream=True)
fd.write(write_value)
lfd.commit()
-
+
# Adjust the reflog
if logmsg is not None:
self.log_append(oldbinsha, logmsg)
- #END handle reflog
-
+ # END handle reflog
+
return self
-
# aliased reference
reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")
ref = reference
-
+
def is_valid(self):
"""
:return:
@@ -350,7 +351,7 @@ class SymbolicReference(object):
return False
else:
return True
-
+
@property
def is_detached(self):
"""
@@ -362,32 +363,32 @@ class SymbolicReference(object):
return False
except TypeError:
return True
-
+
def log(self):
"""
:return: RefLog for this reference. Its last entry reflects the latest change
applied to this reference
-
+
.. note:: As the log is parsed every time, its recommended to cache it for use
instead of calling this method repeatedly. It should be considered read-only."""
return RefLog.from_file(RefLog.path(self))
-
+
def log_append(self, oldbinsha, message, newbinsha=None):
"""Append a logentry to the logfile of this ref
-
+
:param oldbinsha: binary sha this ref used to point to
:param message: A message describing the change
:param newbinsha: The sha the ref points to now. If None, our current commit sha
will be used
:return: added RefLogEntry instance"""
- return RefLog.append_entry(self.repo.config_reader(), RefLog.path(self), oldbinsha,
- (newbinsha is None and self.commit.binsha) or newbinsha,
- message)
+ return RefLog.append_entry(self.repo.config_reader(), RefLog.path(self), oldbinsha,
+ (newbinsha is None and self.commit.binsha) or newbinsha,
+ message)
def log_entry(self, index):
""":return: RefLogEntry at the given index
:param index: python list compatible positive or negative index
-
+
.. note:: This method must read part of the reflog during execution, hence
it should be used sparringly, or only if you need just one index.
In that case, it will be faster than the ``log()`` method"""
@@ -403,17 +404,17 @@ class SymbolicReference(object):
full_ref_path = path
if not cls._common_path_default:
return full_ref_path
- if not path.startswith(cls._common_path_default+"/"):
+ if not path.startswith(cls._common_path_default + "/"):
full_ref_path = '%s/%s' % (cls._common_path_default, path)
return full_ref_path
-
+
@classmethod
def delete(cls, repo, path):
"""Delete the reference at the given path
-
+
:param repo:
Repository to delete the reference from
-
+
:param path:
Short or full path pointing to the reference, i.e. refs/myreference
or just "myreference", hence 'refs/' is implied.
@@ -427,30 +428,30 @@ class SymbolicReference(object):
pack_file_path = cls._get_packed_refs_path(repo)
try:
reader = open(pack_file_path, 'rb')
- except (OSError,IOError):
- pass # it didnt exist at all
+ except (OSError, IOError):
+ pass # it didnt exist at all
else:
new_lines = list()
made_change = False
dropped_last_line = False
for line in reader:
- # keep line if it is a comment or if the ref to delete is not
+ # keep line if it is a comment or if the ref to delete is not
# in the line
- # If we deleted the last line and this one is a tag-reference object,
+ # If we deleted the last line and this one is a tag-reference object,
# we drop it as well
if ( line.startswith('#') or full_ref_path not in line ) and \
- ( not dropped_last_line or dropped_last_line and not line.startswith('^') ):
+ (not dropped_last_line or dropped_last_line and not line.startswith('^')):
new_lines.append(line)
dropped_last_line = False
continue
# END skip comments and lines without our path
-
+
# drop this line
made_change = True
dropped_last_line = True
# END for each line in packed refs
reader.close()
-
+
# write the new lines
if made_change:
# write-binary is required, otherwise windows will
@@ -459,14 +460,13 @@ class SymbolicReference(object):
# END write out file
# END open exception handling
# END handle deletion
-
+
# delete the reflog
reflog_path = RefLog.path(cls(repo, full_ref_path))
if os.path.isfile(reflog_path):
os.remove(reflog_path)
- #END remove reflog
-
-
+ # END remove reflog
+
@classmethod
def _create(cls, repo, path, resolve, reference, force, logmsg=None):
"""internal method used to create a new symbolic reference.
@@ -476,7 +476,7 @@ class SymbolicReference(object):
instead"""
full_ref_path = cls.to_full_path(path)
abs_ref_path = join(repo.git_dir, full_ref_path)
-
+
# figure out target data
target = reference
if resolve:
@@ -488,125 +488,126 @@ class SymbolicReference(object):
target = reference.object.hexsha
else:
target = repo.resolve_object(str(reference))
- #END handle resoltion
- #END need resolution
-
+ # END handle resoltion
+ # END need resolution
+
if not force and isfile(abs_ref_path):
target_data = str(target)
if isinstance(target, SymbolicReference):
target_data = target.path
if not resolve:
target_data = "ref: " + target_data
- existing_data = open(abs_ref_path, 'rb').read().strip()
+ existing_data = open(abs_ref_path, 'rb').read().strip()
if existing_data != target_data:
- raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path, existing_data, target_data))
+ raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" %
+ (full_ref_path, existing_data, target_data))
# END no force handling
-
+
ref = cls(repo, full_ref_path)
ref.set_reference(target, logmsg)
return ref
-
+
@classmethod
def create(cls, repo, path, reference='HEAD', force=False, logmsg=None):
"""Create a new symbolic reference, hence a reference pointing to another reference.
-
+
:param repo:
Repository to create the reference in
-
+
:param path:
full path at which the new symbolic reference is supposed to be
created at, i.e. "NEW_HEAD" or "symrefs/my_new_symref"
-
+
:param reference:
The reference to which the new symbolic reference should point to.
If it is a commit'ish, the symbolic ref will be detached.
-
+
:param force:
if True, force creation even if a symbolic reference with that name already exists.
Raise OSError otherwise
-
+
:param logmsg:
If not None, the message to append to the reflog. Otherwise no reflog
entry is written.
-
+
:return: Newly created symbolic Reference
-
+
:raise OSError:
If a (Symbolic)Reference with the same name but different contents
already exists.
-
+
:note: This does not alter the current HEAD, index or Working Tree"""
return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)
-
+
def rename(self, new_path, force=False):
"""Rename self to a new path
-
+
:param new_path:
Either a simple name or a full path, i.e. new_name or features/new_name.
The prefix refs/ is implied for references and will be set as needed.
In case this is a symbolic ref, there is no implied prefix
-
+
:param force:
If True, the rename will succeed even if a head with the target name
already exists. It will be overwritten in that case
-
+
:return: self
:raise OSError: In case a file at path but a different contents already exists """
new_path = self.to_full_path(new_path)
if self.path == new_path:
return self
-
+
new_abs_path = join(self.repo.git_dir, new_path)
cur_abs_path = join(self.repo.git_dir, self.path)
if isfile(new_abs_path):
if not force:
# if they point to the same file, its not an error
- if open(new_abs_path,'rb').read().strip() != open(cur_abs_path,'rb').read().strip():
+ if open(new_abs_path, 'rb').read().strip() != open(cur_abs_path, 'rb').read().strip():
raise OSError("File at path %r already exists" % new_abs_path)
- # else: we could remove ourselves and use the otherone, but
+ # else: we could remove ourselves and use the otherone, but
# but clarity we just continue as usual
# END not force handling
os.remove(new_abs_path)
# END handle existing target file
-
+
dname = dirname(new_abs_path)
if not isdir(dname):
os.makedirs(dname)
# END create directory
-
+
rename(cur_abs_path, new_abs_path)
self.path = new_path
-
+
return self
-
+
@classmethod
- def _iter_items(cls, repo, common_path = None):
+ def _iter_items(cls, repo, common_path=None):
if common_path is None:
common_path = cls._common_path_default
rela_paths = set()
-
+
# walk loose refs
- # Currently we do not follow links
+ # Currently we do not follow links
for root, dirs, files in os.walk(join_path_native(repo.git_dir, common_path)):
- if 'refs/' not in root: # skip non-refs subfolders
- refs_id = [ d for d in dirs if d == 'refs' ]
+ if 'refs/' not in root: # skip non-refs subfolders
+ refs_id = [d for d in dirs if d == 'refs']
if refs_id:
dirs[0:] = ['refs']
# END prune non-refs folders
-
+
for f in files:
abs_path = to_native_path_linux(join_path(root, f))
rela_paths.add(abs_path.replace(to_native_path_linux(repo.git_dir) + '/', ""))
# END for each file in root directory
# END for each directory to walk
-
+
# read packed refs
for sha, rela_path in cls._iter_packed_refs(repo):
if rela_path.startswith(common_path):
rela_paths.add(rela_path)
# END relative path matches common path
# END packed refs reading
-
+
# return paths in sorted order
for path in sorted(rela_paths):
try:
@@ -614,9 +615,9 @@ class SymbolicReference(object):
except ValueError:
continue
# END for each sorted relative refpath
-
+
@classmethod
- def iter_items(cls, repo, common_path = None):
+ def iter_items(cls, repo, common_path=None):
"""Find all refs in the repository
:param repo: is the repo
@@ -630,11 +631,11 @@ class SymbolicReference(object):
:return:
git.SymbolicReference[], each of them is guaranteed to be a *only* a symbolic
ref, or a derived class which is not detached
-
+
List is lexigraphically sorted
The returned objects represent actual subclasses, such as Head or TagReference"""
- return ( r for r in cls._iter_items(repo, common_path) if r.__class__ == cls or not r.is_detached )
-
+ return (r for r in cls._iter_items(repo, common_path) if r.__class__ == cls or not r.is_detached)
+
@classmethod
def from_path(cls, repo, path):
"""
@@ -645,7 +646,7 @@ class SymbolicReference(object):
depending on the given path"""
if not path:
raise ValueError("Cannot create Reference from %r" % path)
-
+
for ref_type in (cls.HEADCls, cls.HeadCls, cls.RemoteReferenceCls, cls.TagReferenceCls, cls.ReferenceCls, cls):
try:
instance = ref_type(repo, path)
diff --git a/git/refs/tag.py b/git/refs/tag.py
index 1fcdb903..25f42ed4 100644
--- a/git/refs/tag.py
+++ b/git/refs/tag.py
@@ -2,21 +2,23 @@ from reference import Reference
__all__ = ["TagReference", "Tag"]
+
class TagReference(Reference):
+
"""Class representing a lightweight tag reference which either points to a commit
,a tag object or any other object. In the latter case additional information,
like the signature or the tag-creator, is available.
-
+
This tag object will always point to a commit object, but may carray additional
information in a tag object::
-
+
tagref = TagReference.list_items(repo)[0]
print tagref.commit.message
if tagref.tag is not None:
print tagref.tag.message"""
__slots__ = tuple()
_common_path_default = "refs/tags"
-
+
@property
def commit(self):
""":return: Commit object the tag ref points to"""
@@ -27,7 +29,7 @@ class TagReference(Reference):
# it is a tag object which carries the commit as an object - we can point to anything
return obj.object
else:
- raise ValueError( "Tag %s points to a Blob or Tree - have never seen that before" % self )
+ raise ValueError("Tag %s points to a Blob or Tree - have never seen that before" % self)
@property
def tag(self):
@@ -38,49 +40,49 @@ class TagReference(Reference):
if obj.type == "tag":
return obj
return None
-
+
# make object read-only
# It should be reasonably hard to adjust an existing tag
object = property(Reference._get_object)
-
+
@classmethod
def create(cls, repo, path, ref='HEAD', message=None, force=False, **kwargs):
"""Create a new tag reference.
-
+
:param path:
The name of the tag, i.e. 1.0 or releases/1.0.
The prefix refs/tags is implied
-
+
:param ref:
A reference to the object you want to tag. It can be a commit, tree or
blob.
-
+
:param message:
If not None, the message will be used in your tag object. This will also
create an additional tag object that allows to obtain that information, i.e.::
-
+
tagref.tag.message
-
+
:param force:
If True, to force creation of a tag even though that tag already exists.
-
+
:param kwargs:
Additional keyword arguments to be passed to git-tag
-
+
:return: A new TagReference"""
- args = ( path, ref )
+ args = (path, ref)
if message:
- kwargs['m'] = message
+ kwargs['m'] = message
if force:
kwargs['f'] = True
-
+
repo.git.tag(*args, **kwargs)
return TagReference(repo, "%s/%s" % (cls._common_path_default, path))
-
+
@classmethod
def delete(cls, repo, *tags):
"""Delete the given existing tag or tags"""
repo.git.tag("-d", *tags)
-
+
# provide an alias
Tag = TagReference
diff --git a/git/remote.py b/git/remote.py
index a42af1b4..3771f9b4 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -11,10 +11,10 @@ from ConfigParser import NoOptionError
from config import SectionConstraint
from git.util import (
- LazyMixin,
- Iterable,
- IterableList
- )
+ LazyMixin,
+ Iterable,
+ IterableList
+)
from git.db.interface import TransportDB
from refs import RemoteReference
@@ -22,65 +22,68 @@ import os
__all__ = ['Remote']
+
class PushInfo(object):
+
"""Wrapper for basic PushInfo to provide the previous interface which includes
resolved objects instead of plain shas
-
+
old_commit # object for the corresponding old_commit_sha"""
-
-
-
+
+
class FetchInfo(object):
+
"""Wrapper to restore the previous interface, resolving objects and wrapping
references"""
class Remote(LazyMixin, Iterable):
+
"""Provides easy read and write access to a git remote.
-
+
Everything not part of this interface is considered an option for the current
remote, allowing constructs like remote.pushurl to query the pushurl.
-
+
NOTE: When querying configuration, the configuration accessor will be cached
to speed up subsequent accesses."""
-
- __slots__ = ( "repo", "name", "_config_reader" )
+
+ __slots__ = ("repo", "name", "_config_reader")
_id_attribute_ = "name"
-
+
def __init__(self, repo, name):
"""Initialize a remote instance
-
+
:param repo: The repository we are a remote of
:param name: the name of the remote, i.e. 'origin'"""
if not hasattr(repo, 'git'):
# note: at some point we could just create a git command instance ourselves
# but lets just be lazy for now
raise AssertionError("Require repository to provide a git command instance currently")
- #END assert git cmd
-
+ # END assert git cmd
+
if not isinstance(repo, TransportDB):
raise AssertionError("Require TransportDB interface implementation")
- #END verify interface
-
+ # END verify interface
+
self.repo = repo
self.name = name
-
+
if os.name == 'nt':
# some oddity: on windows, python 2.5, it for some reason does not realize
# that it has the config_writer property, but instead calls __getattr__
# which will not yield the expected results. 'pinging' the members
- # with a dir call creates the config_writer property that we require
+ # with a dir call creates the config_writer property that we require
# ... bugs like these make me wonder wheter python really wants to be used
# for production. It doesn't happen on linux though.
dir(self)
# END windows special handling
-
+
def __getattr__(self, attr):
"""Allows to call this instance like
remote.special( *args, **kwargs) to call git-remote special self.name"""
if attr == "_config_reader":
return super(Remote, self).__getattr__(attr)
-
+
# sometimes, probably due to a bug in python itself, we are being called
# even though a slot of the same name exists
try:
@@ -88,32 +91,31 @@ class Remote(LazyMixin, Iterable):
except NoOptionError:
return super(Remote, self).__getattr__(attr)
# END handle exception
-
+
def _config_section_name(self):
return 'remote "%s"' % self.name
-
+
def _set_cache_(self, attr):
if attr == "_config_reader":
self._config_reader = SectionConstraint(self.repo.config_reader(), self._config_section_name())
else:
super(Remote, self)._set_cache_(attr)
-
-
+
def __str__(self):
- return self.name
-
+ return self.name
+
def __repr__(self):
return '<git.%s "%s">' % (self.__class__.__name__, self.name)
-
+
def __eq__(self, other):
return self.name == other.name
-
+
def __ne__(self, other):
- return not ( self == other )
-
+ return not (self == other)
+
def __hash__(self):
return hash(self.name)
-
+
@classmethod
def iter_items(cls, repo):
""":return: Iterator yielding Remote objects of the given repository"""
@@ -124,9 +126,9 @@ class Remote(LazyMixin, Iterable):
rbound = section.rfind('"')
if lbound == -1 or rbound == -1:
raise ValueError("Remote-Section has invalid format: %r" % section)
- yield Remote(repo, section[lbound+1:rbound])
+ yield Remote(repo, section[lbound + 1:rbound])
# END for each configuration section
-
+
@property
def refs(self):
"""
@@ -138,7 +140,7 @@ class Remote(LazyMixin, Iterable):
out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name))
assert out_refs, "Remote %s did not have any references" % self.name
return out_refs
-
+
@property
def stale_refs(self):
"""
@@ -146,21 +148,21 @@ class Remote(LazyMixin, Iterable):
IterableList RemoteReference objects that do not have a corresponding
head in the remote reference anymore as they have been deleted on the
remote side, but are still available locally.
-
+
The IterableList is prefixed, hence the 'origin' must be omitted. See
'refs' property for an example."""
out_refs = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]:
- # expecting
+ # expecting
# * [would prune] origin/new_branch
- token = " * [would prune] "
+ token = " * [would prune] "
if not line.startswith(token):
raise ValueError("Could not parse git-remote prune result: %r" % line)
- fqhn = "%s/%s" % (RemoteReference._common_path_default,line.replace(token, ""))
+ fqhn = "%s/%s" % (RemoteReference._common_path_default, line.replace(token, ""))
out_refs.append(RemoteReference(self.repo, fqhn))
- # END for each line
+ # END for each line
return out_refs
-
+
@classmethod
def create(cls, repo, name, url, **kwargs):
"""Create a new remote to the given repository
@@ -169,54 +171,54 @@ class Remote(LazyMixin, Iterable):
:param url: URL which corresponds to the remote's name
:param kwargs:
Additional arguments to be passed to the git-remote add command
-
+
:return: New Remote instance
-
+
:raise GitCommandError: in case an origin with that name already exists"""
- repo.git.remote( "add", name, url, **kwargs )
+ repo.git.remote("add", name, url, **kwargs)
return cls(repo, name)
-
+
# add is an alias
add = create
-
+
@classmethod
- def remove(cls, repo, name ):
+ def remove(cls, repo, name):
"""Remove the remote with the given name"""
repo.git.remote("rm", name)
-
+
# alias
rm = remove
-
+
def rename(self, new_name):
"""Rename self to the given new_name
:return: self """
if self.name == new_name:
return self
-
+
self.repo.git.remote("rename", self.name, new_name)
self.name = new_name
try:
del(self._config_reader) # it contains cached values, section names are different now
except AttributeError:
pass
- #END handle exception
+ # END handle exception
return self
-
+
def update(self, **kwargs):
"""Fetch all changes for this remote, including new branches which will
be forced in ( in case your local remote branch is not part the new remote branches
ancestry anymore ).
-
+
:param kwargs:
Additional arguments passed to git-remote update
-
+
:return: self """
self.repo.git.remote("update", self.name)
return self
-
+
def fetch(self, refspec=None, progress=None, **kwargs):
"""Fetch the latest changes for this remote
-
+
:param refspec:
A "refspec" is used by fetch and push to describe the mapping
between remote ref and local ref. They are combined with a colon in
@@ -226,38 +228,38 @@ class Remote(LazyMixin, Iterable):
branch head". And git push $URL refs/heads/master:refs/heads/to-upstream
means "publish my master branch head as to-upstream branch at $URL".
See also git-push(1).
-
+
Taken from the git manual
:param progress: See 'push' method
:param kwargs: Additional arguments to be passed to git-fetch
:return:
IterableList(FetchInfo, ...) list of FetchInfo instances providing detailed
information about the fetch results
-
+
:note:
As fetch does not provide progress information to non-ttys, we cannot make
it available here unfortunately as in the 'push' method."""
return self.repo.fetch(self.name, refspec, progress, **kwargs)
-
+
def pull(self, refspec=None, progress=None, **kwargs):
"""Pull changes from the given branch, being the same as a fetch followed
by a merge of branch with your local branch.
-
+
:param refspec: see 'fetch' method
:param progress: see 'push' method
:param kwargs: Additional arguments to be passed to git-pull
:return: Please see 'fetch' method """
return self.repo.pull(self.name, refspec, progress, **kwargs)
-
+
def push(self, refspec=None, progress=None, **kwargs):
"""Push changes from source branch in refspec to target branch in refspec.
-
+
: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
-
+
:param kwargs: Additional arguments to be passed to git-push
:return:
IterableList(PushInfo, ...) iterable list of PushInfo instances, each
@@ -268,7 +270,7 @@ class Remote(LazyMixin, Iterable):
If the operation fails completely, the length of the returned IterableList will
be null."""
return self.repo.push(self.name, refspec, progress, **kwargs)
-
+
@property
def config_reader(self):
"""
@@ -276,7 +278,7 @@ class Remote(LazyMixin, Iterable):
GitConfigParser compatible object able to read options for only our remote.
Hence you may simple type config.get("pushurl") to obtain the information"""
return self._config_reader
-
+
@property
def config_writer(self):
"""
@@ -284,16 +286,16 @@ class Remote(LazyMixin, Iterable):
:note:
You can only own one writer at a time - delete it to release the
configuration file and make it useable by others.
-
+
To assure consistent results, you should only query options through the
writer. Once you are done writing, you are free to use the config reader
once again."""
writer = self.repo.config_writer()
-
+
# clear our cache to assure we re-read the possibly changed configuration
try:
del(self._config_reader)
except AttributeError:
pass
- #END handle exception
+ # END handle exception
return SectionConstraint(writer, self._config_section_name())
diff --git a/git/repo.py b/git/repo.py
index 0a52a3df..640ff19e 100644
--- a/git/repo.py
+++ b/git/repo.py
@@ -14,21 +14,22 @@ __all__ = ('Repo', )
class Repo(CmdCompatibilityGitDB):
+
"""Represents a git repository and allows you to query references,
gather commit information, generate diffs, create and clone repositories query
the log.
-
+
The following attributes are worth using:
-
+
'working_dir' is the working directory of the git command, wich is the working tree
directory if available or the .git directory in case of bare repositories
-
+
'working_tree_dir' is the working tree directory, but will raise AssertionError
if we are a bare repository.
-
+
'git_dir' is the .git repository directoy, which is always set."""
-
- def __init__(self, path=None, odbt = None):
+
+ def __init__(self, path=None, odbt=None):
"""Create a new Repo instance
:param path: is the path to either the root git directory or the bare git repo::
@@ -41,5 +42,5 @@ class Repo(CmdCompatibilityGitDB):
:return: git.Repo """
if odbt is not None:
warnings.warn("deprecated use of odbt", DeprecationWarning)
- #END handle old parameter
+ # END handle old parameter
super(Repo, self).__init__(path)
diff --git a/git/stream.py b/git/stream.py
index a47d7bc7..64fe8a2c 100644
--- a/git/stream.py
+++ b/git/stream.py
@@ -9,22 +9,22 @@ import mmap
import os
from fun import (
- msb_size,
- stream_copy,
- apply_delta_data,
- connect_deltas,
- DeltaChunkList,
- delta_types
- )
+ msb_size,
+ stream_copy,
+ apply_delta_data,
+ connect_deltas,
+ DeltaChunkList,
+ delta_types
+)
from util import (
- allocate_memory,
- LazyMixin,
- make_sha,
- write,
- close,
- zlib
- )
+ allocate_memory,
+ LazyMixin,
+ make_sha,
+ write,
+ close,
+ zlib
+)
has_perf_mod = False
try:
@@ -33,35 +33,36 @@ try:
except ImportError:
pass
-__all__ = ( 'DecompressMemMapReader', 'FDCompressedSha1Writer', 'DeltaApplyReader',
- 'Sha1Writer', 'FlexibleSha1Writer', 'ZippedStoreShaWriter', 'FDCompressedSha1Writer',
- 'FDStream', 'NullStream')
+__all__ = ('DecompressMemMapReader', 'FDCompressedSha1Writer', 'DeltaApplyReader',
+ 'Sha1Writer', 'FlexibleSha1Writer', 'ZippedStoreShaWriter', 'FDCompressedSha1Writer',
+ 'FDStream', 'NullStream')
#{ RO Streams
class DecompressMemMapReader(LazyMixin):
+
"""Reads data in chunks from a memory map and decompresses it. The client sees
only the uncompressed data, respective file-like read calls are handling on-demand
buffered decompression accordingly
-
+
A constraint on the total size of bytes is activated, simulating
a logical file within a possibly larger physical memory area
-
+
To read efficiently, you clearly don't want to read individual bytes, instead,
read a few kilobytes at least.
-
+
:note: The chunk-size should be carefully selected as it will involve quite a bit
of string copying due to the way the zlib is implemented. Its very wasteful,
hence we try to find a good tradeoff between allocation time and number of
times we actually allocate. An own zlib implementation would be good here
to better support streamed reading - it would only need to keep the mmap
and decompress it into chunks, thats all ... """
- __slots__ = ('_m', '_zip', '_buf', '_buflen', '_br', '_cws', '_cwe', '_s', '_close',
- '_cbr', '_phi')
-
- max_read_size = 512*1024 # currently unused
-
+ __slots__ = ('_m', '_zip', '_buf', '_buflen', '_br', '_cws', '_cwe', '_s', '_close',
+ '_cbr', '_phi')
+
+ max_read_size = 512 * 1024 # currently unused
+
def __init__(self, m, close_on_deletion, size=None):
"""Initialize with mmap for stream reading
:param m: must be content data - use new if you have object data and no size"""
@@ -77,22 +78,22 @@ class DecompressMemMapReader(LazyMixin):
self._cbr = 0 # number of compressed bytes read
self._phi = False # is True if we parsed the header info
self._close = close_on_deletion # close the memmap on deletion ?
-
+
def _set_cache_(self, attr):
assert attr == '_s'
- # only happens for size, which is a marker to indicate we still
+ # only happens for size, which is a marker to indicate we still
# have to parse the header from the stream
self._parse_header_info()
-
+
def __del__(self):
if self._close:
self._m.close()
# END handle resource freeing
-
+
def _parse_header_info(self):
"""If this stream contains object data, parse the header info and skip the
stream to a point where each read will yield object content
-
+
:return: parsed type_string, size"""
# read header
maxb = 512 # should really be enough, cgit uses 8192 I believe
@@ -102,26 +103,26 @@ class DecompressMemMapReader(LazyMixin):
type, size = hdr[:hdrend].split(" ")
size = int(size)
self._s = size
-
+
# adjust internal state to match actual header length that we ignore
# The buffer will be depleted first on future reads
self._br = 0
hdrend += 1 # count terminating \0
self._buf = StringIO(hdr[hdrend:])
self._buflen = len(hdr) - hdrend
-
+
self._phi = True
-
+
return type, size
-
- #{ Interface
-
+
+ #{ Interface
+
@classmethod
def new(self, m, close_on_deletion=False):
"""Create a new DecompressMemMapReader instance for acting as a read-only stream
This method parses the object header from m and returns the parsed
type and size, as well as the created stream instance.
-
+
:param m: memory map on which to oparate. It must be object data ( header + contents )
:param close_on_deletion: if True, the memory map will be closed once we are
being deleted"""
@@ -131,30 +132,30 @@ class DecompressMemMapReader(LazyMixin):
def data(self):
""":return: random access compatible data we are working on"""
- return self._m
-
+ return self._m
+
def compressed_bytes_read(self):
"""
:return: number of compressed bytes read. This includes the bytes it
took to decompress the header ( if there was one )"""
# ABSTRACT: When decompressing a byte stream, it can be that the first
- # x bytes which were requested match the first x bytes in the loosely
+ # x bytes which were requested match the first x bytes in the loosely
# compressed datastream. This is the worst-case assumption that the reader
# does, it assumes that it will get at least X bytes from X compressed bytes
# in call cases.
- # The caveat is that the object, according to our known uncompressed size,
+ # The caveat is that the object, according to our known uncompressed size,
# is already complete, but there are still some bytes left in the compressed
# stream that contribute to the amount of compressed bytes.
# How can we know that we are truly done, and have read all bytes we need
- # to read ?
- # Without help, we cannot know, as we need to obtain the status of the
+ # to read ?
+ # Without help, we cannot know, as we need to obtain the status of the
# decompression. If it is not finished, we need to decompress more data
# until it is finished, to yield the actual number of compressed bytes
# belonging to the decompressed object
- # We are using a custom zlib module for this, if its not present,
+ # We are using a custom zlib module for this, if its not present,
# we try to put in additional bytes up for decompression if feasible
# and check for the unused_data.
-
+
# Only scrub the stream forward if we are officially done with the
# bytes we were to have.
if self._br == self._s and not self._zip.unused_data:
@@ -171,45 +172,44 @@ class DecompressMemMapReader(LazyMixin):
self.read(mmap.PAGESIZE)
# END scrub-loop default zlib
# END handle stream scrubbing
-
+
# reset bytes read, just to be sure
self._br = self._s
# END handle stream scrubbing
-
+
# unused data ends up in the unconsumed tail, which was removed
# from the count already
return self._cbr
-
- #} END interface
-
+
+ #} END interface
+
def seek(self, offset, whence=getattr(os, 'SEEK_SET', 0)):
"""Allows to reset the stream to restart reading
:raise ValueError: If offset and whence are not 0"""
if offset != 0 or whence != getattr(os, 'SEEK_SET', 0):
raise ValueError("Can only seek to position 0")
# END handle offset
-
+
self._zip = zlib.decompressobj()
self._br = self._cws = self._cwe = self._cbr = 0
if self._phi:
self._phi = False
del(self._s) # trigger header parsing on first access
# END skip header
-
+
def read(self, size=-1):
if size < 1:
size = self._s - self._br
else:
size = min(size, self._s - self._br)
# END clamp size
-
+
if size == 0:
return str()
# END handle depletion
-
-
- # deplete the buffer, then just continue using the decompress object
- # which has an own buffer. We just need this to transparently parse the
+
+ # deplete the buffer, then just continue using the decompress object
+ # which has an own buffer. We just need this to transparently parse the
# header from the zlib stream
dat = str()
if self._buf:
@@ -223,26 +223,26 @@ class DecompressMemMapReader(LazyMixin):
dat = self._buf.read() # ouch, duplicates data
size -= self._buflen
self._br += self._buflen
-
+
self._buflen = 0
self._buf = None
# END handle buffer len
# END handle buffer
-
+
# decompress some data
- # Abstract: zlib needs to operate on chunks of our memory map ( which may
+ # Abstract: zlib needs to operate on chunks of our memory map ( which may
# be large ), as it will otherwise and always fill in the 'unconsumed_tail'
- # attribute which possible reads our whole map to the end, forcing
+ # attribute which possible reads our whole map to the end, forcing
# everything to be read from disk even though just a portion was requested.
- # As this would be a nogo, we workaround it by passing only chunks of data,
- # moving the window into the memory map along as we decompress, which keeps
+ # As this would be a nogo, we workaround it by passing only chunks of data,
+ # moving the window into the memory map along as we decompress, which keeps
# the tail smaller than our chunk-size. This causes 'only' the chunk to be
# copied once, and another copy of a part of it when it creates the unconsumed
# tail. We have to use it to hand in the appropriate amount of bytes durin g
# the next read.
tail = self._zip.unconsumed_tail
if tail:
- # move the window, make it as large as size demands. For code-clarity,
+ # move the window, make it as large as size demands. For code-clarity,
# we just take the chunk from our map again instead of reusing the unconsumed
# tail. The latter one would safe some memory copying, but we could end up
# with not getting enough data uncompressed, so we had to sort that out as well.
@@ -253,18 +253,17 @@ class DecompressMemMapReader(LazyMixin):
else:
cws = self._cws
self._cws = self._cwe
- self._cwe = cws + size
+ self._cwe = cws + size
# END handle tail
-
-
+
# if window is too small, make it larger so zip can decompress something
if self._cwe - self._cws < 8:
self._cwe = self._cws + 8
# END adjust winsize
-
- # takes a slice, but doesn't copy the data, it says ...
+
+ # takes a slice, but doesn't copy the data, it says ...
indata = buffer(self._m, self._cws, self._cwe - self._cws)
-
+
# get the actual window end to be sure we don't use it for computations
self._cwe = self._cws + len(indata)
dcompdat = self._zip.decompress(indata, size)
@@ -274,85 +273,86 @@ class DecompressMemMapReader(LazyMixin):
# if we hit the end of the stream
self._cbr += len(indata) - len(self._zip.unconsumed_tail)
self._br += len(dcompdat)
-
+
if dat:
dcompdat = dat + dcompdat
# END prepend our cached data
-
- # it can happen, depending on the compression, that we get less bytes
- # than ordered as it needs the final portion of the data as well.
+
+ # it can happen, depending on the compression, that we get less bytes
+ # than ordered as it needs the final portion of the data as well.
# Recursively resolve that.
# Note: dcompdat can be empty even though we still appear to have bytes
# to read, if we are called by compressed_bytes_read - it manipulates
# us to empty the stream
if dcompdat and (len(dcompdat) - len(dat)) < size and self._br < self._s:
- dcompdat += self.read(size-len(dcompdat))
+ dcompdat += self.read(size - len(dcompdat))
# END handle special case
return dcompdat
-
+
class DeltaApplyReader(LazyMixin):
+
"""A reader which dynamically applies pack deltas to a base object, keeping the
memory demands to a minimum.
-
+
The size of the final object is only obtainable once all deltas have been
applied, unless it is retrieved from a pack index.
-
+
The uncompressed Delta has the following layout (MSB being a most significant
bit encoded dynamic size):
-
+
* MSB Source Size - the size of the base against which the delta was created
* MSB Target Size - the size of the resulting data after the delta was applied
* A list of one byte commands (cmd) which are followed by a specific protocol:
-
+
* cmd & 0x80 - copy delta_data[offset:offset+size]
-
+
* Followed by an encoded offset into the delta data
* Followed by an encoded size of the chunk to copy
-
+
* cmd & 0x7f - insert
-
+
* insert cmd bytes from the delta buffer into the output stream
-
+
* cmd == 0 - invalid operation ( or error in delta stream )
"""
__slots__ = (
- "_bstream", # base stream to which to apply the deltas
- "_dstreams", # tuple of delta stream readers
- "_mm_target", # memory map of the delta-applied data
- "_size", # actual number of bytes in _mm_target
- "_br" # number of bytes read
- )
-
+ "_bstream", # base stream to which to apply the deltas
+ "_dstreams", # tuple of delta stream readers
+ "_mm_target", # memory map of the delta-applied data
+ "_size", # actual number of bytes in _mm_target
+ "_br" # number of bytes read
+ )
+
#{ Configuration
- k_max_memory_move = 250*1000*1000
+ k_max_memory_move = 250 * 1000 * 1000
#} END configuration
-
+
def __init__(self, stream_list):
"""Initialize this instance with a list of streams, the first stream being
the delta to apply on top of all following deltas, the last stream being the
base object onto which to apply the deltas"""
assert len(stream_list) > 1, "Need at least one delta and one base stream"
-
+
self._bstream = stream_list[-1]
self._dstreams = tuple(stream_list[:-1])
self._br = 0
-
+
def _set_cache_too_slow_without_c(self, attr):
- # the direct algorithm is fastest and most direct if there is only one
+ # the direct algorithm is fastest and most direct if there is only one
# delta. Also, the extra overhead might not be worth it for items smaller
- # than X - definitely the case in python, every function call costs
+ # than X - definitely the case in python, every function call costs
# huge amounts of time
# if len(self._dstreams) * self._bstream.size < self.k_max_memory_move:
if len(self._dstreams) == 1:
return self._set_cache_brute_(attr)
-
- # Aggregate all deltas into one delta in reverse order. Hence we take
+
+ # Aggregate all deltas into one delta in reverse order. Hence we take
# the last delta, and reverse-merge its ancestor delta, until we receive
# the final delta data stream.
# print "Handling %i delta streams, sizes: %s" % (len(self._dstreams), [ds.size for ds in self._dstreams])
dcl = connect_deltas(self._dstreams)
-
+
# call len directly, as the (optional) c version doesn't implement the sequence
# protocol
if dcl.rbound() == 0:
@@ -360,22 +360,22 @@ class DeltaApplyReader(LazyMixin):
self._mm_target = allocate_memory(0)
return
# END handle empty list
-
+
self._size = dcl.rbound()
self._mm_target = allocate_memory(self._size)
-
+
bbuf = allocate_memory(self._bstream.size)
stream_copy(self._bstream.read, bbuf.write, self._bstream.size, 256 * mmap.PAGESIZE)
-
+
# APPLY CHUNKS
write = self._mm_target.write
dcl.apply(bbuf, write)
-
+
self._mm_target.seek(0)
-
+
def _set_cache_brute_(self, attr):
"""If we are here, we apply the actual deltas"""
-
+
# TODO: There should be a special case if there is only one stream
# Then the default-git algorithm should perform a tad faster, as the
# delta is not peaked into, causing less overhead.
@@ -388,37 +388,36 @@ class DeltaApplyReader(LazyMixin):
buffer_info_list.append((buffer(buf, offset), offset, src_size, target_size))
max_target_size = max(max_target_size, target_size)
# END for each delta stream
-
+
# sanity check - the first delta to apply should have the same source
# size as our actual base stream
base_size = self._bstream.size
target_size = max_target_size
-
+
# if we have more than 1 delta to apply, we will swap buffers, hence we must
# assure that all buffers we use are large enough to hold all the results
if len(self._dstreams) > 1:
base_size = target_size = max(base_size, max_target_size)
# END adjust buffer sizes
-
-
+
# Allocate private memory map big enough to hold the first base buffer
# We need random access to it
bbuf = allocate_memory(base_size)
stream_copy(self._bstream.read, bbuf.write, base_size, 256 * mmap.PAGESIZE)
-
+
# allocate memory map large enough for the largest (intermediate) target
- # We will use it as scratch space for all delta ops. If the final
+ # We will use it as scratch space for all delta ops. If the final
# target buffer is smaller than our allocated space, we just use parts
# of it upon return.
tbuf = allocate_memory(target_size)
-
- # for each delta to apply, memory map the decompressed delta and
+
+ # for each delta to apply, memory map the decompressed delta and
# work on the op-codes to reconstruct everything.
# For the actual copying, we use a seek and write pattern of buffer
# slices.
final_target_size = None
for (dbuf, offset, src_size, target_size), dstream in reversed(zip(buffer_info_list, self._dstreams)):
- # allocate a buffer to hold all delta data - fill in the data for
+ # allocate a buffer to hold all delta data - fill in the data for
# fast access. We do this as we know that reading individual bytes
# from our stream would be slower than necessary ( although possible )
# The dbuf buffer contains commands after the first two MSB sizes, the
@@ -426,38 +425,37 @@ class DeltaApplyReader(LazyMixin):
ddata = allocate_memory(dstream.size - offset)
ddata.write(dbuf)
# read the rest from the stream. The size we give is larger than necessary
- stream_copy(dstream.read, ddata.write, dstream.size, 256*mmap.PAGESIZE)
-
+ stream_copy(dstream.read, ddata.write, dstream.size, 256 * mmap.PAGESIZE)
+
#######################################################################
if 'c_apply_delta' in globals():
- c_apply_delta(bbuf, ddata, tbuf);
+ c_apply_delta(bbuf, ddata, tbuf)
else:
apply_delta_data(bbuf, src_size, ddata, len(ddata), tbuf.write)
#######################################################################
-
- # finally, swap out source and target buffers. The target is now the
+
+ # finally, swap out source and target buffers. The target is now the
# base for the next delta to apply
bbuf, tbuf = tbuf, bbuf
bbuf.seek(0)
tbuf.seek(0)
final_target_size = target_size
# END for each delta to apply
-
+
# its already seeked to 0, constrain it to the actual size
# NOTE: in the end of the loop, it swaps buffers, hence our target buffer
# is not tbuf, but bbuf !
self._mm_target = bbuf
self._size = final_target_size
-
-
+
#{ Configuration
if not has_perf_mod:
_set_cache_ = _set_cache_brute_
else:
_set_cache_ = _set_cache_too_slow_without_c
-
+
#} END configuration
-
+
def read(self, count=0):
bl = self._size - self._br # bytes left
if count < 1 or count > bl:
@@ -467,73 +465,74 @@ class DeltaApplyReader(LazyMixin):
data = self._mm_target.read(count)
self._br += len(data)
return data
-
+
def seek(self, offset, whence=getattr(os, 'SEEK_SET', 0)):
"""Allows to reset the stream to restart reading
-
+
:raise ValueError: If offset and whence are not 0"""
if offset != 0 or whence != getattr(os, 'SEEK_SET', 0):
raise ValueError("Can only seek to position 0")
# END handle offset
self._br = 0
self._mm_target.seek(0)
-
- #{ Interface
-
+
+ #{ Interface
+
@classmethod
def new(cls, stream_list):
"""
Convert the given list of streams into a stream which resolves deltas
when reading from it.
-
+
:param stream_list: two or more stream objects, first stream is a Delta
to the object that you want to resolve, followed by N additional delta
streams. The list's last stream must be a non-delta stream.
-
+
:return: Non-Delta OPackStream object whose stream can be used to obtain
the decompressed resolved data
:raise ValueError: if the stream list cannot be handled"""
if len(stream_list) < 2:
raise ValueError("Need at least two streams")
# END single object special handling
-
+
if stream_list[-1].type_id in delta_types:
- raise ValueError("Cannot resolve deltas if there is no base object stream, last one was type: %s" % stream_list[-1].type)
+ raise ValueError(
+ "Cannot resolve deltas if there is no base object stream, last one was type: %s" % stream_list[-1].type)
# END check stream
-
+
return cls(stream_list)
-
+
#} END interface
-
-
+
#{ OInfo like Interface
-
+
@property
def type(self):
return self._bstream.type
-
+
@property
def type_id(self):
return self._bstream.type_id
-
+
@property
def size(self):
""":return: number of uncompressed bytes in the stream"""
return self._size
-
- #} END oinfo like interface
-
-
+
+ #} END oinfo like interface
+
+
#} END RO streams
#{ W Streams
class Sha1Writer(object):
+
"""Simple stream writer which produces a sha whenever you like as it degests
everything it is supposed to write"""
__slots__ = "sha1"
-
+
def __init__(self):
self.sha1 = make_sha()
@@ -545,53 +544,56 @@ class Sha1Writer(object):
self.sha1.update(data)
return len(data)
- # END stream interface
+ # END stream interface
#{ Interface
-
- def sha(self, as_hex = False):
+
+ def sha(self, as_hex=False):
""":return: sha so far
:param as_hex: if True, sha will be hex-encoded, binary otherwise"""
if as_hex:
return self.sha1.hexdigest()
return self.sha1.digest()
-
- #} END interface
+
+ #} END interface
class FlexibleSha1Writer(Sha1Writer):
+
"""Writer producing a sha1 while passing on the written bytes to the given
write function"""
__slots__ = 'writer'
-
+
def __init__(self, writer):
Sha1Writer.__init__(self)
self.writer = writer
-
+
def write(self, data):
Sha1Writer.write(self, data)
self.writer(data)
class ZippedStoreShaWriter(Sha1Writer):
+
"""Remembers everything someone writes to it and generates a sha"""
__slots__ = ('buf', 'zip')
+
def __init__(self):
Sha1Writer.__init__(self)
self.buf = StringIO()
self.zip = zlib.compressobj(zlib.Z_BEST_SPEED)
-
+
def __getattr__(self, attr):
return getattr(self.buf, attr)
-
+
def write(self, data):
alen = Sha1Writer.write(self, data)
self.buf.write(self.zip.compress(data))
return alen
-
+
def close(self):
self.buf.write(self.zip.flush())
-
+
def seek(self, offset, whence=getattr(os, 'SEEK_SET', 0)):
"""Seeking currently only supports to rewind written data
Multiple writes are not supported"""
@@ -599,23 +601,24 @@ class ZippedStoreShaWriter(Sha1Writer):
raise ValueError("Can only seek to position 0")
# END handle offset
self.buf.seek(0)
-
+
def getvalue(self):
""":return: string value from the current stream position to the end"""
return self.buf.getvalue()
class FDCompressedSha1Writer(Sha1Writer):
+
"""Digests data written to it, making the sha available, then compress the
data and write it to the file descriptor
-
+
:note: operates on raw file descriptors
:note: for this to work, you have to use the close-method of this instance"""
__slots__ = ("fd", "sha1", "zip")
-
+
# default exception
exc = IOError("Failed to write all bytes to filedescriptor")
-
+
def __init__(self, fd):
super(FDCompressedSha1Writer, self).__init__()
self.fd = fd
@@ -643,52 +646,53 @@ class FDCompressedSha1Writer(Sha1Writer):
class FDStream(object):
+
"""A simple wrapper providing the most basic functions on a file descriptor
with the fileobject interface. Cannot use os.fdopen as the resulting stream
takes ownership"""
__slots__ = ("_fd", '_pos')
+
def __init__(self, fd):
self._fd = fd
self._pos = 0
-
+
def write(self, data):
self._pos += len(data)
os.write(self._fd, data)
-
+
def read(self, count=0):
if count == 0:
count = os.path.getsize(self._filepath)
# END handle read everything
-
+
bytes = os.read(self._fd, count)
self._pos += len(bytes)
return bytes
-
+
def fileno(self):
return self._fd
-
+
def tell(self):
return self._pos
-
+
def close(self):
close(self._fd)
class NullStream(object):
+
"""A stream that does nothing but providing a stream interface.
Use it like /dev/null"""
__slots__ = tuple()
-
+
def read(self, size=0):
return ''
-
+
def close(self):
pass
-
+
def write(self, data):
return len(data)
#} END W streams
-
-
diff --git a/git/test/__init__.py b/git/test/__init__.py
index f71cbdf0..bc832619 100644
--- a/git/test/__init__.py
+++ b/git/test/__init__.py
@@ -6,9 +6,9 @@
import git.util
+
def _init_pool():
"""Assure the pool is actually threaded"""
size = 2
print "Setting ThreadPool to %i" % size
git.util.pool.set_size(size)
-
diff --git a/git/test/db/base.py b/git/test/db/base.py
index 80cb9ebb..dd8e9d8f 100644
--- a/git/test/db/base.py
+++ b/git/test/db/base.py
@@ -14,7 +14,8 @@ from git.util import join_path_native
from git.exc import BadObject
from git.util import hex_to_bin, bin_to_hex
-import os, sys
+import os
+import sys
import tempfile
import shutil
from cStringIO import StringIO
@@ -24,12 +25,13 @@ from git.db.compat import RepoCompatibilityInterface
class RepoGlobalsItemDeletorMetaCls(GlobalsItemDeletorMetaCls):
ModuleToDelete = 'RepoBase'
-
+
class RepoBase(TestDBBase):
+
"""Basic test for everything a fully implemented repository should support"""
__metaclass__ = RepoGlobalsItemDeletorMetaCls
-
+
def test_new_should_raise_on_invalid_repo_location(self):
self.failUnlessRaises(InvalidGitRepositoryError, self.RepoCls, tempfile.gettempdir())
@@ -55,21 +57,21 @@ class RepoBase(TestDBBase):
def test_heads_should_populate_head_data(self):
for head in self.rorepo.heads:
assert head.name
- assert isinstance(head.commit,Commit)
- # END for each head
-
+ assert isinstance(head.commit, Commit)
+ # END for each head
+
assert isinstance(self.rorepo.heads.master, Head)
assert isinstance(self.rorepo.heads['master'], Head)
-
+
def test_tree_from_revision(self):
tree = self.rorepo.tree('0.1.6')
- assert len(tree.hexsha) == 40
+ assert len(tree.hexsha) == 40
assert tree.type == "tree"
assert self.rorepo.tree(tree) == tree
-
+
# try from invalid revision that does not exist
self.failUnlessRaises(BadObject, self.rorepo.tree, 'hello world')
-
+
def test_commit_from_revision(self):
commit = self.rorepo.commit('0.1.4')
assert commit.type == 'commit'
@@ -79,7 +81,7 @@ class RepoBase(TestDBBase):
mc = 10
commits = list(self.rorepo.iter_commits('0.1.6', max_count=mc))
assert len(commits) == mc
-
+
c = commits[0]
assert_equal('9a4b1d4d11eee3c5362a4152216376e634bd14cf', c.hexsha)
assert_equal(["c76852d0bff115720af3f27acdb084c59361e5f6"], [p.hexsha for p in c.parents])
@@ -87,11 +89,11 @@ class RepoBase(TestDBBase):
assert_equal("Michael Trier", c.author.name)
assert_equal("mtrier@gmail.com", c.author.email)
assert_equal(1232829715, c.authored_date)
- assert_equal(5*3600, c.author_tz_offset)
+ assert_equal(5 * 3600, c.author_tz_offset)
assert_equal("Michael Trier", c.committer.name)
assert_equal("mtrier@gmail.com", c.committer.email)
assert_equal(1232829715, c.committed_date)
- assert_equal(5*3600, c.committer_tz_offset)
+ assert_equal(5 * 3600, c.committer_tz_offset)
assert_equal("Bumped version 0.1.6\n", c.message)
c = commits[1]
@@ -106,34 +108,32 @@ class RepoBase(TestDBBase):
# END for each tree
assert num_trees == mc
-
def _assert_empty_repo(self, repo):
- # test all kinds of things with an empty, freshly initialized repo.
+ # test all kinds of things with an empty, freshly initialized repo.
# It should throw good errors
-
+
# entries should be empty
assert len(repo.index.entries) == 0
-
+
# head is accessible
assert repo.head
assert repo.head.ref
assert not repo.head.is_valid()
-
+
# we can change the head to some other ref
head_ref = Head.from_path(repo, Head.to_full_path('some_head'))
assert not head_ref.is_valid()
repo.head.ref = head_ref
-
+
# is_dirty can handle all kwargs
for args in ((1, 0, 0), (0, 1, 0), (0, 0, 1)):
assert not repo.is_dirty(*args)
- # END for each arg
-
+ # END for each arg
+
# we can add a file to the index ( if we are not bare )
if not repo.bare:
pass
# END test repos with working tree
-
def test_init(self):
prev_cwd = os.getcwd()
@@ -148,15 +148,14 @@ class RepoBase(TestDBBase):
assert isinstance(r, self.RepoCls)
assert r.bare == True
assert os.path.isdir(r.git_dir)
-
+
self._assert_empty_repo(r)
-
+
# test clone
clone_path = path + "_clone"
rc = r.clone(clone_path)
self._assert_empty_repo(rc)
-
-
+
try:
shutil.rmtree(clone_path)
except OSError:
@@ -164,11 +163,11 @@ class RepoBase(TestDBBase):
# of the parent directory
pass
# END exception handling
-
+
# try again, this time with the absolute version
rc = self.RepoCls.clone_from(r.git_dir, clone_path)
self._assert_empty_repo(rc)
-
+
shutil.rmtree(git_dir_abs)
try:
shutil.rmtree(clone_path)
@@ -177,14 +176,14 @@ class RepoBase(TestDBBase):
# of the parent directory
pass
# END exception handling
-
+
# END for each path
-
+
os.makedirs(git_dir_rela)
os.chdir(git_dir_rela)
r = self.RepoCls.init(bare=False)
r.bare == False
-
+
self._assert_empty_repo(r)
finally:
try:
@@ -193,26 +192,26 @@ class RepoBase(TestDBBase):
pass
os.chdir(prev_cwd)
# END restore previous state
-
+
def test_bare_property(self):
if isinstance(self.rorepo, RepoCompatibilityInterface):
self.rorepo.bare
- #END handle compatability
+ # END handle compatability
self.rorepo.is_bare
def test_daemon_export(self):
orig_val = self.rorepo.daemon_export
self.rorepo.daemon_export = not orig_val
- assert self.rorepo.daemon_export == ( not orig_val )
+ assert self.rorepo.daemon_export == (not orig_val)
self.rorepo.daemon_export = orig_val
assert self.rorepo.daemon_export == orig_val
-
+
def test_alternates(self):
cur_alternates = self.rorepo.alternates
# empty alternates
self.rorepo.alternates = []
assert self.rorepo.alternates == []
- alts = [ "other/location", "this/location" ]
+ alts = ["other/location", "this/location"]
self.rorepo.alternates = alts
assert alts == self.rorepo.alternates
self.rorepo.alternates = cur_alternates
@@ -224,13 +223,13 @@ class RepoBase(TestDBBase):
orig_value = self.rorepo._bare
self.rorepo._bare = True
assert_false(self.rorepo.is_dirty())
- self.rorepo._bare = orig_value
+ self.rorepo._bare = orig_value
def test_is_dirty(self):
self.rorepo._bare = False
- for index in (0,1):
- for working_tree in (0,1):
- for untracked_files in (0,1):
+ for index in (0, 1):
+ for working_tree in (0, 1):
+ for untracked_files in (0, 1):
assert self.rorepo.is_dirty(index, working_tree, untracked_files) in (True, False)
# END untracked files
# END working tree
@@ -246,28 +245,28 @@ class RepoBase(TestDBBase):
def test_index(self):
index = self.rorepo.index
assert isinstance(index, IndexFile)
-
+
def test_tag(self):
assert self.rorepo.tag('0.1.5').commit
assert self.rorepo.tag('refs/tags/0.1.5').commit
-
+
def test_archive(self):
tmpfile = os.tmpfile()
self.rorepo.archive(tmpfile, '0.1.5')
assert tmpfile.tell()
-
+
@patch.object(Git, '_call_process')
def test_should_display_blame_information(self, git):
git.return_value = fixture('blame')
- b = self.rorepo.blame( 'master', 'lib/git.py')
+ b = self.rorepo.blame('master', 'lib/git.py')
assert_equal(13, len(b))
- assert_equal( 2, len(b[0]) )
+ assert_equal(2, len(b[0]))
# assert_equal(25, reduce(lambda acc, x: acc + len(x[-1]), b))
assert_equal(hash(b[0][0]), hash(b[9][0]))
c = b[0][0]
assert_true(git.called)
assert_equal(git.call_args, (('blame', 'master', '--', 'lib/git.py'), {'p': True}))
-
+
assert_equal('634396b2f541a9f2d58b00be1a07f0c358b999b3', c.hexsha)
assert_equal('Tom Preston-Werner', c.author.name)
assert_equal('tom@mojombo.com', c.author.email)
@@ -276,35 +275,35 @@ class RepoBase(TestDBBase):
assert_equal('tom@mojombo.com', c.committer.email)
assert_equal(1191997100, c.committed_date)
assert_equal('initial grit setup', c.message)
-
+
# test the 'lines per commit' entries
tlist = b[0][1]
- assert_true( tlist )
- assert_true( isinstance( tlist[0], basestring ) )
- assert_true( len( tlist ) < sum( len(t) for t in tlist ) ) # test for single-char bug
-
+ assert_true(tlist)
+ assert_true(isinstance(tlist[0], basestring))
+ assert_true(len(tlist) < sum(len(t) for t in tlist)) # test for single-char bug
+
def test_blame_real(self):
c = 0
for item in self.rorepo.head.commit.tree.traverse(
- predicate=lambda i, d: i.type == 'blob' and i.path.endswith('.py')):
+ predicate=lambda i, d: i.type == 'blob' and i.path.endswith('.py')):
c += 1
b = self.rorepo.blame(self.rorepo.head, item.path)
- #END for each item to traverse
+ # END for each item to traverse
assert c
-
+
def test_untracked_files(self):
base = self.rorepo.working_tree_dir
- files = ( join_path_native(base, "__test_myfile"),
- join_path_native(base, "__test_other_file") )
+ files = (join_path_native(base, "__test_myfile"),
+ join_path_native(base, "__test_other_file"))
num_recently_untracked = 0
try:
for fpath in files:
- fd = open(fpath,"wb")
+ fd = open(fpath, "wb")
fd.close()
# END for each filename
untracked_files = self.rorepo.untracked_files
num_recently_untracked = len(untracked_files)
-
+
# assure we have all names - they are relative to the git-dir
num_test_untracked = 0
for utfile in untracked_files:
@@ -314,80 +313,81 @@ class RepoBase(TestDBBase):
for fpath in files:
if os.path.isfile(fpath):
os.remove(fpath)
- # END handle files
-
+ # END handle files
+
assert len(self.rorepo.untracked_files) == (num_recently_untracked - len(files))
-
+
def test_config_reader(self):
- reader = self.rorepo.config_reader() # all config files
+ reader = self.rorepo.config_reader() # all config files
assert reader.read_only
reader = self.rorepo.config_reader("repository") # single config file
assert reader.read_only
-
+
def test_config_writer(self):
for config_level in self.rorepo.config_level:
try:
writer = self.rorepo.config_writer(config_level)
assert not writer.read_only
except IOError:
- # its okay not to get a writer for some configuration files if we
+ # its okay not to get a writer for some configuration files if we
# have no permissions
- pass
- # END for each config level
-
+ pass
+ # END for each config level
+
def test_creation_deletion(self):
- # just a very quick test to assure it generally works. There are
+ # just a very quick test to assure it generally works. There are
# specialized cases in the test_refs module
head = self.rorepo.create_head("new_head", "HEAD~1")
self.rorepo.delete_head(head)
-
+
tag = self.rorepo.create_tag("new_tag", "HEAD~2")
self.rorepo.delete_tag(tag)
self.rorepo.config_writer()
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
self.rorepo.delete_remote(remote)
-
+
def test_comparison_and_hash(self):
# this is only a preliminary test, more testing done in test_index
assert self.rorepo == self.rorepo and not (self.rorepo != self.rorepo)
assert len(set((self.rorepo, self.rorepo))) == 1
-
+
def test_git_cmd(self):
# test CatFileContentStream, just to be very sure we have no fencepost errors
# last \n is the terminating newline that it expects
l1 = "0123456789\n"
l2 = "abcdefghijklmnopqrstxy\n"
- l3 = "z\n"
+ l3 = "z\n"
d = "%s%s%s\n" % (l1, l2, l3)
-
+
l1p = l1[:5]
-
+
# full size
# size is without terminating newline
def mkfull():
- return Git.CatFileContentStream(len(d)-1, StringIO(d))
-
+ return Git.CatFileContentStream(len(d) - 1, StringIO(d))
+
ts = 5
+
def mktiny():
return Git.CatFileContentStream(ts, StringIO(d))
-
+
# readlines no limit
s = mkfull()
lines = s.readlines()
assert len(lines) == 3 and lines[-1].endswith('\n')
assert s._stream.tell() == len(d) # must have scrubbed to the end
-
+
# realines line limit
s = mkfull()
lines = s.readlines(5)
assert len(lines) == 1
-
+
# readlines on tiny sections
s = mktiny()
lines = s.readlines()
assert len(lines) == 1 and lines[0] == l1p
- assert s._stream.tell() == ts+1
-
+ assert s._stream.tell() == ts + 1
+
# readline no limit
s = mkfull()
assert s.readline() == l1
@@ -395,52 +395,51 @@ class RepoBase(TestDBBase):
assert s.readline() == l3
assert s.readline() == ''
assert s._stream.tell() == len(d)
-
+
# readline limit
s = mkfull()
assert s.readline(5) == l1p
assert s.readline() == l1[5:]
-
+
# readline on tiny section
s = mktiny()
assert s.readline() == l1p
assert s.readline() == ''
- assert s._stream.tell() == ts+1
-
+ assert s._stream.tell() == ts + 1
+
# read no limit
s = mkfull()
assert s.read() == d[:-1]
assert s.read() == ''
assert s._stream.tell() == len(d)
-
+
# read limit
s = mkfull()
assert s.read(5) == l1p
assert s.read(6) == l1[5:]
assert s._stream.tell() == 5 + 6 # its not yet done
-
+
# read tiny
s = mktiny()
assert s.read(2) == l1[:2]
assert s._stream.tell() == 2
assert s.read() == l1[2:ts]
- assert s._stream.tell() == ts+1
-
+ assert s._stream.tell() == ts + 1
+
def _assert_rev_parse_types(self, name, rev_obj):
rev_parse = self.rorepo.rev_parse
-
+
if rev_obj.type == 'tag':
rev_obj = rev_obj.object
-
+
# tree and blob type
obj = rev_parse(name + '^{tree}')
assert obj == rev_obj.tree
-
+
obj = rev_parse(name + ':CHANGES')
assert obj.type == 'blob' and obj.path == 'CHANGES'
assert rev_obj.tree['CHANGES'] == obj
-
-
+
def _assert_rev_parse(self, name):
"""tries multiple different rev-parse syntaxes with the given name
:return: parsed object"""
@@ -450,62 +449,62 @@ class RepoBase(TestDBBase):
obj = orig_obj.object
else:
obj = orig_obj
- # END deref tags by default
-
+ # END deref tags by default
+
# try history
rev = name + "~"
obj2 = rev_parse(rev)
assert obj2 == obj.parents[0]
self._assert_rev_parse_types(rev, obj2)
-
+
# history with number
ni = 11
history = [obj.parents[0]]
for pn in range(ni):
history.append(history[-1].parents[0])
# END get given amount of commits
-
+
for pn in range(11):
- rev = name + "~%i" % (pn+1)
+ rev = name + "~%i" % (pn + 1)
obj2 = rev_parse(rev)
assert obj2 == history[pn]
self._assert_rev_parse_types(rev, obj2)
# END history check
-
+
# parent ( default )
rev = name + "^"
obj2 = rev_parse(rev)
assert obj2 == obj.parents[0]
self._assert_rev_parse_types(rev, obj2)
-
+
# parent with number
for pn, parent in enumerate(obj.parents):
- rev = name + "^%i" % (pn+1)
+ rev = name + "^%i" % (pn + 1)
assert rev_parse(rev) == parent
self._assert_rev_parse_types(rev, parent)
# END for each parent
-
+
return orig_obj
-
+
@with_rw_repo('HEAD', bare=False)
def test_rw_rev_parse(self, rwrepo):
# verify it does not confuse branches with hexsha ids
ahead = rwrepo.create_head('aaaaaaaa')
assert(rwrepo.rev_parse(str(ahead)) == ahead.commit)
-
+
def test_rev_parse(self):
rev_parse = self.rorepo.rev_parse
-
+
# try special case: This one failed at some point, make sure its fixed
assert rev_parse("33ebe").hexsha == "33ebe7acec14b25c5f84f35a664803fcab2f7781"
-
+
# start from reference
num_resolved = 0
-
+
for ref in Reference.iter_items(self.rorepo):
path_tokens = ref.path.split("/")
for pt in range(len(path_tokens)):
- path_section = '/'.join(path_tokens[-(pt+1):])
+ path_section = '/'.join(path_tokens[-(pt + 1):])
try:
obj = self._assert_rev_parse(path_section)
assert obj.type == ref.object.type
@@ -518,106 +517,102 @@ class RepoBase(TestDBBase):
# END for each token
# END for each reference
assert num_resolved
-
+
# it works with tags !
tag = self._assert_rev_parse('0.1.4')
assert tag.type == 'tag'
-
+
# try full sha directly ( including type conversion )
assert tag.object == rev_parse(tag.object.hexsha)
self._assert_rev_parse_types(tag.object.hexsha, tag.object)
-
-
+
# multiple tree types result in the same tree: HEAD^{tree}^{tree}:CHANGES
rev = '0.1.4^{tree}^{tree}'
assert rev_parse(rev) == tag.object.tree
- assert rev_parse(rev+':CHANGES') == tag.object.tree['CHANGES']
-
-
+ assert rev_parse(rev + ':CHANGES') == tag.object.tree['CHANGES']
+
# try to get parents from first revision - it should fail as no such revision
# exists
first_rev = "33ebe7acec14b25c5f84f35a664803fcab2f7781"
commit = rev_parse(first_rev)
assert len(commit.parents) == 0
assert commit.hexsha == first_rev
- self.failUnlessRaises(BadObject, rev_parse, first_rev+"~")
- self.failUnlessRaises(BadObject, rev_parse, first_rev+"^")
-
+ self.failUnlessRaises(BadObject, rev_parse, first_rev + "~")
+ self.failUnlessRaises(BadObject, rev_parse, first_rev + "^")
+
# short SHA1
commit2 = rev_parse(first_rev[:20])
assert commit2 == commit
commit2 = rev_parse(first_rev[:5])
assert commit2 == commit
-
-
+
# todo: dereference tag into a blob 0.1.7^{blob} - quite a special one
# needs a tag which points to a blob
-
-
+
# ref^0 returns commit being pointed to, same with ref~0, and ^{}
tag = rev_parse('0.1.4')
for token in (('~0', '^0', '^{}')):
assert tag.object == rev_parse('0.1.4%s' % token)
# END handle multiple tokens
-
+
# try partial parsing
max_items = 40
for i, binsha in enumerate(self.rorepo.odb.sha_iter()):
- assert rev_parse(bin_to_hex(binsha)[:8-(i%2)]).binsha == binsha
+ assert rev_parse(bin_to_hex(binsha)[:8 - (i % 2)]).binsha == binsha
if i > max_items:
# this is rather slow currently, as rev_parse returns an object
# which requires accessing packs, it has some additional overhead
break
# END for each binsha in repo
-
+
# missing closing brace commit^{tree
self.failUnlessRaises(ValueError, rev_parse, '0.1.4^{tree')
-
+
# missing starting brace
self.failUnlessRaises(ValueError, rev_parse, '0.1.4^tree}')
-
+
# REVLOG
#######
head = self.rorepo.head
-
+
# need to specify a ref when using the @ syntax
self.failUnlessRaises(BadObject, rev_parse, "%s@{0}" % head.commit.hexsha)
-
+
# uses HEAD.ref by default
assert rev_parse('@{0}') == head.commit
if not head.is_detached:
refspec = '%s@{0}' % head.ref.name
assert rev_parse(refspec) == head.ref.commit
# all additional specs work as well
- assert rev_parse(refspec+"^{tree}") == head.commit.tree
- assert rev_parse(refspec+":CHANGES").type == 'blob'
- #END operate on non-detached head
-
+ assert rev_parse(refspec + "^{tree}") == head.commit.tree
+ assert rev_parse(refspec + ":CHANGES").type == 'blob'
+ # END operate on non-detached head
+
# the most recent previous position of the currently checked out branch
-
+
try:
assert rev_parse('@{1}') != head.commit
except IndexError:
# on new checkouts, there isn't even a single past branch position
# in the log
pass
- #END handle fresh checkouts
-
+ # END handle fresh checkouts
+
# position doesn't exist
self.failUnlessRaises(IndexError, rev_parse, '@{10000}')
-
+
# currently, nothing more is supported
self.failUnlessRaises(NotImplementedError, rev_parse, "@{1 week ago}")
-
+
def test_submodules(self):
assert len(self.rorepo.submodules) == 2 # non-recursive
# in previous configurations, we had recursive repositories so this would compare to 2
# now there is only one left, as gitdb was merged, but we have smmap instead
assert len(list(self.rorepo.iter_submodules())) == 2
-
+
assert isinstance(self.rorepo.submodule("async"), Submodule)
self.failUnlessRaises(ValueError, self.rorepo.submodule, "doesn't exist")
-
+
@with_rw_repo('HEAD', bare=False)
def test_submodule_update(self, rwrepo):
# fails in bare mode
@@ -629,13 +624,11 @@ class RepoBase(TestDBBase):
rwrepo._bare = False
if rwrepo.bare:
rwrepo.bare = False
- #END special repo handling
-
+ # END special repo handling
+
# test create submodule
sm = rwrepo.submodules[0]
sm = rwrepo.create_submodule("my_new_sub", "some_path", join_path_native(self.rorepo.working_tree_dir, sm.path))
assert isinstance(sm, Submodule)
-
+
# note: the rest of this functionality is tested in test_submodule
-
-
diff --git a/git/test/db/cmd/test_base.py b/git/test/db/cmd/test_base.py
index 890c0232..9eee7223 100644
--- a/git/test/db/cmd/test_base.py
+++ b/git/test/db/cmd/test_base.py
@@ -13,79 +13,77 @@ from git.db.cmd.base import *
from git.refs import TagReference, Reference, RemoteReference
+
class TestBase(RepoBase):
RepoCls = CmdCompatibilityGitDB
def test_basics(self):
gdb = self.rorepo
-
+
# partial to complete - works with everything
hexsha = bin_to_hex(gdb.partial_to_complete_sha_hex("0.1.6"))
assert len(hexsha) == 40
-
+
assert bin_to_hex(gdb.partial_to_complete_sha_hex(hexsha[:20])) == hexsha
-
+
# fails with BadObject
for invalid_rev in ("0000", "bad/ref", "super bad"):
self.failUnlessRaises(BadObject, gdb.partial_to_complete_sha_hex, invalid_rev)
-
+
def test_fetch_info(self):
self.failUnlessRaises(ValueError, CmdCmdFetchInfo._from_line, self.rorepo, "nonsense", '')
- self.failUnlessRaises(ValueError, CmdCmdFetchInfo._from_line, self.rorepo, "? [up to date] 0.1.7RC -> origin/0.1.7RC", '')
-
-
+ self.failUnlessRaises(ValueError, CmdCmdFetchInfo._from_line, self.rorepo,
+ "? [up to date] 0.1.7RC -> origin/0.1.7RC", '')
+
def test_fetch_info(self):
# assure we can handle remote-tracking branches
fetch_info_line_fmt = "c437ee5deb8d00cf02f03720693e4c802e99f390 not-for-merge %s '0.3' of git://github.com/gitpython-developers/GitPython"
remote_info_line_fmt = "* [new branch] nomatter -> %s"
fi = CmdFetchInfo._from_line(self.rorepo,
- remote_info_line_fmt % "local/master",
- fetch_info_line_fmt % 'remote-tracking branch')
-
+ remote_info_line_fmt % "local/master",
+ fetch_info_line_fmt % 'remote-tracking branch')
+
# we wouldn't be here if it wouldn't have worked
-
+
# handles non-default refspecs: One can specify a different path in refs/remotes
# or a special path just in refs/something for instance
-
+
fi = CmdFetchInfo._from_line(self.rorepo,
- remote_info_line_fmt % "subdir/tagname",
- fetch_info_line_fmt % 'tag')
-
+ remote_info_line_fmt % "subdir/tagname",
+ fetch_info_line_fmt % 'tag')
+
assert isinstance(fi.ref, TagReference)
assert fi.ref.path.startswith('refs/tags')
-
+
# it could be in a remote direcftory though
fi = CmdFetchInfo._from_line(self.rorepo,
- remote_info_line_fmt % "remotename/tags/tagname",
- fetch_info_line_fmt % 'tag')
-
+ remote_info_line_fmt % "remotename/tags/tagname",
+ fetch_info_line_fmt % 'tag')
+
assert isinstance(fi.ref, TagReference)
assert fi.ref.path.startswith('refs/remotes/')
-
+
# it can also be anywhere !
tag_path = "refs/something/remotename/tags/tagname"
fi = CmdFetchInfo._from_line(self.rorepo,
- remote_info_line_fmt % tag_path,
- fetch_info_line_fmt % 'tag')
-
+ remote_info_line_fmt % tag_path,
+ fetch_info_line_fmt % 'tag')
+
assert isinstance(fi.ref, TagReference)
assert fi.ref.path == tag_path
-
+
# branches default to refs/remotes
fi = CmdFetchInfo._from_line(self.rorepo,
- remote_info_line_fmt % "remotename/branch",
- fetch_info_line_fmt % 'branch')
-
+ remote_info_line_fmt % "remotename/branch",
+ fetch_info_line_fmt % 'branch')
+
assert isinstance(fi.ref, RemoteReference)
assert fi.ref.remote_name == 'remotename'
-
+
# but you can force it anywhere, in which case we only have a references
fi = CmdFetchInfo._from_line(self.rorepo,
- remote_info_line_fmt % "refs/something/branch",
- fetch_info_line_fmt % 'branch')
-
+ remote_info_line_fmt % "refs/something/branch",
+ fetch_info_line_fmt % 'branch')
+
assert type(fi.ref) is Reference
assert fi.ref.path == "refs/something/branch"
-
-
-
diff --git a/git/test/db/dulwich/lib.py b/git/test/db/dulwich/lib.py
index a58469f1..bd6a0564 100644
--- a/git/test/db/dulwich/lib.py
+++ b/git/test/db/dulwich/lib.py
@@ -1,14 +1,15 @@
"""dulwich specific utilities, as well as all the default ones"""
from git.test.lib import (
- InheritedTestMethodsOverrideWrapperMetaClsAutoMixin,
- needs_module_or_skip
- )
+ InheritedTestMethodsOverrideWrapperMetaClsAutoMixin,
+ needs_module_or_skip
+)
__all__ = ['needs_dulwich_or_skip', 'DulwichRequiredMetaMixin']
#{ Decoorators
+
def needs_dulwich_or_skip(func):
"""Skip this test if we have no dulwich - print warning"""
return needs_module_or_skip('dulwich')(func)
@@ -17,6 +18,7 @@ def needs_dulwich_or_skip(func):
#{ MetaClasses
+
class DulwichRequiredMetaMixin(InheritedTestMethodsOverrideWrapperMetaClsAutoMixin):
decorator = [needs_dulwich_or_skip]
diff --git a/git/test/db/dulwich/test_base.py b/git/test/db/dulwich/test_base.py
index ed2f8975..82713103 100644
--- a/git/test/db/dulwich/test_base.py
+++ b/git/test/db/dulwich/test_base.py
@@ -7,7 +7,6 @@ from git.test.lib import TestBase, with_rw_repo
from git.test.db.base import RepoBase
-
try:
import dulwich
except ImportError:
@@ -17,16 +16,15 @@ except ImportError:
else:
# now we know dulwich is available, to do futher imports
from git.db.dulwich.complex import DulwichCompatibilityGitDB as DulwichDB
-
-#END handle imports
+
+# END handle imports
+
class TestDulwichDBBase(RepoBase):
__metaclass__ = DulwichRequiredMetaMixin
RepoCls = DulwichDB
-
+
@needs_dulwich_or_skip
@with_rw_repo('HEAD', bare=False)
def test_basics(self, rw_repo):
db = DulwichDB(rw_repo.working_tree_dir)
-
-
diff --git a/git/test/db/lib.py b/git/test/db/lib.py
index d406382a..74a6509e 100644
--- a/git/test/db/lib.py
+++ b/git/test/db/lib.py
@@ -10,15 +10,15 @@ from git.test.lib import (
fixture_path,
TestBase,
rorepo_dir,
- )
+)
from git.stream import Sha1Writer
from git.base import (
- IStream,
- OStream,
- OInfo
- )
-
+ IStream,
+ OStream,
+ OInfo
+)
+
from git.exc import BadObject
from git.typ import str_blob_type
@@ -28,41 +28,43 @@ from struct import pack
__all__ = ('TestDBBase', 'with_rw_directory', 'with_packs_rw', 'fixture_path')
-
+
+
class TestDBBase(TestBase):
+
"""Base Class providing default functionality to all tests such as:
-
+
- Utility functions provided by the TestCase base of the unittest method such as::
self.fail("todo")
self.failUnlessRaises(...)
-
+
- Class level repository which is considered read-only as it is shared among
all test cases in your type.
Access it using::
self.rorepo # 'ro' stands for read-only
-
+
The rorepo is in fact your current project's git repo. If you refer to specific
shas for your objects, be sure you choose some that are part of the immutable portion
of the project history ( to assure tests don't fail for others ).
-
+
Derived types can override the default repository type to create a different
read-only repo, allowing to test their specific type
"""
-
+
# data
two_lines = "1234\nhello world"
all_data = (two_lines, )
-
+
#{ Configuration
# The repository type to instantiate. It takes at least a path to operate upon
# during instantiation.
RepoCls = None
-
+
# if True, a read-only repo will be provided and RepoCls must be set.
# Otherwise it may remain unset
needs_ro_repo = True
#} END configuration
-
+
@classmethod
def setUp(cls):
"""
@@ -73,8 +75,8 @@ class TestDBBase(TestBase):
if cls is not TestDBBase:
assert cls.RepoCls is not None, "RepoCls class member must be set in %s" % cls
cls.rorepo = cls.RepoCls(rorepo_dir())
- #END handle rorepo
-
+ # END handle rorepo
+
def _assert_object_writing_simple(self, db):
# write a bunch of objects and query their streams and info
null_objs = db.size()
@@ -85,23 +87,22 @@ class TestDBBase(TestBase):
new_istream = db.store(istream)
assert new_istream is istream
assert db.has_object(istream.binsha)
-
+
info = db.info(istream.binsha)
assert isinstance(info, OInfo)
assert info.type == istream.type and info.size == istream.size
-
+
stream = db.stream(istream.binsha)
assert isinstance(stream, OStream)
assert stream.binsha == info.binsha and stream.type == info.type
assert stream.read() == data
# END for each item
-
+
assert db.size() == null_objs + ni
shas = list(db.sha_iter())
assert len(shas) == db.size()
assert len(shas[0]) == 20
-
-
+
def _assert_object_writing(self, db):
"""General tests to verify object writing, compatible to ObjectDBW
:note: requires write access to the database"""
@@ -115,25 +116,25 @@ class TestDBBase(TestBase):
ostream = ostreamcls()
assert isinstance(ostream, Sha1Writer)
# END create ostream
-
+
prev_ostream = db.set_ostream(ostream)
- assert type(prev_ostream) in ostreams or prev_ostream in ostreams
-
+ assert type(prev_ostream) in ostreams or prev_ostream in ostreams
+
istream = IStream(str_blob_type, len(data), StringIO(data))
-
+
# store returns same istream instance, with new sha set
my_istream = db.store(istream)
sha = istream.binsha
assert my_istream is istream
assert db.has_object(sha) != dry_run
- assert len(sha) == 20
-
+ assert len(sha) == 20
+
# verify data - the slow way, we want to run code
if not dry_run:
info = db.info(sha)
assert str_blob_type == info.type
assert info.size == len(data)
-
+
ostream = db.stream(sha)
assert ostream.read() == data
assert ostream.type == str_blob_type
@@ -141,57 +142,58 @@ class TestDBBase(TestBase):
else:
self.failUnlessRaises(BadObject, db.info, sha)
self.failUnlessRaises(BadObject, db.stream, sha)
-
+
# DIRECT STREAM COPY
# our data hase been written in object format to the StringIO
# we pasesd as output stream. No physical database representation
# was created.
- # Test direct stream copy of object streams, the result must be
+ # Test direct stream copy of object streams, the result must be
# identical to what we fed in
ostream.seek(0)
istream.stream = ostream
assert istream.binsha is not None
prev_sha = istream.binsha
-
+
db.set_ostream(ZippedStoreShaWriter())
db.store(istream)
assert istream.binsha == prev_sha
new_ostream = db.ostream()
-
+
# note: only works as long our store write uses the same compression
# level, which is zip_best
assert ostream.getvalue() == new_ostream.getvalue()
# END for each data set
# END for each dry_run mode
-
+
def _assert_object_writing_async(self, db):
"""Test generic object writing using asynchronous access"""
ni = 5000
+
def istream_generator(offset=0, ni=ni):
for data_src in xrange(ni):
data = str(data_src + offset)
yield IStream(str_blob_type, len(data), StringIO(data))
# END for each item
# END generator utility
-
+
# for now, we are very trusty here as we expect it to work if it worked
# in the single-stream case
-
+
# write objects
reader = IteratorReader(istream_generator())
istream_reader = db.store_async(reader)
istreams = istream_reader.read() # read all
assert istream_reader.task().error() is None
assert len(istreams) == ni
-
+
for stream in istreams:
assert stream.error is None
assert len(stream.binsha) == 20
assert isinstance(stream, IStream)
# END assert each stream
-
+
# test has-object-async - we must have all previously added ones
- reader = IteratorReader( istream.binsha for istream in istreams )
+ reader = IteratorReader(istream.binsha for istream in istreams)
hasobject_reader = db.has_object_async(reader)
count = 0
for sha, has_object in hasobject_reader:
@@ -199,11 +201,11 @@ class TestDBBase(TestBase):
count += 1
# END for each sha
assert count == ni
-
+
# read the objects we have just written
- reader = IteratorReader( istream.binsha for istream in istreams )
+ reader = IteratorReader(istream.binsha for istream in istreams)
ostream_reader = db.stream_async(reader)
-
+
# read items individually to prevent hitting possible sys-limits
count = 0
for ostream in ostream_reader:
@@ -212,30 +214,29 @@ class TestDBBase(TestBase):
# END for each ostream
assert ostream_reader.task().error() is None
assert count == ni
-
+
# get info about our items
- reader = IteratorReader( istream.binsha for istream in istreams )
+ reader = IteratorReader(istream.binsha for istream in istreams)
info_reader = db.info_async(reader)
-
+
count = 0
for oinfo in info_reader:
assert isinstance(oinfo, OInfo)
count += 1
# END for each oinfo instance
assert count == ni
-
-
+
# combined read-write using a converter
# add 2500 items, and obtain their output streams
nni = 2500
reader = IteratorReader(istream_generator(offset=ni, ni=nni))
- istream_to_sha = lambda istreams: [ istream.binsha for istream in istreams ]
-
+ istream_to_sha = lambda istreams: [istream.binsha for istream in istreams]
+
istream_reader = db.store_async(reader)
istream_reader.set_post_cb(istream_to_sha)
-
+
ostream_reader = db.stream_async(istream_reader)
-
+
count = 0
# read it individually, otherwise we might run into the ulimit
for ostream in ostream_reader:
@@ -243,5 +244,3 @@ class TestDBBase(TestBase):
count += 1
# END for each ostream
assert count == nni
-
-
diff --git a/git/test/db/py/test_base.py b/git/test/db/py/test_base.py
index 5d076bb2..cd1bed0f 100644
--- a/git/test/db/py/test_base.py
+++ b/git/test/db/py/test_base.py
@@ -7,10 +7,10 @@ from git.test.db.base import RepoBase
from git.db.complex import PureCompatibilityGitDB
+
class TestPyDBBase(RepoBase):
-
+
RepoCls = PureCompatibilityGitDB
-
+
def test_basics(self):
pass
-
diff --git a/git/test/db/py/test_git.py b/git/test/db/py/test_git.py
index 4f5b5fb5..207d2864 100644
--- a/git/test/db/py/test_git.py
+++ b/git/test/db/py/test_git.py
@@ -11,15 +11,16 @@ from git.util import hex_to_bin, bin_to_hex
import os
+
class TestGitDB(TestDBBase):
needs_ro_repo = False
-
+
def test_reading(self):
gdb = PureGitODB(os.path.join(rorepo_dir(), 'objects'))
-
+
# we have packs and loose objects, alternates doesn't necessarily exist
assert 1 < len(gdb.databases()) < 4
-
+
# access should be possible
git_sha = hex_to_bin("5aebcd5cb3340fb31776941d7e4d518a712a8655")
assert isinstance(gdb.info(git_sha), OInfo)
@@ -27,25 +28,24 @@ class TestGitDB(TestDBBase):
assert gdb.size() > 200
sha_list = list(gdb.sha_iter())
assert len(sha_list) == gdb.size()
-
-
- # This is actually a test for compound functionality, but it doesn't
+
+ # This is actually a test for compound functionality, but it doesn't
# have a separate test module
# test partial shas
# this one as uneven and quite short
assert gdb.partial_to_complete_sha_hex('5aebcd') == hex_to_bin("5aebcd5cb3340fb31776941d7e4d518a712a8655")
-
+
# mix even/uneven hexshas
for i, binsha in enumerate(sha_list[:50]):
- assert gdb.partial_to_complete_sha_hex(bin_to_hex(binsha)[:8-(i%2)]) == binsha
+ assert gdb.partial_to_complete_sha_hex(bin_to_hex(binsha)[:8 - (i % 2)]) == binsha
# END for each sha
-
+
self.failUnlessRaises(BadObject, gdb.partial_to_complete_sha_hex, "0000")
-
+
@with_rw_directory
def test_writing(self, path):
gdb = PureGitODB(path)
-
+
# its possible to write objects
self._assert_object_writing(gdb)
self._assert_object_writing_async(gdb)
diff --git a/git/test/db/py/test_loose.py b/git/test/db/py/test_loose.py
index cfb0ca3a..b3ffb64f 100644
--- a/git/test/db/py/test_loose.py
+++ b/git/test/db/py/test_loose.py
@@ -6,31 +6,31 @@ from git.test.db.lib import TestDBBase, with_rw_directory
from git.db.py.loose import PureLooseObjectODB
from git.exc import BadObject
from git.util import bin_to_hex
-
+
+
class TestLooseDB(TestDBBase):
-
+
needs_ro_repo = False
-
+
@with_rw_directory
def test_basics(self, path):
ldb = PureLooseObjectODB(path)
-
+
# write data
self._assert_object_writing(ldb)
self._assert_object_writing_async(ldb)
-
+
# verify sha iteration and size
shas = list(ldb.sha_iter())
assert shas and len(shas[0]) == 20
-
+
assert len(shas) == ldb.size()
-
+
# verify find short object
long_sha = bin_to_hex(shas[-1])
for short_sha in (long_sha[:20], long_sha[:5]):
assert bin_to_hex(ldb.partial_to_complete_sha_hex(short_sha)) == long_sha
# END for each sha
-
+
self.failUnlessRaises(BadObject, ldb.partial_to_complete_sha_hex, '0000')
# raises if no object could be foudn
-
diff --git a/git/test/db/py/test_mem.py b/git/test/db/py/test_mem.py
index bb879554..0468b8af 100644
--- a/git/test/db/py/test_mem.py
+++ b/git/test/db/py/test_mem.py
@@ -5,26 +5,27 @@
from git.test.db.lib import TestDBBase, with_rw_directory
from git.db.py.mem import PureMemoryDB
from git.db.py.loose import PureLooseObjectODB
-
+
+
class TestPureMemoryDB(TestDBBase):
-
+
needs_ro_repo = False
@with_rw_directory
def test_writing(self, path):
mdb = PureMemoryDB()
-
+
# write data
self._assert_object_writing_simple(mdb)
-
+
# test stream copy
ldb = PureLooseObjectODB(path)
assert ldb.size() == 0
num_streams_copied = mdb.stream_copy(mdb.sha_iter(), ldb)
assert num_streams_copied == mdb.size()
-
+
assert ldb.size() == mdb.size()
for sha in mdb.sha_iter():
assert ldb.has_object(sha)
- assert ldb.stream(sha).read() == mdb.stream(sha).read()
+ assert ldb.stream(sha).read() == mdb.stream(sha).read()
# END verify objects where copied and are equal
diff --git a/git/test/db/py/test_pack.py b/git/test/db/py/test_pack.py
index 54dc2e2c..2cb7ea70 100644
--- a/git/test/db/py/test_pack.py
+++ b/git/test/db/py/test_pack.py
@@ -12,48 +12,48 @@ from git.exc import BadObject, AmbiguousObjectName
import os
import random
+
class TestPackDB(TestDBBase):
-
- needs_ro_repo = False
-
+
+ needs_ro_repo = False
+
@with_packs_rw
def test_writing(self, path):
pdb = PurePackedODB(path)
-
+
# on demand, we init our pack cache
num_packs = len(pdb.entities())
assert num_packs
assert pdb._st_mtime != 0
-
- # test pack directory changed:
+
+ # test pack directory changed:
# packs removed - rename a file, should affect the glob
pack_path = pdb.entities()[0].pack().path()
new_pack_path = pack_path + "renamed"
os.rename(pack_path, new_pack_path)
-
+
pdb.update_cache(force=True)
assert len(pdb.entities()) == num_packs - 1
-
+
# packs added
os.rename(new_pack_path, pack_path)
pdb.update_cache(force=True)
assert len(pdb.entities()) == num_packs
-
+
# bang on the cache
# access the Entities directly, as there is no iteration interface
# yet ( or required for now )
sha_list = list(pdb.sha_iter())
assert len(sha_list) == pdb.size()
-
+
# hit all packs in random order
random.shuffle(sha_list)
-
+
for sha in sha_list:
info = pdb.info(sha)
stream = pdb.stream(sha)
# END for each sha to query
-
-
+
# test short finding - be a bit more brutal here
max_bytes = 19
min_bytes = 2
@@ -61,16 +61,16 @@ class TestPackDB(TestDBBase):
for i, sha in enumerate(sha_list):
short_sha = sha[:max((i % max_bytes), min_bytes)]
try:
- assert pdb.partial_to_complete_sha(short_sha, len(short_sha)*2) == sha
+ assert pdb.partial_to_complete_sha(short_sha, len(short_sha) * 2) == sha
except AmbiguousObjectName:
num_ambiguous += 1
- pass # valid, we can have short objects
+ pass # valid, we can have short objects
# END exception handling
# END for each sha to find
-
+
# we should have at least one ambiguous, considering the small sizes
- # but in our pack, there is no ambigious ...
+ # but in our pack, there is no ambigious ...
# assert num_ambiguous
-
+
# non-existing
self.failUnlessRaises(BadObject, pdb.partial_to_complete_sha, "\0\0", 4)
diff --git a/git/test/db/py/test_ref.py b/git/test/db/py/test_ref.py
index dfaf9644..4b5dd134 100644
--- a/git/test/db/py/test_ref.py
+++ b/git/test/db/py/test_ref.py
@@ -6,16 +6,17 @@ from git.test.db.lib import *
from git.db.py.ref import PureReferenceDB
from git.util import (
- NULL_BIN_SHA,
- hex_to_bin
- )
+ NULL_BIN_SHA,
+ hex_to_bin
+)
import os
-
+
+
class TestPureReferenceDB(TestDBBase):
-
+
needs_ro_repo = False
-
+
def make_alt_file(self, alt_path, alt_list):
"""Create an alternates file which contains the given alternates.
The list can be empty"""
@@ -23,40 +24,37 @@ class TestPureReferenceDB(TestDBBase):
for alt in alt_list:
alt_file.write(alt + "\n")
alt_file.close()
-
+
@with_rw_directory
def test_writing(self, path):
- NULL_BIN_SHA = '\0' * 20
-
+ NULL_BIN_SHA = '\0' * 20
+
alt_path = os.path.join(path, 'alternates')
rdb = PureReferenceDB(alt_path)
assert len(rdb.databases()) == 0
assert rdb.size() == 0
assert len(list(rdb.sha_iter())) == 0
-
+
# try empty, non-existing
assert not rdb.has_object(NULL_BIN_SHA)
-
-
+
# setup alternate file
# add two, one is invalid
own_repo_path = fixture_path('../../../.git/objects') # use own repo
self.make_alt_file(alt_path, [own_repo_path, "invalid/path"])
rdb.update_cache()
assert len(rdb.databases()) == 1
-
+
# we should now find a default revision of ours
git_sha = hex_to_bin("5aebcd5cb3340fb31776941d7e4d518a712a8655")
assert rdb.has_object(git_sha)
-
+
# remove valid
self.make_alt_file(alt_path, ["just/one/invalid/path"])
rdb.update_cache()
assert len(rdb.databases()) == 0
-
+
# add valid
self.make_alt_file(alt_path, [own_repo_path])
rdb.update_cache()
assert len(rdb.databases()) == 1
-
-
diff --git a/git/test/db/pygit2/lib.py b/git/test/db/pygit2/lib.py
index fab762e7..76441333 100644
--- a/git/test/db/pygit2/lib.py
+++ b/git/test/db/pygit2/lib.py
@@ -1,14 +1,15 @@
"""pygit2 specific utilities, as well as all the default ones"""
from git.test.lib import (
- InheritedTestMethodsOverrideWrapperMetaClsAutoMixin,
- needs_module_or_skip
- )
+ InheritedTestMethodsOverrideWrapperMetaClsAutoMixin,
+ needs_module_or_skip
+)
__all__ = ['needs_pygit2_or_skip', 'Pygit2RequiredMetaMixin']
#{ Decoorators
+
def needs_pygit2_or_skip(func):
"""Skip this test if we have no pygit2 - print warning"""
return needs_module_or_skip('pygit2')(func)
@@ -17,6 +18,7 @@ def needs_pygit2_or_skip(func):
#{ MetaClasses
+
class Pygit2RequiredMetaMixin(InheritedTestMethodsOverrideWrapperMetaClsAutoMixin):
decorator = [needs_pygit2_or_skip]
diff --git a/git/test/db/pygit2/test_base.py b/git/test/db/pygit2/test_base.py
index 52ee24f5..dc1b0ac5 100644
--- a/git/test/db/pygit2/test_base.py
+++ b/git/test/db/pygit2/test_base.py
@@ -7,7 +7,6 @@ from git.test.lib import TestBase, with_rw_repo
from git.test.db.base import RepoBase
-
try:
import pygit2
except ImportError:
@@ -17,16 +16,15 @@ except ImportError:
else:
# now we know pygit2 is available, to do futher imports
from git.db.pygit2.complex import Pygit2CompatibilityGitDB as Pygit2DB
-
-#END handle imports
+
+# END handle imports
+
class TestPyGit2DBBase(RepoBase):
__metaclass__ = Pygit2RequiredMetaMixin
RepoCls = Pygit2DB
-
+
@needs_pygit2_or_skip
@with_rw_repo('HEAD', bare=False)
def test_basics(self, rw_repo):
db = Pygit2DB(rw_repo.working_tree_dir)
-
-
diff --git a/git/test/db/test_base.py b/git/test/db/test_base.py
index 78da9f04..39c935a6 100644
--- a/git/test/db/test_base.py
+++ b/git/test/db/test_base.py
@@ -5,6 +5,7 @@
from lib import *
from git.db import RefSpec
+
class TestBase(TestDBBase):
needs_ro_repo = False
@@ -17,4 +18,3 @@ class TestBase(TestDBBase):
assert rs.delete_destination()
assert rs.source is None
assert rs.destination == "something"
-
diff --git a/git/test/lib/__init__.py b/git/test/lib/__init__.py
index a0656438..a94b6617 100644
--- a/git/test/lib/__init__.py
+++ b/git/test/lib/__init__.py
@@ -5,7 +5,7 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import inspect
-# TODO: Separate names - they do repeat unfortunately. Also deduplicate it,
+# TODO: Separate names - they do repeat unfortunately. Also deduplicate it,
# redesign decorators to support multiple database types in succession.
from base import *
@@ -14,5 +14,5 @@ from asserts import *
from helper import *
-__all__ = [ name for name, obj in locals().items()
- if not (name.startswith('_') or inspect.ismodule(obj)) ]
+__all__ = [name for name, obj in locals().items()
+ if not (name.startswith('_') or inspect.ismodule(obj))]
diff --git a/git/test/lib/asserts.py b/git/test/lib/asserts.py
index fa754b92..351901dc 100644
--- a/git/test/lib/asserts.py
+++ b/git/test/lib/asserts.py
@@ -10,41 +10,49 @@ from nose import tools
from nose.tools import *
import stat
-__all__ = ['assert_instance_of', 'assert_not_instance_of',
+__all__ = ['assert_instance_of', 'assert_not_instance_of',
'assert_none', 'assert_not_none',
'assert_match', 'assert_not_match', 'assert_mode_644',
'assert_mode_755'] + tools.__all__
+
def assert_instance_of(expected, actual, msg=None):
"""Verify that object is an instance of expected """
assert isinstance(actual, expected), msg
+
def assert_not_instance_of(expected, actual, msg=None):
"""Verify that object is not an instance of expected """
assert not isinstance(actual, expected, msg)
-
+
+
def assert_none(actual, msg=None):
"""verify that item is None"""
assert actual is None, msg
+
def assert_not_none(actual, msg=None):
"""verify that item is None"""
assert actual is not None, msg
+
def assert_match(pattern, string, msg=None):
"""verify that the pattern matches the string"""
assert_not_none(re.search(pattern, string), msg)
+
def assert_not_match(pattern, string, msg=None):
"""verify that the pattern does not match the string"""
assert_none(re.search(pattern, string), msg)
-
+
+
def assert_mode_644(mode):
"""Verify given mode is 644"""
- assert (mode & stat.S_IROTH) and (mode & stat.S_IRGRP)
+ assert (mode & stat.S_IROTH) and (mode & stat.S_IRGRP)
assert (mode & stat.S_IWUSR) and (mode & stat.S_IRUSR) and not (mode & stat.S_IXUSR)
+
def assert_mode_755(mode):
"""Verify given mode is 755"""
assert (mode & stat.S_IROTH) and (mode & stat.S_IRGRP) and (mode & stat.S_IXOTH) and (mode & stat.S_IXGRP)
- assert (mode & stat.S_IWUSR) and (mode & stat.S_IRUSR) and (mode & stat.S_IXUSR) \ No newline at end of file
+ assert (mode & stat.S_IWUSR) and (mode & stat.S_IRUSR) and (mode & stat.S_IXUSR)
diff --git a/git/test/lib/base.py b/git/test/lib/base.py
index 298e8e05..39bc9b73 100644
--- a/git/test/lib/base.py
+++ b/git/test/lib/base.py
@@ -4,15 +4,15 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Utilities used in ODB testing"""
from git.base import OStream
-from git.stream import (
- Sha1Writer,
- ZippedStoreShaWriter
- )
+from git.stream import (
+ Sha1Writer,
+ ZippedStoreShaWriter
+)
from git.util import (
- zlib,
- dirname
- )
+ zlib,
+ dirname
+)
import sys
import random
@@ -32,6 +32,7 @@ import gc
def with_rw_directory(func):
"""Create a temporary directory which can be written to, remove it if the
test suceeds, but leave it otherwise to aid additional debugging"""
+
def wrapper(self):
path = maketemp(prefix=func.__name__)
os.mkdir(path)
@@ -45,7 +46,7 @@ def with_rw_directory(func):
raise
finally:
# Need to collect here to be sure all handles have been closed. It appears
- # a windows-only issue. In fact things should be deleted, as well as
+ # a windows-only issue. In fact things should be deleted, as well as
# memory maps closed, once objects go out of scope. For some reason
# though this is not the case here unless we collect explicitly.
if not keep:
@@ -53,7 +54,7 @@ def with_rw_directory(func):
shutil.rmtree(path)
# END handle exception
# END wrapper
-
+
wrapper.__name__ = func.__name__
return wrapper
@@ -65,6 +66,7 @@ def with_rw_repo(func):
being on a certain branch or on anything really except for the default tags
that should exist
Wrapped function obtains a git repository """
+
def wrapper(self, path):
src_dir = dirname(dirname(dirname(__file__)))
assert(os.path.isdir(path))
@@ -73,24 +75,24 @@ def with_rw_repo(func):
target_gitdir = os.path.join(path, '.git')
assert os.path.isdir(target_gitdir)
return func(self, self.RepoCls(target_gitdir))
- #END wrapper
+ # END wrapper
wrapper.__name__ = func.__name__
return with_rw_directory(wrapper)
-
def with_packs_rw(func):
"""Function that provides a path into which the packs for testing should be
copied. Will pass on the path to the actual function afterwards
-
+
:note: needs with_rw_directory wrapped around it"""
+
def wrapper(self, path):
src_pack_glob = fixture_path('packs/*')
print src_pack_glob
copy_files_globbed(src_pack_glob, path, hard_link_ok=True)
return func(self, path)
# END wrapper
-
+
wrapper.__name__ = func.__name__
return with_rw_directory(wrapper)
@@ -98,6 +100,7 @@ def with_packs_rw(func):
#{ Routines
+
def rorepo_dir():
""":return: path to our own repository, being our own .git directory.
:note: doesn't work in bare repositories"""
@@ -105,6 +108,7 @@ def rorepo_dir():
assert os.path.isdir(base)
return base
+
def maketemp(*args, **kwargs):
"""Wrapper around default tempfile.mktemp to fix an osx issue"""
tdir = tempfile.mktemp(*args, **kwargs)
@@ -112,19 +116,23 @@ def maketemp(*args, **kwargs):
tdir = '/private' + tdir
return tdir
+
def fixture_path(relapath=''):
""":return: absolute path into the fixture directory
:param relapath: relative path into the fixtures directory, or ''
to obtain the fixture directory itself"""
test_dir = os.path.dirname(os.path.dirname(__file__))
return os.path.join(test_dir, "fixtures", relapath)
-
+
+
def fixture(name):
return open(fixture_path(name), 'rb').read()
+
def absolute_project_path():
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+
def copy_files_globbed(source_glob, target_dir, hard_link_ok=False):
"""Copy all files found according to the given source glob into the target directory
:param hard_link_ok: if True, hard links will be created if possible. Otherwise
@@ -141,7 +149,7 @@ def copy_files_globbed(source_glob, target_dir, hard_link_ok=False):
shutil.copy(src_file, target_dir)
# END try hard link
# END for each file to copy
-
+
def make_bytes(size_in_bytes, randomize=False):
""":return: string with given size in bytes
@@ -155,11 +163,13 @@ def make_bytes(size_in_bytes, randomize=False):
a = array('i', producer)
return a.tostring()
+
def make_object(type, data):
""":return: bytes resembling an uncompressed object"""
odata = "blob %i\0" % len(data)
return odata + data
-
+
+
def make_memory_file(size_in_bytes, randomize=False):
""":return: tuple(size_of_stream, stream)
:param randomize: try to produce a very random stream"""
@@ -170,31 +180,33 @@ def make_memory_file(size_in_bytes, randomize=False):
#{ Stream Utilities
+
class DummyStream(object):
- def __init__(self):
- self.was_read = False
- self.bytes = 0
- self.closed = False
-
- def read(self, size):
- self.was_read = True
- self.bytes = size
-
- def close(self):
- self.closed = True
-
- def _assert(self):
- assert self.was_read
+
+ def __init__(self):
+ self.was_read = False
+ self.bytes = 0
+ self.closed = False
+
+ def read(self, size):
+ self.was_read = True
+ self.bytes = size
+
+ def close(self):
+ self.closed = True
+
+ def _assert(self):
+ assert self.was_read
class DeriveTest(OStream):
+
def __init__(self, sha, type, size, stream, *args, **kwargs):
self.myarg = kwargs.pop('myarg')
self.args = args
-
+
def _assert(self):
assert self.args
assert self.myarg
#} END stream utilitiess
-
diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py
index bb17745c..db6eb121 100644
--- a/git/test/lib/helper.py
+++ b/git/test/lib/helper.py
@@ -16,36 +16,37 @@ import warnings
from nose import SkipTest
from base import (
- maketemp,
- rorepo_dir
- )
+ maketemp,
+ rorepo_dir
+)
__all__ = (
- 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', 'InheritedTestMethodsOverrideWrapperMetaClsAutoMixin',
- 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'needs_module_or_skip'
- )
+ 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', 'InheritedTestMethodsOverrideWrapperMetaClsAutoMixin',
+ 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'needs_module_or_skip'
+)
-
-#{ Adapters
-
+#{ Adapters
+
class StringProcessAdapter(object):
+
"""Allows to use strings as Process object as returned by SubProcess.Popen.
Its tailored to work with the test system only"""
-
+
def __init__(self, input_string):
self.stdout = cStringIO.StringIO(input_string)
self.stderr = cStringIO.StringIO()
-
+
def wait(self):
return 0
-
+
poll = wait
-
+
#} END adapters
-#{ Decorators
+#{ Decorators
+
def _rmtree_onerror(osremove, fullpath, exec_info):
"""
@@ -54,35 +55,37 @@ def _rmtree_onerror(osremove, fullpath, exec_info):
"""
if os.name != 'nt' or osremove is not os.remove:
raise
-
+
os.chmod(fullpath, 0777)
os.remove(fullpath)
+
def with_rw_repo(working_tree_ref, bare=False):
"""
Same as with_bare_repo, but clones the rorepo as non-bare repository, checking
out the working tree at the given working_tree_ref.
-
+
This repository type is more costly due to the working copy checkout.
-
+
To make working with relative paths easier, the cwd will be set to the working
dir of the repository.
"""
assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout"
+
def argument_passer(func):
def repo_creator(self):
prefix = 'non_'
if bare:
prefix = ''
- #END handle prefix
+ # END handle prefix
repo_dir = maketemp("%sbare_%s" % (prefix, func.__name__))
rw_repo = self.rorepo.clone(repo_dir, shared=True, bare=bare, n=True)
-
+
rw_repo.head.commit = rw_repo.commit(working_tree_ref)
if not bare:
rw_repo.head.reference.checkout()
# END handle checkout
-
+
prev_cwd = os.getcwd()
os.chdir(rw_repo.working_dir)
try:
@@ -104,7 +107,8 @@ def with_rw_repo(working_tree_ref, bare=False):
return repo_creator
# END argument passer
return argument_passer
-
+
+
def with_rw_and_rw_remote_repo(working_tree_ref):
"""
Same as with_rw_repo, but also provides a writable remote repository from which the
@@ -112,36 +116,38 @@ def with_rw_and_rw_remote_repo(working_tree_ref):
run the remote_repo.
The remote repository was cloned as bare repository from the rorepo, wheras
the rw repo has a working tree and was cloned from the remote repository.
-
+
remote_repo has two remotes: origin and daemon_origin. One uses a local url,
the other uses a server url. The daemon setup must be done on system level
and should be an inetd service that serves tempdir.gettempdir() and all
directories in it.
-
+
The following scetch demonstrates this::
rorepo ---<bare clone>---> rw_remote_repo ---<clone>---> rw_repo
-
+
The test case needs to support the following signature::
def case(self, rw_repo, rw_remote_repo)
-
+
This setup allows you to test push and pull scenarios and hooks nicely.
-
+
See working dir info in with_rw_repo
"""
assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout"
+
def argument_passer(func):
def remote_repo_creator(self):
remote_repo_dir = maketemp("remote_repo_%s" % func.__name__)
repo_dir = maketemp("remote_clone_non_bare_repo")
-
+
rw_remote_repo = self.rorepo.clone(remote_repo_dir, shared=True, bare=True)
- rw_repo = rw_remote_repo.clone(repo_dir, shared=True, bare=False, n=True) # recursive alternates info ?
+ # recursive alternates info ?
+ rw_repo = rw_remote_repo.clone(repo_dir, shared=True, bare=False, n=True)
rw_repo.head.commit = working_tree_ref
rw_repo.head.reference.checkout()
-
+
# prepare for git-daemon
rw_remote_repo.daemon_export = True
-
+
# this thing is just annoying !
crw = rw_remote_repo.config_writer()
section = "daemon"
@@ -152,28 +158,30 @@ def with_rw_and_rw_remote_repo(working_tree_ref):
crw.set(section, "receivepack", True)
# release lock
del(crw)
-
- # initialize the remote - first do it as local remote and pull, then
+
+ # initialize the remote - first do it as local remote and pull, then
# we change the url to point to the daemon. The daemon should be started
# by the user, not by us
d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir)
d_remote.fetch()
remote_repo_url = "git://localhost%s" % remote_repo_dir
-
+
d_remote.config_writer.set('url', remote_repo_url)
-
+
# try to list remotes to diagnoes whether the server is up
try:
rw_repo.git.ls_remote(d_remote)
- except GitCommandError,e:
+ except GitCommandError, e:
print str(e)
if os.name == 'nt':
- raise AssertionError('git-daemon needs to run this test, but windows does not have one. Otherwise, run: git-daemon "%s"' % os.path.dirname(_mktemp()))
+ raise AssertionError(
+ 'git-daemon needs to run this test, but windows does not have one. Otherwise, run: git-daemon "%s"' % os.path.dirname(_mktemp()))
else:
- raise AssertionError('Please start a git-daemon to run this test, execute: git-daemon "%s"' % os.path.dirname(_mktemp()))
+ raise AssertionError(
+ 'Please start a git-daemon to run this test, execute: git-daemon "%s"' % os.path.dirname(_mktemp()))
# END make assertion
- #END catch ls remote error
-
+ # END catch ls remote error
+
# adjust working dir
prev_cwd = os.getcwd()
os.chdir(rw_repo.working_dir)
@@ -191,9 +199,10 @@ def with_rw_and_rw_remote_repo(working_tree_ref):
return remote_repo_creator
# END remote repo creator
# END argument parsser
-
+
return argument_passer
-
+
+
def needs_module_or_skip(module):
"""Decorator to be used for test cases only.
Print a warning if the given module could not be imported, and skip the test.
@@ -207,25 +216,28 @@ def needs_module_or_skip(module):
msg = "Module %r is required to run this test - skipping" % module
warnings.warn(msg)
raise SkipTest(msg)
- #END check import
+ # END check import
return func(self, *args, **kwargs)
- #END wrapper
+ # END wrapper
wrapper.__name__ = func.__name__
return wrapper
- #END argpasser
+ # END argpasser
return argpasser
-
+
#} END decorators
#{ Meta Classes
+
+
class GlobalsItemDeletorMetaCls(type):
+
"""Utiltiy to prevent the RepoBase to be picked up by nose as the metacls
will delete the instance from the globals"""
#{ Configuration
# Set this to a string name of the module to delete
ModuleToDelete = None
#} END configuration
-
+
def __new__(metacls, name, bases, clsdict):
assert metacls.ModuleToDelete is not None, "Invalid metaclass configuration"
new_type = super(GlobalsItemDeletorMetaCls, metacls).__new__(metacls, name, bases, clsdict)
@@ -235,22 +247,23 @@ class GlobalsItemDeletorMetaCls(type):
delattr(mod, metacls.ModuleToDelete)
except AttributeError:
pass
- #END skip case that people import our base without actually using it
- #END handle deletion
+ # END skip case that people import our base without actually using it
+ # END handle deletion
return new_type
-
-
+
+
class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin(object):
+
"""Automatically picks up the actual metaclass of the the type to be created,
that is the one inherited by one of the bases, and patch up its __new__ to use
the InheritedTestMethodsOverrideWrapperInstanceDecorator with our configured decorator"""
-
+
#{ Configuration
# decorator function to use when wrapping the inherited methods. Put it into a list as first member
# to hide it from being created as class method
decorator = []
#}END configuration
-
+
@classmethod
def _find_metacls(metacls, bases):
"""emulate pythons lookup"""
@@ -259,9 +272,9 @@ class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin(object):
if hasattr(base, mcls_attr):
return getattr(base, mcls_attr)
return metacls._find_metacls(base.__bases__)
- #END for each base
+ # END for each base
raise AssertionError("base class had not metaclass attached")
-
+
@classmethod
def _patch_methods_recursive(metacls, bases, clsdict):
"""depth-first patching of methods"""
@@ -270,33 +283,35 @@ class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin(object):
for name, item in base.__dict__.iteritems():
if not name.startswith('test_'):
continue
- #END skip non-tests
+ # END skip non-tests
clsdict[name] = metacls.decorator[0](item)
- #END for each item
- #END for each base
-
+ # END for each item
+ # END for each base
+
def __new__(metacls, name, bases, clsdict):
assert metacls.decorator, "'decorator' member needs to be set in subclass"
base_metacls = metacls._find_metacls(bases)
metacls._patch_methods_recursive(bases, clsdict)
return base_metacls.__new__(base_metacls, name, bases, clsdict)
-
+
#} END meta classes
-
+
+
class TestBase(TestCase):
+
"""
Base Class providing default functionality to all tests such as:
- Utility functions provided by the TestCase base of the unittest method such as::
self.fail("todo")
self.failUnlessRaises(...)
"""
-
+
@classmethod
def setUp(cls):
"""This method is only called to provide the most basic functionality
Subclasses may just override it or implement it differently"""
cls.rorepo = Repo(rorepo_dir())
-
+
def _make_file(self, rela_path, data, repo=None):
"""
Create a file at the given path relative to our repository, filled
diff --git a/git/test/objects/__init__.py b/git/test/objects/__init__.py
index 8b137891..e69de29b 100644
--- a/git/test/objects/__init__.py
+++ b/git/test/objects/__init__.py
@@ -1 +0,0 @@
-
diff --git a/git/test/objects/lib.py b/git/test/objects/lib.py
index e3860ba5..08ecaa2a 100644
--- a/git/test/objects/lib.py
+++ b/git/test/objects/lib.py
@@ -1,14 +1,16 @@
"""Provide customized obhject testing facilities"""
from git.test.lib import (
- rorepo_dir,
- TestBase,
- assert_equal,
- assert_not_equal,
- with_rw_repo,
- StringProcessAdapter,
- )
+ rorepo_dir,
+ TestBase,
+ assert_equal,
+ assert_not_equal,
+ with_rw_repo,
+ StringProcessAdapter,
+)
+
class TestObjectBase(TestBase):
+
"""Provides a default read-only repository in the rorepo member"""
pass
diff --git a/git/test/objects/test_blob.py b/git/test/objects/test_blob.py
index 978ab931..96fccd44 100644
--- a/git/test/objects/test_blob.py
+++ b/git/test/objects/test_blob.py
@@ -8,16 +8,16 @@ from lib import *
from git.objects.blob import *
from git.util import hex_to_bin
+
class TestBlob(TestObjectBase):
-
+
def test_mime_type_should_return_mime_type_for_known_types(self):
blob = Blob(self.rorepo, **{'binsha': Blob.NULL_BIN_SHA, 'path': 'foo.png'})
assert_equal("image/png", blob.mime_type)
-
+
def test_mime_type_should_return_text_plain_for_unknown_types(self):
- blob = Blob(self.rorepo, **{'binsha': Blob.NULL_BIN_SHA,'path': 'something'})
+ blob = Blob(self.rorepo, **{'binsha': Blob.NULL_BIN_SHA, 'path': 'something'})
assert_equal("text/plain", blob.mime_type)
-
+
def test_nodict(self):
self.failUnlessRaises(AttributeError, setattr, self.rorepo.tree()['AUTHORS'], 'someattr', 2)
-
diff --git a/git/test/objects/test_commit.py b/git/test/objects/test_commit.py
index 1b8b69c7..996c4367 100644
--- a/git/test/objects/test_commit.py
+++ b/git/test/objects/test_commit.py
@@ -10,9 +10,9 @@ from git.objects.commit import *
from git.base import IStream
from git.util import (
- hex_to_bin,
- Actor,
- )
+ hex_to_bin,
+ Actor,
+)
from cStringIO import StringIO
import time
@@ -25,49 +25,50 @@ def assert_commit_serialization(rwrepo, commit_id, print_performance_info=False)
:param print_performance_info: if True, we will show how fast we are"""
ns = 0 # num serializations
nds = 0 # num deserializations
-
+
st = time.time()
for cm in rwrepo.commit(commit_id).traverse():
nds += 1
-
- # assert that we deserialize commits correctly, hence we get the same
+
+ # assert that we deserialize commits correctly, hence we get the same
# sha on serialization
stream = StringIO()
cm._serialize(stream)
ns += 1
streamlen = stream.tell()
stream.seek(0)
-
+
istream = rwrepo.odb.store(IStream(Commit.type, streamlen, stream))
assert istream.hexsha == cm.hexsha
-
+
nc = Commit(rwrepo, Commit.NULL_BIN_SHA, cm.tree,
- cm.author, cm.authored_date, cm.author_tz_offset,
- cm.committer, cm.committed_date, cm.committer_tz_offset,
- cm.message, cm.parents, cm.encoding)
-
+ cm.author, cm.authored_date, cm.author_tz_offset,
+ cm.committer, cm.committed_date, cm.committer_tz_offset,
+ cm.message, cm.parents, cm.encoding)
+
assert nc.parents == cm.parents
stream = StringIO()
nc._serialize(stream)
ns += 1
streamlen = stream.tell()
stream.seek(0)
-
+
# reuse istream
istream.size = streamlen
istream.stream = stream
istream.binsha = None
nc.binsha = rwrepo.odb.store(istream).binsha
-
+
# if it worked, we have exactly the same contents !
assert nc.hexsha == cm.hexsha
# END check commits
elapsed = time.time() - st
-
+
if print_performance_info:
- print >> sys.stderr, "Serialized %i and deserialized %i commits in %f s ( (%f, %f) commits / s" % (ns, nds, elapsed, ns/elapsed, nds/elapsed)
+ print >> sys.stderr, "Serialized %i and deserialized %i commits in %f s ( (%f, %f) commits / s" % (
+ ns, nds, elapsed, ns / elapsed, nds / elapsed)
# END handle performance info
-
+
class TestCommit(TestObjectBase):
@@ -76,7 +77,7 @@ class TestCommit(TestObjectBase):
commit = self.rorepo.commit('2454ae89983a4496a445ce347d7a41c0bb0ea7ae')
# commits have no dict
self.failUnlessRaises(AttributeError, setattr, commit, 'someattr', 1)
- commit.author # bake
+ commit.author # bake
assert_equal("Sebastian Thiel", commit.author.name)
assert_equal("byronimo@gmail.com", commit.author.email)
@@ -85,26 +86,25 @@ class TestCommit(TestObjectBase):
assert isinstance(commit.author_tz_offset, int) and isinstance(commit.committer_tz_offset, int)
assert commit.message == "Added missing information to docstrings of commit and stats module\n"
-
def test_stats(self):
commit = self.rorepo.commit('33ebe7acec14b25c5f84f35a664803fcab2f7781')
stats = commit.stats
-
+
def check_entries(d):
assert isinstance(d, dict)
for key in ("insertions", "deletions", "lines"):
assert key in d
- # END assertion helper
- assert stats.files
+ # END assertion helper
+ assert stats.files
assert stats.total
-
- check_entries(stats.total)
+
+ check_entries(stats.total)
assert "files" in stats.total
-
+
for filepath, d in stats.files.items():
check_entries(d)
# END for each stated file
-
+
# assure data is parsed properly
michael = Actor._from_string("Michael Trier <mtrier@gmail.com>")
assert commit.author == michael
@@ -114,7 +114,7 @@ class TestCommit(TestObjectBase):
assert commit.author_tz_offset == 14400, commit.author_tz_offset
assert commit.committer_tz_offset == 14400, commit.committer_tz_offset
assert commit.message == "initial project\n"
-
+
def test_unicode_actor(self):
# assure we can parse unicode actors correctly
name = "Üäöß ÄußÉ".decode("utf-8")
@@ -122,7 +122,7 @@ class TestCommit(TestObjectBase):
special = Actor._from_string(u"%s <something@this.com>" % name)
assert special.name == name
assert isinstance(special.name, unicode)
-
+
def test_traversal(self):
start = self.rorepo.commit("a4d06724202afccd2b5c54f81bcf2bf26dea7fff")
first = self.rorepo.commit("33ebe7acec14b25c5f84f35a664803fcab2f7781")
@@ -130,73 +130,73 @@ class TestCommit(TestObjectBase):
p1 = start.parents[1]
p00 = p0.parents[0]
p10 = p1.parents[0]
-
+
# basic branch first, depth first
dfirst = start.traverse(branch_first=False)
bfirst = start.traverse(branch_first=True)
assert dfirst.next() == p0
assert dfirst.next() == p00
-
+
assert bfirst.next() == p0
assert bfirst.next() == p1
assert bfirst.next() == p00
assert bfirst.next() == p10
-
+
# at some point, both iterations should stop
assert list(bfirst)[-1] == first
stoptraverse = self.rorepo.commit("254d04aa3180eb8b8daf7b7ff25f010cd69b4e7d").traverse(as_edge=True)
l = list(stoptraverse)
assert len(l[0]) == 2
-
+
# ignore self
assert start.traverse(ignore_self=False).next() == start
-
- # depth
+
+ # depth
assert len(list(start.traverse(ignore_self=False, depth=0))) == 1
-
+
# prune
- assert start.traverse(branch_first=1, prune=lambda i,d: i==p0).next() == p1
-
+ assert start.traverse(branch_first=1, prune=lambda i, d: i == p0).next() == p1
+
# predicate
- assert start.traverse(branch_first=1, predicate=lambda i,d: i==p1).next() == p1
-
+ assert start.traverse(branch_first=1, predicate=lambda i, d: i == p1).next() == p1
+
# traversal should stop when the beginning is reached
self.failUnlessRaises(StopIteration, first.traverse().next)
-
- # parents of the first commit should be empty ( as the only parent has a null
+
+ # parents of the first commit should be empty ( as the only parent has a null
# sha )
assert len(first.parents) == 0
-
+
def test_iteration(self):
# we can iterate commits
all_commits = Commit.list_items(self.rorepo, self.rorepo.head)
assert all_commits
assert all_commits == list(self.rorepo.iter_commits())
-
+
# this includes merge commits
mcomit = self.rorepo.commit('d884adc80c80300b4cc05321494713904ef1df2d')
assert mcomit in all_commits
-
+
# we can limit the result to paths
ltd_commits = list(self.rorepo.iter_commits(paths='CHANGES'))
assert ltd_commits and len(ltd_commits) < len(all_commits)
-
+
# show commits of multiple paths, resulting in a union of commits
less_ltd_commits = list(Commit.iter_items(self.rorepo, 'master', paths=('CHANGES', 'AUTHORS')))
assert len(ltd_commits) < len(less_ltd_commits)
-
+
def test_iter_items(self):
# pretty not allowed
self.failUnlessRaises(ValueError, Commit.iter_items, self.rorepo, 'master', pretty="raw")
-
+
def test_rev_list_bisect_all(self):
"""
'git rev-list --bisect-all' returns additional information
in the commit header. This test ensures that we properly parse it.
"""
revs = self.rorepo.git.rev_list('933d23bf95a5bd1624fbcdf328d904e1fa173474',
- first_parent=True,
- bisect_all=True)
+ first_parent=True,
+ bisect_all=True)
commits = Commit._iter_from_process_or_stream(self.rorepo, StringProcessAdapter(revs))
expected_ids = (
@@ -209,10 +209,11 @@ class TestCommit(TestObjectBase):
assert_equal(sha1, commit.hexsha)
def test_count(self):
- assert self.rorepo.tag('refs/tags/0.1.5').commit.count( ) == 143
-
+ assert self.rorepo.tag('refs/tags/0.1.5').commit.count() == 143
+
def test_list(self):
- assert isinstance(Commit.list_items(self.rorepo, '0.1.5', max_count=5)[hex_to_bin('5117c9c8a4d3af19a9958677e45cda9269de1541')], Commit)
+ assert isinstance(Commit.list_items(self.rorepo, '0.1.5', max_count=5)[
+ hex_to_bin('5117c9c8a4d3af19a9958677e45cda9269de1541')], Commit)
def test_str(self):
commit = Commit(self.rorepo, Commit.NULL_BIN_SHA)
@@ -225,10 +226,10 @@ class TestCommit(TestObjectBase):
def test_equality(self):
commit1 = Commit(self.rorepo, Commit.NULL_BIN_SHA)
commit2 = Commit(self.rorepo, Commit.NULL_BIN_SHA)
- commit3 = Commit(self.rorepo, "\1"*20)
+ commit3 = Commit(self.rorepo, "\1" * 20)
assert_equal(commit1, commit2)
assert_not_equal(commit2, commit3)
-
+
def test_iter_parents(self):
# should return all but ourselves, even if skip is defined
c = self.rorepo.commit('0.1.5')
@@ -237,43 +238,42 @@ class TestCommit(TestObjectBase):
first_parent = piter.next()
assert first_parent != c
assert first_parent == c.parents[0]
- # END for each
-
+ # END for each
+
def test_base(self):
name_rev = self.rorepo.head.commit.name_rev
assert isinstance(name_rev, basestring)
-
+
@with_rw_repo('HEAD', bare=True)
def test_serialization(self, rwrepo):
# create all commits of our repo
assert_commit_serialization(rwrepo, '0.1.6')
-
+
def test_serialization_unicode_support(self):
assert Commit.default_encoding.lower() == 'utf-8'
-
+
# create a commit with unicode in the message, and the author's name
# Verify its serialization and deserialization
cmt = self.rorepo.commit('0.1.6')
assert isinstance(cmt.message, unicode) # it automatically decodes it as such
- assert isinstance(cmt.author.name, unicode) # same here
-
+ assert isinstance(cmt.author.name, unicode) # same here
+
cmt.message = "üäêèß".decode("utf-8")
assert len(cmt.message) == 5
-
+
cmt.author.name = "äüß".decode("utf-8")
assert len(cmt.author.name) == 3
-
+
cstream = StringIO()
cmt._serialize(cstream)
cstream.seek(0)
assert len(cstream.getvalue())
-
+
ncmt = Commit(self.rorepo, cmt.binsha)
ncmt._deserialize(cstream)
-
+
assert cmt.author.name == ncmt.author.name
assert cmt.message == ncmt.message
# actually, it can't be printed in a shell as repr wants to have ascii only
# it appears
cmt.author.__repr__()
-
diff --git a/git/test/objects/test_submodule.py b/git/test/objects/test_submodule.py
index bfafb150..650bd706 100644
--- a/git/test/objects/test_submodule.py
+++ b/git/test/objects/test_submodule.py
@@ -22,18 +22,21 @@ if sys.platform == 'win32':
smmap.util.MapRegion._test_read_into_memory = True
except ImportError:
sys.stderr.write("The submodule tests will fail as some files cannot be removed due to open file handles.\n")
- sys.stderr.write("The latest version of gitdb uses a memory map manager which can be configured to work around this problem")
-#END handle windows platform
+ sys.stderr.write(
+ "The latest version of gitdb uses a memory map manager which can be configured to work around this problem")
+# END handle windows platform
class TestRootProgress(RootUpdateProgress):
+
"""Just prints messages, for now without checking the correctness of the states"""
-
+
def update(self, op, index, max_count, message='', input=''):
print message
-
+
prog = TestRootProgress()
+
class TestSubmodule(TestObjectBase):
k_subm_current = "468cad66ff1f80ddaeee4123c24e4d53a032c00d"
@@ -41,7 +44,7 @@ class TestSubmodule(TestObjectBase):
k_no_subm_tag = "0.1.6"
k_github_gitdb_url = 'git://github.com/gitpython-developers/gitdb.git'
env_gitdb_local_path = "GITPYTHON_TEST_GITDB_LOCAL_PATH"
-
+
def _generate_async_local_path(self):
return to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, 'git/ext/async'))
@@ -57,25 +60,26 @@ class TestSubmodule(TestObjectBase):
assert smgitdb.config_reader().get_value('url') == new_smclone_path
assert smgitdb.url == new_smclone_path
else:
- sys.stderr.write("Submodule tests need the gitdb repository. You can specify a local source setting the %s environment variable. Otherwise it will be downloaded from the internet" % self.env_gitdb_local_path)
- #END handle submodule path
+ sys.stderr.write(
+ "Submodule tests need the gitdb repository. You can specify a local source setting the %s environment variable. Otherwise it will be downloaded from the internet" % self.env_gitdb_local_path)
+ # END handle submodule path
return new_smclone_path
def _do_base_tests(self, rwrepo):
"""Perform all tests in the given repository, it may be bare or nonbare"""
# manual instantiation
- smm = Submodule(rwrepo, "\0"*20)
+ smm = Submodule(rwrepo, "\0" * 20)
# name needs to be set in advance
- self.failUnlessRaises(AttributeError, getattr, smm, 'name')
-
+ self.failUnlessRaises(AttributeError, getattr, smm, 'name')
+
# iterate - 1 submodule
sms = Submodule.list_items(rwrepo, self.k_subm_current)
assert len(sms) == 1
sm = sms[0]
-
+
# at a different time, there is None
assert len(Submodule.list_items(rwrepo, self.k_no_subm_tag)) == 0
-
+
assert sm.path == 'git/ext/gitdb'
assert sm.path != sm.name # in our case, we have ids there, which don't equal the path
assert sm.url == self.k_github_gitdb_url
@@ -86,55 +90,55 @@ class TestSubmodule(TestObjectBase):
assert sm.size == 0
# the module is not checked-out yet
self.failUnlessRaises(InvalidGitRepositoryError, sm.module)
-
+
# which is why we can't get the branch either - it points into the module() repository
self.failUnlessRaises(InvalidGitRepositoryError, getattr, sm, 'branch')
-
+
# branch_path works, as its just a string
assert isinstance(sm.branch_path, basestring)
-
+
# some commits earlier we still have a submodule, but its at a different commit
smold = Submodule.iter_items(rwrepo, self.k_subm_changed).next()
assert smold.binsha != sm.binsha
assert smold != sm # the name changed
-
+
# force it to reread its information
del(smold._url)
smold.url == sm.url
-
+
# test config_reader/writer methods
sm.config_reader()
- new_smclone_path = None # keep custom paths for later
- new_csmclone_path = None #
+ new_smclone_path = None # keep custom paths for later
+ new_csmclone_path = None #
if rwrepo.bare:
self.failUnlessRaises(InvalidGitRepositoryError, sm.config_writer)
else:
# for faster checkout, set the url to the local path
# Note: This is nice but doesn't work anymore with the latest git-python
- # version. This would also mean we need internet for this to work which
+ # version. This would also mean we need internet for this to work which
# is why we allow an override using an environment variable
new_smclone_path = self._rewrite_gitdb_to_local_path(sm)
# END handle bare repo
smold.config_reader()
-
+
# cannot get a writer on historical submodules
if not rwrepo.bare:
self.failUnlessRaises(ValueError, smold.config_writer)
# END handle bare repo
-
+
# make the old into a new - this doesn't work as the name changed
prev_parent_commit = smold.parent_commit
self.failUnlessRaises(ValueError, smold.set_parent_commit, self.k_subm_current)
# the sha is properly updated
- smold.set_parent_commit(self.k_subm_changed+"~1")
+ smold.set_parent_commit(self.k_subm_changed + "~1")
assert smold.binsha != sm.binsha
-
- # raises if the sm didn't exist in new parent - it keeps its
+
+ # raises if the sm didn't exist in new parent - it keeps its
# parent_commit unchanged
self.failUnlessRaises(ValueError, smold.set_parent_commit, self.k_no_subm_tag)
-
+
# TEST TODO: if a path in the gitmodules file, but not in the index, it raises
-
+
# TEST UPDATE
##############
# module retrieval is not always possible
@@ -146,108 +150,106 @@ class TestSubmodule(TestObjectBase):
# its not checked out in our case
self.failUnlessRaises(InvalidGitRepositoryError, sm.module)
assert not sm.module_exists()
-
+
# currently there is only one submodule
assert len(list(rwrepo.iter_submodules())) == 1
- assert sm.binsha != "\0"*20
-
+ assert sm.binsha != "\0" * 20
+
# TEST ADD
###########
# preliminary tests
# adding existing returns exactly the existing
sma = Submodule.add(rwrepo, sm.name, sm.path)
assert sma.path == sm.path
-
+
# no url and no module at path fails
self.failUnlessRaises(ValueError, Submodule.add, rwrepo, "newsubm", "pathtorepo", url=None)
-
+
# CONTINUE UPDATE
#################
-
+
# lets update it - its a recursive one too
newdir = os.path.join(sm.abspath, 'dir')
os.makedirs(newdir)
-
+
# update fails if the path already exists non-empty
self.failUnlessRaises(OSError, sm.update)
os.rmdir(newdir)
-
+
# dry-run does nothing
sm.update(dry_run=True, progress=prog)
assert not sm.module_exists()
-
+
assert sm.update() is sm
sm_repopath = sm.path # cache for later
assert sm.module_exists()
assert isinstance(sm.module(), git.Repo)
assert sm.module().working_tree_dir == sm.abspath
-
+
# INTERLEAVE ADD TEST
#####################
# url must match the one in the existing repository ( if submodule name suggests a new one )
# or we raise
self.failUnlessRaises(ValueError, Submodule.add, rwrepo, "newsubm", sm.path, "git://someurl/repo.git")
-
-
+
# CONTINUE UPDATE
#################
# we should have setup a tracking branch, which is also active
assert sm.module().head.ref.tracking_branch() is not None
-
+
# delete the whole directory and re-initialize
shutil.rmtree(sm.abspath)
assert len(sm.children()) == 0
# dry-run does nothing
sm.update(dry_run=True, recursive=False, progress=prog)
assert len(sm.children()) == 0
-
+
sm.update(recursive=False)
assert len(list(rwrepo.iter_submodules())) == 2
assert len(sm.children()) == 1 # its not checked out yet
csm = sm.children()[0]
assert not csm.module_exists()
csm_repopath = csm.path
-
+
# adjust the path of the submodules module to point to the local destination
# In the current gitpython version, async is used directly by gitpython
new_csmclone_path = self._generate_async_local_path()
csm.config_writer().set_value('url', new_csmclone_path)
assert csm.url == new_csmclone_path
-
+
# dry-run does nothing
assert not csm.module_exists()
sm.update(recursive=True, dry_run=True, progress=prog)
assert not csm.module_exists()
-
+
# update recursively again
sm.update(recursive=True)
assert csm.module_exists()
-
+
# tracking branch once again
csm.module().head.ref.tracking_branch() is not None
-
+
# this flushed in a sub-submodule
assert len(list(rwrepo.iter_submodules())) == 2
-
-
+
# reset both heads to the previous version, verify that to_latest_revision works
smods = (sm.module(), csm.module())
for repo in smods:
repo.head.reset('HEAD~2', working_tree=1)
# END for each repo to reset
-
- # dry run does nothing
+
+ # dry run does nothing
sm.update(recursive=True, dry_run=True, progress=prog)
for repo in smods:
assert repo.head.commit != repo.head.ref.tracking_branch().commit
# END for each repo to check
-
+
sm.update(recursive=True, to_latest_revision=True)
for repo in smods:
assert repo.head.commit == repo.head.ref.tracking_branch().commit
# END for each repo to check
del(smods)
-
+
# if the head is detached, it still works ( but warns )
smref = sm.module().head.ref
sm.module().head.ref = 'HEAD~1'
@@ -255,15 +257,15 @@ class TestSubmodule(TestObjectBase):
csm_tracking_branch = csm.module().head.ref.tracking_branch()
csm.module().head.ref.set_tracking_branch(None)
sm.update(recursive=True, to_latest_revision=True)
-
+
# to_latest_revision changes the child submodule's commit, it needs an
# update now
csm.set_parent_commit(csm.repo.head.commit)
-
+
# undo the changes
sm.module().head.ref = smref
csm.module().head.ref.set_tracking_branch(csm_tracking_branch)
-
+
# REMOVAL OF REPOSITOTRY
########################
# must delete something
@@ -281,24 +283,24 @@ class TestSubmodule(TestObjectBase):
# still, we have the file modified
self.failUnlessRaises(InvalidGitRepositoryError, sm.remove, dry_run=True)
sm.module().index.reset(working_tree=True)
-
+
# make sure sub-submodule is not modified by forcing it to update
# to the revision it is supposed to point to.
for subitem in sm.traverse():
subitem.update()
- #END checkout to right commit
-
+ # END checkout to right commit
+
# this would work
assert sm.remove(dry_run=True) is sm
assert sm.module_exists()
sm.remove(force=True, dry_run=True)
assert sm.module_exists()
-
+
# but ... we have untracked files in the child submodule
fn = join_path_native(csm.module().working_tree_dir, "newfile")
open(fn, 'w').write("hi")
self.failUnlessRaises(InvalidGitRepositoryError, sm.remove)
-
+
# forcibly delete the child repository
prev_count = len(sm.children())
assert csm.remove(force=True) is csm
@@ -308,62 +310,62 @@ class TestSubmodule(TestObjectBase):
# now we have a changed index, as configuration was altered.
# fix this
sm.module().index.reset(working_tree=True)
-
+
# now delete only the module of the main submodule
assert sm.module_exists()
sm.remove(configuration=False)
assert sm.exists()
assert not sm.module_exists()
assert sm.config_reader().get_value('url')
-
+
# delete the rest
sm.remove()
assert not sm.exists()
assert not sm.module_exists()
-
+
assert len(rwrepo.submodules) == 0
-
+
# ADD NEW SUBMODULE
###################
# add a simple remote repo - trailing slashes are no problem
smid = "newsub"
osmid = "othersub"
- nsm = Submodule.add(rwrepo, smid, sm_repopath, new_smclone_path+"/", None, no_checkout=True)
+ nsm = Submodule.add(rwrepo, smid, sm_repopath, new_smclone_path + "/", None, no_checkout=True)
assert nsm.name == smid
assert nsm.module_exists()
assert nsm.exists()
# its not checked out
assert not os.path.isfile(join_path_native(nsm.module().working_tree_dir, Submodule.k_modules_file))
assert len(rwrepo.submodules) == 1
-
+
# add another submodule, but into the root, not as submodule
osm = Submodule.add(rwrepo, osmid, csm_repopath, new_csmclone_path, Submodule.k_head_default)
assert osm != nsm
assert osm.module_exists()
assert osm.exists()
assert os.path.isfile(join_path_native(osm.module().working_tree_dir, 'setup.py'))
-
+
assert len(rwrepo.submodules) == 2
-
+
# commit the changes, just to finalize the operation
rwrepo.index.commit("my submod commit")
assert len(rwrepo.submodules) == 2
-
- # needs update as the head changed, it thinks its in the history
+
+ # needs update as the head changed, it thinks its in the history
# of the repo otherwise
nsm.set_parent_commit(rwrepo.head.commit)
osm.set_parent_commit(rwrepo.head.commit)
-
+
# MOVE MODULE
#############
# invalid inptu
self.failUnlessRaises(ValueError, nsm.move, 'doesntmatter', module=False, configuration=False)
-
+
# renaming to the same path does nothing
assert nsm.move(sm.path) is nsm
-
+
# rename a module
- nmp = join_path_native("new", "module", "dir") + "/" # new module path
+ nmp = join_path_native("new", "module", "dir") + "/" # new module path
pmp = nsm.path
abspmp = nsm.abspath
assert nsm.move(nmp) is nsm
@@ -371,43 +373,43 @@ class TestSubmodule(TestObjectBase):
nmpl = to_native_path_linux(nmp)
assert nsm.path == nmpl
assert rwrepo.submodules[0].path == nmpl
-
+
mpath = 'newsubmodule'
absmpath = join_path_native(rwrepo.working_tree_dir, mpath)
open(absmpath, 'w').write('')
self.failUnlessRaises(ValueError, nsm.move, mpath)
os.remove(absmpath)
-
+
# now it works, as we just move it back
nsm.move(pmp)
assert nsm.path == pmp
assert rwrepo.submodules[0].path == pmp
-
+
# TODO lowprio: test remaining exceptions ... for now its okay, the code looks right
-
+
# REMOVE 'EM ALL
################
# if a submodule's repo has no remotes, it can't be added without an explicit url
osmod = osm.module()
-
+
osm.remove(module=False)
for remote in osmod.remotes:
remote.remove(osmod, remote.name)
assert not osm.exists()
- self.failUnlessRaises(ValueError, Submodule.add, rwrepo, osmid, csm_repopath, url=None)
+ self.failUnlessRaises(ValueError, Submodule.add, rwrepo, osmid, csm_repopath, url=None)
# END handle bare mode
-
+
# Error if there is no submodule file here
self.failUnlessRaises(IOError, Submodule._config_parser, rwrepo, rwrepo.commit(self.k_no_subm_tag), True)
-
+
@with_rw_repo(k_subm_current)
def test_base_rw(self, rwrepo):
self._do_base_tests(rwrepo)
-
+
@with_rw_repo(k_subm_current, bare=True)
def test_base_bare(self, rwrepo):
self._do_base_tests(rwrepo)
-
+
@with_rw_repo(k_subm_current, bare=False)
def test_root_module(self, rwrepo):
# Can query everything without problems
@@ -415,7 +417,7 @@ class TestSubmodule(TestObjectBase):
# test new constructor
assert rm.parent_commit == RootModule(self.rorepo, self.rorepo.commit(self.k_subm_current)).parent_commit
assert rm.module() is rwrepo
-
+
# try attributes
rm.binsha
rm.mode
@@ -424,24 +426,24 @@ class TestSubmodule(TestObjectBase):
assert rm.parent_commit == self.rorepo.commit(self.k_subm_current)
rm.url
rm.branch
-
+
assert len(rm.list_items(rm.module())) == 1
rm.config_reader()
rm.config_writer()
-
+
# deep traversal git / async
rsmsp = [sm.path for sm in rm.traverse()]
assert len(rsmsp) == 1 # gitdb only - its not yet uptodate so it has no submodule
-
+
# cannot set the parent commit as root module's path didn't exist
self.failUnlessRaises(ValueError, rm.set_parent_commit, 'HEAD')
-
+
# TEST UPDATE
#############
# setup commit which remove existing, add new and modify existing submodules
rm = RootModule(rwrepo)
assert len(rm.children()) == 1
-
+
# modify path without modifying the index entry
# ( which is what the move method would do properly )
#==================================================
@@ -450,37 +452,37 @@ class TestSubmodule(TestObjectBase):
fp = join_path_native(pp, sm.path)
prep = sm.path
assert not sm.module_exists() # was never updated after rwrepo's clone
-
+
# assure we clone from a local source
self._rewrite_gitdb_to_local_path(sm)
-
+
# dry-run does nothing
sm.update(recursive=False, dry_run=True, progress=prog)
assert not sm.module_exists()
-
+
sm.update(recursive=False)
assert sm.module_exists()
sm.config_writer().set_value('path', fp) # change path to something with prefix AFTER url change
-
+
# update fails as list_items in such a situations cannot work, as it cannot
# find the entry at the changed path
self.failUnlessRaises(InvalidGitRepositoryError, rm.update, recursive=False)
-
+
# move it properly - doesn't work as it its path currently points to an indexentry
# which doesn't exist ( move it to some path, it doesn't matter here )
self.failUnlessRaises(InvalidGitRepositoryError, sm.move, pp)
# reset the path(cache) to where it was, now it works
sm.path = prep
sm.move(fp, module=False) # leave it at the old location
-
+
assert not sm.module_exists()
- cpathchange = rwrepo.index.commit("changed sm path") # finally we can commit
-
+ cpathchange = rwrepo.index.commit("changed sm path") # finally we can commit
+
# update puts the module into place
rm.update(recursive=False, progress=prog)
sm.set_parent_commit(cpathchange)
assert sm.module_exists()
-
+
# add submodule
#================
nsmn = "newsubmodule"
@@ -494,17 +496,14 @@ class TestSubmodule(TestObjectBase):
# repo and a new submodule comes into life
nsm.remove(configuration=False, module=True)
assert not nsm.module_exists() and nsm.exists()
-
-
+
# dry-run does nothing
rm.update(recursive=False, dry_run=True, progress=prog)
-
+
# otherwise it will work
rm.update(recursive=False, progress=prog)
assert nsm.module_exists()
-
-
-
+
# remove submodule - the previous one
#====================================
sm.set_parent_commit(csmadded)
@@ -512,49 +511,48 @@ class TestSubmodule(TestObjectBase):
assert not sm.remove(module=False).exists()
assert os.path.isdir(smp) # module still exists
csmremoved = rwrepo.index.commit("Removed submodule")
-
+
# an update will remove the module
# not in dry_run
rm.update(recursive=False, dry_run=True)
assert os.path.isdir(smp)
-
+
rm.update(recursive=False)
assert not os.path.isdir(smp)
-
-
- # change url
+
+ # change url
#=============
- # to the first repository, this way we have a fast checkout, and a completely different
+ # to the first repository, this way we have a fast checkout, and a completely different
# repository at the different url
nsm.set_parent_commit(csmremoved)
nsmurl = os.environ.get(self.env_gitdb_local_path, self.k_github_gitdb_url)
-
- # Note: We would have liked to have a different url, but we cannot
+
+ # Note: We would have liked to have a different url, but we cannot
# provoke this case
assert nsm.url != nsmurl
nsm.config_writer().set_value('url', nsmurl)
csmpathchange = rwrepo.index.commit("changed url")
nsm.set_parent_commit(csmpathchange)
-
+
prev_commit = nsm.module().head.commit
# dry-run does nothing
rm.update(recursive=False, dry_run=True, progress=prog)
assert nsm.module().remotes.origin.url != nsmurl
-
+
rm.update(recursive=False, progress=prog)
assert nsm.module().remotes.origin.url == nsmurl
# head changed, as the remote url and its commit changed
assert prev_commit != nsm.module().head.commit
-
+
# add the submodule's changed commit to the index, which is what the
# user would do
# beforehand, update our instance's binsha with the new one
nsm.binsha = nsm.module().head.commit.binsha
rwrepo.index.add([nsm])
-
+
# change branch
#=================
- # we only have one branch, so we switch to a virtual one, and back
+ # we only have one branch, so we switch to a virtual one, and back
# to the current one to trigger the difference
cur_branch = nsm.branch
nsmm = nsm.module()
@@ -564,33 +562,32 @@ class TestSubmodule(TestObjectBase):
csmbranchchange = rwrepo.index.commit("changed branch to %s" % branch)
nsm.set_parent_commit(csmbranchchange)
# END for each branch to change
-
+
# Lets remove our tracking branch to simulate some changes
nsmmh = nsmm.head
assert nsmmh.ref.tracking_branch() is None # never set it up until now
assert not nsmmh.is_detached
-
- #dry run does nothing
+
+ # dry run does nothing
rm.update(recursive=False, dry_run=True, progress=prog)
assert nsmmh.ref.tracking_branch() is None
-
+
# the real thing does
rm.update(recursive=False, progress=prog)
-
+
assert nsmmh.ref.tracking_branch() is not None
assert not nsmmh.is_detached
-
+
# recursive update
# =================
# finally we recursively update a module, just to run the code at least once
# remove the module so that it has more work
- assert len(nsm.children()) >= 1 # could include smmap
+ assert len(nsm.children()) >= 1 # could include smmap
assert nsm.exists() and nsm.module_exists() and len(nsm.children()) >= 1
# assure we pull locally only
- nsmc = nsm.children()[0]
+ nsmc = nsm.children()[0]
nsmc.config_writer().set_value('url', async_url)
rm.update(recursive=True, progress=prog, dry_run=True) # just to run the code
rm.update(recursive=True, progress=prog)
-
+
assert len(nsm.children()) >= 2 and nsmc.module_exists()
-
diff --git a/git/test/objects/test_tree.py b/git/test/objects/test_tree.py
index 6317f4db..00cca5e4 100644
--- a/git/test/objects/test_tree.py
+++ b/git/test/objects/test_tree.py
@@ -7,16 +7,17 @@
from lib import *
from git.objects.fun import (
- traverse_tree_recursive,
- traverse_trees_recursive
- )
+ traverse_tree_recursive,
+ traverse_trees_recursive
+)
from git.objects.blob import Blob
from git.objects.tree import Tree
from cStringIO import StringIO
import os
+
class TestTree(TestObjectBase):
-
+
def test_serializable(self):
# tree at the given commit contains a submodule as well
roottree = self.rorepo.tree('6c1faef799095f3990e9970bc2cb10aa0221cf9c')
@@ -27,75 +28,74 @@ class TestTree(TestObjectBase):
tree = item
# trees have no dict
self.failUnlessRaises(AttributeError, setattr, tree, 'someattr', 1)
-
+
orig_data = tree.data_stream.read()
orig_cache = tree._cache
-
+
stream = StringIO()
tree._serialize(stream)
assert stream.getvalue() == orig_data
-
+
stream.seek(0)
testtree = Tree(self.rorepo, Tree.NULL_BIN_SHA, 0, '')
testtree._deserialize(stream)
assert testtree._cache == orig_cache
-
-
+
# TEST CACHE MUTATOR
mod = testtree.cache
self.failUnlessRaises(ValueError, mod.add, "invalid sha", 0, "name")
self.failUnlessRaises(ValueError, mod.add, Tree.NULL_HEX_SHA, 0, "invalid mode")
self.failUnlessRaises(ValueError, mod.add, Tree.NULL_HEX_SHA, tree.mode, "invalid/name")
-
+
# add new item
name = "fake_dir"
mod.add(testtree.NULL_HEX_SHA, tree.mode, name)
assert name in testtree
-
+
# its available in the tree immediately
assert isinstance(testtree[name], Tree)
-
+
# adding it again will not cause multiple of them to be presents
cur_count = len(testtree)
mod.add(testtree.NULL_HEX_SHA, tree.mode, name)
assert len(testtree) == cur_count
-
+
# fails with a different sha - name exists
- hexsha = "1"*40
+ hexsha = "1" * 40
self.failUnlessRaises(ValueError, mod.add, hexsha, tree.mode, name)
-
+
# force it - replace existing one
mod.add(hexsha, tree.mode, name, force=True)
assert testtree[name].hexsha == hexsha
assert len(testtree) == cur_count
-
+
# unchecked addition always works, even with invalid items
invalid_name = "hi/there"
mod.add_unchecked(hexsha, 0, invalid_name)
assert len(testtree) == cur_count + 1
-
+
del(mod[invalid_name])
assert len(testtree) == cur_count
# del again, its fine
del(mod[invalid_name])
-
+
# have added one item, we are done
mod.set_done()
mod.set_done() # multiple times are okay
-
+
# serialize, its different now
stream = StringIO()
testtree._serialize(stream)
stream.seek(0)
assert stream.getvalue() != orig_data
-
+
# replaces cache, but we make sure of it
del(testtree._cache)
testtree._deserialize(stream)
assert name in testtree
assert invalid_name not in testtree
# END for each item in tree
-
+
def test_traverse(self):
root = self.rorepo.tree('0.1.6')
num_recursive = 0
@@ -103,34 +103,34 @@ class TestTree(TestObjectBase):
for obj in root.traverse():
if "/" in obj.path:
num_recursive += 1
-
+
assert isinstance(obj, (Blob, Tree))
all_items.append(obj)
# END for each object
assert all_items == root.list_traverse()
-
+
# limit recursion level to 0 - should be same as default iteration
assert all_items
assert 'CHANGES' in root
assert len(list(root)) == len(list(root.traverse(depth=1)))
-
+
# only choose trees
- trees_only = lambda i,d: i.type == "tree"
- trees = list(root.traverse(predicate = trees_only))
- assert len(trees) == len(list( i for i in root.traverse() if trees_only(i,0) ))
-
+ trees_only = lambda i, d: i.type == "tree"
+ trees = list(root.traverse(predicate=trees_only))
+ assert len(trees) == len(list(i for i in root.traverse() if trees_only(i, 0)))
+
# test prune
- lib_folder = lambda t,d: t.path == "lib"
- pruned_trees = list(root.traverse(predicate = trees_only,prune = lib_folder))
+ lib_folder = lambda t, d: t.path == "lib"
+ pruned_trees = list(root.traverse(predicate=trees_only, prune=lib_folder))
assert len(pruned_trees) < len(trees)
-
+
# trees and blobs
- assert len(set(trees)|set(root.trees)) == len(trees)
- assert len(set(b for b in root if isinstance(b, Blob)) | set(root.blobs)) == len( root.blobs )
+ assert len(set(trees) | set(root.trees)) == len(trees)
+ assert len(set(b for b in root if isinstance(b, Blob)) | set(root.blobs)) == len(root.blobs)
subitem = trees[0][0]
assert "/" in subitem.path
assert subitem.name == os.path.basename(subitem.path)
-
+
# assure that at some point the traversed paths have a slash in them
found_slash = False
for item in root.traverse():
@@ -138,9 +138,8 @@ class TestTree(TestObjectBase):
if '/' in item.path:
found_slash = True
# END check for slash
-
- # slashes in paths are supported as well
- assert root[item.path] == item == root/item.path
+
+ # slashes in paths are supported as well
+ assert root[item.path] == item == root / item.path
# END for each item
assert found_slash
-
diff --git a/git/test/performance/db/__init__.py b/git/test/performance/db/__init__.py
index 8b137891..e69de29b 100644
--- a/git/test/performance/db/__init__.py
+++ b/git/test/performance/db/__init__.py
@@ -1 +0,0 @@
-
diff --git a/git/test/performance/db/looseodb_impl.py b/git/test/performance/db/looseodb_impl.py
index 1da69945..6cdbaa32 100644
--- a/git/test/performance/db/looseodb_impl.py
+++ b/git/test/performance/db/looseodb_impl.py
@@ -4,9 +4,9 @@ from git.base import *
from git.stream import *
from async import ChannelThreadTask
from git.util import (
- pool,
- bin_to_hex
- )
+ pool,
+ bin_to_hex
+)
import os
import sys
from time import time
@@ -15,7 +15,7 @@ from git.test.lib import (
GlobalsItemDeletorMetaCls,
make_memory_file,
with_rw_repo
- )
+)
from git.test.performance.lib import TestBigRepoR
@@ -32,16 +32,18 @@ def read_chunked_stream(stream):
# END read stream loop
assert total == stream.size
return stream
-
-
+
+
class TestStreamReader(ChannelThreadTask):
+
"""Expects input streams and reads them in chunks. It will read one at a time,
requireing a queue chunk of size 1"""
+
def __init__(self, *args):
super(TestStreamReader, self).__init__(*args)
self.fun = read_chunked_stream
self.max_chunksize = 1
-
+
#} END utilities
@@ -51,29 +53,29 @@ class PerfBaseDeletorMetaClass(GlobalsItemDeletorMetaCls):
class TestLooseDBWPerformanceBase(TestBigRepoR):
__metaclass__ = PerfBaseDeletorMetaClass
-
- large_data_size_bytes = 1000*1000*10 # some MiB should do it
- moderate_data_size_bytes = 1000*1000*1 # just 1 MiB
-
+
+ large_data_size_bytes = 1000 * 1000 * 10 # some MiB should do it
+ moderate_data_size_bytes = 1000 * 1000 * 1 # just 1 MiB
+
#{ Configuration
LooseODBCls = None
#} END configuration
-
+
@classmethod
def setUp(cls):
super(TestLooseDBWPerformanceBase, cls).setUp()
if cls.LooseODBCls is None:
raise AssertionError("LooseODBCls must be set in subtype")
- #END assert configuration
+ # END assert configuration
# currently there is no additional configuration
-
+
@with_rw_repo("HEAD")
def test_large_data_streaming(self, rwrepo):
# TODO: This part overlaps with the same file in git.test.performance.test_stream
# It should be shared if possible
objects_path = rwrepo.db_path('')
ldb = self.LooseODBCls(objects_path)
-
+
for randomize in range(2):
desc = (randomize and 'random ') or ''
print >> sys.stderr, "Creating %s data ..." % desc
@@ -81,8 +83,8 @@ class TestLooseDBWPerformanceBase(TestBigRepoR):
size, stream = make_memory_file(self.large_data_size_bytes, randomize)
elapsed = time() - st
print >> sys.stderr, "Done (in %f s)" % elapsed
-
- # writing - due to the compression it will seem faster than it is
+
+ # writing - due to the compression it will seem faster than it is
st = time()
binsha = ldb.store(IStream('blob', size, stream)).binsha
elapsed_add = time() - st
@@ -90,24 +92,24 @@ class TestLooseDBWPerformanceBase(TestBigRepoR):
hexsha = bin_to_hex(binsha)
db_file = os.path.join(objects_path, hexsha[:2], hexsha[2:])
fsize_kib = os.path.getsize(db_file) / 1000
-
-
+
size_kib = size / 1000
- print >> sys.stderr, "%s: Added %i KiB (filesize = %i KiB) of %s data to loose odb in %f s ( %f Write KiB / s)" % (self.LooseODBCls.__name__, size_kib, fsize_kib, desc, elapsed_add, size_kib / elapsed_add)
-
+ print >> sys.stderr, "%s: Added %i KiB (filesize = %i KiB) of %s data to loose odb in %f s ( %f Write KiB / s)" % (
+ self.LooseODBCls.__name__, size_kib, fsize_kib, desc, elapsed_add, size_kib / elapsed_add)
+
# reading all at once
st = time()
ostream = ldb.stream(binsha)
shadata = ostream.read()
elapsed_readall = time() - st
-
+
stream.seek(0)
assert shadata == stream.getvalue()
- print >> sys.stderr, "%s: Read %i KiB of %s data at once from loose odb in %f s ( %f Read KiB / s)" % (self.LooseODBCls.__name__, size_kib, desc, elapsed_readall, size_kib / elapsed_readall)
-
-
+ print >> sys.stderr, "%s: Read %i KiB of %s data at once from loose odb in %f s ( %f Read KiB / s)" % (
+ self.LooseODBCls.__name__, size_kib, desc, elapsed_readall, size_kib / elapsed_readall)
+
# reading in chunks of 1 MiB
- cs = 512*1000
+ cs = 512 * 1000
chunks = list()
st = time()
ostream = ldb.stream(binsha)
@@ -118,15 +120,14 @@ class TestLooseDBWPerformanceBase(TestBigRepoR):
break
# END read in chunks
elapsed_readchunks = time() - st
-
+
stream.seek(0)
assert ''.join(chunks) == stream.getvalue()
-
+
cs_kib = cs / 1000
- print >> sys.stderr, "%s: Read %i KiB of %s data in %i KiB chunks from loose odb in %f s ( %f Read KiB / s)" % (self.LooseODBCls.__name__, size_kib, desc, cs_kib, elapsed_readchunks, size_kib / elapsed_readchunks)
-
+ print >> sys.stderr, "%s: Read %i KiB of %s data in %i KiB chunks from loose odb in %f s ( %f Read KiB / s)" % (
+ self.LooseODBCls.__name__, size_kib, desc, cs_kib, elapsed_readchunks, size_kib / elapsed_readchunks)
+
# del db file so git has something to do
os.remove(db_file)
# END for each randomization factor
-
-
diff --git a/git/test/performance/db/odb_impl.py b/git/test/performance/db/odb_impl.py
index 887604c0..afe9a32b 100644
--- a/git/test/performance/db/odb_impl.py
+++ b/git/test/performance/db/odb_impl.py
@@ -7,31 +7,33 @@ import stat
from git.test.performance.lib import (
TestBigRepoR,
GlobalsItemDeletorMetaCls
- )
+)
+
class PerfBaseDeletorMetaClass(GlobalsItemDeletorMetaCls):
ModuleToDelete = 'TestObjDBPerformanceBase'
-
+
class TestObjDBPerformanceBase(TestBigRepoR):
__metaclass__ = PerfBaseDeletorMetaClass
-
- #{ Configuration
+
+ #{ Configuration
RepoCls = None # to be set by subclass
#} END configuration
-
+
def test_random_access_test(self):
repo = self.rorepo
-
+
# GET COMMITS
st = time()
root_commit = repo.commit(self.head_sha_2k)
commits = list(root_commit.traverse())
nc = len(commits)
elapsed = time() - st
-
- print >> sys.stderr, "%s: Retrieved %i commits from ObjectStore in %g s ( %f commits / s )" % (type(repo.odb), nc, elapsed, nc / elapsed)
-
+
+ print >> sys.stderr, "%s: Retrieved %i commits from ObjectStore in %g s ( %f commits / s )" % (
+ type(repo.odb), nc, elapsed, nc / elapsed)
+
# GET TREES
# walk all trees of all commits
st = time()
@@ -49,9 +51,10 @@ class TestObjDBPerformanceBase(TestBigRepoR):
blobs_per_commit.append(blobs)
# END for each commit
elapsed = time() - st
-
- print >> sys.stderr, "%s: Retrieved %i objects from %i commits in %g s ( %f objects / s )" % (type(repo.odb), nt, len(commits), elapsed, nt / elapsed)
-
+
+ print >> sys.stderr, "%s: Retrieved %i objects from %i commits in %g s ( %f objects / s )" % (
+ type(repo.odb), nt, len(commits), elapsed, nt / elapsed)
+
# GET BLOBS
st = time()
nb = 0
@@ -66,7 +69,6 @@ class TestObjDBPerformanceBase(TestBigRepoR):
break
# END for each bloblist
elapsed = time() - st
-
- print >> sys.stderr, "%s: Retrieved %i blob (%i KiB) and their data in %g s ( %f blobs / s, %f KiB / s )" % (type(repo.odb), nb, data_bytes/1000, elapsed, nb / elapsed, (data_bytes / 1000) / elapsed)
-
-
+
+ print >> sys.stderr, "%s: Retrieved %i blob (%i KiB) and their data in %g s ( %f blobs / s, %f KiB / s )" % (
+ type(repo.odb), nb, data_bytes / 1000, elapsed, nb / elapsed, (data_bytes / 1000) / elapsed)
diff --git a/git/test/performance/db/packedodb_impl.py b/git/test/performance/db/packedodb_impl.py
index 23d00444..2aaf99a2 100644
--- a/git/test/performance/db/packedodb_impl.py
+++ b/git/test/performance/db/packedodb_impl.py
@@ -4,9 +4,9 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Performance tests for object store"""
from git.test.performance.lib import (
- TestBigRepoR,
+ TestBigRepoR,
GlobalsItemDeletorMetaCls
- )
+)
from git.exc import UnsupportedOperation
@@ -19,31 +19,32 @@ import random
class PerfBaseDeletorMetaClass(GlobalsItemDeletorMetaCls):
ModuleToDelete = 'TestPurePackedODBPerformanceBase'
+
class TestPurePackedODBPerformanceBase(TestBigRepoR):
__metaclass__ = PerfBaseDeletorMetaClass
-
+
#{ Configuration
PackedODBCls = None
#} END configuration
-
+
@classmethod
def setUp(cls):
super(TestPurePackedODBPerformanceBase, cls).setUp()
if cls.PackedODBCls is None:
raise AssertionError("PackedODBCls must be set in subclass")
- #END assert configuration
+ # END assert configuration
cls.ropdb = cls.PackedODBCls(cls.rorepo.db_path("pack"))
-
+
def test_pack_random_access(self):
pdb = self.ropdb
-
+
# sha lookup
st = time()
sha_list = list(pdb.sha_iter())
elapsed = time() - st
ns = len(sha_list)
print >> sys.stderr, "PDB: looked up %i shas by index in %f s ( %f shas/s )" % (ns, elapsed, ns / elapsed)
-
+
# sha lookup: best-case and worst case access
pdb_pack_info = pdb._pack_info
# END shuffle shas
@@ -52,13 +53,14 @@ class TestPurePackedODBPerformanceBase(TestBigRepoR):
pdb_pack_info(sha)
# END for each sha to look up
elapsed = time() - st
-
+
# discard cache
del(pdb._entities)
pdb.entities()
- print >> sys.stderr, "PDB: looked up %i sha in %i packs in %f s ( %f shas/s )" % (ns, len(pdb.entities()), elapsed, ns / elapsed)
+ print >> sys.stderr, "PDB: looked up %i sha in %i packs in %f s ( %f shas/s )" % (
+ ns, len(pdb.entities()), elapsed, ns / elapsed)
# END for each random mode
-
+
# query info and streams only
max_items = 10000 # can wait longer when testing memory
for pdb_fun in (pdb.info, pdb.stream):
@@ -66,9 +68,10 @@ class TestPurePackedODBPerformanceBase(TestBigRepoR):
for sha in sha_list[:max_items]:
pdb_fun(sha)
elapsed = time() - st
- print >> sys.stderr, "PDB: Obtained %i object %s by sha in %f s ( %f items/s )" % (max_items, pdb_fun.__name__.upper(), elapsed, max_items / elapsed)
+ print >> sys.stderr, "PDB: Obtained %i object %s by sha in %f s ( %f items/s )" % (
+ max_items, pdb_fun.__name__.upper(), elapsed, max_items / elapsed)
# END for each function
-
+
# retrieve stream and read all
max_items = 5000
pdb_stream = pdb.stream
@@ -80,8 +83,9 @@ class TestPurePackedODBPerformanceBase(TestBigRepoR):
total_size += stream.size
elapsed = time() - st
total_kib = total_size / 1000
- print >> sys.stderr, "PDB: Obtained %i streams by sha and read all bytes totallying %i KiB ( %f KiB / s ) in %f s ( %f streams/s )" % (max_items, total_kib, total_kib/elapsed , elapsed, max_items / elapsed)
-
+ print >> sys.stderr, "PDB: Obtained %i streams by sha and read all bytes totallying %i KiB ( %f KiB / s ) in %f s ( %f streams/s )" % (
+ max_items, total_kib, total_kib / elapsed, elapsed, max_items / elapsed)
+
def test_correctness(self):
pdb = self.ropdb
# disabled for now as it used to work perfectly, checking big repositories takes a long time
@@ -102,6 +106,6 @@ class TestPurePackedODBPerformanceBase(TestBigRepoR):
# END for each index
# END for each entity
elapsed = time() - st
- print >> sys.stderr, "PDB: verified %i objects (crc=%i) in %f s ( %f objects/s )" % (count, crc, elapsed, count / elapsed)
+ print >> sys.stderr, "PDB: verified %i objects (crc=%i) in %f s ( %f objects/s )" % (
+ count, crc, elapsed, count / elapsed)
# END for each verify mode
-
diff --git a/git/test/performance/db/test_looseodb_cmd.py b/git/test/performance/db/test_looseodb_cmd.py
index 9147eff6..f96e4c3e 100644
--- a/git/test/performance/db/test_looseodb_cmd.py
+++ b/git/test/performance/db/test_looseodb_cmd.py
@@ -3,9 +3,10 @@ from looseodb_impl import TestLooseDBWPerformanceBase
import sys
+
class TestCmdLooseDB(TestLooseDBWPerformanceBase):
LooseODBCls = CmdCompatibilityGitDB
-
+
def test_info(self):
- sys.stderr.write("This test does not check the write performance of the git command as it is implemented in pure python")
-
+ sys.stderr.write(
+ "This test does not check the write performance of the git command as it is implemented in pure python")
diff --git a/git/test/performance/db/test_looseodb_dulwich.py b/git/test/performance/db/test_looseodb_dulwich.py
index 174be83d..e23327f7 100644
--- a/git/test/performance/db/test_looseodb_dulwich.py
+++ b/git/test/performance/db/test_looseodb_dulwich.py
@@ -2,12 +2,12 @@ try:
from git.db.dulwich.complex import DulwichGitODB
except ImportError:
from git.db.py.complex import PureGitODB as DulwichGitODB
-#END handle import
+# END handle import
from git.test.db.dulwich.lib import DulwichRequiredMetaMixin
from looseodb_impl import TestLooseDBWPerformanceBase
+
class TestPureLooseDB(TestLooseDBWPerformanceBase):
__metaclass__ = DulwichRequiredMetaMixin
LooseODBCls = DulwichGitODB
-
diff --git a/git/test/performance/db/test_looseodb_pure.py b/git/test/performance/db/test_looseodb_pure.py
index bb080612..bc4b54fe 100644
--- a/git/test/performance/db/test_looseodb_pure.py
+++ b/git/test/performance/db/test_looseodb_pure.py
@@ -1,6 +1,6 @@
from git.db.py.loose import PureLooseObjectODB
from looseodb_impl import TestLooseDBWPerformanceBase
+
class TestPureLooseDB(TestLooseDBWPerformanceBase):
LooseODBCls = PureLooseObjectODB
-
diff --git a/git/test/performance/db/test_looseodb_pygit2.py b/git/test/performance/db/test_looseodb_pygit2.py
index a9661111..06ece5c7 100644
--- a/git/test/performance/db/test_looseodb_pygit2.py
+++ b/git/test/performance/db/test_looseodb_pygit2.py
@@ -2,12 +2,12 @@ try:
from git.db.pygit2.complex import Pygit2GitODB
except ImportError:
from git.db.py.complex import PureGitODB as Pygit2GitODB
-#END handle import
+# END handle import
from git.test.db.pygit2.lib import Pygit2RequiredMetaMixin
from looseodb_impl import TestLooseDBWPerformanceBase
+
class TestPureLooseDB(TestLooseDBWPerformanceBase):
__metaclass__ = Pygit2RequiredMetaMixin
LooseODBCls = Pygit2GitODB
-
diff --git a/git/test/performance/db/test_odb_cmd.py b/git/test/performance/db/test_odb_cmd.py
index 37af34fd..a7dcfb0d 100644
--- a/git/test/performance/db/test_odb_cmd.py
+++ b/git/test/performance/db/test_odb_cmd.py
@@ -1,6 +1,6 @@
from git.db.complex import CmdCompatibilityGitDB
from odb_impl import TestObjDBPerformanceBase
+
class TestCmdDB(TestObjDBPerformanceBase):
RepoCls = CmdCompatibilityGitDB
-
diff --git a/git/test/performance/db/test_odb_dulwich.py b/git/test/performance/db/test_odb_dulwich.py
index 33abc88c..a5b8e57c 100644
--- a/git/test/performance/db/test_odb_dulwich.py
+++ b/git/test/performance/db/test_odb_dulwich.py
@@ -2,12 +2,12 @@ try:
from git.db.dulwich.complex import DulwichCompatibilityGitDB
except ImportError:
from git.db.complex import PureCompatibilityGitDB as DulwichCompatibilityGitDB
-#END handle dulwich compatibility
+# END handle dulwich compatibility
from git.test.db.dulwich.lib import DulwichRequiredMetaMixin
from odb_impl import TestObjDBPerformanceBase
+
class TestDulwichDB(TestObjDBPerformanceBase):
__metaclass__ = DulwichRequiredMetaMixin
RepoCls = DulwichCompatibilityGitDB
-
diff --git a/git/test/performance/db/test_odb_pure.py b/git/test/performance/db/test_odb_pure.py
index 93139c57..48c42659 100644
--- a/git/test/performance/db/test_odb_pure.py
+++ b/git/test/performance/db/test_odb_pure.py
@@ -1,6 +1,6 @@
from git.db.complex import PureCompatibilityGitDB
from odb_impl import TestObjDBPerformanceBase
+
class TestPureDB(TestObjDBPerformanceBase):
RepoCls = PureCompatibilityGitDB
-
diff --git a/git/test/performance/db/test_odb_pygit2.py b/git/test/performance/db/test_odb_pygit2.py
index c5911ae3..f44bfac8 100644
--- a/git/test/performance/db/test_odb_pygit2.py
+++ b/git/test/performance/db/test_odb_pygit2.py
@@ -2,12 +2,12 @@ try:
from git.db.pygit2.complex import Pygit2CompatibilityGitDB
except ImportError:
from git.db.complex import PureCompatibilityGitDB as Pygit2CompatibilityGitDB
-#END handle pygit2 compatibility
+# END handle pygit2 compatibility
from git.test.db.pygit2.lib import Pygit2RequiredMetaMixin
from odb_impl import TestObjDBPerformanceBase
+
class TestPygit2DB(TestObjDBPerformanceBase):
__metaclass__ = Pygit2RequiredMetaMixin
RepoCls = Pygit2CompatibilityGitDB
-
diff --git a/git/test/performance/db/test_packedodb_pure.py b/git/test/performance/db/test_packedodb_pure.py
index 90e8381f..94099b83 100644
--- a/git/test/performance/db/test_packedodb_pure.py
+++ b/git/test/performance/db/test_packedodb_pure.py
@@ -18,25 +18,27 @@ from nose import SkipTest
class CountedNullStream(NullStream):
__slots__ = '_bw'
+
def __init__(self):
self._bw = 0
-
+
def bytes_written(self):
return self._bw
-
+
def write(self, d):
self._bw += NullStream.write(self, d)
-
+
class TestPurePackedODB(TestPurePackedODBPerformanceBase):
#{ Configuration
PackedODBCls = PurePackedODB
#} END configuration
-
+
def test_pack_writing_note(self):
- sys.stderr.write("test_pack_writing should be adjusted to support different databases to read from - see test for more info")
+ sys.stderr.write(
+ "test_pack_writing should be adjusted to support different databases to read from - see test for more info")
raise SkipTest()
-
+
def test_pack_writing(self):
# see how fast we can write a pack from object streams.
# This will not be fast, as we take time for decompressing the streams as well
@@ -44,7 +46,7 @@ class TestPurePackedODB(TestPurePackedODBPerformanceBase):
ostream = CountedNullStream()
# NOTE: We use the same repo twice to see whether OS caching helps
for rorepo in (self.rorepo, self.rorepo, self.ropdb):
-
+
ni = 5000
count = 0
total_size = 0
@@ -54,22 +56,23 @@ class TestPurePackedODB(TestPurePackedODBPerformanceBase):
rorepo.stream(sha)
if count == ni:
break
- #END gather objects for pack-writing
+ # END gather objects for pack-writing
elapsed = time() - st
- print >> sys.stderr, "PDB Streaming: Got %i streams from %s by sha in in %f s ( %f streams/s )" % (count, rorepo.__class__.__name__, elapsed, count / elapsed)
-
+ print >> sys.stderr, "PDB Streaming: Got %i streams from %s by sha in in %f s ( %f streams/s )" % (
+ count, rorepo.__class__.__name__, elapsed, count / elapsed)
+
st = time()
PackEntity.write_pack((rorepo.stream(sha) for sha in rorepo.sha_iter()), ostream.write, object_count=ni)
elapsed = time() - st
total_kb = ostream.bytes_written() / 1000
- print >> sys.stderr, "PDB Streaming: Wrote pack of size %i kb in %f s (%f kb/s)" % (total_kb, elapsed, total_kb/elapsed)
- #END for each rorepo
-
-
+ print >> sys.stderr, "PDB Streaming: Wrote pack of size %i kb in %f s (%f kb/s)" % (
+ total_kb, elapsed, total_kb / elapsed)
+ # END for each rorepo
+
def test_stream_reading(self):
raise SkipTest("This test was only used for --with-profile runs")
pdb = self.ropdb
-
+
# streaming only, meant for --with-profile runs
ni = 5000
count = 0
@@ -85,5 +88,5 @@ class TestPurePackedODB(TestPurePackedODBPerformanceBase):
count += 1
elapsed = time() - st
total_kib = total_size / 1000
- print >> sys.stderr, "PDB Streaming: Got %i streams by sha and read all bytes totallying %i KiB ( %f KiB / s ) in %f s ( %f streams/s )" % (ni, total_kib, total_kib/elapsed , elapsed, ni / elapsed)
-
+ print >> sys.stderr, "PDB Streaming: Got %i streams by sha and read all bytes totallying %i KiB ( %f KiB / s ) in %f s ( %f streams/s )" % (
+ ni, total_kib, total_kib / elapsed, elapsed, ni / elapsed)
diff --git a/git/test/performance/lib.py b/git/test/performance/lib.py
index 2772fd7d..d01ef37e 100644
--- a/git/test/performance/lib.py
+++ b/git/test/performance/lib.py
@@ -1,9 +1,9 @@
"""Contains library functions"""
import os
from git.test.lib import (
- TestBase,
- GlobalsItemDeletorMetaCls
- )
+ TestBase,
+ GlobalsItemDeletorMetaCls
+)
import shutil
import tempfile
@@ -26,49 +26,51 @@ def resolve_or_fail(env_var):
#} END utilities
-#{ Base Classes
+#{ Base Classes
class TestBigRepoR(TestBase):
+
"""TestCase providing access to readonly 'big' repositories using the following
member variables:
-
+
* gitrorepo
-
+
* a big read-only git repository
"""
-
+
#{ Invariants
head_sha_2k = '235d521da60e4699e5bd59ac658b5b48bd76ddca'
head_sha_50 = '32347c375250fd470973a5d76185cac718955fd5'
- #} END invariants
-
+ #} END invariants
+
#{ Configuration
RepoCls = Repo
#} END configuration
-
+
@classmethod
def setUp(cls):
super(TestBigRepoR, cls).setUp()
if cls.RepoCls is None:
raise AssertionError("Require RepoCls in class %s to be set" % cls)
- #END assert configuration
+ # END assert configuration
cls.rorepo = cls.RepoCls(resolve_or_fail(k_env_git_repo))
class TestBigRepoRW(TestBigRepoR):
+
"""As above, but provides a big repository that we can write to.
-
+
Provides ``self.rwrepo``"""
-
+
@classmethod
def setUp(cls):
super(TestBigRepoRW, cls).setUp()
dirname = tempfile.mktemp()
os.mkdir(dirname)
cls.rwrepo = cls.rorepo.clone(dirname, shared=True, bare=True)
-
+
@classmethod
def tearDownAll(cls):
shutil.rmtree(cls.rwrepo.working_dir)
-
+
#} END base classes
diff --git a/git/test/performance/objects/__init__.py b/git/test/performance/objects/__init__.py
index 8b137891..e69de29b 100644
--- a/git/test/performance/objects/__init__.py
+++ b/git/test/performance/objects/__init__.py
@@ -1 +0,0 @@
-
diff --git a/git/test/performance/objects/test_commit.py b/git/test/performance/objects/test_commit.py
index e342e6b3..cd8866d3 100644
--- a/git/test/performance/objects/test_commit.py
+++ b/git/test/performance/objects/test_commit.py
@@ -12,8 +12,9 @@ from cStringIO import StringIO
from time import time
import sys
+
class TestPerformance(TestBigRepoRW):
-
+
# ref with about 100 commits in its history
ref_100 = 'v0.99'
@@ -26,15 +27,15 @@ class TestPerformance(TestBigRepoRW):
c.committer_tz_offset
c.message
c.parents
-
+
def test_iteration(self):
no = 0
nc = 0
-
- # find the first commit containing the given path - always do a full
- # iteration ( restricted to the path in question ), but in fact it should
+
+ # find the first commit containing the given path - always do a full
+ # iteration ( restricted to the path in question ), but in fact it should
# return quite a lot of commits, we just take one and hence abort the operation
-
+
st = time()
for c in self.rorepo.iter_commits(self.ref_100):
nc += 1
@@ -46,8 +47,9 @@ class TestPerformance(TestBigRepoRW):
# END for each commit
elapsed_time = time() - st
assert no, "Should have traversed a few objects"
- print >> sys.stderr, "Traversed %i Trees and a total of %i unchached objects in %s [s] ( %f objs/s )" % (nc, no, elapsed_time, no/elapsed_time)
-
+ print >> sys.stderr, "Traversed %i Trees and a total of %i unchached objects in %s [s] ( %f objs/s )" % (
+ nc, no, elapsed_time, no / elapsed_time)
+
def test_commit_traversal(self):
# bound to cat-file parsing performance
nc = 0
@@ -57,8 +59,8 @@ class TestPerformance(TestBigRepoRW):
self._query_commit_info(c)
# END for each traversed commit
elapsed_time = time() - st
- print >> sys.stderr, "Traversed %i Commits in %s [s] ( %f commits/s )" % (nc, elapsed_time, nc/elapsed_time)
-
+ print >> sys.stderr, "Traversed %i Commits in %s [s] ( %f commits/s )" % (nc, elapsed_time, nc / elapsed_time)
+
def test_commit_iteration(self):
# bound to stream parsing performance
nc = 0
@@ -68,33 +70,34 @@ class TestPerformance(TestBigRepoRW):
self._query_commit_info(c)
# END for each traversed commit
elapsed_time = time() - st
- print >> sys.stderr, "Iterated %i Commits in %s [s] ( %f commits/s )" % (nc, elapsed_time, nc/elapsed_time)
-
+ print >> sys.stderr, "Iterated %i Commits in %s [s] ( %f commits/s )" % (nc, elapsed_time, nc / elapsed_time)
+
def test_commit_serialization(self):
assert_commit_serialization(self.rwrepo, self.head_sha_2k, True)
-
+
rwrepo = self.rwrepo
make_object = rwrepo.store
# direct serialization - deserialization can be tested afterwards
# serialization is probably limited on IO
hc = rwrepo.commit(self.head_sha_2k)
-
+
commits = list()
nc = 5000
st = time()
for i in xrange(nc):
- cm = Commit( rwrepo, Commit.NULL_BIN_SHA, hc.tree,
- hc.author, hc.authored_date, hc.author_tz_offset,
- hc.committer, hc.committed_date, hc.committer_tz_offset,
- str(i), parents=hc.parents, encoding=hc.encoding)
-
+ cm = Commit(rwrepo, Commit.NULL_BIN_SHA, hc.tree,
+ hc.author, hc.authored_date, hc.author_tz_offset,
+ hc.committer, hc.committed_date, hc.committer_tz_offset,
+ str(i), parents=hc.parents, encoding=hc.encoding)
+
stream = StringIO()
cm._serialize(stream)
slen = stream.tell()
stream.seek(0)
-
+
cm.binsha = make_object(IStream(Commit.type, slen, stream)).binsha
# END commit creation
elapsed = time() - st
-
- print >> sys.stderr, "Serialized %i commits to loose objects in %f s ( %f commits / s )" % (nc, elapsed, nc / elapsed)
+
+ print >> sys.stderr, "Serialized %i commits to loose objects in %f s ( %f commits / s )" % (
+ nc, elapsed, nc / elapsed)
diff --git a/git/test/performance/test_utils.py b/git/test/performance/test_utils.py
index 8637af48..7db972f7 100644
--- a/git/test/performance/test_utils.py
+++ b/git/test/performance/test_utils.py
@@ -5,33 +5,37 @@ import stat
from lib import (
TestBigRepoR
- )
+)
class TestUtilPerformance(TestBigRepoR):
-
+
def test_access(self):
# compare dict vs. slot access
class Slotty(object):
__slots__ = "attr"
+
def __init__(self):
self.attr = 1
-
+
class Dicty(object):
+
def __init__(self):
self.attr = 1
-
+
class BigSlotty(object):
__slots__ = ('attr', ) + tuple('abcdefghijk')
+
def __init__(self):
for attr in self.__slots__:
setattr(self, attr, 1)
-
+
class BigDicty(object):
+
def __init__(self):
for attr in BigSlotty.__slots__:
setattr(self, attr, 1)
-
+
ni = 1000000
for cls in (Slotty, Dicty, BigSlotty, BigDicty):
cli = cls()
@@ -40,9 +44,10 @@ class TestUtilPerformance(TestBigRepoR):
cli.attr
# END for each access
elapsed = time() - st
- print >> sys.stderr, "Accessed %s.attr %i times in %s s ( %f acc / s)" % (cls.__name__, ni, elapsed, ni / elapsed)
+ print >> sys.stderr, "Accessed %s.attr %i times in %s s ( %f acc / s)" % (
+ cls.__name__, ni, elapsed, ni / elapsed)
# END for each class type
-
+
# check num of sequence-acceses
for cls in (list, tuple):
x = 10
@@ -55,13 +60,14 @@ class TestUtilPerformance(TestBigRepoR):
# END for
elapsed = time() - st
na = ni * 3
- print >> sys.stderr, "Accessed %s[x] %i times in %s s ( %f acc / s)" % (cls.__name__, na, elapsed, na / elapsed)
- # END for each sequence
-
+ print >> sys.stderr, "Accessed %s[x] %i times in %s s ( %f acc / s)" % (
+ cls.__name__, na, elapsed, na / elapsed)
+ # END for each sequence
+
def test_instantiation(self):
ni = 100000
max_num_items = 4
- for mni in range(max_num_items+1):
+ for mni in range(max_num_items + 1):
for cls in (tuple, list):
st = time()
for i in xrange(ni):
@@ -70,71 +76,75 @@ class TestUtilPerformance(TestBigRepoR):
elif mni == 1:
cls((1,))
elif mni == 2:
- cls((1,2))
+ cls((1, 2))
elif mni == 3:
- cls((1,2,3))
+ cls((1, 2, 3))
elif mni == 4:
- cls((1,2,3,4))
+ cls((1, 2, 3, 4))
else:
cls(x for x in xrange(mni))
# END handle empty cls
# END for each item
elapsed = time() - st
- print >> sys.stderr, "Created %i %ss of size %i in %f s ( %f inst / s)" % (ni, cls.__name__, mni, elapsed, ni / elapsed)
+ print >> sys.stderr, "Created %i %ss of size %i in %f s ( %f inst / s)" % (
+ ni, cls.__name__, mni, elapsed, ni / elapsed)
# END for each type
# END for each item count
-
+
# tuple and tuple direct
st = time()
for i in xrange(ni):
- t = (1,2,3,4)
+ t = (1, 2, 3, 4)
# END for each item
elapsed = time() - st
print >> sys.stderr, "Created %i tuples (1,2,3,4) in %f s ( %f tuples / s)" % (ni, elapsed, ni / elapsed)
-
+
st = time()
for i in xrange(ni):
- t = tuple((1,2,3,4))
+ t = tuple((1, 2, 3, 4))
# END for each item
elapsed = time() - st
print >> sys.stderr, "Created %i tuples tuple((1,2,3,4)) in %f s ( %f tuples / s)" % (ni, elapsed, ni / elapsed)
-
+
def test_unpacking_vs_indexing(self):
ni = 1000000
- list_items = [1,2,3,4]
- tuple_items = (1,2,3,4)
-
+ list_items = [1, 2, 3, 4]
+ tuple_items = (1, 2, 3, 4)
+
for sequence in (list_items, tuple_items):
st = time()
for i in xrange(ni):
one, two, three, four = sequence
# END for eac iteration
elapsed = time() - st
- print >> sys.stderr, "Unpacked %i %ss of size %i in %f s ( %f acc / s)" % (ni, type(sequence).__name__, len(sequence), elapsed, ni / elapsed)
-
+ print >> sys.stderr, "Unpacked %i %ss of size %i in %f s ( %f acc / s)" % (
+ ni, type(sequence).__name__, len(sequence), elapsed, ni / elapsed)
+
st = time()
for i in xrange(ni):
one, two, three, four = sequence[0], sequence[1], sequence[2], sequence[3]
# END for eac iteration
elapsed = time() - st
- print >> sys.stderr, "Unpacked %i %ss of size %i individually in %f s ( %f acc / s)" % (ni, type(sequence).__name__, len(sequence), elapsed, ni / elapsed)
-
+ print >> sys.stderr, "Unpacked %i %ss of size %i individually in %f s ( %f acc / s)" % (
+ ni, type(sequence).__name__, len(sequence), elapsed, ni / elapsed)
+
st = time()
for i in xrange(ni):
one, two = sequence[0], sequence[1]
# END for eac iteration
elapsed = time() - st
- print >> sys.stderr, "Unpacked %i %ss of size %i individually (2 of 4) in %f s ( %f acc / s)" % (ni, type(sequence).__name__, len(sequence), elapsed, ni / elapsed)
+ print >> sys.stderr, "Unpacked %i %ss of size %i individually (2 of 4) in %f s ( %f acc / s)" % (
+ ni, type(sequence).__name__, len(sequence), elapsed, ni / elapsed)
# END for each sequence
-
+
def test_large_list_vs_iteration(self):
# what costs more: alloc/realloc of lists, or the cpu strain of iterators ?
def slow_iter(ni):
for i in xrange(ni):
yield i
# END slow iter - be closer to the real world
-
- # alloc doesn't play a role here it seems
+
+ # alloc doesn't play a role here it seems
for ni in (500, 1000, 10000, 20000, 40000):
st = time()
for i in list(xrange(ni)):
@@ -142,7 +152,7 @@ class TestUtilPerformance(TestBigRepoR):
# END for each item
elapsed = time() - st
print >> sys.stderr, "Iterated %i items from list in %f s ( %f acc / s)" % (ni, elapsed, ni / elapsed)
-
+
st = time()
for i in slow_iter(ni):
i
@@ -150,22 +160,23 @@ class TestUtilPerformance(TestBigRepoR):
elapsed = time() - st
print >> sys.stderr, "Iterated %i items from iterator in %f s ( %f acc / s)" % (ni, elapsed, ni / elapsed)
# END for each number of iterations
-
+
def test_type_vs_inst_class(self):
class NewType(object):
pass
-
+
# lets see which way is faster
inst = NewType()
-
+
ni = 1000000
st = time()
for i in xrange(ni):
inst.__class__()
# END for each item
elapsed = time() - st
- print >> sys.stderr, "Created %i items using inst.__class__ in %f s ( %f items / s)" % (ni, elapsed, ni / elapsed)
-
+ print >> sys.stderr, "Created %i items using inst.__class__ in %f s ( %f items / s)" % (
+ ni, elapsed, ni / elapsed)
+
st = time()
for i in xrange(ni):
type(inst)()
diff --git a/git/test/refs/__init__.py b/git/test/refs/__init__.py
index 8b137891..e69de29b 100644
--- a/git/test/refs/__init__.py
+++ b/git/test/refs/__init__.py
@@ -1 +0,0 @@
-
diff --git a/git/test/refs/test_reflog.py b/git/test/refs/test_reflog.py
index 2ac19de9..8a97b5ec 100644
--- a/git/test/refs/test_reflog.py
+++ b/git/test/refs/test_reflog.py
@@ -7,6 +7,7 @@ import tempfile
import shutil
import os
+
class TestRefLog(TestBase):
def test_reflogentry(self):
@@ -14,51 +15,51 @@ class TestRefLog(TestBase):
hexsha = 'F' * 40
actor = Actor('name', 'email')
msg = "message"
-
+
self.failUnlessRaises(ValueError, RefLogEntry.new, nullhexsha, hexsha, 'noactor', 0, 0, "")
e = RefLogEntry.new(nullhexsha, hexsha, actor, 0, 1, msg)
-
+
assert e.oldhexsha == nullhexsha
assert e.newhexsha == hexsha
assert e.actor == actor
assert e.time[0] == 0
assert e.time[1] == 1
assert e.message == msg
-
+
# check representation (roughly)
assert repr(e).startswith(nullhexsha)
-
+
def test_base(self):
rlp_head = fixture_path('reflog_HEAD')
rlp_master = fixture_path('reflog_master')
tdir = tempfile.mktemp(suffix="test_reflogs")
os.mkdir(tdir)
-
- rlp_master_ro = RefLog.path(self.rorepo.head)
+
+ rlp_master_ro = RefLog.path(self.rorepo.head)
assert os.path.isfile(rlp_master_ro)
-
+
# simple read
reflog = RefLog.from_file(rlp_master_ro)
assert reflog._path is not None
assert isinstance(reflog, RefLog)
assert len(reflog)
-
+
# iter_entries works with path and with stream
assert len(list(RefLog.iter_entries(open(rlp_master))))
assert len(list(RefLog.iter_entries(rlp_master)))
-
+
# raise on invalid revlog
# TODO: Try multiple corrupted ones !
pp = 'reflog_invalid_'
for suffix in ('oldsha', 'newsha', 'email', 'date', 'sep'):
- self.failUnlessRaises(ValueError, RefLog.from_file, fixture_path(pp+suffix))
- #END for each invalid file
-
+ self.failUnlessRaises(ValueError, RefLog.from_file, fixture_path(pp + suffix))
+ # END for each invalid file
+
# cannot write an uninitialized reflog
self.failUnlessRaises(ValueError, RefLog().write)
-
+
# test serialize and deserialize - results must match exactly
- binsha = chr(255)*20
+ binsha = chr(255) * 20
msg = "my reflog message"
cr = self.rorepo.config_reader()
for rlp in (rlp_head, rlp_master):
@@ -66,35 +67,34 @@ class TestRefLog(TestBase):
tfile = os.path.join(tdir, os.path.basename(rlp))
reflog.to_file(tfile)
assert reflog.write() is reflog
-
+
# parsed result must match ...
treflog = RefLog.from_file(tfile)
assert treflog == reflog
-
+
# ... as well as each bytes of the written stream
assert open(tfile).read() == open(rlp).read()
-
+
# append an entry
entry = RefLog.append_entry(cr, tfile, IndexObject.NULL_BIN_SHA, binsha, msg)
assert entry.oldhexsha == IndexObject.NULL_HEX_SHA
- assert entry.newhexsha == 'f'*40
+ assert entry.newhexsha == 'f' * 40
assert entry.message == msg
assert RefLog.from_file(tfile)[-1] == entry
-
+
# index entry
# raises on invalid index
self.failUnlessRaises(IndexError, RefLog.entry_at, rlp, 10000)
-
+
# indices can be positive ...
assert isinstance(RefLog.entry_at(rlp, 0), RefLogEntry)
RefLog.entry_at(rlp, 23)
-
+
# ... and negative
for idx in (-1, -24):
RefLog.entry_at(rlp, idx)
- #END for each index to read
- # END for each reflog
-
-
+ # END for each index to read
+ # END for each reflog
+
# finally remove our temporary data
shutil.rmtree(tdir)
diff --git a/git/test/refs/test_refs.py b/git/test/refs/test_refs.py
index d3716cc4..7213c119 100644
--- a/git/test/refs/test_refs.py
+++ b/git/test/refs/test_refs.py
@@ -18,6 +18,7 @@ import os
from nose import SkipTest
+
class TestRefs(TestBase):
def test_from_path(self):
@@ -27,14 +28,14 @@ class TestRefs(TestBase):
full_path = ref_type.to_full_path(name)
instance = ref_type.from_path(self.rorepo, full_path)
assert isinstance(instance, ref_type)
- # END for each name
+ # END for each name
# END for each type
-
+
# invalid path
self.failUnlessRaises(ValueError, TagReference, self.rorepo, "refs/invalid/tag")
# works without path check
TagReference(self.rorepo, "refs/invalid/tag", check_path=False)
-
+
def test_tag_base(self):
tag_object_refs = list()
for tag in TagReference.list_items(self.rorepo):
@@ -46,7 +47,7 @@ class TestRefs(TestBase):
tagobj = tag.tag
# have no dict
self.failUnlessRaises(AttributeError, setattr, tagobj, 'someattr', 1)
- assert isinstance(tagobj, TagObject)
+ assert isinstance(tagobj, TagObject)
assert tagobj.tag == tag.name
assert isinstance(tagobj.tagger, Actor)
assert isinstance(tagobj.tagged_date, int)
@@ -59,7 +60,7 @@ class TestRefs(TestBase):
# END for tag in repo-tags
assert tag_object_refs
assert isinstance(TagReference.list_items(self.rorepo)['0.1.6'], TagReference)
-
+
def test_tags(self):
# tag refs can point to tag objects or to commits
s = set()
@@ -74,8 +75,8 @@ class TestRefs(TestBase):
s.add(ref)
# END for each ref
assert len(s) == ref_count
- assert len(s|s) == ref_count
-
+ assert len(s | s) == ref_count
+
@with_rw_repo("0.1.6")
def test_heads(self, rw_repo):
for head in Head.iter_items(rw_repo):
@@ -86,7 +87,7 @@ class TestRefs(TestBase):
cur_object = head.object
assert prev_object == cur_object # represent the same git object
assert prev_object is not cur_object # but are different instances
-
+
writer = head.config_writer()
tv = "testopt"
writer.set_value(tv, 1)
@@ -94,7 +95,7 @@ class TestRefs(TestBase):
del(writer)
assert head.config_reader().get_value(tv) == 1
head.config_writer().remove_option(tv)
-
+
# after the clone, we might still have a tracking branch setup
head.set_tracking_branch(None)
assert head.tracking_branch() is None
@@ -104,7 +105,7 @@ class TestRefs(TestBase):
head.set_tracking_branch(None)
assert head.tracking_branch() is None
# END for each head
-
+
# verify REFLOG gets altered
head = HEAD(rw_repo)
cur_head = head.ref
@@ -118,75 +119,74 @@ class TestRefs(TestBase):
assert len(thlog) == hlog_len + 1
assert thlog[-1].oldhexsha == cur_commit.hexsha
assert thlog[-1].newhexsha == pcommit.hexsha
-
+
# the ref didn't change though
assert len(cur_head.log()) == blog_len
-
+
# head changes once again, cur_head doesn't change
head.set_reference(cur_head, 'reattach head')
- assert len(head.log()) == hlog_len+2
+ assert len(head.log()) == hlog_len + 2
assert len(cur_head.log()) == blog_len
-
+
# adjusting the head-ref also adjust the head, so both reflogs are
# altered
cur_head.set_commit(pcommit, 'changing commit')
- assert len(cur_head.log()) == blog_len+1
- assert len(head.log()) == hlog_len+3
-
-
+ assert len(cur_head.log()) == blog_len + 1
+ assert len(head.log()) == hlog_len + 3
+
# with automatic dereferencing
assert head.set_commit(cur_commit, 'change commit once again') is head
- assert len(head.log()) == hlog_len+4
- assert len(cur_head.log()) == blog_len+2
-
+ assert len(head.log()) == hlog_len + 4
+ assert len(cur_head.log()) == blog_len + 2
+
# a new branch has just a single entry
other_head = Head.create(rw_repo, 'mynewhead', pcommit, logmsg='new head created')
log = other_head.log()
assert len(log) == 1
assert log[0].oldhexsha == pcommit.NULL_HEX_SHA
assert log[0].newhexsha == pcommit.hexsha
-
+
def test_refs(self):
types_found = set()
for ref in Reference.list_items(self.rorepo):
types_found.add(type(ref))
- assert len(types_found) >= 3
-
+ assert len(types_found) >= 3
+
def test_is_valid(self):
assert Reference(self.rorepo, 'refs/doesnt/exist').is_valid() == False
assert HEAD(self.rorepo).is_valid()
assert HEAD(self.rorepo).reference.is_valid()
assert SymbolicReference(self.rorepo, 'hellothere').is_valid() == False
-
+
def test_orig_head(self):
assert type(HEAD(self.rorepo).orig_head()) == SymbolicReference
-
+
@with_rw_repo("0.1.6")
def test_head_reset(self, rw_repo):
cur_head = HEAD(rw_repo)
old_head_commit = cur_head.commit
new_head_commit = cur_head.ref.commit.parents[0]
-
- cur_head.reset(new_head_commit, index=True) # index only
+
+ cur_head.reset(new_head_commit, index=True) # index only
assert cur_head.reference.commit == new_head_commit
-
+
self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True)
new_head_commit = new_head_commit.parents[0]
cur_head.reset(new_head_commit, index=True, working_tree=True) # index + wt
assert cur_head.reference.commit == new_head_commit
-
+
# paths - make sure we have something to do
rw_repo.index.reset(old_head_commit.parents[0])
- cur_head.reset(cur_head, paths = "test")
- cur_head.reset(new_head_commit, paths = "lib")
+ cur_head.reset(cur_head, paths="test")
+ cur_head.reset(new_head_commit, paths="lib")
# hard resets with paths don't work, its all or nothing
- self.failUnlessRaises(GitCommandError, cur_head.reset, new_head_commit, working_tree=True, paths = "lib")
-
+ self.failUnlessRaises(GitCommandError, cur_head.reset, new_head_commit, working_tree=True, paths="lib")
+
# we can do a mixed reset, and then checkout from the index though
cur_head.reset(new_head_commit)
- rw_repo.index.checkout(["lib"], force=True)#
-
- # now that we have a write write repo, change the HEAD reference - its
+ rw_repo.index.checkout(["lib"], force=True)
+
+ # now that we have a write write repo, change the HEAD reference - its
# like git-reset --soft
heads = Head.list_items(rw_repo)
assert heads
@@ -197,7 +197,7 @@ class TestRefs(TestBase):
assert cur_head.commit == head.commit
assert not cur_head.is_detached
# END for each head
-
+
# detach
active_head = heads[0]
curhead_commit = active_head.commit
@@ -205,50 +205,50 @@ class TestRefs(TestBase):
assert cur_head.commit == curhead_commit
assert cur_head.is_detached
self.failUnlessRaises(TypeError, getattr, cur_head, "reference")
-
+
# tags are references, hence we can point to them
some_tag = TagReference.list_items(rw_repo)[0]
cur_head.reference = some_tag
assert not cur_head.is_detached
assert cur_head.commit == some_tag.commit
- assert isinstance(cur_head.reference, TagReference)
-
+ assert isinstance(cur_head.reference, TagReference)
+
# put HEAD back to a real head, otherwise everything else fails
cur_head.reference = active_head
-
+
# type check
self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that")
-
- # head handling
+
+ # head handling
commit = 'HEAD'
prev_head_commit = cur_head.commit
for count, new_name in enumerate(("my_new_head", "feature/feature1")):
- actual_commit = commit+"^"*count
+ actual_commit = commit + "^" * count
new_head = Head.create(rw_repo, new_name, actual_commit)
assert new_head.is_detached
assert cur_head.commit == prev_head_commit
assert isinstance(new_head, Head)
# already exists, but has the same value, so its fine
Head.create(rw_repo, new_name, new_head.commit)
-
+
# its not fine with a different value
self.failUnlessRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0])
-
+
# force it
new_head = Head.create(rw_repo, new_name, actual_commit, force=True)
old_path = new_head.path
old_name = new_head.name
-
+
assert new_head.rename("hello").name == "hello"
- assert new_head.rename("hello/world").name == "hello/world" # yes, this must work
+ assert new_head.rename("hello/world").name == "hello/world" # yes, this must work
assert new_head.rename(old_name).name == old_name and new_head.path == old_path
-
+
# rename with force
tmp_head = Head.create(rw_repo, "tmphead")
self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head)
tmp_head.rename(new_head, force=True)
assert tmp_head == new_head and tmp_head.object == new_head.object
-
+
logfile = RefLog.path(tmp_head)
assert os.path.isfile(logfile)
Head.delete(rw_repo, tmp_head)
@@ -259,17 +259,17 @@ class TestRefs(TestBase):
# force on deletion testing would be missing here, code looks okay though ;)
# END for each new head name
self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name")
-
+
# tag ref
tag_name = "1.0.2"
light_tag = TagReference.create(rw_repo, tag_name)
self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name)
- light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force = True)
+ light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force=True)
assert isinstance(light_tag, TagReference)
assert light_tag.name == tag_name
assert light_tag.commit == cur_head.commit.parents[0]
assert light_tag.tag is None
-
+
# tag with tag object
other_tag_name = "releases/1.0.2RC"
msg = "my mighty tag\nsecond line"
@@ -278,49 +278,49 @@ class TestRefs(TestBase):
assert obj_tag.name == other_tag_name
assert obj_tag.commit == cur_head.commit
assert obj_tag.tag is not None
-
+
TagReference.delete(rw_repo, light_tag, obj_tag)
tags = rw_repo.tags
assert light_tag not in tags and obj_tag not in tags
-
+
# remote deletion
remote_refs_so_far = 0
- remotes = rw_repo.remotes
+ remotes = rw_repo.remotes
assert remotes
for remote in remotes:
refs = remote.refs
-
+
# If a HEAD exists, it must be deleted first. Otherwise it might
# end up pointing to an invalid ref it the ref was deleted before.
remote_head_name = "HEAD"
if remote_head_name in refs:
RemoteReference.delete(rw_repo, refs[remote_head_name])
del(refs[remote_head_name])
- #END handle HEAD deletion
-
+ # END handle HEAD deletion
+
RemoteReference.delete(rw_repo, *refs)
remote_refs_so_far += len(refs)
for ref in refs:
assert ref.remote_name == remote.name
# END for each ref to delete
assert remote_refs_so_far
-
+
for remote in remotes:
# remotes without references throw
self.failUnlessRaises(AssertionError, getattr, remote, 'refs')
# END for each remote
-
+
# change where the active head points to
if cur_head.is_detached:
cur_head.reference = rw_repo.heads[0]
-
+
head = cur_head.reference
old_commit = head.commit
head.commit = old_commit.parents[0]
assert head.commit == old_commit.parents[0]
assert head.commit == cur_head.commit
head.commit = old_commit
-
+
# setting a non-commit as commit fails, but succeeds as object
head_tree = head.commit.tree
self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree)
@@ -329,8 +329,8 @@ class TestRefs(TestBase):
head.object = head_tree
assert head.object == head_tree
# cannot query tree as commit
- self.failUnlessRaises(TypeError, getattr, head, 'commit')
-
+ self.failUnlessRaises(TypeError, getattr, head, 'commit')
+
# set the commit directly using the head. This would never detach the head
assert not cur_head.is_detached
head.object = old_commit
@@ -340,58 +340,58 @@ class TestRefs(TestBase):
assert cur_head.is_detached
cur_head.commit = parent_commit
assert cur_head.is_detached and cur_head.commit == parent_commit
-
+
cur_head.reference = head
assert not cur_head.is_detached
cur_head.commit = parent_commit
assert not cur_head.is_detached
assert head.commit == parent_commit
-
+
# test checkout
active_branch = rw_repo.active_branch
for head in rw_repo.heads:
checked_out_head = head.checkout()
assert checked_out_head == head
# END for each head to checkout
-
+
# checkout with branch creation
new_head = active_branch.checkout(b="new_head")
assert active_branch != rw_repo.active_branch
assert new_head == rw_repo.active_branch
-
+
# checkout with force as we have a changed a file
# clear file
- open(new_head.commit.tree.blobs[-1].abspath,'w').close()
+ open(new_head.commit.tree.blobs[-1].abspath, 'w').close()
assert len(new_head.commit.diff(None))
-
+
# create a new branch that is likely to touch the file we changed
- far_away_head = rw_repo.create_head("far_head",'HEAD~100')
+ far_away_head = rw_repo.create_head("far_head", 'HEAD~100')
self.failUnlessRaises(GitCommandError, far_away_head.checkout)
assert active_branch == active_branch.checkout(force=True)
assert rw_repo.head.reference != far_away_head
-
+
# test reference creation
partial_ref = 'sub/ref'
full_ref = 'refs/%s' % partial_ref
ref = Reference.create(rw_repo, partial_ref)
assert ref.path == full_ref
assert ref.object == rw_repo.head.commit
-
+
self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20')
# it works if it is at the same spot though and points to the same reference
assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref
Reference.delete(rw_repo, full_ref)
-
+
# recreate the reference using a full_ref
ref = Reference.create(rw_repo, full_ref)
assert ref.path == full_ref
assert ref.object == rw_repo.head.commit
-
+
# recreate using force
ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True)
assert ref.path == full_ref
assert ref.object == rw_repo.head.commit.parents[0]
-
+
# rename it
orig_obj = ref.object
for name in ('refs/absname', 'rela_name', 'feature/rela_name'):
@@ -401,10 +401,10 @@ class TestRefs(TestBase):
assert ref_new_name.object == orig_obj
assert ref_new_name == ref
# END for each name type
-
+
# References that don't exist trigger an error if we want to access them
self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit')
-
+
# exists, fail unless we force
ex_ref_path = far_away_head.path
self.failUnlessRaises(OSError, ref.rename, ex_ref_path)
@@ -412,34 +412,34 @@ class TestRefs(TestBase):
far_away_head.commit = ref.commit
ref.rename(ex_ref_path)
assert ref.path == ex_ref_path and ref.object == orig_obj
- assert ref.rename(ref.path).path == ex_ref_path # rename to same name
-
+ assert ref.rename(ref.path).path == ex_ref_path # rename to same name
+
# create symbolic refs
symref_path = "symrefs/sym"
symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)
assert symref.path == symref_path
assert symref.reference == cur_head.reference
-
+
self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit)
- # it works if the new ref points to the same reference
+ # it works if the new ref points to the same reference
SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path
SymbolicReference.delete(rw_repo, symref)
# would raise if the symref wouldn't have been deletedpbl
symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)
-
+
# test symbolic references which are not at default locations like HEAD
# or FETCH_HEAD - they may also be at spots in refs of course
symbol_ref_path = "refs/symbol_ref"
symref = SymbolicReference(rw_repo, symbol_ref_path)
assert symref.path == symbol_ref_path
-
+
# set it
symref.reference = new_head
assert symref.reference == new_head
assert os.path.isfile(symref.abspath)
assert symref.commit == new_head.commit
-
- for name in ('absname','folder/rela_name'):
+
+ for name in ('absname', 'folder/rela_name'):
symref_new_name = symref.rename(name)
assert isinstance(symref_new_name, SymbolicReference)
assert name in symref_new_name.path
@@ -447,10 +447,10 @@ class TestRefs(TestBase):
assert symref_new_name == symref
assert not symref.is_detached
# END for each ref
-
+
# create a new non-head ref just to be sure we handle it even if packed
Reference.create(rw_repo, full_ref)
-
+
# test ref listing - assure we have packed refs
rw_repo.git.pack_refs(all=True, prune=True)
heads = rw_repo.heads
@@ -458,14 +458,14 @@ class TestRefs(TestBase):
assert new_head in heads
assert active_branch in heads
assert rw_repo.tags
-
+
# we should be able to iterate all symbolic refs as well - in that case
# we should expect only symbolic references to be returned
for symref in SymbolicReference.iter_items(rw_repo):
assert not symref.is_detached
-
+
# when iterating references, we can get references and symrefs
- # when deleting all refs, I'd expect them to be gone ! Even from
+ # when deleting all refs, I'd expect them to be gone ! Even from
# the packed ones
# For this to work, we must not be on any branch
rw_repo.head.reference = rw_repo.head.commit
@@ -477,64 +477,64 @@ class TestRefs(TestBase):
# END delete ref
# END for each ref to iterate and to delete
assert deleted_refs
-
+
for ref in Reference.iter_items(rw_repo):
if ref.is_detached:
assert ref not in deleted_refs
# END for each ref
-
- # reattach head - head will not be returned if it is not a symbolic
+
+ # reattach head - head will not be returned if it is not a symbolic
# ref
rw_repo.head.reference = Head.create(rw_repo, "master")
-
+
# At least the head should still exist
assert os.path.isfile(rw_repo.head.abspath)
refs = list(SymbolicReference.iter_items(rw_repo))
assert len(refs) == 1
-
-
+
# test creation of new refs from scratch
for path in ("basename", "dir/somename", "dir2/subdir/basename"):
- # REFERENCES
+ # REFERENCES
############
fpath = Reference.to_full_path(path)
ref_fp = Reference.from_path(rw_repo, fpath)
assert not ref_fp.is_valid()
ref = Reference(rw_repo, fpath)
assert ref == ref_fp
-
+
# can be created by assigning a commit
ref.commit = rw_repo.head.commit
assert ref.is_valid()
-
+
# if the assignment raises, the ref doesn't exist
Reference.delete(ref.repo, ref.path)
assert not ref.is_valid()
self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense")
assert not ref.is_valid()
-
+
# I am sure I had my reason to make it a class method at first, but
# now it doesn't make so much sense anymore, want an instance method as well
# See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27
Reference.delete(ref.repo, ref.path)
assert not ref.is_valid()
-
+
ref.object = rw_repo.head.commit
assert ref.is_valid()
-
+
Reference.delete(ref.repo, ref.path)
assert not ref.is_valid()
self.failUnlessRaises(ValueError, setattr, ref, 'object', "nonsense")
assert not ref.is_valid()
-
+
# END for each path
-
+
def test_dereference_recursive(self):
# for now, just test the HEAD
assert SymbolicReference.dereference_recursive(self.rorepo, 'HEAD')
-
+
def test_reflog(self):
assert isinstance(Head.list_items(self.rorepo).master.log(), RefLog)
-
+
def test_pure_python_rename(self):
- raise SkipTest("Pure python reference renames cannot properly handle refnames which become a directory after rename")
+ raise SkipTest(
+ "Pure python reference renames cannot properly handle refnames which become a directory after rename")
diff --git a/git/test/test_base.py b/git/test/test_base.py
index 67f370d2..10d98b17 100644
--- a/git/test/test_base.py
+++ b/git/test/test_base.py
@@ -4,20 +4,20 @@
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from lib import (
- TestBase,
- with_rw_repo,
- DummyStream,
- DeriveTest,
- with_rw_and_rw_remote_repo
- )
+ TestBase,
+ with_rw_repo,
+ DummyStream,
+ DeriveTest,
+ with_rw_and_rw_remote_repo
+)
import git.objects.base as base
from git.objects import (
- Blob,
- Tree,
- Commit,
- TagObject
- )
+ Blob,
+ Tree,
+ Commit,
+ TagObject
+)
import git.refs as refs
@@ -30,33 +30,34 @@ import tempfile
from git.util import (
NULL_BIN_SHA
- )
+)
from git.typ import str_blob_type
from git.base import (
- OInfo,
- OPackInfo,
- ODeltaPackInfo,
- OStream,
- OPackStream,
- ODeltaPackStream,
- IStream,
- )
+ OInfo,
+ OPackInfo,
+ ODeltaPackInfo,
+ OStream,
+ OPackStream,
+ ODeltaPackStream,
+ IStream,
+)
import os
+
class TestBase(TestBase):
-
- type_tuples = ( ("blob", "8741fc1d09d61f02ffd8cded15ff603eff1ec070", "blob.py"),
- ("tree", "3a6a5e3eeed3723c09f1ef0399f81ed6b8d82e79", "directory"),
- ("commit", "4251bd59fb8e11e40c40548cba38180a9536118c", None),
- ("tag", "e56a60e8e9cd333cfba0140a77cd12b0d9398f10", None) )
-
- def test_base_object(self):
+
+ type_tuples = (("blob", "8741fc1d09d61f02ffd8cded15ff603eff1ec070", "blob.py"),
+ ("tree", "3a6a5e3eeed3723c09f1ef0399f81ed6b8d82e79", "directory"),
+ ("commit", "4251bd59fb8e11e40c40548cba38180a9536118c", None),
+ ("tag", "e56a60e8e9cd333cfba0140a77cd12b0d9398f10", None))
+
+ def test_base_object(self):
# test interface of base object classes
types = (Blob, Tree, Commit, TagObject)
assert len(types) == len(self.type_tuples)
-
+
s = set()
num_objs = 0
num_index_objs = 0
@@ -64,9 +65,9 @@ class TestBase(TestBase):
binsha = hex_to_bin(hexsha)
item = None
if path is None:
- item = obj_type(self.rorepo,binsha)
+ item = obj_type(self.rorepo, binsha)
else:
- item = obj_type(self.rorepo,binsha, 0, path)
+ item = obj_type(self.rorepo, binsha, 0, path)
# END handle index objects
num_objs += 1
assert item.hexsha == hexsha
@@ -77,88 +78,86 @@ class TestBase(TestBase):
assert str(item) == item.hexsha
assert repr(item)
s.add(item)
-
+
if isinstance(item, base.IndexObject):
num_index_objs += 1
- if hasattr(item,'path'): # never runs here
+ if hasattr(item, 'path'): # never runs here
assert not item.path.startswith("/") # must be relative
assert isinstance(item.mode, int)
# END index object check
-
+
# read from stream
data_stream = item.data_stream
data = data_stream.read()
assert data
-
+
tmpfile = os.tmpfile()
assert item == item.stream_data(tmpfile)
tmpfile.seek(0)
assert tmpfile.read() == data
# END stream to file directly
# END for each object type to create
-
+
# each has a unique sha
assert len(s) == num_objs
- assert len(s|s) == num_objs
+ assert len(s | s) == num_objs
assert num_index_objs == 2
-
+
def test_get_object_type_by_name(self):
for tname in base.Object.TYPES:
assert base.Object in get_object_type_by_name(tname).mro()
- # END for each known type
-
+ # END for each known type
+
self.failUnlessRaises(ValueError, get_object_type_by_name, "doesntexist")
def test_object_resolution(self):
# objects must be resolved to shas so they compare equal
assert self.rorepo.head.reference.object == self.rorepo.active_branch.object
-
+
@with_rw_repo('HEAD', bare=True)
def test_with_bare_rw_repo(self, bare_rw_repo):
assert bare_rw_repo.config_reader("repository").getboolean("core", "bare")
- assert os.path.isfile(os.path.join(bare_rw_repo.git_dir,'HEAD'))
-
+ assert os.path.isfile(os.path.join(bare_rw_repo.git_dir, 'HEAD'))
+
@with_rw_repo('0.1.6')
def test_with_rw_repo(self, rw_repo):
assert not rw_repo.config_reader("repository").getboolean("core", "bare")
- assert os.path.isdir(os.path.join(rw_repo.working_tree_dir,'lib'))
-
+ assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib'))
+
@with_rw_and_rw_remote_repo('0.1.6')
def test_with_rw_remote_and_rw_repo(self, rw_repo, rw_remote_repo):
assert not rw_repo.config_reader("repository").getboolean("core", "bare")
assert rw_remote_repo.config_reader("repository").getboolean("core", "bare")
- assert os.path.isdir(os.path.join(rw_repo.working_tree_dir,'lib'))
-
-
+ assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib'))
+
class TestBaseTypes(TestBase):
-
+
def test_streams(self):
# test info
sha = NULL_BIN_SHA
s = 20
blob_id = 3
-
+
info = OInfo(sha, str_blob_type, s)
assert info.binsha == sha
assert info.type == str_blob_type
assert info.type_id == blob_id
assert info.size == s
-
+
# test pack info
# provides type_id
pinfo = OPackInfo(0, blob_id, s)
assert pinfo.type == str_blob_type
assert pinfo.type_id == blob_id
assert pinfo.pack_offset == 0
-
+
dpinfo = ODeltaPackInfo(0, blob_id, s, sha)
assert dpinfo.type == str_blob_type
assert dpinfo.type_id == blob_id
assert dpinfo.delta_info == sha
assert dpinfo.pack_offset == 0
-
-
+
# test ostream
stream = DummyStream()
ostream = OStream(*(info + (stream, )))
@@ -168,33 +167,33 @@ class TestBaseTypes(TestBase):
assert stream.bytes == 15
ostream.read(20)
assert stream.bytes == 20
-
+
# test packstream
postream = OPackStream(*(pinfo + (stream, )))
assert postream.stream is stream
postream.read(10)
stream._assert()
assert stream.bytes == 10
-
+
# test deltapackstream
dpostream = ODeltaPackStream(*(dpinfo + (stream, )))
dpostream.stream is stream
dpostream.read(5)
stream._assert()
assert stream.bytes == 5
-
+
# derive with own args
- DeriveTest(sha, str_blob_type, s, stream, 'mine',myarg = 3)._assert()
-
+ DeriveTest(sha, str_blob_type, s, stream, 'mine', myarg=3)._assert()
+
# test istream
istream = IStream(str_blob_type, s, stream)
assert istream.binsha == None
istream.binsha = sha
assert istream.binsha == sha
-
+
assert len(istream.binsha) == 20
assert len(istream.hexsha) == 40
-
+
assert istream.size == s
istream.size = s * 2
istream.size == s * 2
@@ -204,9 +203,7 @@ class TestBaseTypes(TestBase):
assert istream.stream is stream
istream.stream = None
assert istream.stream is None
-
+
assert istream.error is None
istream.error = Exception()
assert isinstance(istream.error, Exception)
-
-
diff --git a/git/test/test_cmd.py b/git/test/test_cmd.py
index 5f59c200..adc5173a 100644
--- a/git/test/test_cmd.py
+++ b/git/test/test_cmd.py
@@ -4,18 +4,20 @@
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
-import os, sys
+import os
+import sys
from git.test.lib import (
- TestBase,
- patch,
- raises,
- assert_equal,
- assert_true,
- assert_match,
- fixture_path
- )
+ TestBase,
+ patch,
+ raises,
+ assert_equal,
+ assert_true,
+ assert_match,
+ fixture_path
+)
from git import Git, GitCommandError
+
class TestGit(TestBase):
@classmethod
@@ -42,7 +44,6 @@ class TestGit(TestBase):
def test_it_raises_errors(self):
self.git.this_does_not_exist()
-
def test_it_transforms_kwargs_into_git_command_arguments(self):
assert_equal(["-s"], self.git.transform_kwargs(**{'s': True}))
assert_equal(["-s5"], self.git.transform_kwargs(**{'s': 5}))
@@ -53,7 +54,7 @@ class TestGit(TestBase):
assert_equal(["-s", "-t"], self.git.transform_kwargs(**{'s': True, 't': True}))
def test_it_executes_git_to_shell_and_returns_result(self):
- assert_match('^git version [\d\.]{2}.*$', self.git.execute(["git","version"]))
+ assert_match('^git version [\d\.]{2}.*$', self.git.execute(["git", "version"]))
def test_it_accepts_stdin(self):
filename = fixture_path("cat_file_blob")
@@ -72,13 +73,13 @@ class TestGit(TestBase):
# read header only
import subprocess as sp
hexsha = "b2339455342180c7cc1e9bba3e9f181f7baa5167"
- g = self.git.cat_file(batch_check=True, istream=sp.PIPE,as_process=True)
+ g = self.git.cat_file(batch_check=True, istream=sp.PIPE, as_process=True)
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
g.stdin.flush()
obj_info = g.stdout.readline()
# read header + data
- g = self.git.cat_file(batch=True, istream=sp.PIPE,as_process=True)
+ g = self.git.cat_file(batch=True, istream=sp.PIPE, as_process=True)
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
g.stdin.flush()
obj_info_two = g.stdout.readline()
@@ -94,9 +95,8 @@ class TestGit(TestBase):
g.stdin.flush()
assert g.stdout.readline() == obj_info
-
# same can be achived using the respective command functions
- hexsha, typename, size = self.git.get_object_header(hexsha)
+ hexsha, typename, size = self.git.get_object_header(hexsha)
hexsha, typename_two, size_two, data = self.git.get_object_data(hexsha)
assert typename == typename_two and size == size_two
@@ -105,17 +105,18 @@ class TestGit(TestBase):
assert isinstance(v, tuple)
for n in v:
assert isinstance(n, int)
- #END verify number types
+ # END verify number types
def test_cmd_override(self):
prev_cmd = self.git.GIT_PYTHON_GIT_EXECUTABLE
try:
# set it to something that doens't exist, assure it raises
- type(self.git).GIT_PYTHON_GIT_EXECUTABLE = os.path.join("some", "path", "which", "doesn't", "exist", "gitbinary")
+ type(self.git).GIT_PYTHON_GIT_EXECUTABLE = os.path.join(
+ "some", "path", "which", "doesn't", "exist", "gitbinary")
self.failUnlessRaises(OSError, self.git.version)
finally:
type(self.git).GIT_PYTHON_GIT_EXECUTABLE = prev_cmd
- #END undo adjustment
+ # END undo adjustment
def test_output_strip(self):
import subprocess as sp
diff --git a/git/test/test_config.py b/git/test/test_config.py
index b37db290..b00240b0 100644
--- a/git/test/test_config.py
+++ b/git/test/test_config.py
@@ -10,36 +10,37 @@ from git.config import *
from copy import copy
from ConfigParser import NoSectionError
+
class TestConfig(TestBase):
-
+
def _to_memcache(self, file_path):
fp = open(file_path, "r")
sio = StringIO.StringIO(fp.read())
sio.name = file_path
return sio
-
+
def _parsers_equal_or_raise(self, lhs, rhs):
pass
-
+
def test_read_write(self):
# writer must create the exact same file as the one read before
for filename in ("git_config", "git_config_global"):
file_obj = self._to_memcache(fixture_path(filename))
file_obj_orig = copy(file_obj)
- w_config = GitConfigParser(file_obj, read_only = False)
+ w_config = GitConfigParser(file_obj, read_only=False)
w_config.read() # enforce reading
assert w_config._sections
w_config.write() # enforce writing
-
+
# we stripped lines when reading, so the results differ
assert file_obj.getvalue() != file_obj_orig.getvalue()
-
+
# creating an additional config writer must fail due to exclusive access
- self.failUnlessRaises(IOError, GitConfigParser, file_obj, read_only = False)
-
+ self.failUnlessRaises(IOError, GitConfigParser, file_obj, read_only=False)
+
# should still have a lock and be able to make changes
assert w_config._lock._has_lock()
-
+
# changes should be written right away
sname = "my_section"
oname = "mykey"
@@ -47,23 +48,23 @@ class TestConfig(TestBase):
w_config.add_section(sname)
assert w_config.has_section(sname)
w_config.set(sname, oname, val)
- assert w_config.has_option(sname,oname)
+ assert w_config.has_option(sname, oname)
assert w_config.get(sname, oname) == val
-
+
sname_new = "new_section"
oname_new = "new_key"
ival = 10
w_config.set_value(sname_new, oname_new, ival)
assert w_config.get_value(sname_new, oname_new) == ival
-
+
file_obj.seek(0)
r_config = GitConfigParser(file_obj, read_only=True)
- #print file_obj.getvalue()
+ # print file_obj.getvalue()
assert r_config.has_section(sname)
assert r_config.has_option(sname, oname)
assert r_config.get(sname, oname) == val
# END for each filename
-
+
def test_base(self):
path_repo = fixture_path("git_config")
path_global = fixture_path("git_config_global")
@@ -71,7 +72,7 @@ class TestConfig(TestBase):
assert r_config.read_only
num_sections = 0
num_options = 0
-
+
# test reader methods
assert r_config._is_initialized == False
for section in r_config.sections():
@@ -84,27 +85,27 @@ class TestConfig(TestBase):
assert val
assert "\n" not in option
assert "\n" not in val
-
+
# writing must fail
self.failUnlessRaises(IOError, r_config.set, section, option, None)
- self.failUnlessRaises(IOError, r_config.remove_option, section, option )
+ self.failUnlessRaises(IOError, r_config.remove_option, section, option)
# END for each option
self.failUnlessRaises(IOError, r_config.remove_section, section)
- # END for each section
+ # END for each section
assert num_sections and num_options
assert r_config._is_initialized == True
-
+
# get value which doesnt exist, with default
default = "my default value"
assert r_config.get_value("doesnt", "exist", default) == default
-
+
# it raises if there is no default though
self.failUnlessRaises(NoSectionError, r_config.get_value, "doesnt", "exist")
-
+
def test_values(self):
file_obj = self._to_memcache(fixture_path("git_config_values"))
- w_config = GitConfigParser(file_obj, read_only = False)
- w_config.write() # enforce writing
+ w_config = GitConfigParser(file_obj, read_only=False)
+ w_config.write() # enforce writing
orig_value = file_obj.getvalue()
# Reading must unescape backslashes
@@ -122,7 +123,7 @@ class TestConfig(TestBase):
# Writing must escape backslashes and quotes
w_config.set('values', 'backslash', backslash)
w_config.set('values', 'quote', quote)
- w_config.write() # enforce writing
+ w_config.write() # enforce writing
# Contents shouldn't differ
assert file_obj.getvalue() == orig_value
diff --git a/git/test/test_diff.py b/git/test/test_diff.py
index 98e72d6c..e55cbecc 100644
--- a/git/test/test_diff.py
+++ b/git/test/test_diff.py
@@ -5,17 +5,18 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from git.test.lib import (
- TestBase,
- StringProcessAdapter,
- fixture,
- assert_equal,
- assert_true
- )
+ TestBase,
+ StringProcessAdapter,
+ fixture,
+ assert_equal,
+ assert_true
+)
from git.diff import *
+
class TestDiff(TestBase):
-
+
def _assert_diff_format(self, diffs):
# verify that the format of the diff is sane
for diff in diffs:
@@ -23,19 +24,19 @@ class TestDiff(TestBase):
assert isinstance(diff.a_mode, int)
if diff.b_mode:
assert isinstance(diff.b_mode, int)
-
+
if diff.a_blob:
assert not diff.a_blob.path.endswith('\n')
if diff.b_blob:
assert not diff.b_blob.path.endswith('\n')
# END for each diff
return diffs
-
+
def test_list_from_string_new_mode(self):
output = StringProcessAdapter(fixture('diff_new_mode'))
diffs = Diff._index_from_patch_format(self.rorepo, output.stdout)
self._assert_diff_format(diffs)
-
+
assert_equal(1, len(diffs))
assert_equal(10, len(diffs[0].diff.splitlines()))
@@ -43,7 +44,7 @@ class TestDiff(TestBase):
output = StringProcessAdapter(fixture('diff_rename'))
diffs = Diff._index_from_patch_format(self.rorepo, output.stdout)
self._assert_diff_format(diffs)
-
+
assert_equal(1, len(diffs))
diff = diffs[0]
@@ -67,10 +68,10 @@ class TestDiff(TestBase):
def test_diff_patch_format(self):
# test all of the 'old' format diffs for completness - it should at least
# be able to deal with it
- fixtures = ("diff_2", "diff_2f", "diff_f", "diff_i", "diff_mode_only",
- "diff_new_mode", "diff_numstat", "diff_p", "diff_rename",
- "diff_tree_numstat_root" )
-
+ fixtures = ("diff_2", "diff_2f", "diff_f", "diff_i", "diff_mode_only",
+ "diff_new_mode", "diff_numstat", "diff_p", "diff_rename",
+ "diff_tree_numstat_root")
+
for fixture_name in fixtures:
diff_proc = StringProcessAdapter(fixture(fixture_name))
diffs = Diff._index_from_patch_format(self.rorepo, diff_proc.stdout)
@@ -81,24 +82,24 @@ class TestDiff(TestBase):
assertion_map = dict()
for i, commit in enumerate(self.rorepo.iter_commits('0.1.6', max_count=2)):
diff_item = commit
- if i%2 == 0:
+ if i % 2 == 0:
diff_item = commit.tree
# END use tree every second item
-
+
for other in (None, commit.Index, commit.parents[0]):
for paths in (None, "CHANGES", ("CHANGES", "lib")):
for create_patch in range(2):
diff_index = diff_item.diff(other, paths, create_patch)
assert isinstance(diff_index, DiffIndex)
-
+
if diff_index:
self._assert_diff_format(diff_index)
for ct in DiffIndex.change_type:
- key = 'ct_%s'%ct
+ key = 'ct_%s' % ct
assertion_map.setdefault(key, 0)
- assertion_map[key] = assertion_map[key]+len(list(diff_index.iter_change_type(ct)))
+ assertion_map[key] = assertion_map[key] + len(list(diff_index.iter_change_type(ct)))
# END for each changetype
-
+
# check entries
diff_set = set()
diff_set.add(diff_index[0])
@@ -106,7 +107,7 @@ class TestDiff(TestBase):
assert len(diff_set) == 1
assert diff_index[0] == diff_index[0]
assert not (diff_index[0] != diff_index[0])
- # END diff index checking
+ # END diff index checking
# END for each patch option
# END for each path option
# END for each other side
@@ -119,18 +120,16 @@ class TestDiff(TestBase):
assert len(rename_diffs) == 3
assert rename_diffs[0].rename_from == rename_diffs[0].a_blob.path
assert rename_diffs[0].rename_to == rename_diffs[0].b_blob.path
-
- # assert we could always find at least one instance of the members we
+
+ # assert we could always find at least one instance of the members we
# can iterate in the diff index - if not this indicates its not working correctly
# or our test does not span the whole range of possibilities
- for key,value in assertion_map.items():
+ for key, value in assertion_map.items():
assert value, "Did not find diff for %s" % key
- # END for each iteration type
-
+ # END for each iteration type
+
# test path not existing in the index - should be ignored
c = self.rorepo.head.commit
cp = c.parents[0]
diff_index = c.diff(cp, ["does/not/exist"])
assert len(diff_index) == 0
-
-
diff --git a/git/test/test_example.py b/git/test/test_example.py
index 1fd87b3f..8a80aac8 100644
--- a/git/test/test_example.py
+++ b/git/test/test_example.py
@@ -7,21 +7,22 @@ from lib import TestBase, fixture_path
from git.base import IStream
from git.db.py.loose import PureLooseObjectODB
from git.util import pool
-
+
from cStringIO import StringIO
from async import IteratorReader
-
+
+
class TestExamples(TestBase):
-
+
def test_base(self):
ldb = PureLooseObjectODB(fixture_path("../../../.git/objects"))
-
+
for sha1 in ldb.sha_iter():
oinfo = ldb.info(sha1)
ostream = ldb.stream(sha1)
assert oinfo[:3] == ostream[:3]
-
+
assert len(ostream.read()) == ostream.size
assert ldb.has_object(oinfo.binsha)
# END for each sha in database
@@ -32,33 +33,32 @@ class TestExamples(TestBase):
except UnboundLocalError:
pass
# END ignore exception if there are no loose objects
-
+
data = "my data"
istream = IStream("blob", len(data), StringIO(data))
-
+
# the object does not yet have a sha
assert istream.binsha is None
ldb.store(istream)
# now the sha is set
assert len(istream.binsha) == 20
assert ldb.has_object(istream.binsha)
-
-
+
# async operation
# Create a reader from an iterator
reader = IteratorReader(ldb.sha_iter())
-
+
# get reader for object streams
info_reader = ldb.stream_async(reader)
-
+
# read one
info = info_reader.read(1)[0]
-
+
# read all the rest until depletion
ostreams = info_reader.read()
-
+
# set the pool to use two threads
pool.set_size(2)
-
+
# synchronize the mode of operation
pool.set_size(0)
diff --git a/git/test/test_fun.py b/git/test/test_fun.py
index 15bc20ed..ecefd86f 100644
--- a/git/test/test_fun.py
+++ b/git/test/test_fun.py
@@ -1,29 +1,30 @@
from git.test.lib import TestBase, with_rw_repo
from git.objects.fun import (
- traverse_tree_recursive,
- traverse_trees_recursive,
- tree_to_stream
- )
+ traverse_tree_recursive,
+ traverse_trees_recursive,
+ tree_to_stream
+)
from git.index.fun import (
- aggressive_tree_merge
- )
+ aggressive_tree_merge
+)
from git.util import bin_to_hex
from git.base import IStream
from git.typ import str_tree_type
from stat import (
- S_IFDIR,
- S_IFREG,
- S_IFLNK
- )
+ S_IFDIR,
+ S_IFREG,
+ S_IFLNK
+)
from git.index import IndexFile
from cStringIO import StringIO
+
class TestFun(TestBase):
-
+
def _assert_index_entries(self, entries, trees):
index = IndexFile.from_tree(self.rorepo, *[self.rorepo.tree(bin_to_hex(t)) for t in trees])
assert entries
@@ -31,22 +32,22 @@ class TestFun(TestBase):
for entry in entries:
assert (entry.path, entry.stage) in index.entries
# END assert entry matches fully
-
+
def test_aggressive_tree_merge(self):
# head tree with additions, removals and modification compared to its predecessor
odb = self.rorepo.odb
- HC = self.rorepo.commit("6c1faef799095f3990e9970bc2cb10aa0221cf9c")
+ HC = self.rorepo.commit("6c1faef799095f3990e9970bc2cb10aa0221cf9c")
H = HC.tree
B = HC.parents[0].tree
-
+
# entries from single tree
trees = [H.binsha]
self._assert_index_entries(aggressive_tree_merge(odb, trees), trees)
-
+
# from multiple trees
trees = [B.binsha, H.binsha]
self._assert_index_entries(aggressive_tree_merge(odb, trees), trees)
-
+
# three way, no conflict
tree = self.rorepo.tree
B = tree("35a09c0534e89b2d43ec4101a5fb54576b577905")
@@ -54,16 +55,16 @@ class TestFun(TestBase):
M = tree("1f2b19de3301e76ab3a6187a49c9c93ff78bafbd")
trees = [B.binsha, H.binsha, M.binsha]
self._assert_index_entries(aggressive_tree_merge(odb, trees), trees)
-
+
# three-way, conflict in at least one file, both modified
B = tree("a7a4388eeaa4b6b94192dce67257a34c4a6cbd26")
H = tree("f9cec00938d9059882bb8eabdaf2f775943e00e5")
M = tree("44a601a068f4f543f73fd9c49e264c931b1e1652")
trees = [B.binsha, H.binsha, M.binsha]
self._assert_index_entries(aggressive_tree_merge(odb, trees), trees)
-
+
# too many trees
- self.failUnlessRaises(ValueError, aggressive_tree_merge, odb, trees*2)
+ self.failUnlessRaises(ValueError, aggressive_tree_merge, odb, trees * 2)
def mktree(self, odb, entries):
"""create a tree from the given tree entries and safe it to the database"""
@@ -72,122 +73,123 @@ class TestFun(TestBase):
sio.seek(0)
istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
return istream.binsha
-
+
@with_rw_repo('0.1.6')
def test_three_way_merge(self, rwrepo):
def mkfile(name, sha, executable=0):
- return (sha, S_IFREG | 0644 | executable*0111, name)
+ return (sha, S_IFREG | 0644 | executable * 0111, name)
+
def mkcommit(name, sha):
return (sha, S_IFDIR | S_IFLNK, name)
+
def assert_entries(entries, num_entries, has_conflict=False):
assert len(entries) == num_entries
assert has_conflict == (len([e for e in entries if e.stage != 0]) > 0)
mktree = self.mktree
-
- shaa = "\1"*20
- shab = "\2"*20
- shac = "\3"*20
-
+
+ shaa = "\1" * 20
+ shab = "\2" * 20
+ shac = "\3" * 20
+
odb = rwrepo.odb
-
+
# base tree
bfn = 'basefile'
fbase = mkfile(bfn, shaa)
tb = mktree(odb, [fbase])
-
+
# non-conflicting new files, same data
fa = mkfile('1', shab)
th = mktree(odb, [fbase, fa])
fb = mkfile('2', shac)
tm = mktree(odb, [fbase, fb])
-
+
# two new files, same base file
trees = [tb, th, tm]
assert_entries(aggressive_tree_merge(odb, trees), 3)
-
+
# both delete same file, add own one
fa = mkfile('1', shab)
th = mktree(odb, [fa])
fb = mkfile('2', shac)
tm = mktree(odb, [fb])
-
+
# two new files
trees = [tb, th, tm]
assert_entries(aggressive_tree_merge(odb, trees), 2)
-
+
# same file added in both, differently
fa = mkfile('1', shab)
th = mktree(odb, [fa])
fb = mkfile('1', shac)
tm = mktree(odb, [fb])
-
+
# expect conflict
trees = [tb, th, tm]
assert_entries(aggressive_tree_merge(odb, trees), 2, True)
-
+
# same file added, different mode
fa = mkfile('1', shab)
th = mktree(odb, [fa])
fb = mkcommit('1', shab)
tm = mktree(odb, [fb])
-
+
# expect conflict
trees = [tb, th, tm]
assert_entries(aggressive_tree_merge(odb, trees), 2, True)
-
+
# same file added in both
fa = mkfile('1', shab)
th = mktree(odb, [fa])
fb = mkfile('1', shab)
tm = mktree(odb, [fb])
-
+
# expect conflict
trees = [tb, th, tm]
assert_entries(aggressive_tree_merge(odb, trees), 1)
-
+
# modify same base file, differently
fa = mkfile(bfn, shab)
th = mktree(odb, [fa])
fb = mkfile(bfn, shac)
tm = mktree(odb, [fb])
-
+
# conflict, 3 versions on 3 stages
trees = [tb, th, tm]
assert_entries(aggressive_tree_merge(odb, trees), 3, True)
-
-
+
# change mode on same base file, by making one a commit, the other executable
# no content change ( this is totally unlikely to happen in the real world )
fa = mkcommit(bfn, shaa)
th = mktree(odb, [fa])
fb = mkfile(bfn, shaa, executable=1)
tm = mktree(odb, [fb])
-
+
# conflict, 3 versions on 3 stages, because of different mode
trees = [tb, th, tm]
assert_entries(aggressive_tree_merge(odb, trees), 3, True)
-
+
for is_them in range(2):
# only we/they change contents
fa = mkfile(bfn, shab)
th = mktree(odb, [fa])
-
+
trees = [tb, th, tb]
if is_them:
trees = [tb, tb, th]
entries = aggressive_tree_merge(odb, trees)
assert len(entries) == 1 and entries[0].binsha == shab
-
+
# only we/they change the mode
fa = mkcommit(bfn, shaa)
th = mktree(odb, [fa])
-
+
trees = [tb, th, tb]
if is_them:
trees = [tb, tb, th]
entries = aggressive_tree_merge(odb, trees)
assert len(entries) == 1 and entries[0].binsha == shaa and entries[0].mode == fa[1]
-
+
# one side deletes, the other changes = conflict
fa = mkfile(bfn, shab)
th = mktree(odb, [fa])
@@ -198,16 +200,16 @@ class TestFun(TestBase):
# as one is deleted, there are only 2 entries
assert_entries(aggressive_tree_merge(odb, trees), 2, True)
# END handle ours, theirs
-
+
def _assert_tree_entries(self, entries, num_trees):
for entry in entries:
assert len(entry) == num_trees
paths = set(e[2] for e in entry if e)
-
+
# only one path per set of entries
assert len(paths) == 1
# END verify entry
-
+
def test_tree_traversal(self):
# low level tree tarversal
odb = self.rorepo.odb
@@ -215,29 +217,29 @@ class TestFun(TestBase):
M = self.rorepo.tree('e14e3f143e7260de9581aee27e5a9b2645db72de') # merge tree
B = self.rorepo.tree('f606937a7a21237c866efafcad33675e6539c103') # base tree
B_old = self.rorepo.tree('1f66cfbbce58b4b552b041707a12d437cc5f400a') # old base tree
-
+
# two very different trees
entries = traverse_trees_recursive(odb, [B_old.binsha, H.binsha], '')
self._assert_tree_entries(entries, 2)
-
+
oentries = traverse_trees_recursive(odb, [H.binsha, B_old.binsha], '')
assert len(oentries) == len(entries)
self._assert_tree_entries(oentries, 2)
-
+
# single tree
is_no_tree = lambda i, d: i.type != 'tree'
entries = traverse_trees_recursive(odb, [B.binsha], '')
assert len(entries) == len(list(B.traverse(predicate=is_no_tree)))
self._assert_tree_entries(entries, 1)
-
+
# two trees
entries = traverse_trees_recursive(odb, [B.binsha, H.binsha], '')
self._assert_tree_entries(entries, 2)
-
+
# tree trees
entries = traverse_trees_recursive(odb, [B.binsha, H.binsha, M.binsha], '')
self._assert_tree_entries(entries, 3)
-
+
def test_tree_traversal_single(self):
max_count = 50
count = 0
diff --git a/git/test/test_import.py b/git/test/test_import.py
index 606d4b03..49e04028 100644
--- a/git/test/test_import.py
+++ b/git/test/test_import.py
@@ -10,7 +10,8 @@ import os
from git import *
-def import_all(topdir, topmodule='git', skip = "test"):
+
+def import_all(topdir, topmodule='git', skip="test"):
base = os.path.basename
join = os.path.join
init_script = '__init__.py'
@@ -21,37 +22,37 @@ def import_all(topdir, topmodule='git', skip = "test"):
if init_script not in files:
del(dirs[:])
continue
- #END ignore non-packages
-
+ # END ignore non-packages
+
if skip in root:
continue
- #END handle ignores
-
+ # END handle ignores
+
for relafile in files:
if not relafile.endswith('.py'):
continue
if relafile == init_script:
continue
module_path = join(root, os.path.splitext(relafile)[0]).replace("/", ".").replace("\\", ".")
-
+
m = __import__(module_path, globals(), locals(), [""])
try:
attrlist = m.__all__
for attr in attrlist:
- assert hasattr(m, attr), "Invalid item in %s.__all__: %s" % (module_path, attr)
- #END veriy
+ assert hasattr(m, attr), "Invalid item in %s.__all__: %s" % (module_path, attr)
+ # END veriy
except AttributeError:
pass
# END try each listed attribute
- #END for each file in dir
- #END for each item
+ # END for each file in dir
+ # END for each item
finally:
os.chdir(prev_cwd)
- #END handle previous currentdir
-
-
+ # END handle previous currentdir
+
class TestDummy(object):
+
def test_base(self):
dn = os.path.dirname
# NOTE: i don't think this is working, as the __all__ variable is not used in this case
diff --git a/git/test/test_index.py b/git/test/test_index.py
index 029c961b..76d43cbf 100644
--- a/git/test/test_index.py
+++ b/git/test/test_index.py
@@ -5,11 +5,11 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from git.test.lib import (
- TestBase,
- with_rw_repo,
- fixture_path,
- fixture
- )
+ TestBase,
+ with_rw_repo,
+ fixture_path,
+ fixture
+)
from git import *
import inspect
import os
@@ -20,12 +20,13 @@ import shutil
import time
from stat import *
+
class TestIndex(TestBase):
-
+
def __init__(self, *args):
super(TestIndex, self).__init__(*args)
self._reset_progress()
-
+
def _assert_fprogress(self, entries):
assert len(entries) == len(self._fprogress_map)
for path, call_count in self._fprogress_map.iteritems():
@@ -41,48 +42,48 @@ class TestIndex(TestBase):
if curval == 1:
assert done
self._fprogress_map[path] = curval + 1
-
+
def _fprogress_add(self, path, done, item):
"""Called as progress func - we keep track of the proper
call order"""
assert item is not None
self._fprogress(path, done, item)
-
+
def _reset_progress(self):
# maps paths to the count of calls
self._fprogress_map = dict()
-
+
def _assert_entries(self, entries):
for entry in entries:
assert isinstance(entry, BaseIndexEntry)
assert not os.path.isabs(entry.path)
assert not "\\" in entry.path
# END for each entry
-
+
def test_index_file_base(self):
# read from file
index = IndexFile(self.rorepo, fixture_path("index"))
assert index.entries
assert index.version > 0
-
+
# test entry
last_val = None
entry = index.entries.itervalues().next()
- for attr in ("path","ctime","mtime","dev","inode","mode","uid",
- "gid","size","binsha", "hexsha", "stage"):
+ for attr in ("path", "ctime", "mtime", "dev", "inode", "mode", "uid",
+ "gid", "size", "binsha", "hexsha", "stage"):
val = getattr(entry, attr)
# END for each method
-
+
# test update
entries = index.entries
assert isinstance(index.update(), IndexFile)
assert entries is not index.entries
-
+
# test stage
index_merge = IndexFile(self.rorepo, fixture_path("index_merge"))
assert len(index_merge.entries) == 106
- assert len(list(e for e in index_merge.entries.itervalues() if e.stage != 0 ))
-
+ assert len(list(e for e in index_merge.entries.itervalues() if e.stage != 0))
+
# write the data - it must match the original
tmpfile = tempfile.mktemp()
index_merge.write(tmpfile)
@@ -90,82 +91,81 @@ class TestIndex(TestBase):
assert fp.read() == fixture("index_merge")
fp.close()
os.remove(tmpfile)
-
+
def _cmp_tree_index(self, tree, index):
# fail unless both objects contain the same paths and blobs
if isinstance(tree, str):
tree = self.rorepo.commit(tree).tree
-
+
num_blobs = 0
blist = list()
- for blob in tree.traverse(predicate = lambda e,d: e.type == "blob", branch_first=False):
- assert (blob.path,0) in index.entries
+ for blob in tree.traverse(predicate=lambda e, d: e.type == "blob", branch_first=False):
+ assert (blob.path, 0) in index.entries
blist.append(blob)
# END for each blob in tree
if len(blist) != len(index.entries):
iset = set(k[0] for k in index.entries.keys())
bset = set(b.path for b in blist)
- raise AssertionError( "CMP Failed: Missing entries in index: %s, missing in tree: %s" % (bset-iset, iset-bset) )
+ raise AssertionError("CMP Failed: Missing entries in index: %s, missing in tree: %s" %
+ (bset - iset, iset - bset))
# END assertion message
-
+
@with_rw_repo('0.1.6')
def test_index_file_from_tree(self, rw_repo):
common_ancestor_sha = "5117c9c8a4d3af19a9958677e45cda9269de1541"
cur_sha = "4b43ca7ff72d5f535134241e7c797ddc9c7a3573"
other_sha = "39f85c4358b7346fee22169da9cad93901ea9eb9"
-
- # simple index from tree
+
+ # simple index from tree
base_index = IndexFile.from_tree(rw_repo, common_ancestor_sha)
assert base_index.entries
self._cmp_tree_index(common_ancestor_sha, base_index)
-
+
# merge two trees - its like a fast-forward
two_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha, cur_sha)
assert two_way_index.entries
self._cmp_tree_index(cur_sha, two_way_index)
-
+
# merge three trees - here we have a merge conflict
three_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha, cur_sha, other_sha)
assert len(list(e for e in three_way_index.entries.values() if e.stage != 0))
-
-
+
# ITERATE BLOBS
merge_required = lambda t: t[0] != 0
merge_blobs = list(three_way_index.iter_blobs(merge_required))
assert merge_blobs
- assert merge_blobs[0][0] in (1,2,3)
+ assert merge_blobs[0][0] in (1, 2, 3)
assert isinstance(merge_blobs[0][1], Blob)
-
+
# test BlobFilter
prefix = 'lib/git'
for stage, blob in base_index.iter_blobs(BlobFilter([prefix])):
- assert blob.path.startswith(prefix)
-
-
+ assert blob.path.startswith(prefix)
+
# writing a tree should fail with an unmerged index
self.failUnlessRaises(UnmergedEntriesError, three_way_index.write_tree)
-
+
# removed unmerged entries
unmerged_blob_map = three_way_index.unmerged_blobs()
assert unmerged_blob_map
-
+
# pick the first blob at the first stage we find and use it as resolved version
- three_way_index.resolve_blobs( l[0][1] for l in unmerged_blob_map.itervalues() )
+ three_way_index.resolve_blobs(l[0][1] for l in unmerged_blob_map.itervalues())
tree = three_way_index.write_tree()
assert isinstance(tree, Tree)
num_blobs = 0
- for blob in tree.traverse(predicate=lambda item,d: item.type == "blob"):
- assert (blob.path,0) in three_way_index.entries
+ for blob in tree.traverse(predicate=lambda item, d: item.type == "blob"):
+ assert (blob.path, 0) in three_way_index.entries
num_blobs += 1
# END for each blob
assert num_blobs == len(three_way_index.entries)
-
+
@with_rw_repo('0.1.6')
def test_index_merge_tree(self, rw_repo):
- # A bit out of place, but we need a different repo for this:
+ # A bit out of place, but we need a different repo for this:
assert self.rorepo != rw_repo and not (self.rorepo == rw_repo)
assert len(set((self.rorepo, self.rorepo, rw_repo, rw_repo))) == 2
-
+
# SINGLE TREE MERGE
# current index is at the (virtual) cur_commit
next_commit = "4c39f9da792792d4e73fc3a5effde66576ae128c"
@@ -175,107 +175,106 @@ class TestIndex(TestBase):
rw_repo.index.merge_tree(next_commit)
# only one change should be recorded
assert manifest_entry.binsha != rw_repo.index.entries[manifest_key].binsha
-
+
rw_repo.index.reset(rw_repo.head)
assert rw_repo.index.entries[manifest_key].binsha == manifest_entry.binsha
-
+
# FAKE MERGE
#############
- # Add a change with a NULL sha that should conflict with next_commit. We
- # pretend there was a change, but we do not even bother adding a proper
+ # Add a change with a NULL sha that should conflict with next_commit. We
+ # pretend there was a change, but we do not even bother adding a proper
# sha for it ( which makes things faster of course )
- manifest_fake_entry = BaseIndexEntry((manifest_entry[0], "\0"*20, 0, manifest_entry[3]))
+ manifest_fake_entry = BaseIndexEntry((manifest_entry[0], "\0" * 20, 0, manifest_entry[3]))
# try write flag
self._assert_entries(rw_repo.index.add([manifest_fake_entry], write=False))
- # add actually resolves the null-hex-sha for us as a feature, but we can
+ # add actually resolves the null-hex-sha for us as a feature, but we can
# edit the index manually
assert rw_repo.index.entries[manifest_key].binsha != Object.NULL_BIN_SHA
- # must operate on the same index for this ! Its a bit problematic as
+ # must operate on the same index for this ! Its a bit problematic as
# it might confuse people
- index = rw_repo.index
+ index = rw_repo.index
index.entries[manifest_key] = IndexEntry.from_base(manifest_fake_entry)
index.write()
assert rw_repo.index.entries[manifest_key].hexsha == Diff.NULL_HEX_SHA
-
+
# write an unchanged index ( just for the fun of it )
rw_repo.index.write()
-
- # a three way merge would result in a conflict and fails as the command will
- # not overwrite any entries in our index and hence leave them unmerged. This is
+
+ # a three way merge would result in a conflict and fails as the command will
+ # not overwrite any entries in our index and hence leave them unmerged. This is
# mainly a protection feature as the current index is not yet in a tree
self.failUnlessRaises(GitCommandError, index.merge_tree, next_commit, base=parent_commit)
-
- # the only way to get the merged entries is to safe the current index away into a tree,
+
+ # the only way to get the merged entries is to safe the current index away into a tree,
# which is like a temporary commit for us. This fails as well as the NULL sha deos not
# have a corresponding object
# NOTE: missing_ok is not a kwarg anymore, missing_ok is always true
# self.failUnlessRaises(GitCommandError, index.write_tree)
-
+
# if missing objects are okay, this would work though ( they are always okay now )
tree = index.write_tree()
-
+
# now make a proper three way merge with unmerged entries
unmerged_tree = IndexFile.from_tree(rw_repo, parent_commit, tree, next_commit)
unmerged_blobs = unmerged_tree.unmerged_blobs()
assert len(unmerged_blobs) == 1 and unmerged_blobs.keys()[0] == manifest_key[0]
-
-
+
@with_rw_repo('0.1.6')
def test_index_file_diffing(self, rw_repo):
# default Index instance points to our index
index = IndexFile(rw_repo)
assert index.path is not None
assert len(index.entries)
-
+
# write the file back
index.write()
-
+
# could sha it, or check stats
-
+
# test diff
- # resetting the head will leave the index in a different state, and the
+ # resetting the head will leave the index in a different state, and the
# diff will yield a few changes
cur_head_commit = rw_repo.head.reference.commit
ref = rw_repo.head.reset('HEAD~6', index=True, working_tree=False)
-
+
# diff against same index is 0
diff = index.diff()
assert len(diff) == 0
-
+
# against HEAD as string, must be the same as it matches index
diff = index.diff('HEAD')
assert len(diff) == 0
-
+
# against previous head, there must be a difference
diff = index.diff(cur_head_commit)
assert len(diff)
-
+
# we reverse the result
adiff = index.diff(str(cur_head_commit), R=True)
odiff = index.diff(cur_head_commit, R=False) # now its not reversed anymore
assert adiff != odiff
assert odiff == diff # both unreversed diffs against HEAD
-
+
# against working copy - its still at cur_commit
wdiff = index.diff(None)
assert wdiff != adiff
assert wdiff != odiff
-
+
# against something unusual
self.failUnlessRaises(ValueError, index.diff, int)
-
+
# adjust the index to match an old revision
cur_branch = rw_repo.active_branch
cur_commit = cur_branch.commit
rev_head_parent = 'HEAD~1'
assert index.reset(rev_head_parent) is index
-
+
assert cur_branch == rw_repo.active_branch
assert cur_commit == rw_repo.head.commit
-
+
# there must be differences towards the working tree which is in the 'future'
assert index.diff(None)
-
+
# reset the working copy as well to current head,to pull 'back' as well
new_data = "will be reverted"
file_path = os.path.join(rw_repo.working_tree_dir, "CHANGES")
@@ -286,12 +285,12 @@ class TestIndex(TestBase):
assert not index.diff(None)
assert cur_branch == rw_repo.active_branch
assert cur_commit == rw_repo.head.commit
- fp = open(file_path,'rb')
+ fp = open(file_path, 'rb')
try:
assert fp.read() != new_data
finally:
fp.close()
-
+
# test full checkout
test_file = os.path.join(rw_repo.working_tree_dir, "CHANGES")
open(test_file, 'ab').write("some data")
@@ -299,24 +298,24 @@ class TestIndex(TestBase):
assert 'CHANGES' in list(rval)
self._assert_fprogress([None])
assert os.path.isfile(test_file)
-
+
os.remove(test_file)
rval = index.checkout(None, force=False, fprogress=self._fprogress)
assert 'CHANGES' in list(rval)
self._assert_fprogress([None])
assert os.path.isfile(test_file)
-
+
# individual file
os.remove(test_file)
rval = index.checkout(test_file, fprogress=self._fprogress)
assert list(rval)[0] == 'CHANGES'
self._assert_fprogress([test_file])
assert os.path.exists(test_file)
-
+
# checking out non-existing file throws
self.failUnlessRaises(CheckoutError, index.checkout, "doesnt_exist_ever.txt.that")
self.failUnlessRaises(CheckoutError, index.checkout, paths=["doesnt/exist"])
-
+
# checkout file with modifications
append_data = "hello"
fp = open(test_file, "ab")
@@ -331,16 +330,16 @@ class TestIndex(TestBase):
assert open(test_file).read().endswith(append_data)
else:
raise AssertionError("Exception CheckoutError not thrown")
-
+
# if we force it it should work
index.checkout(test_file, force=True)
assert not open(test_file).read().endswith(append_data)
-
+
# checkout directory
shutil.rmtree(os.path.join(rw_repo.working_tree_dir, "lib"))
rval = index.checkout('lib')
assert len(list(rval)) > 1
-
+
def _count_existing(self, repo, files):
"""
Returns count of files that actually exist in the repository directory.
@@ -352,24 +351,24 @@ class TestIndex(TestBase):
# END for each deleted file
return existing
# END num existing helper
-
+
@with_rw_repo('0.1.6')
def test_index_mutation(self, rw_repo):
index = rw_repo.index
num_entries = len(index.entries)
cur_head = rw_repo.head
-
+
uname = "Some Developer"
umail = "sd@company.com"
rw_repo.config_writer().set_value("user", "name", uname)
- rw_repo.config_writer().set_value("user", "email", umail)
-
- # remove all of the files, provide a wild mix of paths, BaseIndexEntries,
+ rw_repo.config_writer().set_value("user", "email", umail)
+
+ # remove all of the files, provide a wild mix of paths, BaseIndexEntries,
# IndexEntries
def mixed_iterator():
count = 0
for entry in index.entries.itervalues():
- type_id = count % 4
+ type_id = count % 4
if type_id == 0: # path
yield entry.path
elif type_id == 1: # blob
@@ -381,39 +380,39 @@ class TestIndex(TestBase):
else:
raise AssertionError("Invalid Type")
count += 1
- # END for each entry
+ # END for each entry
# END mixed iterator
deleted_files = index.remove(mixed_iterator(), working_tree=False)
assert deleted_files
assert self._count_existing(rw_repo, deleted_files) == len(deleted_files)
assert len(index.entries) == 0
-
+
# reset the index to undo our changes
index.reset()
assert len(index.entries) == num_entries
-
+
# remove with working copy
deleted_files = index.remove(mixed_iterator(), working_tree=True)
assert deleted_files
assert self._count_existing(rw_repo, deleted_files) == 0
-
+
# reset everything
index.reset(working_tree=True)
assert self._count_existing(rw_repo, deleted_files) == len(deleted_files)
-
+
# invalid type
self.failUnlessRaises(TypeError, index.remove, [1])
-
+
# absolute path
- deleted_files = index.remove([os.path.join(rw_repo.working_tree_dir,"lib")], r=True)
+ deleted_files = index.remove([os.path.join(rw_repo.working_tree_dir, "lib")], r=True)
assert len(deleted_files) > 1
self.failUnlessRaises(ValueError, index.remove, ["/doesnt/exists"])
-
+
# TEST COMMITTING
# commit changed index
cur_commit = cur_head.commit
commit_message = "commit default head"
-
+
new_commit = index.commit(commit_message, head=False)
assert cur_commit != new_commit
assert new_commit.author.name == uname
@@ -424,74 +423,77 @@ class TestIndex(TestBase):
assert new_commit.parents[0] == cur_commit
assert len(new_commit.parents) == 1
assert cur_head.commit == cur_commit
-
+
# same index, no parents
commit_message = "index without parents"
commit_no_parents = index.commit(commit_message, parent_commits=list(), head=True)
assert commit_no_parents.message == commit_message
assert len(commit_no_parents.parents) == 0
assert cur_head.commit == commit_no_parents
-
+
# same index, multiple parents
commit_message = "Index with multiple parents\n commit with another line"
- commit_multi_parent = index.commit(commit_message,parent_commits=(commit_no_parents, new_commit))
+ commit_multi_parent = index.commit(commit_message, parent_commits=(commit_no_parents, new_commit))
assert commit_multi_parent.message == commit_message
assert len(commit_multi_parent.parents) == 2
assert commit_multi_parent.parents[0] == commit_no_parents
assert commit_multi_parent.parents[1] == new_commit
assert cur_head.commit == commit_multi_parent
-
+
# re-add all files in lib
# get the lib folder back on disk, but get an index without it
index.reset(new_commit.parents[0], working_tree=True).reset(new_commit, working_tree=False)
lib_file_path = os.path.join("lib", "git", "__init__.py")
assert (lib_file_path, 0) not in index.entries
assert os.path.isfile(os.path.join(rw_repo.working_tree_dir, lib_file_path))
-
+
# directory
entries = index.add(['lib'], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
- assert len(entries)>1
-
- # glob
+ assert len(entries) > 1
+
+ # glob
entries = index.reset(new_commit).add([os.path.join('lib', 'git', '*.py')], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
assert len(entries) == 14
-
- # same file
- entries = index.reset(new_commit).add([os.path.abspath(os.path.join('lib', 'git', 'head.py'))]*2, fprogress=self._fprogress_add)
+
+ # same file
+ entries = index.reset(new_commit).add(
+ [os.path.abspath(os.path.join('lib', 'git', 'head.py'))] * 2, fprogress=self._fprogress_add)
self._assert_entries(entries)
assert entries[0].mode & 0644 == 0644
# would fail, test is too primitive to handle this case
# self._assert_fprogress(entries)
self._reset_progress()
assert len(entries) == 2
-
+
# missing path
self.failUnlessRaises(OSError, index.reset(new_commit).add, ['doesnt/exist/must/raise'])
-
+
# blob from older revision overrides current index revision
old_blob = new_commit.parents[0].tree.blobs[0]
entries = index.reset(new_commit).add([old_blob], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
- assert index.entries[(old_blob.path,0)].hexsha == old_blob.hexsha and len(entries) == 1
-
+ assert index.entries[(old_blob.path, 0)].hexsha == old_blob.hexsha and len(entries) == 1
+
# mode 0 not allowed
null_hex_sha = Diff.NULL_HEX_SHA
null_bin_sha = "\0" * 20
- self.failUnlessRaises(ValueError, index.reset(new_commit).add, [BaseIndexEntry((0, null_bin_sha,0,"doesntmatter"))])
-
+ self.failUnlessRaises(ValueError, index.reset(
+ new_commit).add, [BaseIndexEntry((0, null_bin_sha, 0, "doesntmatter"))])
+
# add new file
new_file_relapath = "my_new_file"
new_file_path = self._make_file(new_file_relapath, "hello world", rw_repo)
- entries = index.reset(new_commit).add([BaseIndexEntry((010644, null_bin_sha, 0, new_file_relapath))], fprogress=self._fprogress_add)
+ entries = index.reset(new_commit).add(
+ [BaseIndexEntry((010644, null_bin_sha, 0, new_file_relapath))], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
assert len(entries) == 1 and entries[0].hexsha != null_hex_sha
-
+
# add symlink
if sys.platform != "win32":
basename = "my_real_symlink"
@@ -503,11 +505,11 @@ class TestIndex(TestBase):
self._assert_fprogress(entries)
assert len(entries) == 1 and S_ISLNK(entries[0].mode)
assert S_ISLNK(index.entries[index.entry_key("my_real_symlink", 0)].mode)
-
+
# we expect only the target to be written
assert index.repo.odb.stream(entries[0].binsha).read() == target
- # END real symlink test
-
+ # END real symlink test
+
# add fake symlink and assure it checks-our as symlink
fake_symlink_relapath = "my_fake_symlink"
link_target = "/etc/that"
@@ -518,83 +520,83 @@ class TestIndex(TestBase):
self._assert_fprogress(entries)
assert entries[0].hexsha != null_hex_sha
assert len(entries) == 1 and S_ISLNK(entries[0].mode)
-
+
# assure this also works with an alternate method
full_index_entry = IndexEntry.from_base(BaseIndexEntry((0120000, entries[0].binsha, 0, entries[0].path)))
entry_key = index.entry_key(full_index_entry)
index.reset(new_commit)
-
+
assert entry_key not in index.entries
index.entries[entry_key] = full_index_entry
index.write()
index.update() # force reread of entries
new_entry = index.entries[entry_key]
assert S_ISLNK(new_entry.mode)
-
+
# a tree created from this should contain the symlink
tree = index.write_tree()
assert fake_symlink_relapath in tree
index.write() # flush our changes for the checkout
-
+
# checkout the fakelink, should be a link then
assert not S_ISLNK(os.stat(fake_symlink_path)[ST_MODE])
os.remove(fake_symlink_path)
index.checkout(fake_symlink_path)
-
+
# on windows we will never get symlinks
if os.name == 'nt':
- # simlinks should contain the link as text ( which is what a
+ # simlinks should contain the link as text ( which is what a
# symlink actually is )
- open(fake_symlink_path,'rb').read() == link_target
+ open(fake_symlink_path, 'rb').read() == link_target
else:
assert S_ISLNK(os.lstat(fake_symlink_path)[ST_MODE])
-
+
# TEST RENAMING
def assert_mv_rval(rval):
for source, dest in rval:
assert not os.path.exists(source) and os.path.exists(dest)
# END for each renamed item
# END move assertion utility
-
+
self.failUnlessRaises(ValueError, index.move, ['just_one_path'])
# file onto existing file
files = ['AUTHORS', 'LICENSE']
self.failUnlessRaises(GitCommandError, index.move, files)
-
- # again, with force
+
+ # again, with force
assert_mv_rval(index.move(files, f=True))
-
+
# files into directory - dry run
paths = ['LICENSE', 'VERSION', 'doc']
rval = index.move(paths, dry_run=True)
assert len(rval) == 2
assert os.path.exists(paths[0])
-
+
# again, no dry run
rval = index.move(paths)
assert_mv_rval(rval)
-
+
# dir into dir
rval = index.move(['doc', 'test'])
assert_mv_rval(rval)
-
-
+
# TEST PATH REWRITING
######################
count = [0]
+
def rewriter(entry):
rval = str(count[0])
count[0] += 1
return rval
# END rewriter
-
+
def make_paths():
# two existing ones, one new one
yield 'CHANGES'
yield 'ez_setup.py'
yield index.entries[index.entry_key('README', 0)]
yield index.entries[index.entry_key('.gitignore', 0)]
-
+
for fid in range(3):
fname = 'newfile%i' % fid
open(fname, 'wb').write("abcd")
@@ -603,11 +605,10 @@ class TestIndex(TestBase):
# END path producer
paths = list(make_paths())
self._assert_entries(index.add(paths, path_rewriter=rewriter))
-
+
for filenum in range(len(paths)):
assert index.entry_key(str(filenum), 0) in index.entries
-
-
+
# TEST RESET ON PATHS
######################
arela = "aa"
@@ -619,34 +620,33 @@ class TestIndex(TestBase):
keys = (akey, bkey)
absfiles = (afile, bfile)
files = (arela, brela)
-
+
for fkey in keys:
assert not fkey in index.entries
-
+
index.add(files, write=True)
nc = index.commit("2 files committed", head=False)
-
+
for fkey in keys:
assert fkey in index.entries
-
+
# just the index
index.reset(paths=(arela, afile))
assert not akey in index.entries
assert bkey in index.entries
-
+
# now with working tree - files on disk as well as entries must be recreated
rw_repo.head.commit = nc
for absfile in absfiles:
os.remove(absfile)
-
+
index.reset(working_tree=True, paths=files)
-
- for fkey in keys:
+
+ for fkey in keys:
assert fkey in index.entries
for absfile in absfiles:
assert os.path.isfile(absfile)
-
-
+
@with_rw_repo('HEAD')
def test_compare_write_tree(self, rw_repo):
# write all trees and compare them
@@ -660,16 +660,14 @@ class TestIndex(TestBase):
index = rw_repo.index.reset(commit)
orig_tree = commit.tree
assert index.write_tree() == orig_tree
- # END for each commit
-
+ # END for each commit
+
def test_index_new(self):
B = self.rorepo.tree("6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e")
H = self.rorepo.tree("25dca42bac17d511b7e2ebdd9d1d679e7626db5f")
M = self.rorepo.tree("e746f96bcc29238b79118123028ca170adc4ff0f")
-
- for args in ((B,), (B,H), (B,H,M)):
+
+ for args in ((B,), (B, H), (B, H, M)):
index = IndexFile.new(self.rorepo, *args)
assert isinstance(index, IndexFile)
# END for each arg tuple
-
-
diff --git a/git/test/test_pack.py b/git/test/test_pack.py
index 1c308689..665a0226 100644
--- a/git/test/test_pack.py
+++ b/git/test/test_pack.py
@@ -4,23 +4,23 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Test everything about packs reading and writing"""
from lib import (
- TestBase,
- with_rw_directory,
- with_packs_rw,
- fixture_path
- )
+ TestBase,
+ with_rw_directory,
+ with_packs_rw,
+ fixture_path
+)
from git.stream import DeltaApplyReader
from git.pack import (
- PackEntity,
- PackIndexFile,
- PackFile
- )
+ PackEntity,
+ PackIndexFile,
+ PackFile
+)
from git.base import (
- OInfo,
- OStream,
- )
+ OInfo,
+ OStream,
+)
from git.fun import delta_types
from git.exc import UnsupportedOperation
@@ -38,16 +38,17 @@ def bin_sha_from_filename(filename):
return to_bin_sha(os.path.splitext(os.path.basename(filename))[0][5:])
#} END utilities
+
class TestPack(TestBase):
-
+
packindexfile_v1 = (fixture_path('packs/pack-c0438c19fb16422b6bbcce24387b3264416d485b.idx'), 1, 67)
packindexfile_v2 = (fixture_path('packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx'), 2, 30)
packindexfile_v2_3_ascii = (fixture_path('packs/pack-a2bf8e71d8c18879e499335762dd95119d93d9f1.idx'), 2, 42)
packfile_v2_1 = (fixture_path('packs/pack-c0438c19fb16422b6bbcce24387b3264416d485b.pack'), 2, packindexfile_v1[2])
packfile_v2_2 = (fixture_path('packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack'), 2, packindexfile_v2[2])
- packfile_v2_3_ascii = (fixture_path('packs/pack-a2bf8e71d8c18879e499335762dd95119d93d9f1.pack'), 2, packindexfile_v2_3_ascii[2])
-
-
+ packfile_v2_3_ascii = (
+ fixture_path('packs/pack-a2bf8e71d8c18879e499335762dd95119d93d9f1.pack'), 2, packindexfile_v2_3_ascii[2])
+
def _assert_index_file(self, index, version, size):
assert index.packfile_checksum() != index.indexfile_checksum()
assert len(index.packfile_checksum()) == 20
@@ -55,102 +56,99 @@ class TestPack(TestBase):
assert index.version() == version
assert index.size() == size
assert len(index.offsets()) == size
-
+
# get all data of all objects
for oidx in xrange(index.size()):
sha = index.sha(oidx)
assert oidx == index.sha_to_index(sha)
-
+
entry = index.entry(oidx)
assert len(entry) == 3
-
+
assert entry[0] == index.offset(oidx)
assert entry[1] == sha
assert entry[2] == index.crc(oidx)
-
+
# verify partial sha
- for l in (4,8,11,17,20):
- assert index.partial_sha_to_index(sha[:l], l*2) == oidx
-
+ for l in (4, 8, 11, 17, 20):
+ assert index.partial_sha_to_index(sha[:l], l * 2) == oidx
+
# END for each object index in indexfile
self.failUnlessRaises(ValueError, index.partial_sha_to_index, "\0", 2)
-
-
+
def _assert_pack_file(self, pack, version, size):
assert pack.version() == 2
assert pack.size() == size
assert len(pack.checksum()) == 20
-
+
num_obj = 0
for obj in pack.stream_iter():
num_obj += 1
info = pack.info(obj.pack_offset)
stream = pack.stream(obj.pack_offset)
-
+
assert info.pack_offset == stream.pack_offset
assert info.type_id == stream.type_id
assert hasattr(stream, 'read')
-
+
# it should be possible to read from both streams
assert obj.read() == stream.read()
-
+
streams = pack.collect_streams(obj.pack_offset)
assert streams
-
+
# read the stream
try:
dstream = DeltaApplyReader.new(streams)
except ValueError:
- # ignore these, old git versions use only ref deltas,
+ # ignore these, old git versions use only ref deltas,
# which we havent resolved ( as we are without an index )
# Also ignore non-delta streams
continue
# END get deltastream
-
+
# read all
data = dstream.read()
assert len(data) == dstream.size
-
+
# test seek
dstream.seek(0)
assert dstream.read() == data
-
-
+
# read chunks
# NOTE: the current implementation is safe, it basically transfers
# all calls to the underlying memory map
-
+
# END for each object
assert num_obj == size
-
-
+
def test_pack_index(self):
# check version 1 and 2
- for indexfile, version, size in (self.packindexfile_v1, self.packindexfile_v2):
+ for indexfile, version, size in (self.packindexfile_v1, self.packindexfile_v2):
index = PackIndexFile(indexfile)
self._assert_index_file(index, version, size)
# END run tests
-
+
def test_pack(self):
- # there is this special version 3, but apparently its like 2 ...
+ # there is this special version 3, but apparently its like 2 ...
for packfile, version, size in (self.packfile_v2_3_ascii, self.packfile_v2_1, self.packfile_v2_2):
pack = PackFile(packfile)
self._assert_pack_file(pack, version, size)
# END for each pack to test
-
+
@with_rw_directory
def test_pack_entity(self, rw_dir):
pack_objs = list()
- for packinfo, indexinfo in ( (self.packfile_v2_1, self.packindexfile_v1),
- (self.packfile_v2_2, self.packindexfile_v2),
- (self.packfile_v2_3_ascii, self.packindexfile_v2_3_ascii)):
+ for packinfo, indexinfo in ((self.packfile_v2_1, self.packindexfile_v1),
+ (self.packfile_v2_2, self.packindexfile_v2),
+ (self.packfile_v2_3_ascii, self.packindexfile_v2_3_ascii)):
packfile, version, size = packinfo
indexfile, version, size = indexinfo
entity = PackEntity(packfile)
assert entity.pack().path() == packfile
assert entity.index().path() == indexfile
pack_objs.extend(entity.stream_iter())
-
+
count = 0
for info, stream in izip(entity.info_iter(), entity.stream_iter()):
count += 1
@@ -158,10 +156,10 @@ class TestPack(TestBase):
assert len(info.binsha) == 20
assert info.type_id == stream.type_id
assert info.size == stream.size
-
+
# we return fully resolved items, which is implied by the sha centric access
assert not info.type_id in delta_types
-
+
# try all calls
assert len(entity.collect_streams(info.binsha))
oinfo = entity.info(info.binsha)
@@ -170,7 +168,7 @@ class TestPack(TestBase):
ostream = entity.stream(info.binsha)
assert isinstance(ostream, OStream)
assert ostream.binsha is not None
-
+
# verify the stream
try:
assert entity.is_valid_stream(info.binsha, use_crc=True)
@@ -180,42 +178,43 @@ class TestPack(TestBase):
assert entity.is_valid_stream(info.binsha, use_crc=False)
# END for each info, stream tuple
assert count == size
-
+
# END for each entity
-
+
# pack writing - write all packs into one
# index path can be None
pack_path = tempfile.mktemp('', "pack", rw_dir)
index_path = tempfile.mktemp('', 'index', rw_dir)
iteration = 0
+
def rewind_streams():
- for obj in pack_objs:
+ for obj in pack_objs:
obj.stream.seek(0)
- #END utility
- for ppath, ipath, num_obj in zip((pack_path, )*2, (index_path, None), (len(pack_objs), None)):
+ # END utility
+ for ppath, ipath, num_obj in zip((pack_path, ) * 2, (index_path, None), (len(pack_objs), None)):
pfile = open(ppath, 'wb')
iwrite = None
if ipath:
ifile = open(ipath, 'wb')
iwrite = ifile.write
- #END handle ip
-
+ # END handle ip
+
# make sure we rewind the streams ... we work on the same objects over and over again
- if iteration > 0:
+ if iteration > 0:
rewind_streams()
- #END rewind streams
+ # END rewind streams
iteration += 1
-
+
pack_sha, index_sha = PackEntity.write_pack(pack_objs, pfile.write, iwrite, object_count=num_obj)
pfile.close()
assert os.path.getsize(ppath) > 100
-
+
# verify pack
pf = PackFile(ppath)
assert pf.size() == len(pack_objs)
assert pf.version() == PackFile.pack_version_default
assert pf.checksum() == pack_sha
-
+
# verify index
if ipath is not None:
ifile.close()
@@ -225,9 +224,9 @@ class TestPack(TestBase):
assert idx.packfile_checksum() == pack_sha
assert idx.indexfile_checksum() == index_sha
assert idx.size() == len(pack_objs)
- #END verify files exist
- #END for each packpath, indexpath pair
-
+ # END verify files exist
+ # END for each packpath, indexpath pair
+
# verify the packs throughly
rewind_streams()
entity = PackEntity.create(pack_objs, rw_dir)
@@ -237,11 +236,10 @@ class TestPack(TestBase):
for use_crc in range(2):
assert entity.is_valid_stream(info.binsha, use_crc)
# END for each crc mode
- #END for each info
+ # END for each info
assert count == len(pack_objs)
-
-
+
def test_pack_64(self):
# TODO: hex-edit a pack helping us to verify that we can handle 64 byte offsets
- # of course without really needing such a huge pack
+ # of course without really needing such a huge pack
raise SkipTest()
diff --git a/git/test/test_remote.py b/git/test/test_remote.py
index 87fcd7fe..18cfda07 100644
--- a/git/test/test_remote.py
+++ b/git/test/test_remote.py
@@ -5,21 +5,21 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from git.test.lib import (
- TestBase,
- with_rw_and_rw_remote_repo,
- with_rw_repo,
- )
+ TestBase,
+ with_rw_and_rw_remote_repo,
+ with_rw_repo,
+)
from git.util import IterableList
from git.db.interface import PushInfo, FetchInfo, RemoteProgress
from git.remote import *
-from git.exc import GitCommandError
+from git.exc import GitCommandError
from git.refs import (
- Reference,
- TagReference,
- RemoteReference,
- Head,
- SymbolicReference
- )
+ Reference,
+ TagReference,
+ RemoteReference,
+ Head,
+ SymbolicReference
+)
from nose import SkipTest
@@ -28,54 +28,55 @@ import shutil
import os
import random
-# assure we have repeatable results
+# assure we have repeatable results
random.seed(0)
+
class TestRemoteProgress(RemoteProgress):
- __slots__ = ( "_seen_lines", "_stages_per_op", '_num_progress_messages')
+ __slots__ = ("_seen_lines", "_stages_per_op", '_num_progress_messages')
+
def __init__(self):
super(TestRemoteProgress, self).__init__()
self._seen_lines = list()
self._stages_per_op = dict()
self._seen_ops = set()
self._num_progress_messages = 0
-
+
def line_dropped(self, line):
try:
self._seen_lines.remove(line)
except ValueError:
pass
-
+
def __call__(self, message, input=''):
pass
-
+
def update(self, op_code, cur_count, max_count=None, message='', input=''):
# check each stage only comes once
if input:
self._seen_lines.append(input)
- #END handle input
+ # END handle input
op_id = op_code & self.OP_MASK
assert op_id in (self.COUNTING, self.COMPRESSING, self.WRITING)
-
+
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)
-
- if op_code & (self.WRITING|self.END) == (self.WRITING|self.END):
+ self._stages_per_op[op_id] = self._stages_per_op[op_id] | (op_code & self.STAGE_MASK)
+
+ if op_code & (self.WRITING | self.END) == (self.WRITING | self.END):
assert message
# END check we get message
-
+
self._num_progress_messages += 1
-
-
+
def make_assertion(self):
# we don't always receive messages
if not self._seen_lines:
return
-
+
# sometimes objects are not compressed which is okay
- assert len(self._stages_per_op.keys()) in (2,3)
+ assert len(self._stages_per_op.keys()) in (2, 3)
assert self._stages_per_op
-
+
# must have seen all stages
for op, stages in self._stages_per_op.items():
assert stages & self.STAGE_MASK == self.STAGE_MASK
@@ -83,15 +84,14 @@ class TestRemoteProgress(RemoteProgress):
def assert_received_message(self):
assert self._num_progress_messages
-
+
class TestRemote(TestBase):
-
+
def _print_fetchhead(self, repo):
fp = open(os.path.join(repo.git_dir, "FETCH_HEAD"))
fp.close()
-
-
+
def _do_test_fetch_result(self, results, remote):
# self._print_fetchhead(remote.repo)
assert len(results) > 0 and isinstance(results[0], FetchInfo)
@@ -99,15 +99,15 @@ class TestRemote(TestBase):
assert isinstance(info.note, basestring)
if isinstance(info.ref, Reference):
assert info.flags != 0
- # END reference type flags handling
+ # END reference type flags handling
assert isinstance(info.ref, (SymbolicReference, Reference))
- if info.flags & (info.FORCED_UPDATE|info.FAST_FORWARD):
+ if info.flags & (info.FORCED_UPDATE | info.FAST_FORWARD):
assert isinstance(info.old_commit_binsha, str) and len(info.old_commit_binsha) == 20
else:
assert info.old_commit_binsha is None
- # END forced update checking
+ # END forced update checking
# END for each info
-
+
def _do_test_push_result(self, results, remote):
assert len(results) > 0 and isinstance(results[0], PushInfo)
for info in results:
@@ -123,24 +123,24 @@ class TestRemote(TestBase):
assert has_one
else:
# there must be a remote commit
- if info.flags & info.DELETED == 0:
+ if info.flags & info.DELETED == 0:
assert isinstance(info.local_ref, Reference)
else:
assert info.local_ref is None
assert type(info.remote_ref) in (TagReference, RemoteReference)
# END error checking
- # END for each info
-
+ # END for each info
+
def _commit_random_file(self, repo):
- #Create a file with a random name and random data and commit it to repo.
+ # Create a file with a random name and random data and commit it to repo.
# Return the commited absolute file path
index = repo.index
- new_file = self._make_file(os.path.basename(tempfile.mktemp()),str(random.random()), repo)
+ new_file = self._make_file(os.path.basename(tempfile.mktemp()), str(random.random()), repo)
index.add([new_file])
index.commit("Committing %s" % new_file)
return new_file
-
- def _do_test_fetch(self,remote, rw_repo, remote_repo):
+
+ def _do_test_fetch(self, remote, rw_repo, remote_repo):
def fetch_and_test(remote, **kwargs):
progress = TestRemoteProgress()
kwargs['progress'] = progress
@@ -149,84 +149,84 @@ class TestRemote(TestBase):
self._do_test_fetch_result(res, remote)
return res
# END fetch and check
-
+
def get_info(res, remote, name):
- return res["%s/%s"%(remote,name)]
-
+ return res["%s/%s" % (remote, name)]
+
# put remote head to master as it is garantueed to exist
remote_repo.head.reference = remote_repo.heads.master
-
+
res = fetch_and_test(remote)
# all uptodate
for info in res:
assert info.flags & info.HEAD_UPTODATE
-
+
# rewind remote head to trigger rejection
# index must be false as remote is a bare repo
rhead = remote_repo.head
remote_commit = rhead.commit
rhead.reset("HEAD~2", index=False)
res = fetch_and_test(remote)
- mkey = "%s/%s"%(remote,'master')
+ mkey = "%s/%s" % (remote, 'master')
master_info = res[mkey]
assert master_info.flags & FetchInfo.FORCED_UPDATE and master_info.note is not None
-
+
# normal fast forward - set head back to previous one
rhead.commit = remote_commit
res = fetch_and_test(remote)
assert res[mkey].flags & FetchInfo.FAST_FORWARD
-
+
# new remote branch
new_remote_branch = Head.create(remote_repo, "new_branch")
res = fetch_and_test(remote)
new_branch_info = get_info(res, remote, new_remote_branch)
assert new_branch_info.flags & FetchInfo.NEW_HEAD
-
+
# remote branch rename ( causes creation of a new one locally )
new_remote_branch.rename("other_branch_name")
res = fetch_and_test(remote)
other_branch_info = get_info(res, remote, new_remote_branch)
assert other_branch_info.ref.commit == new_branch_info.ref.commit
-
+
# remove new branch
Head.delete(new_remote_branch.repo, new_remote_branch)
res = fetch_and_test(remote)
# deleted remote will not be fetched
self.failUnlessRaises(IndexError, get_info, res, remote, new_remote_branch)
-
+
# prune stale tracking branches
stale_refs = remote.stale_refs
assert len(stale_refs) == 2 and isinstance(stale_refs[0], RemoteReference)
RemoteReference.delete(rw_repo, *stale_refs)
-
+
# test single branch fetch with refspec including target remote
- res = fetch_and_test(remote, refspec="master:refs/remotes/%s/master"%remote)
+ res = fetch_and_test(remote, refspec="master:refs/remotes/%s/master" % remote)
assert len(res) == 1 and get_info(res, remote, 'master')
-
+
# ... with respec and no target
res = fetch_and_test(remote, refspec='master')
assert len(res) == 1
-
+
# add new tag reference
rtag = TagReference.create(remote_repo, "1.0-RV_hello.there")
res = fetch_and_test(remote, tags=True)
tinfo = res[str(rtag)]
assert isinstance(tinfo.ref, TagReference) and tinfo.ref.commit == rtag.commit
assert tinfo.flags & tinfo.NEW_TAG
-
+
# adjust tag commit
Reference.set_object(rtag, rhead.commit.parents[0].parents[0])
res = fetch_and_test(remote, tags=True)
tinfo = res[str(rtag)]
assert tinfo.commit == rtag.commit
assert tinfo.flags & tinfo.TAG_UPDATE
-
+
# delete remote tag - local one will stay
TagReference.delete(remote_repo, rtag)
res = fetch_and_test(remote, tags=True)
self.failUnlessRaises(IndexError, get_info, res, remote, str(rtag))
-
- # provoke to receive actual objects to see what kind of output we have to
+
+ # provoke to receive actual objects to see what kind of output we have to
# expect. For that we need a remote transport protocol
# Create a new UN-shared repo and fetch into it after we pushed a change
# to the shared repo
@@ -234,31 +234,31 @@ class TestRemote(TestBase):
# must clone with a local path for the repo implementation not to freak out
# as it wants local paths only ( which I can understand )
other_repo = remote_repo.clone(other_repo_dir, shared=False)
- remote_repo_url = "git://localhost%s"%remote_repo.git_dir
-
+ remote_repo_url = "git://localhost%s" % remote_repo.git_dir
+
# put origin to git-url
- other_origin = other_repo.remotes.origin
+ other_origin = other_repo.remotes.origin
other_origin.config_writer.set("url", remote_repo_url)
# it automatically creates alternates as remote_repo is shared as well.
# It will use the transport though and ignore alternates when fetching
# assert not other_repo.alternates # this would fail
-
+
# assure we are in the right state
rw_repo.head.reset(remote.refs.master, working_tree=True)
try:
self._commit_random_file(rw_repo)
remote.push(rw_repo.head.reference)
-
- # here I would expect to see remote-information about packing
- # objects and so on. Unfortunately, this does not happen
+
+ # here I would expect to see remote-information about packing
+ # objects and so on. Unfortunately, this does not happen
# if we are redirecting the output - git explicitly checks for this
# and only provides progress information to ttys
res = fetch_and_test(other_origin)
finally:
shutil.rmtree(other_repo_dir)
# END test and cleanup
-
- def _verify_push_and_pull(self,remote, rw_repo, remote_repo):
+
+ def _verify_push_and_pull(self, remote, rw_repo, remote_repo):
# push our changes
lhead = rw_repo.head
lindex = rw_repo.index
@@ -266,16 +266,16 @@ class TestRemote(TestBase):
try:
lhead.reference = rw_repo.heads.master
except AttributeError:
- # if the author is on a non-master branch, the clones might not have
+ # if the author is on a non-master branch, the clones might not have
# a local master yet. We simply create it
lhead.reference = rw_repo.create_head('master')
- # END master handling
+ # END master handling
lhead.reset(remote.refs.master, working_tree=True)
-
+
# push without spec should fail ( without further configuration )
# well, works nicely
# self.failUnlessRaises(GitCommandError, remote.push)
-
+
# simple file push
self._commit_random_file(rw_repo)
progress = TestRemoteProgress()
@@ -283,25 +283,25 @@ class TestRemote(TestBase):
assert isinstance(res, IterableList)
self._do_test_push_result(res, remote)
progress.make_assertion()
-
+
# rejected - undo last commit
lhead.reset("HEAD~1")
res = remote.push(lhead.reference)
- assert res[0].flags & PushInfo.ERROR
+ assert res[0].flags & PushInfo.ERROR
assert res[0].flags & PushInfo.REJECTED
self._do_test_push_result(res, remote)
-
+
# force rejected pull
res = remote.push('+%s' % lhead.reference)
- assert res[0].flags & PushInfo.ERROR == 0
+ assert res[0].flags & PushInfo.ERROR == 0
assert res[0].flags & PushInfo.FORCED_UPDATE
self._do_test_push_result(res, remote)
-
+
# invalid refspec
res = remote.push("hellothere")
assert len(res) == 0
-
- # push new tags
+
+ # push new tags
progress = TestRemoteProgress()
to_be_updated = "my_tag.1.0RV"
new_tag = TagReference.create(rw_repo, to_be_updated)
@@ -310,26 +310,26 @@ class TestRemote(TestBase):
assert res[-1].flags & PushInfo.NEW_TAG
progress.make_assertion()
self._do_test_push_result(res, remote)
-
+
# update push new tags
# Rejection is default
new_tag = TagReference.create(rw_repo, to_be_updated, ref='HEAD~1', force=True)
res = remote.push(tags=True)
self._do_test_push_result(res, remote)
assert res[-1].flags & PushInfo.REJECTED and res[-1].flags & PushInfo.ERROR
-
+
# push force this tag
res = remote.push("+%s" % new_tag.path)
assert res[-1].flags & PushInfo.ERROR == 0 and res[-1].flags & PushInfo.FORCED_UPDATE
-
+
# delete tag - have to do it using refspec
res = remote.push(":%s" % new_tag.path)
self._do_test_push_result(res, remote)
assert res[0].flags & PushInfo.DELETED
- # Currently progress is not properly transferred, especially not using
+ # Currently progress is not properly transferred, especially not using
# the git daemon
# progress.assert_received_message()
-
+
# push new branch
new_head = Head.create(rw_repo, "my_new_branch")
progress = TestRemoteProgress()
@@ -337,20 +337,20 @@ class TestRemote(TestBase):
assert res[0].flags & PushInfo.NEW_HEAD
progress.make_assertion()
self._do_test_push_result(res, remote)
-
+
# delete new branch on the remote end and locally
res = remote.push(":%s" % new_head.path)
self._do_test_push_result(res, remote)
Head.delete(rw_repo, new_head)
assert res[-1].flags & PushInfo.DELETED
-
+
# --all
res = remote.push(all=True)
self._do_test_push_result(res, remote)
-
+
remote.pull('master')
-
- # cleanup - delete created tags and branches as we are in an innerloop on
+
+ # cleanup - delete created tags and branches as we are in an innerloop on
# the same repository
TagReference.delete(rw_repo, new_tag, other_tag)
remote.push(":%s" % other_tag.path)
@@ -359,29 +359,28 @@ class TestRemote(TestBase):
# If you see this, plesase remind yourself, that all this needs to be run
# per repository type !
raise SkipTest("todo")
-
@with_rw_and_rw_remote_repo('0.1.6')
def test_base(self, rw_repo, remote_repo):
num_remotes = 0
remote_set = set()
ran_fetch_test = False
-
+
for remote in rw_repo.remotes:
num_remotes += 1
assert remote == remote
assert str(remote) != repr(remote)
remote_set.add(remote)
remote_set.add(remote) # should already exist
-
- # REFS
+
+ # REFS
refs = remote.refs
assert refs
for ref in refs:
assert ref.remote_name == remote.name
assert ref.remote_head
# END for each ref
-
+
# OPTIONS
# cannot use 'fetch' key anymore as it is now a method
for opt in ("url", ):
@@ -389,10 +388,10 @@ class TestRemote(TestBase):
reader = remote.config_reader
assert reader.get(opt) == val
assert reader.get_value(opt, None) == val
-
+
# unable to write with a reader
self.failUnlessRaises(IOError, reader.set, opt, "test")
-
+
# change value
writer = remote.config_writer
new_val = "myval"
@@ -402,9 +401,9 @@ class TestRemote(TestBase):
assert writer.get(opt) == val
del(writer)
assert getattr(remote, opt) == val
- # END for each default option key
-
- # RENAME
+ # END for each default option key
+
+ # RENAME
other_name = "totally_other_name"
prev_name = remote.name
assert remote.rename(other_name) == remote
@@ -413,46 +412,43 @@ class TestRemote(TestBase):
for time in range(2):
assert remote.rename(prev_name).name == prev_name
# END for each rename ( back to prev_name )
-
+
# PUSH/PULL TESTING
self._verify_push_and_pull(remote, rw_repo, remote_repo)
-
+
# FETCH TESTING
- # Only for remotes - local cases are the same or less complicated
+ # Only for remotes - local cases are the same or less complicated
# as additional progress information will never be emitted
if remote.name == "daemon_origin":
self._do_test_fetch(remote, rw_repo, remote_repo)
ran_fetch_test = True
- # END fetch test
-
+ # END fetch test
+
remote.update()
# END for each remote
-
+
assert ran_fetch_test
assert num_remotes
assert num_remotes == len(remote_set)
-
+
origin = rw_repo.remote('origin')
assert origin == rw_repo.remotes.origin
-
+
@with_rw_repo('HEAD', bare=True)
def test_creation_and_removal(self, bare_rw_repo):
new_name = "test_new_one"
arg_list = (new_name, "git@server:hello.git")
- remote = Remote.create(bare_rw_repo, *arg_list )
+ remote = Remote.create(bare_rw_repo, *arg_list)
assert remote.name == "test_new_one"
assert remote in bare_rw_repo.remotes
-
+
# create same one again
self.failUnlessRaises(GitCommandError, Remote.create, bare_rw_repo, *arg_list)
-
+
Remote.remove(bare_rw_repo, new_name)
-
+
for remote in bare_rw_repo.remotes:
if remote.name == new_name:
raise AssertionError("Remote removal failed")
# END if deleted remote matches existing remote's name
# END for each remote
-
-
-
diff --git a/git/test/test_stats.py b/git/test/test_stats.py
index 5210e7bc..c498cfa4 100644
--- a/git/test/test_stats.py
+++ b/git/test/test_stats.py
@@ -5,25 +5,26 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
from git.test.lib import (
- TestBase,
- fixture,
- assert_equal
- )
+ TestBase,
+ fixture,
+ assert_equal
+)
from git.util import Stats
+
class TestStats(TestBase):
-
+
def test__list_from_string(self):
output = fixture('diff_numstat')
stats = Stats._list_from_string(self.rorepo, output)
-
+
assert_equal(2, stats.total['files'])
assert_equal(52, stats.total['lines'])
assert_equal(29, stats.total['insertions'])
assert_equal(23, stats.total['deletions'])
-
+
assert_equal(29, stats.files["a.txt"]['insertions'])
assert_equal(18, stats.files["a.txt"]['deletions'])
-
+
assert_equal(0, stats.files["b.txt"]['insertions'])
assert_equal(5, stats.files["b.txt"]['deletions'])
diff --git a/git/test/test_stream.py b/git/test/test_stream.py
index 7af652b7..508038d7 100644
--- a/git/test/test_stream.py
+++ b/git/test/test_stream.py
@@ -4,24 +4,24 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Test for object db"""
from lib import (
- TestBase,
- DummyStream,
- Sha1Writer,
- make_bytes,
- make_object,
- fixture_path
- )
+ TestBase,
+ DummyStream,
+ Sha1Writer,
+ make_bytes,
+ make_object,
+ fixture_path
+)
from git.stream import *
from git.util import (
NULL_HEX_SHA,
hex_to_bin
- )
+)
from git.util import zlib
from git.typ import (
str_blob_type
- )
+)
from git.db.py.loose import PureLooseObjectODB
import time
@@ -29,13 +29,12 @@ import tempfile
import os
-
-
class TestStream(TestBase):
+
"""Test stream classes"""
-
- data_sizes = (15, 10000, 1000*1024+512)
-
+
+ data_sizes = (15, 10000, 1000 * 1024 + 512)
+
def _assert_stream_reader(self, stream, cdata, rewind_stream=lambda s: None):
"""Make stream tests - the orig_stream is seekable, allowing it to be
rewound and reused
@@ -43,43 +42,43 @@ class TestStream(TestBase):
:param rewind_stream: function called to rewind the stream to make it ready
for reuse"""
ns = 10
- assert len(cdata) > ns-1, "Data must be larger than %i, was %i" % (ns, len(cdata))
-
+ assert len(cdata) > ns - 1, "Data must be larger than %i, was %i" % (ns, len(cdata))
+
# read in small steps
ss = len(cdata) / ns
for i in range(ns):
data = stream.read(ss)
- chunk = cdata[i*ss:(i+1)*ss]
+ chunk = cdata[i * ss:(i + 1) * ss]
assert data == chunk
# END for each step
rest = stream.read()
if rest:
assert rest == cdata[-len(rest):]
# END handle rest
-
+
if isinstance(stream, DecompressMemMapReader):
assert len(stream.data()) == stream.compressed_bytes_read()
# END handle special type
-
+
rewind_stream(stream)
-
+
# read everything
rdata = stream.read()
assert rdata == cdata
-
+
if isinstance(stream, DecompressMemMapReader):
assert len(stream.data()) == stream.compressed_bytes_read()
# END handle special type
-
+
def test_decompress_reader(self):
for close_on_deletion in range(2):
for with_size in range(2):
for ds in self.data_sizes:
cdata = make_bytes(ds, randomize=False)
-
+
# zdata = zipped actual data
# cdata = original content data
-
+
# create reader
if with_size:
# need object data
@@ -87,7 +86,7 @@ class TestStream(TestBase):
type, size, reader = DecompressMemMapReader.new(zdata, close_on_deletion)
assert size == len(cdata)
assert type == str_blob_type
-
+
# even if we don't set the size, it will be set automatically on first read
test_reader = DecompressMemMapReader(zdata, close_on_deletion=False)
assert test_reader._s == len(cdata)
@@ -96,60 +95,59 @@ class TestStream(TestBase):
zdata = zlib.compress(cdata)
reader = DecompressMemMapReader(zdata, close_on_deletion, len(cdata))
assert reader._s == len(cdata)
- # END get reader
-
+ # END get reader
+
self._assert_stream_reader(reader, cdata, lambda r: r.seek(0))
-
+
# put in a dummy stream for closing
dummy = DummyStream()
reader._m = dummy
-
+
assert not dummy.closed
del(reader)
assert dummy.closed == close_on_deletion
# END for each datasize
# END whether size should be used
# END whether stream should be closed when deleted
-
+
def test_sha_writer(self):
writer = Sha1Writer()
assert 2 == writer.write("hi")
assert len(writer.sha(as_hex=1)) == 40
assert len(writer.sha(as_hex=0)) == 20
-
+
# make sure it does something ;)
prev_sha = writer.sha()
writer.write("hi again")
assert writer.sha() != prev_sha
-
+
def test_compressed_writer(self):
for ds in self.data_sizes:
fd, path = tempfile.mkstemp()
ostream = FDCompressedSha1Writer(fd)
data = make_bytes(ds, randomize=False)
-
+
# for now, just a single write, code doesn't care about chunking
assert len(data) == ostream.write(data)
ostream.close()
-
+
# its closed already
self.failUnlessRaises(OSError, os.close, fd)
-
+
# read everything back, compare to data we zip
- fd = os.open(path, os.O_RDONLY|getattr(os, 'O_BINARY', 0))
+ fd = os.open(path, os.O_RDONLY | getattr(os, 'O_BINARY', 0))
written_data = os.read(fd, os.path.getsize(path))
assert len(written_data) == os.path.getsize(path)
os.close(fd)
assert written_data == zlib.compress(data, 1) # best speed
-
+
os.remove(path)
# END for each os
-
+
def test_decompress_reader_special_case(self):
odb = PureLooseObjectODB(fixture_path('objects'))
ostream = odb.stream(hex_to_bin('7bb839852ed5e3a069966281bb08d50012fb309b'))
-
+
# if there is a bug, we will be missing one byte exactly !
data = ostream.read()
assert len(data) == ostream.size
-
diff --git a/git/test/test_util.py b/git/test/test_util.py
index d2ca8bf2..66103d4c 100644
--- a/git/test/test_util.py
+++ b/git/test/test_util.py
@@ -16,69 +16,70 @@ from git.cmd import dashify
import time
from git.util import (
- to_hex_sha,
- to_bin_sha,
- NULL_HEX_SHA,
- LockedFD,
+ to_hex_sha,
+ to_bin_sha,
+ NULL_HEX_SHA,
+ LockedFD,
Actor,
IterableList
- )
+)
class TestIterableMember(object):
+
"""A member of an iterable list"""
__slots__ = ("name", "prefix_name")
-
+
def __init__(self, name):
self.name = name
self.prefix_name = name
-
+
class TestUtils(TestBase):
+
def setup(self):
self.testdict = {
"string": "42",
"int": 42,
- "array": [ 42 ],
+ "array": [42],
}
def test_it_should_dashify(self):
assert 'this-is-my-argument' == dashify('this_is_my_argument')
assert 'foo' == dashify('foo')
-
-
+
def test_lock_file(self):
my_file = tempfile.mktemp()
lock_file = LockFile(my_file)
assert not lock_file._has_lock()
# release lock we don't have - fine
lock_file._release_lock()
-
+
# get lock
lock_file._obtain_lock_or_raise()
assert lock_file._has_lock()
-
+
# concurrent access
other_lock_file = LockFile(my_file)
assert not other_lock_file._has_lock()
self.failUnlessRaises(IOError, other_lock_file._obtain_lock_or_raise)
-
+
lock_file._release_lock()
assert not lock_file._has_lock()
-
+
other_lock_file._obtain_lock_or_raise()
self.failUnlessRaises(IOError, lock_file._obtain_lock_or_raise)
-
+
# auto-release on destruction
del(other_lock_file)
lock_file._obtain_lock_or_raise()
lock_file._release_lock()
-
+
def test_blocking_lock_file(self):
my_file = tempfile.mktemp()
lock_file = BlockingLockFile(my_file)
lock_file._obtain_lock()
-
+
# next one waits for the lock
start = time.time()
wait_time = 0.1
@@ -86,10 +87,10 @@ class TestUtils(TestBase):
self.failUnlessRaises(IOError, wait_lock._obtain_lock)
elapsed = time.time() - start
assert elapsed <= wait_time + 0.02 # some extra time it may cost
-
+
def test_user_id(self):
assert '@' in get_user_id()
-
+
def test_parse_date(self):
# test all supported formats
def assert_rval(rval, veri_time, offset=0):
@@ -97,13 +98,13 @@ class TestUtils(TestBase):
assert isinstance(rval[0], int) and isinstance(rval[1], int)
assert rval[0] == veri_time
assert rval[1] == offset
-
+
# now that we are here, test our conversion functions as well
utctz = altz_to_utctz_str(offset)
assert isinstance(utctz, basestring)
assert utctz_to_altz(verify_utctz(utctz)) == offset
# END assert rval utility
-
+
rfc = ("Thu, 07 Apr 2005 22:13:11 +0000", 0)
iso = ("2005-04-07T22:13:11 -0200", 7200)
iso2 = ("2005-04-07 22:13:11 +0400", -14400)
@@ -114,32 +115,32 @@ class TestUtils(TestBase):
for date, offset in (rfc, iso, iso2, iso3, alt, alt2):
assert_rval(parse_date(date), veri_time, offset)
# END for each date type
-
+
# and failure
self.failUnlessRaises(ValueError, parse_date, 'invalid format')
self.failUnlessRaises(ValueError, parse_date, '123456789 -02000')
self.failUnlessRaises(ValueError, parse_date, ' 123456789 -0200')
-
+
def test_actor(self):
for cr in (None, self.rorepo.config_reader()):
assert isinstance(Actor.committer(cr), Actor)
assert isinstance(Actor.author(cr), Actor)
- #END assure config reader is handled
-
+ # END assure config reader is handled
+
def test_basics(self):
assert to_hex_sha(NULL_HEX_SHA) == NULL_HEX_SHA
assert len(to_bin_sha(NULL_HEX_SHA)) == 20
assert to_hex_sha(to_bin_sha(NULL_HEX_SHA)) == NULL_HEX_SHA
-
+
def _cmp_contents(self, file_path, data):
- # raise if data from file at file_path
+ # raise if data from file at file_path
# does not match data string
fp = open(file_path, "rb")
try:
assert fp.read() == data
finally:
fp.close()
-
+
def test_lockedfd(self):
my_file = tempfile.mktemp()
orig_data = "hello"
@@ -147,43 +148,42 @@ class TestUtils(TestBase):
my_file_fp = open(my_file, "wb")
my_file_fp.write(orig_data)
my_file_fp.close()
-
+
try:
lfd = LockedFD(my_file)
- lockfilepath = lfd._lockfilepath()
-
+ lockfilepath = lfd._lockfilepath()
+
# cannot end before it was started
self.failUnlessRaises(AssertionError, lfd.rollback)
self.failUnlessRaises(AssertionError, lfd.commit)
-
+
# open for writing
assert not os.path.isfile(lockfilepath)
wfd = lfd.open(write=True)
assert lfd._fd is wfd
assert os.path.isfile(lockfilepath)
-
+
# write data and fail
os.write(wfd, new_data)
lfd.rollback()
assert lfd._fd is None
self._cmp_contents(my_file, orig_data)
assert not os.path.isfile(lockfilepath)
-
+
# additional call doesnt fail
lfd.commit()
lfd.rollback()
-
+
# test reading
lfd = LockedFD(my_file)
rfd = lfd.open(write=False)
assert os.read(rfd, len(orig_data)) == orig_data
-
+
assert os.path.isfile(lockfilepath)
# deletion rolls back
del(lfd)
assert not os.path.isfile(lockfilepath)
-
-
+
# write data - concurrently
lfd = LockedFD(my_file)
olfd = LockedFD(my_file)
@@ -192,17 +192,17 @@ class TestUtils(TestBase):
assert os.path.isfile(lockfilepath)
# another one fails
self.failUnlessRaises(IOError, olfd.open)
-
+
wfdstream.write(new_data)
lfd.commit()
assert not os.path.isfile(lockfilepath)
self._cmp_contents(my_file, new_data)
-
+
# could test automatic _end_writing on destruction
finally:
os.remove(my_file)
# END final cleanup
-
+
# try non-existing file for reading
lfd = LockedFD(tempfile.mktemp())
try:
@@ -216,37 +216,37 @@ class TestUtils(TestBase):
def test_iterable_list(self):
for args in (('name',), ('name', 'prefix_')):
l = IterableList('name')
-
+
m1 = TestIterableMember('one')
m2 = TestIterableMember('two')
-
+
l.extend((m1, m2))
-
+
assert len(l) == 2
-
+
# contains works with name and identity
assert m1.name in l
assert m2.name in l
assert m2 in l
assert m2 in l
assert 'invalid' not in l
-
+
# with string index
assert l[m1.name] is m1
assert l[m2.name] is m2
-
+
# with int index
assert l[0] is m1
assert l[1] is m2
-
+
# with getattr
assert l.one is m1
assert l.two is m2
-
+
# test exceptions
self.failUnlessRaises(AttributeError, getattr, l, 'something')
self.failUnlessRaises(IndexError, l.__getitem__, 'something')
-
+
# delete by name and index
self.failUnlessRaises(IndexError, l.__delitem__, 'something')
del(l[m2.name])
@@ -255,21 +255,22 @@ class TestUtils(TestBase):
del(l[0])
assert m1.name not in l
assert len(l) == 0
-
+
self.failUnlessRaises(IndexError, l.__delitem__, 0)
self.failUnlessRaises(IndexError, l.__delitem__, 'something')
- #END for each possible mode
-
+ # END for each possible mode
+
class TestActor(TestBase):
+
def test_from_string_should_separate_name_and_email(self):
a = Actor._from_string("Michael Trier <mtrier@example.com>")
assert "Michael Trier" == a.name
assert "mtrier@example.com" == a.email
-
+
# base type capabilities
assert a == a
- assert not ( a != a )
+ assert not (a != a)
m = set()
m.add(a)
m.add(a)
diff --git a/git/typ.py b/git/typ.py
index f056de5c..68f99eef 100644
--- a/git/typ.py
+++ b/git/typ.py
@@ -4,7 +4,7 @@
# the New BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Module containing information about types known to the database"""
-#{ String types
+#{ String types
# For compatability only, use ObjectType instead
str_blob_type = "blob"
@@ -12,7 +12,9 @@ str_commit_type = "commit"
str_tree_type = "tree"
str_tag_type = "tag"
+
class ObjectType(object):
+
"""Enumeration providing object types as strings and ids"""
blob = str_blob_type
commit = str_commit_type
diff --git a/git/util.py b/git/util.py
index 2096153d..59b4a6da 100644
--- a/git/util.py
+++ b/git/util.py
@@ -16,19 +16,19 @@ import stat
import shutil
import tempfile
from smmap import (
- StaticWindowMapManager,
- SlidingWindowMapManager,
- SlidingWindowMapBuffer
- )
+ StaticWindowMapManager,
+ SlidingWindowMapManager,
+ SlidingWindowMapBuffer
+)
# Import the user database on unix based systems
if os.name == "posix":
import pwd
-__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',
- 'RepoAliasMixin', 'LockedFD', 'LazyMixin', 'rmtree' )
+__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',
+ 'RepoAliasMixin', 'LockedFD', 'LazyMixin', 'rmtree')
from cStringIO import StringIO
@@ -56,6 +56,7 @@ try:
except ImportError:
from struct import unpack, calcsize
__calcsize_cache = dict()
+
def unpack_from(fmt, data, offset=0):
try:
size = __calcsize_cache[fmt]
@@ -63,13 +64,13 @@ except ImportError:
size = calcsize(fmt)
__calcsize_cache[fmt] = size
# END exception handling
- return unpack(fmt, data[offset : offset + size])
+ return unpack(fmt, data[offset: offset + size])
# END own unpack_from implementation
#{ Globals
-# A pool distributing tasks, initially with zero threads, hence everything
+# A pool distributing tasks, initially with zero threads, hence everything
# will be handled in the main thread
pool = ThreadPool(0)
@@ -79,7 +80,7 @@ if sys.version_info[1] < 6:
mman = StaticWindowMapManager()
else:
mman = SlidingWindowMapManager()
-#END handle mman
+# END handle mman
#} END globals
@@ -113,44 +114,47 @@ close = os.close
fsync = os.fsync
# constants
-NULL_HEX_SHA = "0"*40
-NULL_BIN_SHA = "\0"*20
+NULL_HEX_SHA = "0" * 40
+NULL_BIN_SHA = "\0" * 20
#} END Aliases
-#{ compatibility stuff ...
+#{ compatibility stuff ...
+
class _RandomAccessStringIO(object):
+
"""Wrapper to provide required functionality in case memory maps cannot or may
not be used. This is only really required in python 2.4"""
__slots__ = '_sio'
-
+
def __init__(self, buf=''):
self._sio = StringIO(buf)
-
+
def __getattr__(self, attr):
return getattr(self._sio, attr)
-
+
def __len__(self):
return len(self.getvalue())
-
+
def __getitem__(self, i):
return self.getvalue()[i]
-
+
def __getslice__(self, start, end):
return self.getvalue()[start:end]
-
+
#} END compatibility stuff ...
#{ Routines
+
def get_user_id():
""":return: string identifying the currently active system user as name@node
:note: user can be set with the 'USER' environment variable, usually set on windows
:note: on unix based systems you can use the password database
to get the login name of the effective process user"""
if os.name == "posix":
- username = pwd.getpwuid(os.geteuid()).pw_name
+ username = pwd.getpwuid(os.geteuid()).pw_name
else:
ukn = 'UNKNOWN'
username = os.environ.get('USER', os.environ.get('USERNAME', ukn))
@@ -159,6 +163,7 @@ def get_user_id():
# END get username from login
return "%s@%s" % (username, platform.node())
+
def is_git_dir(d):
""" This is taken from the git setup.c:is_git_directory
function."""
@@ -167,10 +172,11 @@ def is_git_dir(d):
isdir(join(d, 'refs')):
headref = join(d, 'HEAD')
return isfile(headref) or \
- (os.path.islink(headref) and
- os.readlink(headref).startswith('refs'))
+ (os.path.islink(headref) and
+ os.readlink(headref).startswith('refs'))
return False
+
def rmtree(path):
"""Remove the given recursively.
:note: we use shutil rmtree but adjust its behaviour to see whether files that
@@ -185,10 +191,11 @@ def rmtree(path):
# END end onerror
return shutil.rmtree(path, False, onerror)
-def stream_copy(source, destination, chunk_size=512*1024):
+
+def stream_copy(source, destination, chunk_size=512 * 1024):
"""Copy all data from the source stream into the destination stream in chunks
of size chunk_size
-
+
:return: amount of bytes written"""
br = 0
while True:
@@ -199,7 +206,8 @@ def stream_copy(source, destination, chunk_size=512*1024):
break
# END reading output stream
return br
-
+
+
def make_sha(source=''):
"""A python2.4 workaround for the sha/hashlib module fiasco
:note: From the dulwich project """
@@ -209,22 +217,23 @@ def make_sha(source=''):
sha1 = sha.sha(source)
return sha1
+
def allocate_memory(size):
""":return: a file-protocol accessible memory block of the given size"""
if size == 0:
return _RandomAccessStringIO('')
# END handle empty chunks gracefully
-
+
try:
return mmap.mmap(-1, size) # read-write by default
except EnvironmentError:
# setup real memory instead
# this of course may fail if the amount of memory is not available in
- # one chunk - would only be the case in python 2.4, being more likely on
+ # one chunk - would only be the case in python 2.4, being more likely on
# 32 bit systems.
- return _RandomAccessStringIO("\0"*size)
+ return _RandomAccessStringIO("\0" * size)
# END handle memory allocation
-
+
def file_contents_ro(fd, stream=False, allow_mmap=True):
""":return: read-only contents of the file represented by the file descriptor fd
@@ -246,13 +255,14 @@ def file_contents_ro(fd, stream=False, allow_mmap=True):
except OSError:
pass
# END exception handling
-
+
# read manully
contents = os.read(fd, os.fstat(fd).st_size)
if stream:
return _RandomAccessStringIO(contents)
return contents
-
+
+
def file_contents_ro_filepath(filepath, stream=False, allow_mmap=True, flags=0):
"""Get the file contents at filepath as fast as possible
:return: random access compatible memory of the given filepath
@@ -263,24 +273,27 @@ def file_contents_ro_filepath(filepath, stream=False, allow_mmap=True, flags=0):
:note: for now we don't try to use O_NOATIME directly as the right value needs to be
shared per database in fact. It only makes a real difference for loose object
databases anyway, and they use it with the help of the ``flags`` parameter"""
- fd = os.open(filepath, os.O_RDONLY|getattr(os, 'O_BINARY', 0)|flags)
+ fd = os.open(filepath, os.O_RDONLY | getattr(os, 'O_BINARY', 0) | flags)
try:
return file_contents_ro(fd, stream, allow_mmap)
finally:
close(fd)
# END assure file is closed
-
+
+
def to_hex_sha(sha):
""":return: hexified version of sha"""
if len(sha) == 40:
return sha
return bin_to_hex(sha)
-
+
+
def to_bin_sha(sha):
if len(sha) == 20:
return sha
return hex_to_bin(sha)
+
def join_path(a, *p):
"""Join path tokens together similar to os.path.join, but always use
'/' instead of possibly '\' on windows."""
@@ -291,17 +304,19 @@ def join_path(a, *p):
if b.startswith('/'):
path += b[1:]
elif path == '' or path.endswith('/'):
- path += b
+ path += b
else:
path += '/' + b
# END for each path token to add
return path
-
+
+
def to_native_path_windows(path):
- return path.replace('/','\\')
-
+ return path.replace('/', '\\')
+
+
def to_native_path_linux(path):
- return path.replace('\\','/')
+ return path.replace('\\', '/')
if sys.platform.startswith('win'):
@@ -312,6 +327,7 @@ else:
return path
to_native_path = to_native_path_linux
+
def join_path_native(a, *p):
"""
As join path, but makes sure an OS native path is returned. This is only
@@ -319,15 +335,16 @@ def join_path_native(a, *p):
use '\'"""
return to_native_path(join_path(a, *p))
+
def assure_directory_exists(path, is_file=False):
"""Assure that the directory pointed to by path exists.
-
+
:param is_file: If True, path is assumed to be a file and handled correctly.
Otherwise it must be a directory
:return: True if the directory was created, False if it already existed"""
if is_file:
path = os.path.dirname(path)
- #END handle file
+ # END handle file
if not os.path.isdir(path):
os.makedirs(path)
return True
@@ -340,15 +357,16 @@ def assure_directory_exists(path, is_file=False):
#{ Utilities
class LazyMixin(object):
+
"""
Base class providing an interface to lazily retrieve attribute values upon
first access. If slots are used, memory will only be reserved once the attribute
is actually accessed and retrieved the first time. All future accesses will
return the cached value as stored in the Instance's dict or slot.
"""
-
+
__slots__ = tuple()
-
+
def __getattr__(self, attr):
"""
Whenever an attribute is requested that we do not know, we allow it
@@ -363,47 +381,48 @@ class LazyMixin(object):
This method should be overridden in the derived class.
It should check whether the attribute named by attr can be created
and cached. Do nothing if you do not know the attribute or call your subclass
-
+
The derived class may create as many additional attributes as it deems
necessary in case a git command returns more information than represented
in the single attribute."""
pass
-
+
class LockedFD(object):
+
"""
This class facilitates a safe read and write operation to a file on disk.
If we write to 'file', we obtain a lock file at 'file.lock' and write to
that instead. If we succeed, the lock file will be renamed to overwrite
the original file.
-
+
When reading, we obtain a lock file, but to prevent other writers from
succeeding while we are reading the file.
-
+
This type handles error correctly in that it will assure a consistent state
on destruction.
-
+
:note: with this setup, parallel reading is not possible"""
__slots__ = ("_filepath", '_fd', '_write')
-
+
def __init__(self, filepath):
"""Initialize an instance with the givne filepath"""
self._filepath = filepath
self._fd = None
self._write = None # if True, we write a file
-
+
def __del__(self):
# will do nothing if the file descriptor is already closed
if self._fd is not None:
self.rollback()
-
+
def _lockfilepath(self):
return "%s.lock" % self._filepath
-
+
def open(self, write=False, stream=False):
"""
Open the file descriptor for reading or writing, both in binary mode.
-
+
:param write: if True, the file descriptor will be opened for writing. Other
wise it will be opened read-only.
:param stream: if True, the file descriptor will be wrapped into a simple stream
@@ -415,12 +434,12 @@ class LockedFD(object):
:note: must only be called once"""
if self._write is not None:
raise AssertionError("Called %s multiple times" % self.open)
-
+
self._write = write
-
+
# try to open the lock file
binary = getattr(os, 'O_BINARY', 0)
- lockmode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | binary
+ lockmode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | binary
try:
fd = os.open(self._lockfilepath(), lockmode, 0600)
if not write:
@@ -431,7 +450,7 @@ class LockedFD(object):
except OSError:
raise IOError("Lock at %r could not be obtained" % self._lockfilepath())
# END handle lock retrieval
-
+
# open actual file if required
if self._fd is None:
# we could specify exlusive here, as we obtained the lock anyway
@@ -443,7 +462,7 @@ class LockedFD(object):
raise
# END handle lockfile
# END open descriptor for reading
-
+
if stream:
# need delayed import
from stream import FDStream
@@ -451,31 +470,31 @@ class LockedFD(object):
else:
return self._fd
# END handle stream
-
+
def commit(self):
"""When done writing, call this function to commit your changes into the
actual file.
The file descriptor will be closed, and the lockfile handled.
:note: can be called multiple times"""
self._end_writing(successful=True)
-
+
def rollback(self):
"""Abort your operation without any changes. The file descriptor will be
closed, and the lock released.
:note: can be called multiple times"""
self._end_writing(successful=False)
-
+
def _end_writing(self, successful=True):
"""Handle the lock according to the write mode """
if self._write is None:
raise AssertionError("Cannot end operation if it wasn't started yet")
-
+
if self._fd is None:
return
-
+
os.close(self._fd)
self._fd = None
-
+
lockfile = self._lockfilepath()
if self._write and successful:
# on windows, rename does not silently overwrite the existing one
@@ -485,7 +504,7 @@ class LockedFD(object):
# END remove if exists
# END win32 special handling
os.rename(lockfile, self._filepath)
-
+
# assure others can at least read the file - the tmpfile left it at rw--
# We may also write that file, on windows that boils down to a remove-
# protection as well
@@ -494,70 +513,72 @@ class LockedFD(object):
# just delete the file so far, we failed
os.remove(lockfile)
# END successful handling
-
-
+
+
class LockFile(object):
+
"""Provides methods to obtain, check for, and release a file based lock which
should be used to handle concurrent access to the same file.
-
+
As we are a utility class to be derived from, we only use protected methods.
-
+
Locks will automatically be released on destruction"""
__slots__ = ("_file_path", "_owns_lock")
-
+
def __init__(self, file_path):
self._file_path = file_path
self._owns_lock = False
-
+
def __del__(self):
self._release_lock()
-
+
def _lock_file_path(self):
""":return: Path to lockfile"""
return "%s.lock" % (self._file_path)
-
+
def _has_lock(self):
""":return: True if we have a lock and if the lockfile still exists
:raise AssertionError: if our lock-file does not exist"""
if not self._owns_lock:
return False
-
+
return True
-
+
def _obtain_lock_or_raise(self):
"""Create a lock file as flag for other instances, mark our instance as lock-holder
-
+
:raise IOError: if a lock was already present or a lock file could not be written"""
if self._has_lock():
- return
+ return
lock_file = self._lock_file_path()
if os.path.isfile(lock_file):
- raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" % (self._file_path, lock_file))
-
+ raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" %
+ (self._file_path, lock_file))
+
try:
fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0)
os.close(fd)
- except OSError,e:
+ except OSError, e:
raise IOError(str(e))
-
+
self._owns_lock = True
-
+
def _obtain_lock(self):
"""The default implementation will raise if a lock cannot be obtained.
Subclasses may override this method to provide a different implementation"""
return self._obtain_lock_or_raise()
-
+
def _release_lock(self):
"""Release our lock if we have one"""
if not self._has_lock():
return
-
+
# if someone removed our file beforhand, lets just flag this issue
# instead of failing, to make it more usable.
lfp = self._lock_file_path()
try:
# on bloody windows, the file needs write permissions to be removable.
- # Why ...
+ # Why ...
if os.name == 'nt':
os.chmod(lfp, 0777)
# END handle win32
@@ -568,25 +589,27 @@ class LockFile(object):
class BlockingLockFile(LockFile):
+
"""The lock file will block until a lock could be obtained, or fail after
a specified timeout.
-
+
:note: If the directory containing the lock was removed, an exception will
be raised during the blocking period, preventing hangs as the lock
can never be obtained."""
__slots__ = ("_check_interval", "_max_block_time")
+
def __init__(self, file_path, check_interval_s=0.3, max_block_time_s=sys.maxint):
"""Configure the instance
-
+
:parm check_interval_s:
Period of time to sleep until the lock is checked the next time.
By default, it waits a nearly unlimited time
-
+
:parm max_block_time_s: Maximum amount of seconds we may lock"""
super(BlockingLockFile, self).__init__(file_path)
self._check_interval = check_interval_s
self._max_block_time = max_block_time_s
-
+
def _obtain_lock(self):
"""This method blocks until it obtained the lock, or raises IOError if
it ran out of time or if the parent directory was not available anymore.
@@ -601,12 +624,13 @@ class BlockingLockFile(LockFile):
# readable anymore, raise an execption
curtime = time.time()
if not os.path.isdir(os.path.dirname(self._lock_file_path())):
- msg = "Directory containing the lockfile %r was not readable anymore after waiting %g seconds" % (self._lock_file_path(), curtime - starttime)
+ msg = "Directory containing the lockfile %r was not readable anymore after waiting %g seconds" % (
+ self._lock_file_path(), curtime - starttime)
raise IOError(msg)
# END handle missing directory
-
+
if curtime >= maxtime:
- msg = "Waited %g seconds for lock at %r" % ( maxtime - starttime, self._lock_file_path())
+ msg = "Waited %g seconds for lock at %r" % (maxtime - starttime, self._lock_file_path())
raise IOError(msg)
# END abort if we wait too long
time.sleep(self._check_interval)
@@ -616,36 +640,37 @@ class BlockingLockFile(LockFile):
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."""
# PRECOMPILED REGEX
- name_only_regex = re.compile( r'<(.+)>' )
- name_email_regex = re.compile( r'(.*) <(.+?)>' )
-
+ name_only_regex = re.compile(r'<(.+)>')
+ name_email_regex = re.compile(r'(.*) <(.+?)>')
+
# ENVIRONMENT VARIABLES
# read when creating new commits
env_author_name = "GIT_AUTHOR_NAME"
env_author_email = "GIT_AUTHOR_EMAIL"
env_committer_name = "GIT_COMMITTER_NAME"
env_committer_email = "GIT_COMMITTER_EMAIL"
-
+
# CONFIGURATION KEYS
conf_name = 'name'
conf_email = 'email'
-
+
__slots__ = ('name', 'email')
-
+
def __init__(self, name, email):
self.name = name
self.email = email
def __eq__(self, other):
return self.name == other.name and self.email == other.email
-
+
def __ne__(self, other):
return not (self == other)
-
+
def __hash__(self):
return hash((self.name, self.email))
@@ -661,7 +686,7 @@ class Actor(object):
:param string: is the string, which is expected to be in regular git format
John Doe <jdoe@example.com>
-
+
:return: Actor """
m = cls.name_email_regex.search(string)
if m:
@@ -676,28 +701,27 @@ class Actor(object):
return cls(string, None)
# END special case name
# END handle name/email matching
-
+
@classmethod
def _main_actor(cls, env_name, env_email, config_reader=None):
actor = cls('', '')
default_email = get_user_id()
default_name = default_email.split('@')[0]
-
- for attr, evar, cvar, default in (('name', env_name, cls.conf_name, default_name),
- ('email', env_email, cls.conf_email, default_email)):
+
+ for attr, evar, cvar, default in (('name', env_name, cls.conf_name, default_name),
+ ('email', env_email, cls.conf_email, default_email)):
try:
setattr(actor, attr, os.environ[evar])
except KeyError:
if config_reader is not None:
setattr(actor, attr, config_reader.get_value('user', cvar, default))
- #END config-reader handling
+ # END config-reader handling
if not getattr(actor, attr):
setattr(actor, attr, default)
- #END handle name
- #END for each item to retrieve
+ # END handle name
+ # END for each item to retrieve
return actor
-
-
+
@classmethod
def committer(cls, config_reader=None):
"""
@@ -708,84 +732,86 @@ class Actor(object):
:param config_reader: ConfigReader to use to retrieve the values from in case
they are not set in the environment"""
return cls._main_actor(cls.env_committer_name, cls.env_committer_email, config_reader)
-
+
@classmethod
def author(cls, config_reader=None):
"""Same as committer(), but defines the main author. It may be specified in the environment,
but defaults to the committer"""
return cls._main_actor(cls.env_author_name, cls.env_author_email, config_reader)
-
+
class Iterable(object):
+
"""Defines an interface for iterable items which is to assure a uniform
way to retrieve and iterate items within the git repository"""
__slots__ = tuple()
_id_attribute_ = "attribute that most suitably identifies your instance"
-
+
@classmethod
def list_items(cls, repo, *args, **kwargs):
"""
Find all items of this type - subclasses can specify args and kwargs differently.
If no args are given, subclasses are obliged to return all items if no additional
arguments arg given.
-
+
:note: Favor the iter_items method as it will
-
+
:return:list(Item,...) list of item instances"""
- out_list = IterableList( cls._id_attribute_ )
+ out_list = IterableList(cls._id_attribute_)
out_list.extend(cls.iter_items(repo, *args, **kwargs))
return out_list
-
-
+
@classmethod
def iter_items(cls, repo, *args, **kwargs):
"""For more information about the arguments, see list_items
:return: iterator yielding Items"""
raise NotImplementedError("To be implemented by Subclass")
-
+
class IterableList(list):
+
"""
List of iterable objects allowing to query an object by id or by named index::
-
+
heads = repo.heads
heads.master
heads['master']
heads[0]
-
+
It requires an id_attribute name to be set which will be queried from its
contained items to have a means for comparison.
-
+
A prefix can be specified which is to be used in case the id returned by the
items always contains a prefix that does not matter to the user, so it
can be left out."""
__slots__ = ('_id_attr', '_prefix')
-
+
def __new__(cls, id_attr, prefix=''):
- return super(IterableList,cls).__new__(cls)
-
+ return super(IterableList, cls).__new__(cls)
+
def __init__(self, id_attr, prefix=''):
self._id_attr = id_attr
self._prefix = prefix
if not isinstance(id_attr, basestring):
- raise ValueError("First parameter must be a string identifying the name-property. Extend the list after initialization")
+ raise ValueError(
+ "First parameter must be a string identifying the name-property. Extend the list after initialization")
# END help debugging !
-
+
def __contains__(self, attr):
# first try identy match for performance
rval = list.__contains__(self, attr)
if rval:
return rval
- #END handle match
-
+ # END handle match
+
# otherwise make a full name search
try:
getattr(self, attr)
return True
except (AttributeError, TypeError):
return False
- #END handle membership
-
+ # END handle membership
+
def __getattr__(self, attr):
attr = self._prefix + attr
for item in self:
@@ -793,17 +819,17 @@ class IterableList(list):
return item
# END for each item
return list.__getattribute__(self, attr)
-
+
def __getitem__(self, index):
if isinstance(index, int):
- return list.__getitem__(self,index)
-
+ return list.__getitem__(self, index)
+
try:
return getattr(self, index)
except AttributeError:
- raise IndexError( "No item found with id %r" % (self._prefix + index) )
+ raise IndexError("No item found with id %r" % (self._prefix + index))
# END handle getattr
-
+
def __delitem__(self, index):
delindex = index
if not isinstance(index, int):
@@ -813,12 +839,12 @@ class IterableList(list):
if getattr(item, self._id_attr) == name:
delindex = i
break
- #END search index
- #END for each item
+ # END search index
+ # END for each item
if delindex == -1:
raise IndexError("Item with name %s not found" % name)
- #END handle error
- #END get index to delete
+ # END handle error
+ # END get index to delete
list.__delitem__(self, delindex)
@@ -827,43 +853,45 @@ class IterableList(list):
#{ Classes
class RepoAliasMixin(object):
+
"""Simple utility providing a repo-property which resolves to the 'odb' attribute
of the actual type. This is for api compatability only, as the types previously
held repository instances, now they hold odb instances instead"""
__slots__ = tuple()
-
+
@property
def repo(self):
return self.odb
-
+
class Stats(object):
+
"""
Represents stat information as presented by git at the end of a merge. It is
created from the output of a diff operation.
-
+
``Example``::
-
+
c = Commit( sha1 )
s = c.stats
s.total # full-stat-dict
s.files # dict( filepath : stat-dict )
-
+
``stat-dict``
-
+
A dictionary with the following keys and values::
-
+
deletions = number of deleted lines as int
insertions = number of inserted lines as int
lines = total number of lines changed as int, or deletions + insertions
-
+
``full-stat-dict``
-
+
In addition to the items in the stat-dict, it features additional information::
-
+
files = number of changed files as int"""
__slots__ = ("total", "files")
-
+
def __init__(self, total, files):
self.total = total
self.files = files
@@ -871,7 +899,7 @@ class Stats(object):
@classmethod
def _list_from_string(cls, repo, text):
"""Create a Stat object from output retrieved by git-diff.
-
+
:return: git.Stat"""
hsh = {'total': {'insertions': 0, 'deletions': 0, 'lines': 0, 'files': 0}, 'files': dict()}
for line in text.splitlines():
@@ -889,15 +917,16 @@ class Stats(object):
class IndexFileSHA1Writer(object):
+
"""Wrapper around a file-like object that remembers the SHA1 of
the data written to it. It will write a sha when the stream is closed
or if the asked for explicitly usign write_sha.
-
+
Only useful to the indexfile
-
+
:note: Based on the dulwich project"""
__slots__ = ("f", "sha1")
-
+
def __init__(self, f):
self.f = f
self.sha1 = make_sha("")
diff --git a/setup.py b/setup.py
index ea0ff12e..7a886aa3 100644
--- a/setup.py
+++ b/setup.py
@@ -18,6 +18,7 @@ v.close()
class build_py(_build_py):
+
def run(self):
init = path.join(self.build_lib, 'git', '__init__.py')
if path.exists(init):
@@ -28,7 +29,8 @@ class build_py(_build_py):
class sdist(_sdist):
- def make_release_tree (self, base_dir, files):
+
+ def make_release_tree(self, base_dir, files):
_sdist.make_release_tree(self, base_dir, files)
orig = path.join('git', '__init__.py')
assert path.exists(orig), orig
@@ -46,7 +48,7 @@ def _stamp_version(filename):
except (IOError, OSError):
print >> sys.stderr, "Couldn't find file %s to stamp version" % filename
return
- #END handle error, usually happens during binary builds
+ # END handle error, usually happens during binary builds
for line in f:
if '__version__ =' in line:
line = line.replace("'git'", "'%s'" % VERSION)
@@ -61,31 +63,31 @@ def _stamp_version(filename):
else:
print >> sys.stderr, "WARNING: Couldn't find version line in file %s" % filename
-setup(name = "GitPython",
+setup(name="GitPython",
cmdclass={'build_py': build_py, 'sdist': sdist},
- version = VERSION,
- description = "Python Git Library",
- author = "Sebastian Thiel, Michael Trier",
- author_email = "byronimo@gmail.com, mtrier@gmail.com",
- url = "http://gitorious.org/projects/git-python/",
- packages = find_packages('.'),
- py_modules = ['git.'+f[:-3] for f in os.listdir('./git') if f.endswith('.py')],
- package_data = {'git.test' : ['fixtures/*']},
- package_dir = {'git':'git'},
- license = "BSD License",
+ version=VERSION,
+ description="Python Git Library",
+ author="Sebastian Thiel, Michael Trier",
+ author_email="byronimo@gmail.com, mtrier@gmail.com",
+ url="http://gitorious.org/projects/git-python/",
+ packages=find_packages('.'),
+ py_modules=['git.' + f[:-3] for f in os.listdir('./git') if f.endswith('.py')],
+ package_data={'git.test': ['fixtures/*']},
+ package_dir={'git': 'git'},
+ license="BSD License",
requires=('gitdb (>=0.5.1)',),
install_requires='gitdb >= 0.5.1',
zip_safe=False,
long_description = """\
GitPython is a python library used to interact with Git repositories""",
classifiers = [
- "Development Status :: 4 - Beta",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: BSD License",
- "Operating System :: OS Independent",
- "Programming Language :: Python",
- "Programming Language :: Python :: 2.5",
- "Programming Language :: Python :: 2.6",
- "Topic :: Software Development :: Libraries :: Python Modules",
- ]
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2.5",
+ "Programming Language :: Python :: 2.6",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ]
)