diff options
-rw-r--r-- | CHANGES | 14 | ||||
-rw-r--r-- | lib/git/actor.py | 3 | ||||
-rw-r--r-- | lib/git/blob.py | 19 | ||||
-rw-r--r-- | lib/git/cmd.py | 56 | ||||
-rw-r--r-- | lib/git/commit.py | 81 | ||||
-rw-r--r-- | lib/git/diff.py | 55 | ||||
-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_blob.py | 8 | ||||
-rw-r--r-- | test/git/test_commit.py | 55 | ||||
-rw-r--r-- | test/git/test_repo.py | 72 |
14 files changed, 340 insertions, 195 deletions
@@ -1,6 +1,20 @@ ======= CHANGES ======= + +0.1.X +===== +( Future Release ) +General +------- +* See changes in Diff class as your client code needs adjustments to work with it + +Diff +---- +* Members a a_commit and b_commit renamed to a_blob and b_blob - they are populated + with Blob objects if possible +* Members a_path and b_path removed as this information is kept in the blobs + 0.1.6 ===== 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 82f92ce3..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 = {} @@ -135,7 +152,7 @@ class Blob(object): m = re.search(r'^\t(.*)$', line) text, = m.groups() blames[-1][0] = c - blames[-1][1] += text + blames[-1][1].append( text ) info = None return blames diff --git a/lib/git/cmd.py b/lib/git/cmd.py index 9cbad673..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): + 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 2b19ea42..1cb863ca 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 """ @@ -68,6 +74,10 @@ class Commit(LazyMixin): self.tree = Tree(repo, id=tree) 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 @@ -79,10 +89,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 @@ -115,10 +133,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 @@ -140,7 +159,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[] @@ -173,7 +192,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 @@ -187,10 +206,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 [] @@ -209,6 +231,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): @@ -223,6 +251,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 = "" @@ -247,7 +282,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/diff.py b/lib/git/diff.py index 0216e061..44f55602 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -5,28 +5,44 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import re -import commit +import blob class Diff(object): """ A Diff contains diff information between two commits. + + It contains two sides a and b of the diff, members are prefixed with + "a" and "b" respectively to inidcate that. + + Diffs keep information about the changed blob objects, the file mode, renames, + deletions and new files. + + There are a few cases where None has to be expected as member variable value: + + ``New File``:: + + a_mode is None + a_blob is None + + ``Deleted File``:: + + b_mode is None + b_blob is NOne """ - def __init__(self, repo, a_path, b_path, a_commit, b_commit, a_mode, + def __init__(self, repo, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, rename_from, rename_to, diff): self.repo = repo - self.a_path = a_path - self.b_path = b_path - if not a_commit or re.search(r'^0{40}$', a_commit): - self.a_commit = None + if not a_blob or re.search(r'^0{40}$', a_blob): + self.a_blob = None else: - self.a_commit = commit.Commit(repo, id=a_commit) - if not b_commit or re.search(r'^0{40}$', b_commit): - self.b_commit = None + self.a_blob = blob.Blob(repo, id=a_blob, mode=a_mode, name=a_path) + if not b_blob or re.search(r'^0{40}$', b_blob): + self.b_blob = None else: - self.b_commit = commit.Commit(repo, id=b_commit) + self.b_blob = blob.Blob(repo, id=b_blob, mode=b_mode, name=b_path) self.a_mode = a_mode self.b_mode = b_mode @@ -39,6 +55,17 @@ class Diff(object): @classmethod def list_from_string(cls, repo, text): + """ + Create a new diff object from the given text + ``repo`` + is the repository we are operating on - it is required + + ``text`` + result of 'git diff' between two commits or one commit and the index + + Returns + git.Diff[] + """ diffs = [] diff_header = re.compile(r""" @@ -51,8 +78,8 @@ class Diff(object): ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))? (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))? (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))? - (?:^index[ ](?P<a_commit>[0-9A-Fa-f]+) - \.\.(?P<b_commit>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))? + (?:^index[ ](?P<a_blob>[0-9A-Fa-f]+) + \.\.(?P<b_blob>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))? """, re.VERBOSE | re.MULTILINE).match for diff in ('\n' + text).split('\ndiff --git')[1:]: @@ -60,10 +87,10 @@ class Diff(object): a_path, b_path, similarity_index, rename_from, rename_to, \ old_mode, new_mode, new_file_mode, deleted_file_mode, \ - a_commit, b_commit, b_mode = header.groups() + a_blob, b_blob, b_mode = header.groups() new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode) - diffs.append(Diff(repo, a_path, b_path, a_commit, b_commit, + diffs.append(Diff(repo, a_path, b_path, a_blob, b_blob, old_mode or deleted_file_mode, new_mode or new_file_mode or b_mode, new_file, deleted_file, rename_from, rename_to, diff[header.end():])) 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_blob.py b/test/git/test_blob.py index 8f83f391..5bd74ff7 100644 --- a/test/git/test_blob.py +++ b/test/git/test_blob.py @@ -69,6 +69,7 @@ class TestBlob(object): git.return_value = fixture('blame') b = Blob.blame(self.repo, 'master', 'lib/git.py') assert_equal(13, len(b)) + assert_equal( 2, len(b[0]) ) # assert_equal(25, reduce(lambda acc, x: acc + len(x[-1]), b)) assert_equal(hash(b[0][0]), hash(b[9][0])) c = b[0][0] @@ -83,6 +84,13 @@ class TestBlob(object): assert_equal('tom@mojombo.com', c.committer.email) assert_equal(time.gmtime(1191997100), c.committed_date) assert_equal('initial grit setup', c.message) + + # test the 'lines per commit' entries + tlist = b[0][1] + assert_true( tlist ) + assert_true( isinstance( tlist[0], basestring ) ) + assert_true( len( tlist ) < sum( len(t) for t in tlist ) ) # test for single-char bug + def test_should_return_appropriate_representation(self): blob = Blob(self.repo, **{'id': 'abc'}) diff --git a/test/git/test_commit.py b/test/git/test_commit.py index 3e37a7a4..c36d0c72 100644 --- a/test/git/test_commit.py +++ b/test/git/test_commit.py @@ -37,18 +37,19 @@ class TestCommit(object): assert_equal(15, len(diffs)) - assert_equal('.gitignore', diffs[0].a_path) - assert_equal('.gitignore', diffs[0].b_path) - assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_commit.id) - assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_commit.id) - assert_equal('100644', diffs[0].b_mode) + assert_equal('.gitignore', diffs[0].a_blob.name) + assert_equal('.gitignore', diffs[0].b_blob.name) + assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_blob.id) + assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_blob.id) + assert_equal('100644', diffs[0].b_blob.mode) assert_equal(False, diffs[0].new_file) assert_equal(False, diffs[0].deleted_file) assert_equal("--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1,2 @@\n coverage\n+pkg", diffs[0].diff) - assert_equal('lib/grit/actor.rb', diffs[5].a_path) - assert_equal(None, diffs[5].a_commit) - assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_commit.id) + assert_equal('lib/grit/actor.rb', diffs[5].b_blob.name) + assert_equal(None, diffs[5].a_blob) + assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_blob.id) + assert_equal( None, diffs[5].a_mode ) assert_equal(True, diffs[5].new_file) assert_true(git.called) @@ -88,7 +89,7 @@ class TestCommit(object): diffs = Commit.diff(self.repo, '59ddc32', ['lib']) assert_equal(1, len(diffs)) - assert_equal('lib/grit/diff.rb', diffs[0].a_path) + assert_equal('lib/grit/diff.rb', diffs[0].a_blob.name) assert_true(git.called) assert_equal(git.call_args, (('diff', '-M', '59ddc32', '--', 'lib'), {'full_index': True})) @@ -100,7 +101,7 @@ class TestCommit(object): diffs = Commit.diff(self.repo, '59ddc32', '13d27d5', ['lib']) assert_equal(1, len(diffs)) - assert_equal('lib/grit/commit.rb', diffs[0].a_path) + assert_equal('lib/grit/commit.rb', diffs[0].a_blob.name) assert_true(git.called) assert_equal(git.call_args, (('diff', '-M', '59ddc32', '13d27d5', '--', 'lib'), {'full_index': True})) @@ -114,18 +115,18 @@ class TestCommit(object): assert_equal(15, len(diffs)) - assert_equal('.gitignore', diffs[0].a_path) - assert_equal('.gitignore', diffs[0].b_path) - assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_commit.id) - assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_commit.id) - assert_equal('100644', diffs[0].b_mode) + assert_equal('.gitignore', diffs[0].a_blob.name) + assert_equal('.gitignore', diffs[0].b_blob.name) + assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_blob.id) + assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_blob.id) + assert_equal('100644', diffs[0].b_blob.mode) assert_equal(False, diffs[0].new_file) assert_equal(False, diffs[0].deleted_file) assert_equal("--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1,2 @@\n coverage\n+pkg", diffs[0].diff) - assert_equal('lib/grit/actor.rb', diffs[5].a_path) - assert_equal(None, diffs[5].a_commit) - assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_commit.id) + assert_equal('lib/grit/actor.rb', diffs[5].b_blob.name) + assert_equal(None, diffs[5].a_blob) + assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_blob.id) assert_equal(True, diffs[5].new_file) assert_true(git.called) @@ -144,18 +145,17 @@ class TestCommit(object): assert_equal(10, len(diffs)) - assert_equal('History.txt', diffs[0].a_path) - assert_equal('History.txt', diffs[0].b_path) - assert_equal(None, diffs[0].a_commit) - assert_equal('100644', diffs[0].b_mode) - assert_equal('81d2c27608b352814cbe979a6acd678d30219678', diffs[0].b_commit.id) + assert_equal('History.txt', diffs[0].b_blob.name) + assert_equal(None, diffs[0].a_blob) + assert_equal('100644', diffs[0].b_blob.mode) + assert_equal('81d2c27608b352814cbe979a6acd678d30219678', diffs[0].b_blob.id) assert_equal(True, diffs[0].new_file) assert_equal(False, diffs[0].deleted_file) assert_equal("--- /dev/null\n+++ b/History.txt\n@@ -0,0 +1,5 @@\n+== 1.0.0 / 2007-10-09\n+\n+* 1 major enhancement\n+ * Birthday!\n+", diffs[0].diff) - assert_equal('lib/grit.rb', diffs[5].a_path) - assert_equal(None, diffs[5].a_commit) - assert_equal('32cec87d1e78946a827ddf6a8776be4d81dcf1d1', diffs[5].b_commit.id) + assert_equal('lib/grit.rb', diffs[5].b_blob.name) + assert_equal(None, diffs[5].a_blob) + assert_equal('32cec87d1e78946a827ddf6a8776be4d81dcf1d1', diffs[5].b_blob.id) assert_equal(True, diffs[5].new_file) assert_true(git.called) @@ -181,7 +181,10 @@ class TestCommit(object): commit.__bake_it__() diffs = commit.diffs + # in case of mode-only changes, there is no blob assert_equal(23, len(diffs)) + assert_equal(None, diffs[0].a_blob) + assert_equal(None, diffs[0].b_blob) assert_equal('100644', diffs[0].a_mode) assert_equal('100755', diffs[0].b_mode) diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 6b82d029..82f27001 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -185,7 +185,7 @@ class TestRepo(object): assert_equal(git.call_args, (('diff', 'master^', 'master', '--', 'foo/bar', 'foo/baz'), {})) @patch_object(Git, '_call_process') - def test_diff(self, git): + def test_diff_with_parents(self, git): git.return_value = fixture('diff_p') diffs = self.repo.commit_diff('master') @@ -193,10 +193,10 @@ class TestRepo(object): assert_true(git.called) def test_archive_tar(self): - self.repo.archive_tar + self.repo.archive_tar() def test_archive_tar_gz(self): - self.repo.archive_tar_gz + self.repo.archive_tar_gz() @patch('git.utils.touch') def test_enable_daemon_serve(self, touch): @@ -207,19 +207,6 @@ class TestRepo(object): self.repo.daemon_serve = True assert_true(self.repo.daemon_serve) - # @patch_object(os.path, 'exists') - # @patch_object('__builtin__', 'open') - # def test_alternates_with_two_alternates(self, exists, read): - # # File.expects(:exist?).with("#{absolute_project_path}/.git/objects/info/alternates").returns(true) - # # File.expects(:read).returns("/path/to/repo1/.git/objects\n/path/to/repo2.git/objects\n") - # exists.return_value = True - # read.return_value = ("/path/to/repo1/.git/objects\n/path/to/repo2.git/objects\n") - # - # assert_equal(["/path/to/repo1/.git/objects", "/path/to/repo2.git/objects"], self.repo.alternates) - # - # assert_true(exists.called) - # assert_true(read.called) - # @patch_object(os.path, 'exists') def test_alternates_no_file(self, os): os.return_value = False @@ -227,32 +214,6 @@ class TestRepo(object): assert_true(os.called) - # @patch_object(os.path, 'exists') - # def test_alternates_setter_ok(self, os): - # os.return_value = True - # alts = ['/path/to/repo.git/objects', '/path/to/repo2.git/objects'] - # - # # File.any_instance.expects(:write).with(alts.join("\n")) - # - # self.repo.alternates = alts - # - # assert_true(os.called) - # # assert_equal(os.call_args, ((alts,), {})) - # # for alt in alts: - # - # @patch_object(os.path, 'exists') - # @raises(NoSuchPathError) - # def test_alternates_setter_bad(self, os): - # os.return_value = False - # - # alts = ['/path/to/repo.git/objects'] - # # File.any_instance.expects(:write).never - # self.repo.alternates = alts - # - # for alt in alts: - # assert_true(os.called) - # assert_equal(os.call_args, (alt, {})) - @patch_object(os, 'remove') def test_alternates_setter_empty(self, os): self.repo.alternates = [] @@ -278,33 +239,6 @@ class TestRepo(object): assert_true(git.called) assert_equal(git.call_args, (('log', 'master', '--', 'file.rb'), {'pretty': 'raw', 'max_count': 1})) - # @patch_object(Git, '_call_process') - # @patch_object(Git, '_call_process') - # def test_commit_deltas_from_nothing_new(self, gitb, gita): - # gitb.return_value = fixture("rev_list_delta_b") - # gita.return_value = fixture("rev_list_delta_a") - # other_repo = Repo(GIT_REPO) - # # self.repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_b")) - # # other_repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_a")) - # - # delta_commits = self.repo.commit_deltas_from(other_repo) - # assert_equal(0, len(delta_commits)) - # assert_true(gitb.called) - # assert_equal(gitb.call_args, (('rev_list', 'master'), {})) - # assert_true(gita.called) - # assert_equal(gita.call_args, (('rev_list', 'master'), {})) - # - # def test_commit_deltas_from_when_other_has_new(self): - # other_repo = Repo(GIT_REPO) - # # self.repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_a")) - # # other_repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_b")) - # # for ref in ['4c8124ffcf4039d292442eeccabdeca5af5c5017', - # # '634396b2f541a9f2d58b00be1a07f0c358b999b3', - # # 'ab25fd8483882c3bda8a458ad2965d2248654335']: - # # Commit.expects(:find_all).with(other_repo, ref, :max_count => 1).returns([stub()]) - # delta_commits = self.repo.commit_deltas_from(other_repo) - # assert_equal(3, len(delta_commits)) - def test_is_dirty_with_bare_repository(self): self.repo.bare = True assert_false(self.repo.is_dirty) |