blob: 9e2b5d1a7f914d0f7196f07befa9ca9dffa93b04 [file] [log] [blame]
# Copyright (C) 2007-2012 International Business Machines Corporation and Others. All Rights Reserved.
# Review module.
# TODO: refactor ticket manipulation items into ticketmgr.
import re
from trac.core import Component, implements
from trac.core import ComponentManager
from trac.core import TracError
from trac.util import Markup
from trac.web import IRequestHandler
from trac.web.chrome import add_stylesheet, add_script, ITemplateProvider, add_ctxtnav
from trac.versioncontrol import Changeset
from trac.web.api import IRequestFilter
from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider
from genshi.builder import tag
#from trac.env import IEnvironmentSetupParticipant
from trac.perm import IPermissionRequestor
from trac.config import ListOption
from icucodetools.ticketmgr import TicketManager
from pkg_resources import resource_filename #@UnresolvedImport
class ReviewModule(Component):
implements(ITemplateProvider, IRequestFilter, IRequestHandler, IPermissionRequestor)
# path to match for review
path_match = re.compile(r'/icureview/([0-9]+)')
voteable_paths = ListOption('icucodetools', 'paths', '/ticket*',
doc='List of URL paths to show reviews on. Globs are supported.')
# search for earliest match, and how many segments to include following
# trunk
# branches/maint/maint-4-8
# tags/release-2-0
branchList = [['trunk',0],['branches',2],['tags',1]]
# IPermissionRequestor methods
def get_permission_actions(self):
return ['ICUREVIEW_VIEW']
# ITemplateProvider methods
def get_templates_dirs(self):
try:
return [resource_filename(__name__, 'templates')]
except Exception, e:
self.log.warning('Could not get template dir: %s: %s' %
(type(e), e))
return ""
def get_htdocs_dirs(self):
return [('icucodetools', resource_filename(__name__, 'htdocs'))]
# IRequestFilter methods
def pre_process_request(self, req, handler):
if 'ICUREVIEW_VIEW' not in req.perm:
return handler
if self.match_ticketpage(req):
self.render_reviewlink(req)
return handler
def post_process_request(self, req, template, data, content_type):
return (template, data, content_type)
def render_reviewlink(self, req):
#add_stylesheet(req, 'icucodetools/css/icuxtn.css')
els = []
ticket_mgr = TicketManager(self.compmgr)
db = self.env.get_db_cnx()
repos = self.env.get_repository()
if not repos:
raise TracError("Could not get repository for %s" % (req.authname))
revs = ticket_mgr.tkt2revs(self.log, db, repos, req, req.args['ticket'])
if not revs:
str = 'No commits.'
li = tag.li(str)
els.append(li)
else:
str = ' %d commits.' % len(revs)
href = req.href.review(req.args['ticket'])
a = tag.a('Review', href=href)
li = tag.li(a + str)
els.append(li)
ul = tag.ul(els, class_='review')
className = ''
title = "Reviews"
add_ctxtnav(req, tag.span(tag.object(ul), id='icureview', title=title, class_=className))
def match_request(self, req):
match = re.match('/review(?:/([^/]+))?(?:/([^/]+))?(?:/(.*)$)?', req.path_info)
if match:
req.args['ticket'] = match.group(1)
return True
def match_ticketpage(self, req):
match = re.match('/ticket(?:/([^/]+))?(?:/([^/]+))?(?:/(.*)$)?', req.path_info)
if match:
req.args['ticket'] = match.group(1)
return True
def pathToBranchName(self, path):
#return '/'.join(path.split('/')[0:2])
windex = None
win = None
for branch in self.branchList:
if(path == branch[0]): # catch changes to just 'trunk'
idx = 0
else:
idx = path.find(branch[0]+'/')
if(idx > -1 and (windex == None or windex > idx)):
windex = idx
win = branch
if windex == None:
segments = path.split('/')
return '/'.join(segments[0:2])
else:
#print "found %s foll %s @ %d" % (win[0],win[1],windex)
segments = path[windex:].split('/')
return path[:windex] + ('/'.join(segments[0:win[1]+1])) # use specified # of following segments
def changeToRange(self, c_new, change):
# q: (u'trunk/Locale.java', 'file', 'add', None, u'-1') from r3
# q: (u'trunk/util.c', 'file', 'edit', u'trunk/util.c', u'2') from r4
# c_path = change[0]
# c_itemtype = change[1]
c_type = change[2]
c_oldpath = change[3]
c_old = int(change[4] or -1)
if(c_type in (Changeset.COPY,Changeset.MOVE)):
return (-1, c_new, c_type, c_old, c_oldpath) # ignore OLD rev for these
elif(c_type in (Changeset.DELETE)):
return (c_old, -1, c_type)
else:
return (c_old, c_new, c_type)
def describeChange(self, file, change, req, db):
what = change[2] or 'change'
where = 'r%d:%d' % (change[0],change[1])
if(change[0] == -1):
if(change[1] == -1):
url = None
what = "noop"
where = None
else:
#if change[2] == 'add+commits':
url = req.href.browser(file, rev=change[1]) # 'add'
where = 'r%d' % change[1]
what = change[2]
elif(change[1] == -1):
url = None # deleted
what = "deleted"
where = None
else:
url = req.href.changeset(old_path=file, old=change[0], new_path=file, new=change[1])
if url:
what = Markup('<a href="%s">%s</a>' % (url,what))
if where:
return (what, tag.a(where, href=req.href.search(q=where)))
#return (what, where)
else:
return (what, '')
def process_request(self, req):
#ok, what are we about.
#db = self.env.get_db_cnx()
#ticketlist = {} # dict of ticket->???
#revlist = {} # dict of revision->
repos = self.env.get_repository()
new_path = req.args.get('new_path')
new_rev = req.args.get('new')
old_path = req.args.get('old_path')
old_rev = req.args.get('old')
new_path = repos.normalize_path(new_path)
new_rev = repos.normalize_rev(new_rev)
old_path = repos.normalize_path(old_path)
old_rev = repos.normalize_rev(old_rev)
# if not req.perm.has_permission('TICKET_MODIFY'):
# return req.redirect(req.href.browser())
old_rev = int(old_rev)
new_rev = int(new_rev)
ticket = req.args.get('ticket')
try:
ticket = int(ticket)
except Exception:
ticket = 0
# req.hdf['review.ticket'] = ticket
# req.hdf['review.tickethtml'] = tag.a(ticket, req.href.ticket(ticket))
data = {}
data['overall_y'] = 0
data['ticket_id'] = req.args['ticket']
data['ticket_href'] = req.href.ticket(req.args['ticket'])
ticket_mgr = TicketManager(self.compmgr)
db = self.env.get_db_cnx()
repos = self.env.get_repository()
revs = ticket_mgr.tkt2revs(self.log, db, repos, req, req.args['ticket'])
if (not revs or len(revs)==0):
# nothing to review. shouldn't happen
return ('nothing.html', data, 'text/html')
elif(len(revs)==1):
# only one change - just do a changeset view
return req.redirect(req.href.changeset(revs[0]))
revcount = 0
branches = {}
files = {}
# may be 0 revs.
revisions = []
for rev in revs:
chgset = repos.get_changeset(rev)
# q: (u'trunk/Locale.java', 'file', 'add', None, u'-1') from r3
# q: (u'trunk/util.c', 'file', 'edit', u'trunk/util.c', u'2') from r4
message = chgset.message or '--'
revcount = revcount + 1
revision = {}
revision['rev'] = tag.a(rev, req.href.changeset(rev))
revision['num'] = rev
revision['comment'] = message #wiki_to_oneliner( message, self.env, db, shorten=False )
rbranches = revision['branches'] = []
for chg in chgset.get_changes():
path = chg[0]
if path in files:
item = files[path]
else:
item = []
files[path] = item;
item.append(self.changeToRange(rev,chg))
branch_name = self.pathToBranchName(path)
if branch_name not in rbranches:
rbranches.append(branch_name)
revisions.append(revision)
data['revisions'] = revisions
if(revcount > 0):
data['revcount'] = revcount
# print "files: %d" % len(files)
# go throuhg each file and calculate its minimum range
filelist = files.keys()
filelist.sort()
# print 'bar to %d len of %s' % (len(filelist),str(filelist))
for file in filelist:
changes = files[file]
i = 0
# print " looping from %d to %d over %d " % (i,len(changes)-1,len(changes))
while len(changes)>1 and i<(len(changes)-1):
if changes[i][1] == changes[i+1][0]:
if changes[i][0] == -1:
changes[i+1] = (changes[i][0],changes[i+1][1],'add+commits') # retain 'first' rev
else:
changes[i+1] = (changes[i][0],changes[i+1][1],'multiple commits') # retain 'first' rev
changes = changes[:i] + changes[i+1:] # and shift down
# print "merged: %s" % str(changes)
files[file] = changes
else:
i = i + 1
# now, write 'em out
sera = 0
#files_data = []
for file in filelist:
sera = sera+1
file_data = {}
file_data['name'] = Markup('<a href="%s">%s</a>' % (req.href.browser(file),file))
branch_name = self.pathToBranchName(file)
#print "branch is: (%s)" % (branch_name)
branches_data = branches.get(branch_name, {})
files_data = branches_data.get('files',[])
changes = files[file]
cha = 0
changes_data = []
for change in changes:
cha = cha + 1
# print "%s output %s " % (file, str(change))
changes_data.append(self.describeChange(file, change, req, db))
file_data['changes'] = changes_data
if(len(changes)>1):
whathtml = self.describeChange(file, (changes[0][0], changes[len(changes)-1][1], 'overall'), req, db)
file_data['overall'] = whathtml
file_data['overall_y'] = 1
data['overall_y'] = 1
else:
file_data['overall_y'] = 0
files_data.append(file_data)
# sets
branches_data['files'] = files_data
branches_data['len'] = len(files_data)
branches_data['name'] = branch_name
branches[branch_name] = branches_data
# .. convert dict to array.
branch_list = []
for branch in branches:
branch_list.append(branches[branch])
data['branches'] = branch_list
data['lastbranch'] = branch
data['branchcount'] = len(branches)
content_type = "text/html"
add_stylesheet(req, 'icucodetools/css/icuxtn.css')
add_script(req, 'icucodetools/js/review.js')
return 'review.html', data, content_type