diff options
-rw-r--r-- | lib/git/actor.py | 3 | ||||
-rw-r--r-- | lib/git/blob.py | 17 | ||||
-rw-r--r-- | lib/git/cmd.py | 54 | ||||
-rw-r--r-- | lib/git/commit.py | 81 | ||||
-rw-r--r-- | lib/git/errors.py | 14 | ||||
-rw-r--r-- | lib/git/head.py | 21 | ||||
-rw-r--r-- | lib/git/repo.py | 80 | ||||
-rw-r--r-- | lib/git/stats.py | 32 | ||||
-rw-r--r-- | lib/git/tag.py | 25 | ||||
-rw-r--r-- | test/git/test_repo.py | 4 |
10 files changed, 245 insertions, 86 deletions
diff --git a/lib/git/actor.py b/lib/git/actor.py index cdcc02f8..bc1a4479 100644 --- a/lib/git/actor.py +++ b/lib/git/actor.py @@ -7,6 +7,9 @@ import re 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.""" def __init__(self, name, email): self.name = name self.email = email diff --git a/lib/git/blob.py b/lib/git/blob.py index a15c5466..82a41f73 100644 --- a/lib/git/blob.py +++ b/lib/git/blob.py @@ -12,6 +12,7 @@ from actor import Actor from commit import Commit class Blob(object): + """A Blob encapsulates a git blob object""" DEFAULT_MIME_TYPE = "text/plain" def __init__(self, repo, id, mode=None, name=None): @@ -48,6 +49,9 @@ class Blob(object): Returns int + + NOTE + The size will be cached after the first access """ if self._size is None: self._size = int(self.repo.git.cat_file(self.id, s=True).rstrip()) @@ -60,6 +64,9 @@ class Blob(object): Returns str + + NOTE + The data will be cached after the first access. """ self.data_stored = self.data_stored or self.repo.git.cat_file(self.id, p=True, with_raw_output=True) return self.data_stored @@ -71,6 +78,9 @@ class Blob(object): Returns str + + NOTE + Defaults to 'text/plain' in case the actual file type is unknown. """ guesses = None if self.name: @@ -79,6 +89,10 @@ class Blob(object): @property def basename(self): + """ + Returns + The basename of the Blobs file name + """ return os.path.basename(self.name) @classmethod @@ -88,6 +102,9 @@ class Blob(object): Returns list: [git.Commit, list: [<line>]] + A list of tuples associating a Commit object with a list of lines that + changed within the given commit. The Commit objects will be given in order + of appearance. """ data = repo.git.blame(commit, '--', file, p=True) commits = {} diff --git a/lib/git/cmd.py b/lib/git/cmd.py index a1b85cdc..aef53350 100644 --- a/lib/git/cmd.py +++ b/lib/git/cmd.py @@ -22,19 +22,47 @@ if sys.platform == 'win32': class Git(object): """ - The Git class manages communication with the Git binary + 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. """ def __init__(self, git_dir=None): + """ + Initialize this instance with: + + ``git_dir`` + Git directory we should work in. If None, we always work in the current + directory as returned by os.getcwd() + """ super(Git, self).__init__() self.git_dir = git_dir def __getattr__(self, name): + """ + A convenience method as it allows to call the command as if it was + an object. + Returns + Callable object that will execute call _call_process with your arguments. + """ if name[:1] == '_': raise AttributeError(name) return lambda *args, **kwargs: self._call_process(name, *args, **kwargs) @property def get_dir(self): + """ + Returns + Git directory we are working on + """ return self.git_dir def execute(self, command, @@ -49,7 +77,9 @@ class Git(object): the returned information (stdout) ``command`` - The command argument list to execute + The command argument list to execute. + It should be a string, or a sequence of program arguments. The + program to execute is the first item in the args sequence or string. ``istream`` Standard input filehandle passed to subprocess.Popen. @@ -68,11 +98,18 @@ class Git(object): ``with_raw_output`` Whether to avoid stripping off trailing whitespace. - Returns - str(output) # extended_output = False (Default) - tuple(int(status), str(output)) # extended_output = True + Returns:: + + str(output) # extended_output = False (Default) + tuple(int(status), str(stdout), str(stderr)) # 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. """ - if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full': print ' '.join(command) @@ -146,7 +183,8 @@ class Git(object): the result as a String ``method`` - is the command + is the command. Contained "_" characters will be converted to dashes, + such as in 'ls_files' to call 'ls-files'. ``args`` is the list of arguments @@ -156,7 +194,7 @@ class Git(object): This function accepts the same optional keyword arguments as execute(). - Examples + Examples:: git.rev_list('master', max_count=10, header=True) Returns diff --git a/lib/git/commit.py b/lib/git/commit.py index ba7a7102..edfe47ca 100644 --- a/lib/git/commit.py +++ b/lib/git/commit.py @@ -14,38 +14,44 @@ import diff import stats class Commit(LazyMixin): + """ + 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. + """ def __init__(self, repo, id, tree=None, author=None, authored_date=None, committer=None, committed_date=None, message=None, parents=None): """ - Instantiate a new Commit + Instantiate a new Commit. All keyword arguments taking None as default will + be implicitly set if id names a valid sha. + + The parameter documentation indicates the type of the argument after a colon ':'. ``id`` - is the id of the commit + is the sha id of the commit - ``parents`` - is a list of commit ids (will be converted into Commit instances) + ``parents`` : list( Commit, ... ) + is a list of commit ids - ``tree`` - is the correspdonding tree id (will be converted into a Tree object) + ``tree`` : Tree + is the corresponding tree id - ``author`` - is the author string + ``author`` : Actor + is the author string ( will be implicitly converted into an Actor object ) - ``authored_date`` + ``authored_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst ) is the authored DateTime - ``committer`` + ``committer`` : Actor is the committer string - ``committed_date`` + ``committed_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst) is the committed DateTime - ``message`` + ``message`` : string is the commit message - ``parents`` - is the list of the parents of the commit - Returns git.Commit """ @@ -74,6 +80,10 @@ class Commit(LazyMixin): return self.id != other.id def __bake__(self): + """ + Called by LazyMixin superclass when the first uninitialized member needs + to be set as it is queried. + """ temp = Commit.find_all(self.repo, self.id, max_count=1)[0] self.parents = temp.parents self.tree = temp.tree @@ -85,10 +95,18 @@ class Commit(LazyMixin): @property def id_abbrev(self): + """ + Returns + First 7 bytes of the commit's sha id as an abbreviation of the full string. + """ return self.id[0:7] @property def summary(self): + """ + Returns + First line of the commit message. + """ return self.message.split('\n', 1)[0] @classmethod @@ -122,10 +140,11 @@ class Commit(LazyMixin): is the ref from which to begin (SHA1 or name) ``path`` - is an optinal path + is an optinal path, if set only Commits that include the path + will be considered - ``options`` - is a Hash of optional arguments to git where + ``kwargs`` + optional keyword arguments to git where ``max_count`` is the maximum number of commits to fetch ``skip`` is the number of commits to skip @@ -147,7 +166,7 @@ class Commit(LazyMixin): is the Repo ``text`` - is the text output from the git command (raw format) + is the text output from the git-rev-list command (raw format) Returns git.Commit[] @@ -180,7 +199,7 @@ class Commit(LazyMixin): @classmethod def diff(cls, repo, a, b=None, paths=None): """ - Show diffs between two trees: + Creates diffs between a tree and the index or between two trees: ``repo`` is the Repo @@ -194,10 +213,13 @@ class Commit(LazyMixin): given paths. ``paths`` - is a list of paths to limit the diff. + is a list of paths to limit the diff to. Returns - git.Diff[] + git.Diff[]:: + + between tree and the index if only a is given + between two trees if a and b are given and are commits """ paths = paths or [] @@ -216,6 +238,12 @@ class Commit(LazyMixin): @property def diffs(self): + """ + Returns + git.Diff[] + Diffs between this commit and its first parent or all changes if this + commit is the first commit and has no parent. + """ if not self.parents: d = self.repo.git.show(self.id, '-M', full_index=True, pretty='raw') if re.search(r'diff --git a', d): @@ -230,6 +258,13 @@ class Commit(LazyMixin): @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.id, '--', numstat=True, root=True) text2 = "" @@ -254,7 +289,7 @@ class Commit(LazyMixin): Parse out the actor (author or committer) info Returns - [str (actor name and email), time (acted at time)] + [Actor, gmtime(acted at time)] """ m = re.search(r'^.+? (.*) (\d+) .*$', line) actor, epoch = m.groups() diff --git a/lib/git/errors.py b/lib/git/errors.py index bf882d33..2632d5f3 100644 --- a/lib/git/errors.py +++ b/lib/git/errors.py @@ -3,14 +3,24 @@ # # 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): - pass + """ + Thrown if the given repository appears to have an invalid format. + """ class NoSuchPathError(Exception): - pass + """ + 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 diff --git a/lib/git/head.py b/lib/git/head.py index 86686f17..639cee40 100644 --- a/lib/git/head.py +++ b/lib/git/head.py @@ -28,16 +28,13 @@ class Head(object): def __init__(self, name, commit): """ - Instantiate a new Head + Initialize a newly instanced Head `name` is the name of the head `commit` - is the Commit that the head points to - - Returns - git.Head + is the Commit object that the head points to """ self.name = name self.commit = commit @@ -45,16 +42,19 @@ class Head(object): @classmethod def find_all(cls, repo, **kwargs): """ - Find all Heads + Find all Heads in the repository `repo` is the Repo `kwargs` - is a dict of options + Additional options given as keyword arguments, will be passed + to git-for-each-ref Returns git.Head[] + + List is sorted by committerdate """ options = {'sort': "committerdate", @@ -67,12 +67,12 @@ class Head(object): @classmethod def list_from_string(cls, repo, text): """ - Parse out head information into an array of baked head objects + Parse out head information into a list of head objects ``repo`` is the Repo ``text`` - is the text output from the git command + is the text output from the git-for-each-ref command Returns git.Head[] @@ -95,7 +95,8 @@ class Head(object): ``line`` is the formatted head information - Format + Format:: + name: [a-zA-Z_/]+ <null byte> id: [0-9A-Fa-f]{40} diff --git a/lib/git/repo.py b/lib/git/repo.py index b7ffcb61..1c4b4095 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -18,6 +18,11 @@ from commit import Commit from tree import Tree class Repo(object): + """ + Represents a git repository and allows you to query references, + gather commit information, generate diffs, create and clone repositories query + the log. + """ DAEMON_EXPORT_FILE = 'git-daemon-export-ok' def __init__(self, path=None): @@ -32,6 +37,9 @@ class Repo(object): repo = Repo("/Users/mtrier/Development/git-python") repo = Repo("/Users/mtrier/Development/git-python.git") + Raises + InvalidGitRepositoryError or NoSuchPathError + Returns ``git.Repo`` """ @@ -110,13 +118,15 @@ class Repo(object): is the branch/commit name (default 'master') ``path`` - is an optional path + is an optional path to limit the returned commits to + Commits that do not contain that path will not be returned. ``max_count`` is the maximum number of commits to return (default 10) ``skip`` - is the number of commits to skip (default 0) + is the number of commits to skip (default 0) which will effectively + move your commit-window by the given number. Returns ``git.Commit[]`` @@ -126,7 +136,7 @@ class Repo(object): return Commit.find_all(self, start, path, **options) - def commits_between(self, frm, to, path = ''): + def commits_between(self, frm, to): """ The Commits objects that are reachable via ``to`` but not via ``frm`` Commits are returned in chronological order. @@ -137,9 +147,6 @@ class Repo(object): ``to`` is the branch/commit name of the older item - ``path`` - is an optional path - Returns ``git.Commit[]`` """ @@ -154,7 +161,8 @@ class Repo(object): is the branch/commit name (default 'master') ``path`` - is an optinal path + is an optinal path to limit the returned commits to. + ``since`` is a string represeting a date/time @@ -174,10 +182,11 @@ class Repo(object): is the branch/commit name (default 'master') ``path`` - is an optinal path + is an optional path + Commits that do not contain the path will not contribute to the count. Returns - int + ``int`` """ return Commit.count(self, start, path) @@ -189,17 +198,17 @@ class Repo(object): is the SHA1 identifier of the commit ``path`` - is an optinal path + is an optional path, if set the returned commit must contain the path. Returns - git.Commit + ``git.Commit`` """ options = {'max_count': 1} commits = Commit.find_all(self, id, path, **options) if not commits: - raise ValueError, 'Invalid identifier %s' % id + raise ValueError, "Invalid identifier %s, or given path '%s' too restrictive" % ( id, path ) return commits[0] def commit_deltas_from(self, other_repo, ref='master', other_ref='master'): @@ -207,7 +216,7 @@ class Repo(object): Returns a list of commits that is in ``other_repo`` but not in self Returns - ``git.Commit[]`` + git.Commit[] """ repo_refs = self.git.rev_list(ref, '--').strip().splitlines() other_repo_refs = other_repo.git.rev_list(other_ref, '--').strip().splitlines() @@ -246,7 +255,11 @@ class Repo(object): def log(self, commit='master', path=None, **kwargs): """ - The commit log for a treeish + The Commit for a treeish, and all commits leading to it. + + ``kwargs`` + keyword arguments specifying flags to be used in git-log command, + i.e.: max_count=1 to limit the amount of commits returned Returns ``git.Commit[]`` @@ -270,6 +283,9 @@ class Repo(object): ``paths`` is an optional list of file paths on which to restrict the diff + + Returns + ``str`` """ return self.git.diff(a, b, '--', *paths) @@ -296,7 +312,7 @@ class Repo(object): already exists. Creates the directory with a mode=0755. ``kwargs`` - is any additional options to the git init command + keyword arguments serving as additional options to the git init command Examples:: @@ -321,8 +337,8 @@ class Repo(object): ``path`` is the full path of the new repo (traditionally ends with /<name>.git) - ``options`` - is any additional options to the git clone command + ``kwargs`` + keyword arguments to be given to the git clone command Returns ``git.Repo`` (the newly forked repo) @@ -340,7 +356,7 @@ class Repo(object): is the treeish name/id (default 'master') ``prefix`` - is the optional prefix + is the optional prefix to prepend to each filename in the archive Examples:: @@ -351,10 +367,10 @@ class Repo(object): <String containing tar archive for commit a87ff14> >>> repo.archive_tar('master', 'myproject/') - <String containing tar archive and prefixed with 'myproject/'> + <String containing tar bytes archive, whose files are prefixed with 'myproject/'> Returns - str (containing tar archive) + str (containing bytes of tar archive) """ options = {} if prefix: @@ -369,7 +385,7 @@ class Repo(object): is the treeish name/id (default 'master') ``prefix`` - is the optional prefix + is the optional prefix to prepend to each filename in the archive Examples:: @@ -383,7 +399,7 @@ class Repo(object): <String containing tar.gz archive and prefixed with 'myproject/'> Returns - str (containing tar.gz archive) + str (containing the bytes of tar.gz archive) """ kwargs = {} if prefix: @@ -408,16 +424,16 @@ class Repo(object): os.unlink(filename) daemon_export = property(_get_daemon_export, _set_daemon_export, - doc="git-daemon export of this repository") + doc="If True, git-daemon may export this repository") del _get_daemon_export del _set_daemon_export def _get_alternates(self): """ - The list of alternates for this repo + The list of alternates for this repo from which objects can be retrieved Returns - list[str] (pathnames of alternates) + list of strings being pathnames of alternates """ alternates_path = os.path.join(self.path, 'objects', 'info', 'alternates') @@ -436,8 +452,12 @@ class Repo(object): Sets the alternates ``alts`` - is the Array of String paths representing the alternates + is the array of string paths representing the alternates at which + git should look for objects, i.e. /home/user/repo/.git/objects + Raises + NoSuchPathError + Returns None """ @@ -454,17 +474,19 @@ class Repo(object): finally: f.close() - alternates = property(_get_alternates, _set_alternates) + alternates = property(_get_alternates, _set_alternates, doc="Retrieve a list of alternates paths or set a list paths to be used as alternates") @property def is_dirty(self): """ - Return the status of the working directory. + Return the status of the index. Returns - ``True``, if the working directory has any uncommitted changes, + ``True``, if the index has any uncommitted changes, otherwise ``False`` + NOTE + Working tree changes that have not been staged will not be detected ! """ if self.bare: # Bare repositories with no associated working directory are diff --git a/lib/git/stats.py b/lib/git/stats.py index 74a23126..307e2f2f 100644 --- a/lib/git/stats.py +++ b/lib/git/stats.py @@ -5,6 +5,32 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php 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 + + """ def __init__(self, repo, total, files): self.repo = repo self.total = total @@ -12,6 +38,12 @@ class Stats(object): @classmethod def list_from_string(cls, repo, text): + """ + Create a Stat object from output retrieved by git-diff. + + Returns + git.Stat + """ hsh = {'total': {'insertions': 0, 'deletions': 0, 'lines': 0, 'files': 0}, 'files': {}} for line in text.splitlines(): (raw_insertions, raw_deletions, filename) = line.split("\t") diff --git a/lib/git/tag.py b/lib/git/tag.py index f7bc140e..8413ce73 100644 --- a/lib/git/tag.py +++ b/lib/git/tag.py @@ -9,16 +9,13 @@ from commit import Commit class Tag(object): def __init__(self, name, commit): """ - Instantiate a new Tag + Initialize a newly instantiated Tag ``name`` is the name of the head ``commit`` is the Commit that the head points to - - Returns - ``git.Tag`` """ self.name = name self.commit = commit @@ -26,16 +23,19 @@ class Tag(object): @classmethod def find_all(cls, repo, **kwargs): """ - Find all Tags + Find all Tags in the repository ``repo`` is the Repo ``kwargs`` - is a dict of options + Additional options given as keyword arguments, will be passed + to git-for-each-ref Returns ``git.Tag[]`` + + List is sorted by committerdate """ options = {'sort': "committerdate", 'format': "%(refname)%00%(objectname)"} @@ -47,16 +47,16 @@ class Tag(object): @classmethod def list_from_string(cls, repo, text): """ - Parse out tag information into an array of baked Tag objects + Parse out tag information into an array of Tag objects ``repo`` is the Repo ``text`` - is the text output from the git command + is the text output from the git-for-each command Returns - ``git.Tag[]`` + git.Tag[] """ tags = [] for line in text.splitlines(): @@ -74,13 +74,14 @@ class Tag(object): ``line`` is the formatted tag information - Format + Format:: + name: [a-zA-Z_/]+ <null byte> id: [0-9A-Fa-f]{40} - + Returns - ``git.Tag`` + git.Tag """ full_name, ids = line.split("\x00") name = full_name.split("/")[-1] diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 1c5b50d9..a9d3beaf 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -193,10 +193,10 @@ class TestRepo(object): assert_true(git.called) def test_archive_tar(self): - self.repo.archive_tar() + assert self.repo.archive_tar() def test_archive_tar_gz(self): - self.repo.archive_tar_gz() + assert self.repo.archive_tar_gz() @patch('git.utils.touch') def test_enable_daemon_serve(self, touch): |