From 702d5cde20ab05815fdf663fe1ea43a206aae69f Mon Sep 17 00:00:00 2001 From: Stephen Bash Date: Tue, 19 Oct 2010 16:09:58 -0400 Subject: [PATCH 1/2] Implement ITimelineEventProvider to show git tags on timeline. Adds the ITimlineEventProvider methods to GitConnector. Currently queries all Git repos for tags (could potentially limit to a subset of repos). Should handle varying timezones correctly (not tested). Creates timeline entries like "TagName created in RepoName by User" with the tag message on the next line. The timeline entry should be a link to the object pointed to by the Git tag. --- tracext/git/git_fs.py | 67 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/tracext/git/git_fs.py b/tracext/git/git_fs.py index 0d3088b..b223770 100644 --- a/tracext/git/git_fs.py +++ b/tracext/git/git_fs.py @@ -14,7 +14,7 @@ from trac.core import * from trac.util import TracError, shorten_line -from trac.util.datefmt import FixedOffset, to_timestamp, format_datetime +from trac.util.datefmt import FixedOffset, to_timestamp, format_datetime, parse_date from trac.util.text import to_unicode from trac.versioncontrol.api import \ Changeset, Node, Repository, IRepositoryConnector, NoSuchChangeset, NoSuchNode @@ -24,6 +24,9 @@ from trac.config import BoolOption, IntOption, PathOption, Option from trac.web.chrome import Chrome +from trac.versioncontrol.api import RepositoryManager +from trac.timeline.api import ITimelineEventProvider + from genshi.builder import tag from datetime import datetime @@ -107,7 +110,7 @@ def _parse_user_time(s): return user, time class GitConnector(Component): - implements(IRepositoryConnector, IWikiSyntaxProvider) + implements(IRepositoryConnector, IWikiSyntaxProvider, ITimelineEventProvider) def __init__(self): self._version = None @@ -124,6 +127,66 @@ def __init__(self): self.log.error("GIT version %s installed not compatible (need >= %s)" % (self._version['v_str'], self._version['v_min_str'])) + ######################### + # ITimelineEventProvider + + def get_timeline_filters(self, req): + # (internal name, human-readable name, on-by-default) + return [('git-tags', 'Git Tags', True)] + + def get_timeline_events(self, req, start, stop, filters): + # if git-tags is enabled, get all the git repositories, then + # get the tags from each repository and compare their timestamp + # to the requested time range. If they are between start and + # stop, return them using Trac's event tuple + tags = [] + if 'git-tags' in filters: + rm = RepositoryManager(self.env) + all_repositories = rm.get_all_repositories() + for reponame in all_repositories: + repoinfo = all_repositories[reponame] + if 'type' in repoinfo and repoinfo['type'] == 'git': + repo = rm.get_repository(reponame) + for_each_args = [ '--sort=-taggerdate', + '--format=%(refname:short)|+%(*objectname)|+' + + '%(taggername) %(taggeremail)|+%(taggerdate:iso8601)|+' + + '%(subject)', + 'refs/tags' ] + if self._cached_repository: + tag_info = repo.repos.git.repo.for_each_ref(*for_each_args) + else: + tag_info = repo.git.repo.for_each_ref(*for_each_args) + for line in tag_info.splitlines(): + (tag_name,tag_deref,user,tag_time_str,tag_msg) = line.split('|+') + + # Parse time using Trac's parse_time (requires T and Z unfortunately) + date_str, time_str, tz_str = tag_time_str.rsplit(None, 2) + tag_time = parse_date(date_str+'T'+time_str+'Z'+tz_str) + + if tag_time > start and tag_time < stop: + self.log.debug( 'found tag %s in repo %s'%(tag_name,repoinfo['name']) ) + # (internal-name,date,author,opaque-data) + tags.append(('git-tags',tag_time,user, + { 'name':tag_name, + 'repo':repoinfo['name'], + 'deref':tag_deref, + 'msg':tag_msg })) + elif tag_time < start: + # git returns tags in reverse chronological order, so if the current + # tag is before the start, all the remaining will also be + break + return tags + + def render_timeline_event(self, context, field, event): + # Timeline will create "title by user
description" + if field == 'title': + title = 'Tag %s created in %s'%(event[3]['name'], event[3]['repo']) + return title + elif field == 'description': + return event[3]['msg'] + elif field == 'url': + return context.href.changeset(event[3]['deref'], event[3]['repo'] or None) + ####################### # IWikiSyntaxProvider From 593c64cb0a96378d6d51e5e22fddf1a791fed297 Mon Sep 17 00:00:00 2001 From: Stephen Bash Date: Thu, 21 Oct 2010 12:29:49 -0400 Subject: [PATCH 2/2] Correctly handle lightweight tags Lightweight tags do not have a date or tagger associated with them. So if the date string is empty, just continue to the next tag. --- tracext/git/git_fs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tracext/git/git_fs.py b/tracext/git/git_fs.py index b223770..2a78d7f 100644 --- a/tracext/git/git_fs.py +++ b/tracext/git/git_fs.py @@ -158,6 +158,8 @@ def get_timeline_events(self, req, start, stop, filters): tag_info = repo.git.repo.for_each_ref(*for_each_args) for line in tag_info.splitlines(): (tag_name,tag_deref,user,tag_time_str,tag_msg) = line.split('|+') + if not tag_time_str: + continue # probably a lightweight tag # Parse time using Trac's parse_time (requires T and Z unfortunately) date_str, time_str, tz_str = tag_time_str.rsplit(None, 2)