From 160081b9a7ca191afbec077c4bf970cfd9070d2c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 29 Jun 2010 18:28:31 +0200 Subject: Updated and fixed sphinx API docs, which included one quick skim-through --- lib/git/cmd.py | 2 +- lib/git/index/base.py | 26 +++++++++++++++----------- lib/git/index/fun.py | 4 +++- lib/git/index/typ.py | 14 +++++++------- lib/git/objects/blob.py | 3 ++- lib/git/objects/commit.py | 2 +- lib/git/objects/tree.py | 14 ++++++++------ lib/git/objects/utils.py | 8 ++++---- lib/git/refs.py | 25 ++++++++++++++++++------- lib/git/remote.py | 27 +++++++++++++++++---------- lib/git/utils.py | 9 ++++++--- 11 files changed, 82 insertions(+), 52 deletions(-) (limited to 'lib/git') diff --git a/lib/git/cmd.py b/lib/git/cmd.py index d0f2a19e..e9c1ce24 100644 --- a/lib/git/cmd.py +++ b/lib/git/cmd.py @@ -272,7 +272,7 @@ class Git(object): This merely is a workaround as data will be copied from the output pipe to the given output stream directly. - :param **subprocess_kwargs: + :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. diff --git a/lib/git/index/base.py b/lib/git/index/base.py index 03da52b7..1a8bee93 100644 --- a/lib/git/index/base.py +++ b/lib/git/index/base.py @@ -4,7 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php """Module containing Index implementation, allowing to perform all kinds of index -manipulations such as querying and merging. """ +manipulations such as querying and merging.""" import tempfile import os import sys @@ -75,22 +75,26 @@ __all__ = ( 'IndexFile', 'CheckoutError' ) class IndexFile(LazyMixin, diff.Diffable, Serializable): - """Implements an Index that can be manipulated using a native implementation in + """ + 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 - Otherwise changes to it will be lost when changing the index using its methods. - """ + + 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 = 0160000 # a submodule @@ -250,7 +254,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): :param repo: The repository treeish are located in. - :param *tree_sha: + :param tree_sha: 20 byte or 40 byte tree sha or tree objects :return: @@ -276,7 +280,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): :param repo: The repository treeish are located in. - :param *treeish: + :param treeish: One, two or three Tree Objects, Commits or 40 byte hexshas. The result changes according to the amount of trees. If 1 Tree is given, it will just be read into a new index @@ -287,7 +291,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' tree, tree 3 is the 'other' one - :param **kwargs: + :param kwargs: Additional arguments passed to git-read-tree :return: @@ -790,7 +794,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): removing the respective file. This may fail if there are uncommited changes in it. - :param **kwargs: + :param kwargs: Additional keyword arguments to be passed to git-rm, such as 'r' to allow recurive removal of @@ -828,7 +832,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): :param skip_errors: If True, errors such as ones resulting from missing source files will be skpped. - :param **kwargs: + :param kwargs: Additional arguments you would like to pass to git-mv, such as dry_run or force. @@ -924,7 +928,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): explicit paths are given. Otherwise progress information will be send prior and after a file has been checked out - :param **kwargs: + :param kwargs: Additional arguments to be pasesd to git-checkout-index :return: diff --git a/lib/git/index/fun.py b/lib/git/index/fun.py index ef950761..cc18c65c 100644 --- a/lib/git/index/fun.py +++ b/lib/git/index/fun.py @@ -51,8 +51,10 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1 :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 @@ -103,7 +105,7 @@ def read_header(stream): def entry_key(*entry): """:return: Key suitable to be used for the index.entries dictionary - :param *entry: One instance of type BaseIndexEntry or the path and the stage""" + :param entry: One instance of type BaseIndexEntry or the path and the stage""" if len(entry) == 1: return (entry[0].path, entry[0].stage) else: diff --git a/lib/git/index/typ.py b/lib/git/index/typ.py index 7654b402..3a01cd65 100644 --- a/lib/git/index/typ.py +++ b/lib/git/index/typ.py @@ -78,10 +78,10 @@ class BaseIndexEntry(tuple): 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 + * 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 """ @@ -112,10 +112,10 @@ class IndexEntry(BaseIndexEntry): See the properties for a mapping between names and tuple indices. """ @property def ctime(self): - """:return: - Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the - file's creation time """ + :return: + Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the + file's creation time""" return unpack(">LL", self[4]) @property diff --git a/lib/git/objects/blob.py b/lib/git/objects/blob.py index 8263e9a2..d0ef54c7 100644 --- a/lib/git/objects/blob.py +++ b/lib/git/objects/blob.py @@ -28,7 +28,8 @@ class Blob(base.IndexObject): @property def mime_type(self): - """ :return:String describing the mime type of this file (based on the filename) + """ + :return: String describing the mime type of this file (based on the filename) :note: Defaults to 'text/plain' in case the actual file type is unknown. """ guesses = None if self.path: diff --git a/lib/git/objects/commit.py b/lib/git/objects/commit.py index f88bb0e8..11263e07 100644 --- a/lib/git/objects/commit.py +++ b/lib/git/objects/commit.py @@ -182,11 +182,11 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): 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 :param kwargs: All arguments allowed by git-rev-list - :return: Iterator yielding Commit objects which are parents of self """ # skip ourselves skip = kwargs.get("skip", 1) diff --git a/lib/git/objects/tree.py b/lib/git/objects/tree.py index 056d3da9..64725128 100644 --- a/lib/git/objects/tree.py +++ b/lib/git/objects/tree.py @@ -23,8 +23,8 @@ from gitdb.util import ( __all__ = ("TreeModifier", "Tree") class TreeModifier(object): - """A utility class providing methods to alter the underlying cache in a list-like - fashion. + """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' @@ -57,6 +57,7 @@ class TreeModifier(object): 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 @@ -203,10 +204,11 @@ class Tree(IndexObject, diff.Diffable, utils.Traversable, utils.Serializable): @property def cache(self): - """:return: An object allowing to modify the internal cache. This can be used - to change the tree's contents. When done, make sure you call ``set_done`` - on the tree modifier, or serialization behaviour will be incorrect. - See the ``TreeModifier`` for more information on how to alter the cache""" + """ + :return: An object allowing to modify the internal cache. This can be used + to change the tree's contents. When done, make sure you call ``set_done`` + on the tree modifier, or serialization behaviour will be incorrect. + See the ``TreeModifier`` for more information on how to alter the cache""" return TreeModifier(self._cache) def traverse( self, predicate = lambda i,d: True, diff --git a/lib/git/objects/utils.py b/lib/git/objects/utils.py index c0ddd6e6..fd648f09 100644 --- a/lib/git/objects/utils.py +++ b/lib/git/objects/utils.py @@ -103,15 +103,15 @@ def verify_utctz(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 + The T can be a space as well - :return: Tuple(int(timestamp), int(offset), both in seconds since epoch + :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 - """ + :note: Date can also be YYYY.MM.DD, MM/DD/YYYY and DD.MM.YYYY""" # git time try: if string_date.count(' ') == 1 and string_date.rfind(':') == -1: diff --git a/lib/git/refs.py b/lib/git/refs.py index 8258ca8d..c8d67d3f 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -64,7 +64,8 @@ class SymbolicReference(object): @property def name(self): - """:return: + """ + :return: In case of symbolic references, the shortest assumable name is the path itself.""" return self.path @@ -244,7 +245,8 @@ class SymbolicReference(object): @property def is_detached(self): - """:return: + """ + :return: True if we are a detached reference, hence we point to a specific commit instead to another reference""" try: @@ -256,8 +258,9 @@ class SymbolicReference(object): @classmethod def to_full_path(cls, path): - """:return: string with a full path name which can be used to initialize - a Reference instance, for instance by using ``Reference.from_path``""" + """ + :return: string with a full path name which can be used to initialize + a Reference instance, for instance by using ``Reference.from_path``""" if isinstance(path, SymbolicReference): path = path.path full_ref_path = path @@ -369,6 +372,7 @@ class SymbolicReference(object): :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, False, reference, force) @@ -563,17 +567,21 @@ class Reference(SymbolicReference, LazyMixin, Iterable): @classmethod def create(cls, repo, path, commit='HEAD', force=False ): """Create a new reference. + :param repo: Repository to create the reference in :param path: The relative path of the reference, i.e. 'new_branch' or feature/feature1. The path prefix 'refs/' is implied if not given explicitly + :param commit: Commit to which the new reference should point, defaults to the current HEAD + :param force: if True, force creation even if a reference with that name already exists. Raise OSError otherwise + :return: Newly created Reference :note: This does not alter the current HEAD, index or Working Tree""" @@ -666,15 +674,18 @@ class Head(Reference): :param path: The name or path of the head, i.e. 'new_branch' or feature/feature1. The prefix refs/heads is implied. + :param commit: Commit to which the new head should point, defaults to the current HEAD + :param force: if True, force creation even if branch with that name already exists. - :param **kwargs: + :param kwargs: Additional keyword arguments to be passed to git-branch, i.e. track, no-track, l + :return: Newly created Head :note: This does not alter the current HEAD, index or Working Tree""" if cls is not Head: @@ -734,7 +745,7 @@ class Head(Reference): If True, changes to the index and the working tree will be discarded. If False, GitCommandError will be raised in that situation. - :param **kwargs: + :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. @@ -818,7 +829,7 @@ class TagReference(Reference): :param force: If True, to force creation of a tag even though that tag already exists. - :param **kwargs: + :param kwargs: Additional keyword arguments to be passed to git-tag :return: A new TagReference""" diff --git a/lib/git/remote.py b/lib/git/remote.py index 9c46a027..1558eeb3 100644 --- a/lib/git/remote.py +++ b/lib/git/remote.py @@ -52,8 +52,10 @@ class _SectionConstraint(object): 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.""" + """ + 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. + """ BEGIN, END, COUNTING, COMPRESSING, WRITING = [ 1 << x for x in range(5) ] STAGE_MASK = BEGIN|END OP_MASK = COUNTING|COMPRESSING|WRITING @@ -168,7 +170,8 @@ class RemoteProgress(object): class PushInfo(object): - """Carries information about the result of a push operation of a single head:: + """ + Carries information about the result of a push operation of a single head:: info = remote.push()[0] info.flags # bitflags providing more information about the result @@ -179,7 +182,8 @@ class PushInfo(object): # the remote_ref_string. It can be a TagReference as well. info.old_commit # commit at which the remote_ref was standing before we pushed # it to local_ref.commit. Will be None if an error was indicated - info.summary # summary line providing human readable english text about the push""" + info.summary # summary line providing human readable english text about the push + """ __slots__ = ('local_ref', 'remote_ref_string', 'flags', 'old_commit', '_remote', 'summary') NEW_TAG, NEW_HEAD, NO_MATCH, REJECTED, REMOTE_REJECTED, REMOTE_FAILURE, DELETED, \ @@ -201,7 +205,8 @@ class PushInfo(object): @property def remote_ref(self): - """:return: + """ + :return: Remote Reference or TagReference in the local repository corresponding to the remote_ref_string kept in this instance.""" # translate heads to a local remote, tags stay as they are @@ -266,7 +271,8 @@ class PushInfo(object): class FetchInfo(object): - """Carries information about the results of a fetch operation of a single head:: + """ + Carries information about the results of a fetch operation of a single head:: info = remote.fetch()[0] info.ref # Symbolic Reference or RemoteReference to the changed @@ -276,7 +282,8 @@ class FetchInfo(object): # is 0 if ref is SymbolicReference info.note # additional notes given by git-fetch intended for the user info.old_commit # if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD, - # field is set to the previous location of ref, otherwise None""" + # field is set to the previous location of ref, otherwise None + """ __slots__ = ('ref','old_commit', 'flags', 'note') NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \ @@ -501,7 +508,7 @@ class Remote(LazyMixin, Iterable): :param repo: Repository instance that is to receive the new remote :param name: Desired name of the remote :param url: URL which corresponds to the remote's name - :param **kwargs: + :param kwargs: Additional arguments to be passed to the git-remote add command :return: New Remote instance @@ -644,7 +651,7 @@ class Remote(LazyMixin, Iterable): Taken from the git manual :param progress: See 'push' method - :param **kwargs: Additional arguments to be passed to git-fetch + :param kwargs: Additional arguments to be passed to git-fetch :return: IterableList(FetchInfo, ...) list of FetchInfo instances providing detailed information about the fetch results @@ -675,7 +682,7 @@ class Remote(LazyMixin, Iterable): progress information until the method returns. If None, progress information will be discarded - :param **kwargs: Additional arguments to be passed to git-push + :param kwargs: Additional arguments to be passed to git-push :return: IterableList(PushInfo, ...) iterable list of PushInfo instances, each one informing about an individual head which had been updated on the remote diff --git a/lib/git/utils.py b/lib/git/utils.py index e49fcc2a..54c3414e 100644 --- a/lib/git/utils.py +++ b/lib/git/utils.py @@ -269,7 +269,8 @@ class BlockingLockFile(LockFile): class IterableList(list): - """List of iterable objects allowing to query an object by id or by named index:: + """ + List of iterable objects allowing to query an object by id or by named index:: heads = repo.heads heads.master @@ -317,11 +318,13 @@ class Iterable(object): @classmethod def list_items(cls, repo, *args, **kwargs): - """Find all items of this type - subclasses can specify args and kwargs differently. + """ + 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 + :note: Favor the iter_items method as it will + :return:list(Item,...) list of item instances""" out_list = IterableList( cls._id_attribute_ ) out_list.extend(cls.iter_items(repo, *args, **kwargs)) -- cgit v1.2.1 From 791765c0dc2d00a9ffa4bc857d09f615cfe3a759 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 29 Jun 2010 18:59:01 +0200 Subject: Removed repo tests which for some reason left the 'repos' directory around, replaced them by a real test which actually executes code, and puts everything into the tmp directory --- lib/git/repo.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/git') diff --git a/lib/git/repo.py b/lib/git/repo.py index 9b25653f..2df2cb6c 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -669,7 +669,12 @@ class Repo(object): path = prev_path # END reset previous working dir # END bad windows handling - return Repo(path, odbt = odbt) + + # 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 self.git.working_dir: + path = os.path.join(self.git._working_dir, path) + return Repo(os.path.abspath(path), odbt = odbt) def archive(self, ostream, treeish=None, prefix=None, **kwargs): -- cgit v1.2.1 From 77cd6659b64cb1950a82e6a3cccdda94f15ae739 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 29 Jun 2010 20:00:46 +0200 Subject: Renamed modules utils to util, and errors to exc to be more conforming to the submodules's naming conventions --- lib/git/__init__.py | 4 +- lib/git/cmd.py | 4 +- lib/git/config.py | 2 +- lib/git/diff.py | 4 +- lib/git/errors.py | 56 ------- lib/git/exc.py | 56 +++++++ lib/git/index/base.py | 6 +- lib/git/index/fun.py | 4 +- lib/git/objects/__init__.py | 2 +- lib/git/objects/base.py | 4 +- lib/git/objects/commit.py | 4 +- lib/git/objects/tag.py | 2 +- lib/git/objects/tree.py | 6 +- lib/git/objects/util.py | 365 ++++++++++++++++++++++++++++++++++++++++++++ lib/git/objects/utils.py | 365 -------------------------------------------- lib/git/refs.py | 4 +- lib/git/remote.py | 4 +- lib/git/repo.py | 2 +- lib/git/util.py | 340 +++++++++++++++++++++++++++++++++++++++++ lib/git/utils.py | 340 ----------------------------------------- 20 files changed, 787 insertions(+), 787 deletions(-) delete mode 100644 lib/git/errors.py create mode 100644 lib/git/exc.py create mode 100644 lib/git/objects/util.py delete mode 100644 lib/git/objects/utils.py create mode 100644 lib/git/util.py delete mode 100644 lib/git/utils.py (limited to 'lib/git') diff --git a/lib/git/__init__.py b/lib/git/__init__.py index 7860a3c1..5ebeaa7d 100644 --- a/lib/git/__init__.py +++ b/lib/git/__init__.py @@ -28,12 +28,12 @@ from git.config import GitConfigParser from git.objects import * from git.refs import * from git.diff import * -from git.errors import * +from git.exc import * from git.cmd import Git from git.repo import Repo from git.remote import * from git.index import * -from git.utils import ( +from git.util import ( LockFile, BlockingLockFile, Stats diff --git a/lib/git/cmd.py b/lib/git/cmd.py index e9c1ce24..cd848e05 100644 --- a/lib/git/cmd.py +++ b/lib/git/cmd.py @@ -5,8 +5,8 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import os, sys -from utils import * -from errors import GitCommandError +from util import * +from exc import GitCommandError from subprocess import ( call, diff --git a/lib/git/config.py b/lib/git/config.py index e4af57f0..92d64aea 100644 --- a/lib/git/config.py +++ b/lib/git/config.py @@ -13,7 +13,7 @@ import inspect import cStringIO from git.odict import OrderedDict -from git.utils import LockFile +from git.util import LockFile __all__ = ('GitConfigParser', ) diff --git a/lib/git/diff.py b/lib/git/diff.py index 79a0c3c1..788bf238 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -6,8 +6,8 @@ import re from objects.blob import Blob -from objects.utils import mode_str_to_int -from errors import GitCommandError +from objects.util import mode_str_to_int +from exc import GitCommandError from gitdb.util import hex_to_bin diff --git a/lib/git/errors.py b/lib/git/errors.py deleted file mode 100644 index 93919d5e..00000000 --- a/lib/git/errors.py +++ /dev/null @@ -1,56 +0,0 @@ -# errors.py -# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors -# -# This module is part of GitPython and is released under -# the BSD License: http://www.opensource.org/licenses/bsd-license.php -""" Module containing all exceptions thrown througout the git package, """ - -class InvalidGitRepositoryError(Exception): - """ Thrown if the given repository appears to have an invalid format. """ - - -class NoSuchPathError(OSError): - """ Thrown if a path could not be access by the system. """ - - -class GitCommandError(Exception): - """ 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)) - - -class CheckoutError( Exception ): - """Thrown if a file could not be checked out from the index as it contained - changes. - - The .failed_files attribute contains a list of relative paths that failed - to be checked out as they contained changes that did not exist in the index. - - The .failed_reasons attribute contains a string informing about the actual - cause of the issue. - - 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 - self.failed_reasons = failed_reasons - self.valid_files = valid_files - - def __str__(self): - return Exception.__str__(self) + ":%s" % self.failed_files - - -class CacheError(Exception): - """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/lib/git/exc.py b/lib/git/exc.py new file mode 100644 index 00000000..93919d5e --- /dev/null +++ b/lib/git/exc.py @@ -0,0 +1,56 @@ +# errors.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php +""" Module containing all exceptions thrown througout the git package, """ + +class InvalidGitRepositoryError(Exception): + """ Thrown if the given repository appears to have an invalid format. """ + + +class NoSuchPathError(OSError): + """ Thrown if a path could not be access by the system. """ + + +class GitCommandError(Exception): + """ 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)) + + +class CheckoutError( Exception ): + """Thrown if a file could not be checked out from the index as it contained + changes. + + The .failed_files attribute contains a list of relative paths that failed + to be checked out as they contained changes that did not exist in the index. + + The .failed_reasons attribute contains a string informing about the actual + cause of the issue. + + 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 + self.failed_reasons = failed_reasons + self.valid_files = valid_files + + def __str__(self): + return Exception.__str__(self) + ":%s" % self.failed_files + + +class CacheError(Exception): + """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/lib/git/index/base.py b/lib/git/index/base.py index 1a8bee93..4b3197a2 100644 --- a/lib/git/index/base.py +++ b/lib/git/index/base.py @@ -36,7 +36,7 @@ from util import ( import git.objects import git.diff as diff -from git.errors import ( +from git.exc import ( GitCommandError, CheckoutError ) @@ -48,9 +48,9 @@ from git.objects import ( Commit, ) -from git.objects.utils import Serializable +from git.objects.util import Serializable -from git.utils import ( +from git.util import ( IndexFileSHA1Writer, LazyMixin, LockedFD, diff --git a/lib/git/index/fun.py b/lib/git/index/fun.py index cc18c65c..fac559c5 100644 --- a/lib/git/index/fun.py +++ b/lib/git/index/fun.py @@ -5,8 +5,8 @@ more versatile from stat import S_IFDIR from cStringIO import StringIO -from git.utils import IndexFileSHA1Writer -from git.errors import UnmergedEntriesError +from git.util import IndexFileSHA1Writer +from git.exc import UnmergedEntriesError from git.objects.fun import ( tree_to_stream, traverse_tree_recursive, diff --git a/lib/git/objects/__init__.py b/lib/git/objects/__init__.py index 209d8b51..85c7e38c 100644 --- a/lib/git/objects/__init__.py +++ b/lib/git/objects/__init__.py @@ -8,7 +8,7 @@ from blob import * from tree import * from commit import * from submodule import * -from utils import Actor +from util import Actor __all__ = [ name for name, obj in locals().items() if not (name.startswith('_') or inspect.ismodule(obj)) ] \ No newline at end of file diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py index 97b7898c..d4a46788 100644 --- a/lib/git/objects/base.py +++ b/lib/git/objects/base.py @@ -3,8 +3,8 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from git.utils import LazyMixin, join_path_native, stream_copy -from utils import get_object_type_by_name +from git.util import LazyMixin, join_path_native, stream_copy +from util import get_object_type_by_name from gitdb.util import ( hex_to_bin, bin_to_hex, diff --git a/lib/git/objects/commit.py b/lib/git/objects/commit.py index 11263e07..132d794b 100644 --- a/lib/git/objects/commit.py +++ b/lib/git/objects/commit.py @@ -4,7 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from git.utils import ( +from git.util import ( Iterable, Stats, ) @@ -17,7 +17,7 @@ import base from gitdb.util import ( hex_to_bin ) -from utils import ( +from util import ( Traversable, Serializable, get_user_id, diff --git a/lib/git/objects/tag.py b/lib/git/objects/tag.py index 702eae35..ea480fc2 100644 --- a/lib/git/objects/tag.py +++ b/lib/git/objects/tag.py @@ -6,7 +6,7 @@ """ Module containing all object based types. """ import base from gitdb.util import hex_to_bin -from utils import ( +from util import ( get_object_type_by_name, parse_actor_and_date ) diff --git a/lib/git/objects/tree.py b/lib/git/objects/tree.py index 64725128..d6e16a31 100644 --- a/lib/git/objects/tree.py +++ b/lib/git/objects/tree.py @@ -3,7 +3,7 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -import utils +import util from base import IndexObject from blob import Blob @@ -101,7 +101,7 @@ class TreeModifier(object): #} END mutators -class Tree(IndexObject, diff.Diffable, utils.Traversable, utils.Serializable): +class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): """Tree objects represent an ordered list of Blobs and other Trees. ``Tree as a list``:: @@ -214,7 +214,7 @@ class Tree(IndexObject, diff.Diffable, utils.Traversable, utils.Serializable): 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 utils.Traversable.traverse + """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) diff --git a/lib/git/objects/util.py b/lib/git/objects/util.py new file mode 100644 index 00000000..fd648f09 --- /dev/null +++ b/lib/git/objects/util.py @@ -0,0 +1,365 @@ +# util.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php +"""Module for general utility functions""" +import re +from collections import deque as Deque +import platform + +from string import digits +import time +import os + +__all__ = ('get_object_type_by_name', 'get_user_id', 'parse_date', 'parse_actor_and_date', + 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz', + 'verify_utctz') + +#{ Functions + +def mode_str_to_int(modestr): + """ + :param modestr: string like 755 or 644 or 100644 - only the last 6 chars will be used + :return: + String identifying a mode compatible to the mode methods ids of the + stat module regarding the rwx permissions for user, group and other, + special flags and file system flags, i.e. whether it is a symlink + for example.""" + mode = 0 + for iteration, char in enumerate(reversed(modestr[-6:])): + 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 + return commit.Commit + elif object_type_name == "tag": + import tag + return tag.TagObject + elif object_type_name == "blob": + import blob + return blob.Blob + elif object_type_name == "tree": + import tree + return tree.Tree + else: + raise ValueError("Cannot handle unknown object type: %s" % object_type_name) + + +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""" + ukn = 'UNKNOWN' + username = os.environ.get('USER', ukn) + if username == ukn and hasattr(os, 'getlogin'): + username = os.getlogin() + # END get username from login + return "%s@%s" % (username, platform.node()) + + +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 timezon 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) + +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) + utcs = str(abs(utci)) + utcs = "0"*(4-len(utcs)) + utcs + prefix = (utci < 0 and '-') or '+' + return prefix + utcs + + +def verify_utctz(offset): + """:raise ValueError: if offset is incorrect + :return: offset""" + fmt_exc = ValueError("Invalid timezone offset format: %s" % offset) + if len(offset) != 5: + raise fmt_exc + 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: + 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""" + # git time + try: + if string_date.count(' ') == 1 and string_date.rfind(':') == -1: + timestamp, offset = string_date.split() + timestamp = int(timestamp) + return timestamp, utctz_to_altz(verify_utctz(offset)) + else: + offset = "+0000" # local time by default + if string_date[-5] in '-+': + 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 + if ',' in string_date: + date_formats.append("%a, %d %b %Y") + splitter = string_date.rfind(' ') + else: + # iso plus additional + date_formats.append("%Y-%m-%d") + 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 + + assert splitter > -1 + + # split date and time + 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, + 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) + except ValueError: + 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) + # 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 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 + +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'(.*) <(.+?)>' ) + + 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)) + + def __str__(self): + return self.name + + def __repr__(self): + return '">' % (self.name, self.email) + + @classmethod + def _from_string(cls, string): + """Create an Actor from a string. + :param string: is the string, which is expected to be in regular git format + + John Doe + + :return: Actor """ + m = cls.name_email_regex.search(string) + if m: + name, email = m.groups() + return Actor(name, email) + else: + m = cls.name_only_regex.search(string) + if m: + return Actor(m.group(1), None) + else: + # assume best and use the whole string as name + return Actor(string, None) + # END special case name + # END handle name/email matching + + +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): + """ + Returns: + List of items connected to the given item. + Must be implemented in subclass + """ + raise NotImplementedError("To be implemented in subclass") + + + 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 ) + if not lst: + return + if branch_first: + 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 ) + # 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 ): + continue + + skipStartItem = ignore_self and ( item == 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 ) + # END for each item on work stack + + +class Serializable(object): + """Defines methods to serialize and deserialize objects from and into a data stream""" + + 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 + :return: self""" + raise NotImplementedError("To be implemented in subclass") diff --git a/lib/git/objects/utils.py b/lib/git/objects/utils.py deleted file mode 100644 index fd648f09..00000000 --- a/lib/git/objects/utils.py +++ /dev/null @@ -1,365 +0,0 @@ -# util.py -# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors -# -# This module is part of GitPython and is released under -# the BSD License: http://www.opensource.org/licenses/bsd-license.php -"""Module for general utility functions""" -import re -from collections import deque as Deque -import platform - -from string import digits -import time -import os - -__all__ = ('get_object_type_by_name', 'get_user_id', 'parse_date', 'parse_actor_and_date', - 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz', - 'verify_utctz') - -#{ Functions - -def mode_str_to_int(modestr): - """ - :param modestr: string like 755 or 644 or 100644 - only the last 6 chars will be used - :return: - String identifying a mode compatible to the mode methods ids of the - stat module regarding the rwx permissions for user, group and other, - special flags and file system flags, i.e. whether it is a symlink - for example.""" - mode = 0 - for iteration, char in enumerate(reversed(modestr[-6:])): - 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 - return commit.Commit - elif object_type_name == "tag": - import tag - return tag.TagObject - elif object_type_name == "blob": - import blob - return blob.Blob - elif object_type_name == "tree": - import tree - return tree.Tree - else: - raise ValueError("Cannot handle unknown object type: %s" % object_type_name) - - -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""" - ukn = 'UNKNOWN' - username = os.environ.get('USER', ukn) - if username == ukn and hasattr(os, 'getlogin'): - username = os.getlogin() - # END get username from login - return "%s@%s" % (username, platform.node()) - - -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 timezon 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) - -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) - utcs = str(abs(utci)) - utcs = "0"*(4-len(utcs)) + utcs - prefix = (utci < 0 and '-') or '+' - return prefix + utcs - - -def verify_utctz(offset): - """:raise ValueError: if offset is incorrect - :return: offset""" - fmt_exc = ValueError("Invalid timezone offset format: %s" % offset) - if len(offset) != 5: - raise fmt_exc - 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: - 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""" - # git time - try: - if string_date.count(' ') == 1 and string_date.rfind(':') == -1: - timestamp, offset = string_date.split() - timestamp = int(timestamp) - return timestamp, utctz_to_altz(verify_utctz(offset)) - else: - offset = "+0000" # local time by default - if string_date[-5] in '-+': - 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 - if ',' in string_date: - date_formats.append("%a, %d %b %Y") - splitter = string_date.rfind(' ') - else: - # iso plus additional - date_formats.append("%Y-%m-%d") - 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 - - assert splitter > -1 - - # split date and time - 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, - 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) - except ValueError: - 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) - # 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 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 - -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'(.*) <(.+?)>' ) - - 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)) - - def __str__(self): - return self.name - - def __repr__(self): - return '">' % (self.name, self.email) - - @classmethod - def _from_string(cls, string): - """Create an Actor from a string. - :param string: is the string, which is expected to be in regular git format - - John Doe - - :return: Actor """ - m = cls.name_email_regex.search(string) - if m: - name, email = m.groups() - return Actor(name, email) - else: - m = cls.name_only_regex.search(string) - if m: - return Actor(m.group(1), None) - else: - # assume best and use the whole string as name - return Actor(string, None) - # END special case name - # END handle name/email matching - - -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): - """ - Returns: - List of items connected to the given item. - Must be implemented in subclass - """ - raise NotImplementedError("To be implemented in subclass") - - - 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 ) - if not lst: - return - if branch_first: - 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 ) - # 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 ): - continue - - skipStartItem = ignore_self and ( item == 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 ) - # END for each item on work stack - - -class Serializable(object): - """Defines methods to serialize and deserialize objects from and into a data stream""" - - 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 - :return: self""" - raise NotImplementedError("To be implemented in subclass") diff --git a/lib/git/refs.py b/lib/git/refs.py index c8d67d3f..343a0afb 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -10,8 +10,8 @@ from objects import ( Object, Commit ) -from objects.utils import get_object_type_by_name -from utils import ( +from objects.util import get_object_type_by_name +from util import ( LazyMixin, Iterable, join_path, diff --git a/lib/git/remote.py b/lib/git/remote.py index 1558eeb3..1598e55a 100644 --- a/lib/git/remote.py +++ b/lib/git/remote.py @@ -5,10 +5,10 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php """Module implementing a remote object allowing easy access to git remotes""" -from errors import GitCommandError +from exc import GitCommandError from objects import Commit -from git.utils import ( +from git.util import ( LazyMixin, Iterable, IterableList diff --git a/lib/git/repo.py b/lib/git/repo.py index 2df2cb6c..d9b943cd 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -4,7 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from errors import InvalidGitRepositoryError, NoSuchPathError +from exc import InvalidGitRepositoryError, NoSuchPathError from cmd import Git from objects import Actor from refs import * diff --git a/lib/git/util.py b/lib/git/util.py new file mode 100644 index 00000000..54c3414e --- /dev/null +++ b/lib/git/util.py @@ -0,0 +1,340 @@ +# utils.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php + +import os +import sys +import time +import tempfile + +from gitdb.util import ( + make_sha, + LockedFD, + file_contents_ro, + LazyMixin, + to_hex_sha, + to_bin_sha + ) + +__all__ = ( "stream_copy", "join_path", "to_native_path_windows", "to_native_path_linux", + "join_path_native", "Stats", "IndexFileSHA1Writer", "Iterable", "IterableList", + "BlockingLockFile", "LockFile" ) + +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: + chunk = source.read(chunk_size) + destination.write(chunk) + br += len(chunk) + if len(chunk) < chunk_size: + break + # END reading output stream + return br + +def join_path(a, *p): + """Join path tokens together similar to os.path.join, but always use + '/' instead of possibly '\' on windows.""" + path = a + for b in p: + if b.startswith('/'): + path += b[1:] + elif path == '' or path.endswith('/'): + path += b + else: + path += '/' + b + return path + +def to_native_path_windows(path): + return path.replace('/','\\') + +def to_native_path_linux(path): + return path.replace('\\','/') + +if sys.platform.startswith('win'): + to_native_path = to_native_path_windows +else: + # no need for any work on linux + def to_native_path_linux(path): + 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 + needed to play it safe on my dear windows and to assure nice paths that only + use '\'""" + return to_native_path(join_path(a, *p)) + + +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 + + @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(): + (raw_insertions, raw_deletions, filename) = line.split("\t") + insertions = raw_insertions != '-' and int(raw_insertions) or 0 + deletions = raw_deletions != '-' and int(raw_deletions) or 0 + hsh['total']['insertions'] += insertions + hsh['total']['deletions'] += deletions + hsh['total']['lines'] += insertions + deletions + hsh['total']['files'] += 1 + hsh['files'][filename.strip()] = {'insertions': insertions, + 'deletions': deletions, + 'lines': insertions + deletions} + return Stats(hsh['total'], hsh['files']) + + +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("") + + def write(self, data): + self.sha1.update(data) + return self.f.write(data) + + def write_sha(self): + sha = self.sha1.digest() + self.f.write(sha) + return sha + + def close(self): + sha = self.write_sha() + self.f.close() + return sha + + def tell(self): + return self.f.tell() + + +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 + 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)) + + try: + fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0) + os.close(fd) + 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: + os.remove(lfp) + except OSError: + pass + self._owns_lock = False + + +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. + If this method returns, you are guranteed to own the lock""" + starttime = time.time() + maxtime = starttime + float(self._max_block_time) + while True: + try: + super(BlockingLockFile, self)._obtain_lock() + except IOError: + # synity check: if the directory leading to the lockfile is not + # 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) + raise IOError(msg) + # END handle missing directory + + if curtime >= maxtime: + 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) + else: + break + # END endless loop + + +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) + + def __init__(self, id_attr, prefix=''): + self._id_attr = id_attr + self._prefix = prefix + + def __getattr__(self, attr): + attr = self._prefix + attr + for item in self: + if getattr(item, self._id_attr) == attr: + return item + # END for each item + return list.__getattribute__(self, attr) + + def __getitem__(self, index): + if isinstance(index, int): + return list.__getitem__(self,index) + + try: + return getattr(self, index) + except AttributeError: + raise IndexError( "No item found with id %r" % (self._prefix + index) ) + + +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.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") + + diff --git a/lib/git/utils.py b/lib/git/utils.py deleted file mode 100644 index 54c3414e..00000000 --- a/lib/git/utils.py +++ /dev/null @@ -1,340 +0,0 @@ -# utils.py -# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors -# -# This module is part of GitPython and is released under -# the BSD License: http://www.opensource.org/licenses/bsd-license.php - -import os -import sys -import time -import tempfile - -from gitdb.util import ( - make_sha, - LockedFD, - file_contents_ro, - LazyMixin, - to_hex_sha, - to_bin_sha - ) - -__all__ = ( "stream_copy", "join_path", "to_native_path_windows", "to_native_path_linux", - "join_path_native", "Stats", "IndexFileSHA1Writer", "Iterable", "IterableList", - "BlockingLockFile", "LockFile" ) - -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: - chunk = source.read(chunk_size) - destination.write(chunk) - br += len(chunk) - if len(chunk) < chunk_size: - break - # END reading output stream - return br - -def join_path(a, *p): - """Join path tokens together similar to os.path.join, but always use - '/' instead of possibly '\' on windows.""" - path = a - for b in p: - if b.startswith('/'): - path += b[1:] - elif path == '' or path.endswith('/'): - path += b - else: - path += '/' + b - return path - -def to_native_path_windows(path): - return path.replace('/','\\') - -def to_native_path_linux(path): - return path.replace('\\','/') - -if sys.platform.startswith('win'): - to_native_path = to_native_path_windows -else: - # no need for any work on linux - def to_native_path_linux(path): - 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 - needed to play it safe on my dear windows and to assure nice paths that only - use '\'""" - return to_native_path(join_path(a, *p)) - - -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 - - @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(): - (raw_insertions, raw_deletions, filename) = line.split("\t") - insertions = raw_insertions != '-' and int(raw_insertions) or 0 - deletions = raw_deletions != '-' and int(raw_deletions) or 0 - hsh['total']['insertions'] += insertions - hsh['total']['deletions'] += deletions - hsh['total']['lines'] += insertions + deletions - hsh['total']['files'] += 1 - hsh['files'][filename.strip()] = {'insertions': insertions, - 'deletions': deletions, - 'lines': insertions + deletions} - return Stats(hsh['total'], hsh['files']) - - -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("") - - def write(self, data): - self.sha1.update(data) - return self.f.write(data) - - def write_sha(self): - sha = self.sha1.digest() - self.f.write(sha) - return sha - - def close(self): - sha = self.write_sha() - self.f.close() - return sha - - def tell(self): - return self.f.tell() - - -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 - 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)) - - try: - fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0) - os.close(fd) - 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: - os.remove(lfp) - except OSError: - pass - self._owns_lock = False - - -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. - If this method returns, you are guranteed to own the lock""" - starttime = time.time() - maxtime = starttime + float(self._max_block_time) - while True: - try: - super(BlockingLockFile, self)._obtain_lock() - except IOError: - # synity check: if the directory leading to the lockfile is not - # 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) - raise IOError(msg) - # END handle missing directory - - if curtime >= maxtime: - 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) - else: - break - # END endless loop - - -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) - - def __init__(self, id_attr, prefix=''): - self._id_attr = id_attr - self._prefix = prefix - - def __getattr__(self, attr): - attr = self._prefix + attr - for item in self: - if getattr(item, self._id_attr) == attr: - return item - # END for each item - return list.__getattribute__(self, attr) - - def __getitem__(self, index): - if isinstance(index, int): - return list.__getitem__(self,index) - - try: - return getattr(self, index) - except AttributeError: - raise IndexError( "No item found with id %r" % (self._prefix + index) ) - - -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.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") - - -- cgit v1.2.1 From 18be0972304dc7f1a2a509595de7da689bddbefa Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 29 Jun 2010 20:16:37 +0200 Subject: Removed blob.data property as there is no real reason for an exception to the rule of trying not to cache possibly heavy data. The data_stream method should be used instead --- lib/git/objects/blob.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'lib/git') diff --git a/lib/git/objects/blob.py b/lib/git/objects/blob.py index d0ef54c7..32f8c61c 100644 --- a/lib/git/objects/blob.py +++ b/lib/git/objects/blob.py @@ -14,17 +14,7 @@ class Blob(base.IndexObject): DEFAULT_MIME_TYPE = "text/plain" type = "blob" - __slots__ = "data" - - def _set_cache_(self, attr): - if attr == "data": - ostream = self.repo.odb.stream(self.binsha) - self.size = ostream.size - self.data = ostream.read() - # assert ostream.type == self.type, _assertion_msg_format % (self.binsha, ostream.type, self.type) - else: - super(Blob, self)._set_cache_(attr) - # END handle data + __slots__ = tuple() @property def mime_type(self): -- cgit v1.2.1 From 28a33ca17ac5e0816a3e24febb47ffcefa663980 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 1 Jul 2010 15:45:25 +0200 Subject: Added further information about the required submodules, and how to install them. Incremeneted version to 0.3.0 beta1 --- lib/git/ext/gitdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/git') diff --git a/lib/git/ext/gitdb b/lib/git/ext/gitdb index c265c97f..7562fdd9 160000 --- a/lib/git/ext/gitdb +++ b/lib/git/ext/gitdb @@ -1 +1 @@ -Subproject commit c265c97f9130d2225b923b427736796c0a0d957c +Subproject commit 7562fdd96ab995f6c25fc102ef40a285283c844e -- cgit v1.2.1