From 233e3ffe0ef35dbabe49340ba567499690dcc166 Mon Sep 17 00:00:00 2001 From: Michael Trier Date: Fri, 30 May 2008 21:01:44 -0400 Subject: renamed git_python to git. Removed pop_key and replaced with dict.pop. Fixed up tests so they pass except for stderr test. Modified version information retrieval. --- lib/git/commit.py | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 lib/git/commit.py (limited to 'lib/git/commit.py') diff --git a/lib/git/commit.py b/lib/git/commit.py new file mode 100644 index 00000000..701f6c04 --- /dev/null +++ b/lib/git/commit.py @@ -0,0 +1,235 @@ +import re +import time + +from actor import Actor +from lazy import LazyMixin +import tree +import diff +import stats + +class Commit(LazyMixin): + def __init__(self, repo, **kwargs): + """ + Instantiate a new Commit + + ``id`` + is the id of the commit + + ``parents`` + is a list of commit ids (will be converted into Commit instances) + + ``tree`` + is the correspdonding tree id (will be converted into a Tree object) + + ``author`` + is the author string + + ``authored_date`` + is the authored DateTime + + ``committer`` + is the committer string + + ``committed_date`` + is the committed DateTime + + ``message`` + is the first line of the commit message + + Returns + GitPython.Commit + """ + LazyMixin.__init__(self) + + self.repo = repo + self.id = None + self.tree = None + self.author = None + self.authored_date = None + self.committer = None + self.committed_date = None + self.message = None + self.parents = None + + for k, v in kwargs.items(): + setattr(self, k, v) + + if self.id: + if 'parents' in kwargs: + self.parents = map(lambda p: Commit(repo, **{'id': p}), kwargs['parents']) + if 'tree' in kwargs: + self.tree = tree.Tree(repo, **{'id': kwargs['tree']}) + + def __bake__(self): + temp = Commit.find_all(self.repo, self.id, **{'max_count': 1})[0] + self.parents = temp.parents + self.tree = temp.tree + self.author = temp.author + self.authored_date = temp.authored_date + self.committer = temp.committer + self.committed_date = temp.committed_date + self.message = temp.message + + @property + def id_abbrev(self): + return self.id[0:7] + + @classmethod + def count(cls, repo, ref): + """ + Count the number of commits reachable from this ref + + ``repo`` + is the Repo + + ``ref`` + is the ref from which to begin (SHA1 or name) + + Returns + int + """ + return len(repo.git.rev_list(ref).strip().splitlines()) + + @classmethod + def find_all(cls, repo, ref, **kwargs): + """ + Find all commits matching the given criteria. + ``repo`` + is the Repo + + ``ref`` + is the ref from which to begin (SHA1 or name) + + ``options`` + is a Hash of optional arguments to git where + ``max_count`` is the maximum number of commits to fetch + ``skip`` is the number of commits to skip + + Returns + GitPython.Commit[] + """ + options = {'pretty': 'raw'} + options.update(kwargs) + + output = repo.git.rev_list(ref, **options) + return cls.list_from_string(repo, output) + + @classmethod + def list_from_string(cls, repo, text): + """ + Parse out commit information into a list of Commit objects + + ``repo`` + is the Repo + + ``text`` + is the text output from the git command (raw format) + + Returns + GitPython.Commit[] + """ + lines = [l for l in text.splitlines() if l.strip()] + + commits = [] + + while lines: + id = lines.pop(0).split()[-1] + tree = lines.pop(0).split()[-1] + + parents = [] + while lines and re.search(r'^parent', lines[0]): + parents.append(lines.pop(0).split()[-1]) + author, authored_date = cls.actor(lines.pop(0)) + committer, committed_date = cls.actor(lines.pop(0)) + + messages = [] + while lines and re.search(r'^ {4}', lines[0]): + messages.append(lines.pop(0).strip()) + + message = messages and messages[0] or '' + + commits.append(Commit(repo, id=id, parents=parents, tree=tree, author=author, authored_date=authored_date, + committer=committer, committed_date=committed_date, message=message)) + + return commits + + @classmethod + def diff(cls, repo, a, b = None, paths = []): + """ + Show diffs between two trees: + + ``repo`` + is the Repo + + ``a`` + is a named commit + + ``b`` + is an optional named commit. Passing a list assumes you + wish to omit the second named commit and limit the diff to the + given paths. + + ``paths`` + is a list of paths to limit the diff. + + Returns + GitPython.Diff[] + """ + if isinstance(b, list): + paths = b + b = None + + if paths: + paths.insert(0, "--") + + if b: + paths.insert(0, b) + paths.insert(0, a) + text = repo.git.diff(*paths, **{'full_index': True}) + return diff.Diff.list_from_string(repo, text) + + @property + def diffs(self): + if not self.parents: + d = self.repo.git.show(self.id, **{'full_index': True, 'pretty': 'raw'}) + if re.search(r'diff --git a', d): + if not re.search(r'^diff --git a', d): + p = re.compile(r'.+?(diff --git a)', re.MULTILINE | re.DOTALL) + d = p.sub(r'diff --git a', d, 1) + else: + d = '' + return diff.Diff.list_from_string(self.repo, d) + else: + return self.diff(self.repo, self.parents[0].id, self.id) + + @property + def stats(self): + if not self.parents: + text = self.repo.git.diff(self.id, **{'numstat': True}) + text2 = "" + for line in text.splitlines(): + (insertions, deletions, filename) = line.split("\t") + text2 += "%s\t%s\t%s\n" % (deletions, insertions, filename) + text = text2 + else: + text = self.repo.git.diff(self.parents[0].id, self.id, **{'numstat': True}) + return stats.Stats.list_from_string(self.repo, text) + + def __str__(self): + """ Convert commit to string which is SHA1 """ + return self.id + + def __repr__(self): + return '' % self.id + + @classmethod + def actor(cls, line): + """ + Parse out the actor (author or committer) info + + Returns + [str (actor name and email), time (acted at time)] + """ + m = re.search(r'^.+? (.*) (\d+) .*$', line) + actor, epoch = m.groups() + return [Actor.from_string(actor), time.gmtime(int(epoch))] -- cgit v1.2.1