From 99aa6ddda3f4df40597f3a1a3fc353830eaf37e4 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 11 Jun 2014 17:13:21 +1200 Subject: [PATCH] Support comments in commits Gerrit represents changes to commits with a file change of /COMMIT_MSG and magically offset file comments :/. In this patch I reproduce that logic sufficiently well to match up on all the commits I have looked at. Since diffs vs the base should not show the old commit's commit message, I have hinted to the diff function when to include the commit message in the output. Change-Id: I8adb9fa22b384cace88f114f770a3eb5d3a89f5c --- gertty/gitrepo.py | 85 +++++++++++++++++++++++++++++++++++++++++++-- gertty/view/diff.py | 5 ++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/gertty/gitrepo.py b/gertty/gitrepo.py index 53082d2..89d1ad1 100644 --- a/gertty/gitrepo.py +++ b/gertty/gitrepo.py @@ -12,8 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import logging import difflib +import itertools import os import re @@ -27,6 +29,74 @@ END = 1 LINENO = 0 LINE = 1 +class GitTimeZone(datetime.tzinfo): + """Because we can't have nice things.""" + + def __init__(self, offset_seconds): + self._offset = offset_seconds + + def utcoffset(self, dt): + return datetime.timedelta(seconds=self._offset) + + def dst(self, dt): + return datetime.timedelta(0) + + def tzname(self, dt): + return None + + +class CommitContext(object): + """A git.diff.Diff for commit messages.""" + + def decorateGitTime(self, seconds, tz): + dt = datetime.datetime.fromtimestamp(seconds, GitTimeZone(-tz)) + return dt.strftime('%Y-%m-%d %H:%M:%S %Z%z') + + def decorateMessage(self, commit): + """Put the Gerrit commit metadata at the front of the message. + + e.g.: + Parent: cc8a51ca (Initial commit) 1 + Author: Robert Collins 2 + AuthorDate: 2014-05-27 14:05:47 +1200 3 + Commit: Robert Collins 4 + CommitDate: 2014-05-27 14:07:57 +1200 5 + 6 + """ + # NB: If folk report that commits have comments at the wrong place + # Then this function, which reproduces gerrit behaviour, will need + # to be fixed (e.g. by making the behaviour match more closely. + if not commit: + return [] + if commit.parents: + parentsha = commit.parents[0].hexsha[:8] + else: + parentsha = None + author = commit.author + committer = commit.committer + author_date = self.decorateGitTime( + commit.authored_date, commit.author_tz_offset) + commit_date = self.decorateGitTime( + commit.committed_date, commit.committer_tz_offset) + return ["Parent: %s\n" % parentsha, + "Author: %s <%s>\n" % (author.name, author.email), + "AuthorDate: %s\n" % author_date, + "Commit: %s <%s>\n" % (committer.name, committer.email), + "CommitDate: %s\n" % commit_date, + "\n"] + commit.message.splitlines(True) + + def __init__(self, old, new): + """Create a CommitContext. + + :param old: A git.objects.commit object or None. + :param new: A git.objects.commit object. + """ + self.rename_from = self.rename_to = None + self.diff = ''.join(difflib.unified_diff( + self.decorateMessage(old), self.decorateMessage(new), + fromfile="/a/COMMIT_MSG", tofile="/b/COMMIT_MSG")) + + class DiffChunk(object): def __init__(self): self.oldlines = [] @@ -258,13 +328,24 @@ class Repo(object): return output_old, output_new header_re = re.compile('@@ -(\d+)(,\d+)? \+(\d+)(,\d+)? @@') - def diff(self, old, new, context=10000): + def diff(self, old, new, context=10000, show_old_commit=False): + """Create a diff from old to new. + + Note that the commit message is also diffed, and listed as /COMMIT_MSG. + """ repo = git.Repo(self.path) #'-y', '-x', 'diff -C10', old, new, path).split('\n'): oldc = repo.commit(old) newc = repo.commit(new) files = [] - for diff_context in oldc.diff(newc, create_patch=True, U=context): + extra_contexts = [] + if show_old_commit: + extra_contexts.append(CommitContext(oldc, newc)) + else: + extra_contexts.append(CommitContext(None, newc)) + contexts = itertools.chain( + extra_contexts, oldc.diff(newc, create_patch=True, U=context)) + for diff_context in contexts: # Each iteration of this is a file f = DiffFile() files.append(f) diff --git a/gertty/view/diff.py b/gertty/view/diff.py index c7824e8..c4a8031 100644 --- a/gertty/view/diff.py +++ b/gertty/view/diff.py @@ -235,12 +235,14 @@ This Screen old_str = 'patchset %s' % self.old_revision_num self.base_commit = old_revision.commit old_comments = old_revision.comments + show_old_commit = True else: old_revision = None self.old_revision_num = None old_str = 'base' self.base_commit = new_revision.parent old_comments = [] + show_old_commit = False self.title = u'Diff of %s change %s from %s to patchset %s' % ( new_revision.change.project.name, new_revision.change.number, @@ -291,7 +293,8 @@ This Screen lines = [] # The initial set of lines to display self.file_diffs = [{}, {}] # Mapping of fn -> DiffFile object (old, new) # this is a list of files: - for i, diff in enumerate(repo.diff(self.base_commit, self.commit)): + for i, diff in enumerate(repo.diff(self.base_commit, self.commit, + show_old_commit=show_old_commit)): if i > 0: lines.append(urwid.Text('')) self.file_diffs[gitrepo.OLD][diff.oldname] = diff