def parse_sources(props): sources = {} for line in props[name].splitlines(): path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is not None: inheritable, non_inheritable = _partition_inheritable(revs) sources[spath] = (set(Ranges(inheritable)), set(Ranges(non_inheritable))) return sources
def parse_sources(props): sources = {} value = props[name] lines = value.splitlines() if name == 'svn:mergeinfo' \ else value.split() for line in lines: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is not None: inheritable, non_inheritable = _partition_inheritable(revs) sources[spath] = (set(Ranges(inheritable)), set(Ranges(non_inheritable))) return sources
def _normalize_ranges(self, repos, path, revs): ranges = revs.replace(':', '-') try: # fast path; only numbers return Ranges(ranges, reorder=True) except ValueError: # slow path, normalize each rev splitted_ranges = re.split(r'([-,])', ranges) try: revs = [repos.normalize_rev(r) for r in splitted_ranges[::2]] except NoSuchChangeset: return None seps = splitted_ranges[1::2] + [''] ranges = ''.join([str(rev) + sep for rev, sep in zip(revs, seps)]) return Ranges(ranges)
def _tkt_id_conditions(column, tkt_ids): ranges = Ranges() ranges.appendrange(','.join(map(str, sorted(tkt_ids)))) condition = [] tkt_ids = [] for a, b in ranges.pairs: if a == b: tkt_ids.append(a) elif a + 1 == b: tkt_ids.extend((a, b)) else: condition.append('%s BETWEEN %d AND %d' % (column, a, b)) if tkt_ids: condition.append('%s IN (%s)' % (column, ','.join(map(str, tkt_ids)))) return ' OR '.join(condition)
def _reduce(self): if all( isinstance(pair[0], (int, long)) and isinstance(pair[1], (int, long)) for pair in self.pairs): try: ranges = Ranges(unicode(self), reorder=True) except: pass else: self.pairs[:] = ranges.pairs else: seen = set() pairs = self.pairs[:] for idx, pair in enumerate(pairs): if pair in seen: pairs[idx] = None else: seen.add(pair) if len(pairs) != len(seen): self.pairs[:] = filter(None, pairs) if self.pairs: self.a = self.pairs[0][0] self.b = self.pairs[-1][1] else: self.a = self.b = None
def get_annotation_data(self, context): try: marks = Ranges(context.get_hint('marks')) except ValueError: marks = None return { 'id': context.get_hint('id', '') + 'L%s', 'marks': marks, 'offset': context.get_hint('lineno', 1) - 1 }
def _normalize_ranges(self, repos, path, revs): try: # fast path; only numbers return Ranges(revs.replace(':', '-'), reorder=True) except ValueError: # slow path, normalize each rev ranges = [] for range in revs.split(','): try: a, b = range.replace(':', '-').split('-') range = (a, b) except ValueError: range = (range, ) ranges.append('-'.join( str(repos.normalize_rev(r)) for r in range)) ranges = ','.join(ranges) try: return Ranges(ranges) except ValueError: return None
def get_deco(self, formatter, ns, target, label, fullmatch=None): link, params, fragment = formatter.split_link(target) # @UnusedVariable r = Ranges(link) if len(r) == 1: num = r.a ticket = formatter.resource('ticket', num) from trac.ticket.model import Ticket if Ticket.id_is_valid(num) and \ 'TICKET_VIEW' in formatter.perm(ticket): ticket = Ticket(self.env, num) fields = self.config.getlist('ticket', 'decorate_fields') return ['%s_is_%s' % (field, ticket.values[field]) for field in fields if field in ticket.values]
def _format_link(self, formatter, ns, target, label, fullmatch=None): intertrac = formatter.shorthand_intertrac_helper( ns, target, label, fullmatch) if intertrac: return intertrac try: link, params, fragment = formatter.split_link(target) r = Ranges(link) if len(r) == 1: num = r.a ticket = formatter.resource('ticket', num) from trac.ticket.model import Ticket if Ticket.id_is_valid(num) and \ 'TICKET_VIEW' in formatter.perm(ticket): # TODO: attempt to retrieve ticket view directly, # something like: t = Ticket.view(num) for type, summary, status, resolution in \ self.env.db_query(""" SELECT type, summary, status, resolution FROM ticket WHERE id=%s """, (str(num),)): title = self.format_summary(summary, status, resolution, type) href = formatter.href.ticket(num) + params + fragment return tag.a(label, title=title, href=href, class_='%s ticket' % status) else: ranges = str(r) if params: params = '&' + params[1:] if isinstance(label, Markup): _label = unescape(label) else: _label = label label_wrap = _label.replace(',', u',\u200b') ranges_wrap = ranges.replace(',', u', ') return tag.a(label_wrap, title=_("Tickets %(ranges)s", ranges=ranges_wrap), href=formatter.href.query(id=ranges) + params) except ValueError: pass return tag.a(label, class_='missing ticket')
def _format_link(self, formatter, ns, target, label, fullmatch=None): intertrac = formatter.shorthand_intertrac_helper( ns, target, label, fullmatch) if intertrac: return intertrac try: link, params, fragment = formatter.split_link(target) r = Ranges(link) if len(r) == 1: num = r.a ticket = formatter.resource('ticket', num) from trac.ticket.model import Ticket if Ticket.id_is_valid(num) and \ 'TICKET_VIEW' in formatter.perm(ticket): # TODO: watch #6436 and when done, attempt to retrieve # ticket directly (try: Ticket(self.env, num) ...) cursor = formatter.db.cursor() cursor.execute( "SELECT type,summary,status,resolution " "FROM ticket WHERE id=%s", (str(num), )) for type, summary, status, resolution in cursor: title = self.format_summary(summary, status, resolution, type) href = formatter.href.ticket(num) + params + fragment return tag.a(label, class_='%s ticket' % status, title=title, href=href) else: ranges = str(r) if params: params = '&' + params[1:] return tag.a(label, title='Tickets ' + ranges, href=formatter.href.query(id=ranges) + params) except ValueError: pass return tag.a(label, class_='missing ticket')
def process_request(self, req): req.perm.require('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') revs = req.args.get('revs') format = req.args.get('format') verbose = req.args.get('verbose') limit = int(req.args.get('limit') or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: raise ResourceNotFound( _("Repository '%(repo)s' not found", repo=reponame)) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect( req.href.log(repos.reponame or None, path) + ('?' + qs if qs else '')) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. revranges = None if revs: try: revranges = Ranges(revs) rev = revranges.b except ValueError: pass rev = unicode(repos.normalize_rev(rev)) display_rev = repos.display_rev # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child('changeset') show_graph = False if mode == 'path_history': def history(): for h in repos.get_path_history(path, rev): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h elif revranges: def history(): prevpath = path expected_next_item = None ranges = list(revranges.pairs) ranges.reverse() for (a, b) in ranges: a = repos.normalize_rev(a) b = repos.normalize_rev(b) while not repos.rev_older_than(b, a) and b != a: node = get_existing_node(req, repos, prevpath, b) node_history = list(node.get_history(2)) p, rev, chg = node_history[0] if repos.rev_older_than(rev, a): break # simply skip, no separator if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): if expected_next_item: # check whether we're continuing previous range np, nrev, nchg = expected_next_item if rev != nrev: # no, we need a separator yield (np, nrev, None) yield node_history[0] prevpath = node_history[-1][0] # follow copy b = repos.previous_rev(rev) if len(node_history) > 1: expected_next_item = node_history[-1] else: expected_next_item = None if expected_next_item: yield (expected_next_item[0], expected_next_item[1], None) else: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'path': old_path, 'rev': old_rev, 'existing_rev': old_rev, 'change': old_chg, 'depth': depth, } if old_chg == Changeset.DELETE: item['existing_rev'] = repos.previous_rev(old_rev, old_path) if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and \ not (mode == 'path_history' and old_path == normpath): depth += 1 item['depth'] = depth item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break elif mode == 'path_history': depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path if info == []: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError( _( "The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any previous revision.", path=path, rev=display_rev(rev)), _('Nonexistent path')) # Generate graph data graph = {} if show_graph: threads, vertices, columns = \ make_log_graph(repos, (item['rev'] for item in info)) graph.update(threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1) add_script(req, 'common/js/excanvas.js', ie_if='IE') add_script(req, 'common/js/log_graph.js') add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] next_revranges = None if revranges: next_revranges = str(revranges.truncate(next_rev)) if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link( req, 'next', older_revisions_href, _('Revision Log (restarting at %(path)s, rev. %(rev)s)', path=next_path, rev=display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None revisions = [i['rev'] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == 'changelog': for rev in revisions: changeset = changes[rev] cs = {} cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs['files'] = files cs['actions'] = actions extra_changes[rev] = cs data = { 'context': web_context(req, 'source', path, parent=repos.resource), 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': display_rev, 'revranges': revranges, 'mode': mode, 'verbose': verbose, 'limit': limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'graph': graph, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages') } if format == 'changelog': return 'revisionlog.txt', data, 'text/plain' elif format == 'rss': data['email_map'] = Chrome(self.env).get_email_map() data['context'] = web_context(req, 'source', path, parent=repos.resource, absurls=True) return 'revisionlog.rss', data, 'application/rss+xml' item_ranges = [] range = [] for item in info: if item['change'] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data['item_ranges'] = item_ranges add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/browser.css') path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data['path_links'] = path_links if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) rss_href = make_log_href(path, format='rss', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) if 'next' in req.chrome['links']: next = req.chrome['links']['next'][0] add_ctxtnav( req, tag.span(tag.a(_('Older Revisions'), href=next['href']), Markup(' →'))) return 'revisionlog.html', data, None
def render_property(self, name, mode, context, props): """Parse svn:mergeinfo and svnmerge-* properties, converting branch names to links and providing links to the revision log for merged and eligible revisions. """ has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') revs_label = _('blocked') if name.endswith('blocked') else _('merged') revs_cols = 2 if has_eligible else None reponame = context.resource.parent.id target_path = context.resource.id repos = RepositoryManager(self.env).get_repository(reponame) target_rev = context.resource.version if has_eligible: node = repos.get_node(target_path, target_rev) branch_starts = {} for path, rev in node.get_copy_ancestry(): if path not in branch_starts: branch_starts[path] = rev + 1 rows = [] eligible_infos = [] if name.startswith('svnmerge-'): sources = props[name].split() else: sources = props[name].splitlines() for line in sources: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is None: continue revs = revs.strip() inheritable, non_inheritable = _partition_inheritable(revs) revs = ','.join(inheritable) deleted = False try: node = repos.get_node(spath, target_rev) resource = context.resource.parent.child('source', spath) if 'LOG_VIEW' in context.perm(resource): row = [ _get_source_link(spath, context), _get_revs_link(revs_label, context, spath, revs) ] if non_inheritable: non_inheritable = ','.join(non_inheritable) row.append( _get_revs_link( _('non-inheritable'), context, spath, non_inheritable, _('merged on the directory ' 'itself but not below'))) if has_eligible: first_rev = branch_starts.get(spath) if not first_rev: first_rev = node.get_branch_origin() eligible = set(xrange(first_rev or 1, target_rev + 1)) eligible -= set(Ranges(revs)) blocked = _get_blocked_revs(props, name, spath) if blocked: eligible -= set(Ranges(blocked)) if eligible: node = repos.get_node(spath, max(eligible)) eligible_infos.append((spath, node, eligible, row)) continue eligible = to_ranges(eligible) row.append( _get_revs_link(_('eligible'), context, spath, eligible)) rows.append((False, spath, [tag.td(each) for each in row])) continue except NoSuchNode: deleted = True revs = revs.replace(',', u',\u200b') rows.append( (deleted, spath, [tag.td('/' + spath), tag.td(revs, colspan=revs_cols)])) # fetch eligible revisions for each path at a time changed_revs = {} changed_nodes = [(node, min(eligible)) for spath, node, eligible, row in eligible_infos] if changed_nodes: changed_revs = repos._get_changed_revs(changed_nodes) for spath, node, eligible, row in eligible_infos: if spath in changed_revs: eligible &= set(changed_revs[spath]) else: eligible.clear() row.append( _get_revs_link(_("eligible"), context, spath, to_ranges(eligible))) rows.append((False, spath, [tag.td(each) for each in row])) if not rows: return None rows.sort() if rows and rows[-1][0]: toggledeleted = tag.a(_("(toggle deleted branches)"), class_='trac-toggledeleted', href='#') else: toggledeleted = None return tag( toggledeleted, tag.table(tag.tbody([ tag.tr(row, class_='trac-deleted' if deleted else None) for deleted, spath, row in rows ]), class_='props'))
def process_request(self, req): req.perm.require('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') revs = req.args.get('revs') format = req.args.get('format') verbose = req.args.get('verbose') limit = int(req.args.get('limit') or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.log(repos.reponame or None, path) + ('?' + qs if qs else '')) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. revranges = None if revs: try: revranges = Ranges(revs) rev = revranges.b except ValueError: pass rev = unicode(repos.normalize_rev(rev)) display_rev = repos.display_rev # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child('changeset') show_graph = False if mode == 'path_history': def history(): for h in repos.get_path_history(path, rev): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h elif revranges: def history(): prevpath = path expected_next_item = None ranges = list(revranges.pairs) ranges.reverse() for (a, b) in ranges: a = repos.normalize_rev(a) b = repos.normalize_rev(b) while not repos.rev_older_than(b, a): node = get_existing_node(req, repos, prevpath, b) node_history = list(node.get_history(2)) p, rev, chg = node_history[0] if repos.rev_older_than(rev, a): break # simply skip, no separator if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): if expected_next_item: # check whether we're continuing previous range np, nrev, nchg = expected_next_item if rev != nrev: # no, we need a separator yield (np, nrev, None) yield node_history[0] prevpath = node_history[-1][0] # follow copy b = repos.previous_rev(rev) if len(node_history) > 1: expected_next_item = node_history[-1] else: expected_next_item = None if expected_next_item: yield (expected_next_item[0], expected_next_item[1], None) else: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'path': old_path, 'rev': old_rev, 'existing_rev': old_rev, 'change': old_chg, 'depth': depth, } if old_chg == Changeset.DELETE: item['existing_rev'] = repos.previous_rev(old_rev, old_path) if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and \ not (mode == 'path_history' and old_path == normpath): depth += 1 item['depth'] = depth item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break elif mode == 'path_history': depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path if info == []: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError(_("The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any previous revision.", path=path, rev=display_rev(rev)), _('Nonexistent path')) # Generate graph data graph = {} if show_graph: threads, vertices, columns = \ make_log_graph(repos, (item['rev'] for item in info)) graph.update(threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1) add_script(req, 'common/js/excanvas.js', ie_if='IE') add_script(req, 'common/js/log_graph.js') add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] next_revranges = None if revranges: next_revranges = str(revranges.truncate(next_rev)) if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link(req, 'next', older_revisions_href, _('Revision Log (restarting at %(path)s, rev. %(rev)s)', path=next_path, rev=display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None revisions = [i['rev'] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == 'changelog': for rev in revisions: changeset = changes[rev] cs = {} cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs['files'] = files cs['actions'] = actions extra_changes[rev] = cs data = { 'context': web_context(req, 'source', path, parent=repos.resource), 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': display_rev, 'revranges': revranges, 'mode': mode, 'verbose': verbose, 'limit' : limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'graph': graph, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages') } if format == 'changelog': return 'revisionlog.txt', data, 'text/plain' elif format == 'rss': data['email_map'] = Chrome(self.env).get_email_map() data['context'] = web_context(req, 'source', path, parent=repos.resource, absurls=True) return 'revisionlog.rss', data, 'application/rss+xml' item_ranges = [] range = [] for item in info: if item['change'] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data['item_ranges'] = item_ranges add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/browser.css') path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data['path_links'] = path_links if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) rss_href = make_log_href(path, format='rss', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) if 'next' in req.chrome['links']: next = req.chrome['links']['next'][0] add_ctxtnav(req, tag.span(tag.a(_('Older Revisions'), href=next['href']), Markup(' →'))) return 'revisionlog.html', data, None
def _format_link(self, formatter, ns, match, label, fullmatch=None): if ns == 'log1': groups = fullmatch.groupdict() it_log = groups.get('it_log') revs = groups.get('log_revs') path = groups.get('log_path') or '/' target = '%s%s@%s' % (it_log, path, revs) # prepending it_log is needed, as the helper expects it there intertrac = formatter.shorthand_intertrac_helper( 'log', target, label, fullmatch) if intertrac: return intertrac path, query, fragment = formatter.split_link(path) else: assert ns in ('log', 'log2') if ns == 'log': match, query, fragment = formatter.split_link(match) else: query = fragment = '' match = ''.join(reversed(match.split('/', 1))) path = match revs = '' if self.LOG_LINK_RE.match(match): indexes = [sep in match and match.index(sep) for sep in ':@'] idx = min([i for i in indexes if i is not False]) path, revs = match[:idx], match[idx + 1:] rm = RepositoryManager(self.env) try: reponame, repos, path = rm.get_repository_by_path(path) if not reponame: reponame = rm.get_default_repository(formatter.context) if reponame is not None: repos = rm.get_repository(reponame) if repos: if 'LOG_VIEW' in formatter.perm: revranges = None if any(c in revs for c in ':-,'): try: # try to parse into integer rev ranges revranges = Ranges(revs.replace(':', '-'), reorder=True) revs = str(revranges) except ValueError: revranges = self._normalize_ranges( repos, path, revs) if revranges: href = formatter.href.log(repos.reponame or None, path or '/', revs=revs) else: repos.normalize_rev(revs) # verify revision href = formatter.href.log(repos.reponame or None, path or '/', rev=revs or None) if query and '?' in href: query = '&' + query[1:] return tag.a(label, class_='source', href=href + query + fragment) errmsg = _("No permission to view change log") elif reponame: errmsg = _("Repository '%(repo)s' not found", repo=reponame) else: errmsg = _("No default repository defined") except TracError as e: errmsg = to_unicode(e) return tag.a(label, class_='missing source', title=errmsg)
def render(self, context, mimetype, content, filename=None, url=None, annotations=None, force_source=False): """Render an XHTML preview of the given `content`. `content` is the same as an `IHTMLPreviewRenderer.render`'s `content` argument. The specified `mimetype` will be used to select the most appropriate `IHTMLPreviewRenderer` implementation available for this MIME type. If not given, the MIME type will be infered from the filename or the content. Return a string containing the XHTML text. When rendering with an `IHTMLPreviewRenderer` fails, a warning is added to the request associated with the context (if any), unless the `disable_warnings` hint is set to `True`. """ if not content: return '' if not isinstance(context, RenderingContext): raise TypeError("RenderingContext expected (since 0.11)") # Ensure we have a MIME type for this content full_mimetype = mimetype if not full_mimetype: if hasattr(content, 'read'): content = content.read(self.max_preview_size) full_mimetype = self.get_mimetype(filename, content) if full_mimetype: mimetype = ct_mimetype(full_mimetype) # split off charset else: mimetype = full_mimetype = 'text/plain' # fallback if not binary # Determine candidate `IHTMLPreviewRenderer`s candidates = [] for renderer in self.renderers: qr = renderer.get_quality_ratio(mimetype) if qr > 0: candidates.append((qr, renderer)) candidates.sort(lambda x, y: cmp(y[0], x[0])) # Wrap file-like object so that it can be read multiple times if hasattr(content, 'read'): content = Content(content, self.max_preview_size) # First candidate which renders successfully wins. # Also, we don't want to expand tabs more than once. expanded_content = None for qr, renderer in candidates: if force_source and not getattr(renderer, 'returns_source', False): continue # skip non-source renderers in force_source mode if isinstance(content, Content): content.reset() try: ann_names = ', '.join(annotations) if annotations else \ 'no annotations' self.log.debug('Trying to render HTML preview using %s [%s]', renderer.__class__.__name__, ann_names) # check if we need to perform a tab expansion rendered_content = content if getattr(renderer, 'expand_tabs', False): if expanded_content is None: content = content_to_unicode(self.env, content, full_mimetype) expanded_content = content.expandtabs(self.tab_width) rendered_content = expanded_content result = renderer.render(context, full_mimetype, rendered_content, filename, url) if not result: continue if not (force_source or getattr(renderer, 'returns_source', False)): # Direct rendering of content if isinstance(result, basestring): if not isinstance(result, unicode): result = to_unicode(result) return Markup(to_unicode(result)) elif isinstance(result, Fragment): return result.generate() else: return result # Render content as source code if annotations: m = context.req.args.get('marks') if context.req else None return self._render_source(context, result, annotations, m and Ranges(m)) else: if isinstance(result, list): result = Markup('\n').join(result) return tag.div(class_='code')(tag.pre(result)).generate() except Exception, e: self.log.warning('HTML preview using %s failed: %s', renderer.__class__.__name__, exception_to_unicode(e, traceback=True)) if context.req and not context.get_hint('disable_warnings'): from trac.web.chrome import add_warning add_warning( context.req, _("HTML preview using %(renderer)s failed (%(err)s)", renderer=renderer.__class__.__name__, err=exception_to_unicode(e)))
def get_sql(self, req=None, cached_ids=None): """Return a (sql, params) tuple for the query.""" self.get_columns() enum_columns = ('resolution', 'priority', 'severity') # Build the list of actual columns to query cols = self.cols[:] def add_cols(*args): for col in args: if not col in cols: cols.append(col) if self.group and not self.group in cols: add_cols(self.group) if self.rows: add_cols('reporter', *self.rows) add_cols('status', 'priority', 'time', 'changetime', self.order) cols.extend([c for c in self.constraints.keys() if not c in cols]) custom_fields = [f['name'] for f in self.fields if 'custom' in f] sql = [] sql.append("SELECT " + ",".join(['t.%s AS %s' % (c, c) for c in cols if c not in custom_fields])) sql.append(",priority.value AS priority_value") for k in [k for k in cols if k in custom_fields]: sql.append(",%s.value AS %s" % (k, k)) sql.append("\nFROM ticket AS t") # Join with ticket_custom table as necessary for k in [k for k in cols if k in custom_fields]: sql.append("\n LEFT OUTER JOIN ticket_custom AS %s ON " \ "(id=%s.ticket AND %s.name='%s')" % (k, k, k, k)) # Join with the enum table for proper sorting for col in [c for c in enum_columns if c == self.order or c == self.group or c == 'priority']: sql.append("\n LEFT OUTER JOIN enum AS %s ON " "(%s.type='%s' AND %s.name=%s)" % (col, col, col, col, col)) # Join with the version/milestone tables for proper sorting for col in [c for c in ['milestone', 'version'] if c == self.order or c == self.group]: sql.append("\n LEFT OUTER JOIN %s ON (%s.name=%s)" % (col, col, col)) def get_constraint_sql(name, value, mode, neg): if name not in custom_fields: name = 't.' + name else: name = name + '.value' value = value[len(mode) + neg:] if mode == '': return ("COALESCE(%s,'')%s=%%s" % (name, neg and '!' or ''), value) if not value: return None db = self.env.get_db_cnx() value = db.like_escape(value) if mode == '~': value = '%' + value + '%' elif mode == '^': value = value + '%' elif mode == '$': value = '%' + value return ("COALESCE(%s,'') %s%s" % (name, neg and 'NOT ' or '', db.like()), value) clauses = [] args = [] for k, v in self.constraints.items(): if req: v = [val.replace('$USER', req.authname) for val in v] # Determine the match mode of the constraint (contains, # starts-with, negation, etc.) neg = v[0].startswith('!') mode = '' if len(v[0]) > neg and v[0][neg] in ('~', '^', '$'): mode = v[0][neg] # Special case id ranges if k == 'id': ranges = Ranges() for r in v: r = r.replace('!', '') ranges.appendrange(r) ids = [] id_clauses = [] for a,b in ranges.pairs: if a == b: ids.append(str(a)) else: id_clauses.append('id BETWEEN %s AND %s') args.append(a) args.append(b) if ids: id_clauses.append('id IN (%s)' % (','.join(ids))) if id_clauses: clauses.append('%s(%s)' % (neg and 'NOT ' or '', ' OR '.join(id_clauses))) # Special case for exact matches on multiple values elif not mode and len(v) > 1: if k not in custom_fields: col = 't.' + k else: col = k + '.value' clauses.append("COALESCE(%s,'') %sIN (%s)" % (col, neg and 'NOT ' or '', ','.join(['%s' for val in v]))) args += [val[neg:] for val in v] elif len(v) > 1: constraint_sql = filter(None, [get_constraint_sql(k, val, mode, neg) for val in v]) if not constraint_sql: continue if neg: clauses.append("(" + " AND ".join( [item[0] for item in constraint_sql]) + ")") else: clauses.append("(" + " OR ".join( [item[0] for item in constraint_sql]) + ")") args += [item[1] for item in constraint_sql] elif len(v) == 1: constraint_sql = get_constraint_sql(k, v[0], mode, neg) if constraint_sql: clauses.append(constraint_sql[0]) args.append(constraint_sql[1]) clauses = filter(None, clauses) if clauses: sql.append("\nWHERE ") sql.append(" AND ".join(clauses)) if cached_ids: sql.append(" OR ") sql.append("id in (%s)" % (','.join( [str(id) for id in cached_ids]))) sql.append("\nORDER BY ") order_cols = [(self.order, self.desc)] if self.group and self.group != self.order: order_cols.insert(0, (self.group, self.groupdesc)) for name, desc in order_cols: if name in custom_fields or name in enum_columns: col = name + '.value' else: col = 't.' + name desc = desc and ' DESC' or '' # FIXME: This is a somewhat ugly hack. Can we also have the # column type for this? If it's an integer, we do first # one, if text, we do 'else' if name in ('id', 'time', 'changetime'): sql.append("COALESCE(%s,0)=0%s," % (col, desc)) else: sql.append("COALESCE(%s,'')=''%s," % (col, desc)) if name in enum_columns: # These values must be compared as ints, not as strings db = self.env.get_db_cnx() sql.append(db.cast(col, 'int') + desc) elif name == 'milestone': sql.append("COALESCE(milestone.completed,0)=0%s," "milestone.completed%s," "COALESCE(milestone.due,0)=0%s,milestone.due%s," "%s%s" % (desc, desc, desc, desc, col, desc)) elif name == 'version': sql.append("COALESCE(version.time,0)=0%s,version.time%s,%s%s" % (desc, desc, col, desc)) else: sql.append("%s%s" % (col, desc)) if name == self.group and not name == self.order: sql.append(",") if self.order != 'id': sql.append(",t.id") return "".join(sql), args