def pretty_dateinfo(date, format=None, dateonly=False): if not date: return '' if format == 'date': absolute = user_time(req, format_date, date) else: absolute = user_time(req, format_datetime, date) now = datetime_now(localtz) relative = pretty_timedelta(date, now) if not format: format = req.session.get( 'dateinfo', Chrome(self.env).default_dateinfo_format) if format == 'relative': if date > now: label = _("in %(relative)s", relative=relative) \ if not dateonly else relative title = _("on %(date)s at %(time)s", date=user_time(req, format_date, date), time=user_time(req, format_time, date)) return tag.span(label, title=title) else: label = _("%(relative)s ago", relative=relative) \ if not dateonly else relative title = _("See timeline at %(absolutetime)s", absolutetime=absolute) else: if dateonly: label = absolute elif req.lc_time == 'iso8601': label = _("at %(iso8601)s", iso8601=absolute) elif format == 'date': label = _("on %(date)s", date=absolute) else: label = _("on %(date)s at %(time)s", date=user_time(req, format_date, date), time=user_time(req, format_time, date)) if date > now: title = _("in %(relative)s", relative=relative) return tag.span(label, title=title) title = _("See timeline %(relativetime)s ago", relativetime=relative) return self.get_timeline_link(req, date, label, precision='second', title=title)
def filter_stream(self, req, method, filename, stream, data): if 'ticket' in data: latest = max([0] + [ int(c[3]) for c in data['ticket'].get_changelog() if c[2] == u'comment' and c[3] != u'' ]) ticket = data['ticket'].resource if ticket.version == None: # showing latest version, add fix-version link add_ctxtnav(req, _('Previous Version'), '?version=%s' % latest) else: # showing specified version; add latest-version link r, i, v = ticket.realm, ticket.id, ticket.version prev_link, next_link = None, None if v > 0: prev_link = tag.a(_('Previous Version'), href=get_resource_url( self.env, Resource(r, i, v - 1), req.href), class_='prev') if v < latest: next_link = tag.a(_('Next Version'), href=get_resource_url( self.env, Resource(r, i, v + 1), req.href), class_='next') add_ctxtnav( req, tag.span(Markup('← '), prev_link or _('Previous Version'), class_=not prev_link and 'missing' or None)) add_ctxtnav( req, tag.a(_('View Latest Version'), href=get_resource_url(self.env, Resource(r, i, None), req.href))) add_ctxtnav( req, tag.span(next_link or _('Next Version'), Markup(' →'), class_=not next_link and 'missing' or None)) return stream
def _get_revs_link(label, context, spath, revs, title=None): """Return a link to the revision log when more than one revision is given, to the revision itself for a single revision, or a `<span>` with "no revision" for none. """ reponame = context.resource.parent.id if not revs: return tag.span(label, title=_('No revisions')) elif ',' in revs or '-' in revs: revs_href = context.href.log(reponame or None, spath, revs=revs) else: revs_href = context.href.changeset(revs, reponame or None, spath) revs = revs.replace(',', ', ') if title: title = _("%(title)s: %(revs)s", title=title, revs=revs) else: title = revs return tag.a(label, title=title, href=revs_href)
def _format_tagged(self, formatter, ns, target, label, fullmatch=None): """Tag and tag query expression link formatter.""" def unquote(text): """Strip all matching pairs of outer quotes from string.""" while re.match(WikiParser.QUOTED_STRING, text): # Remove outer whitespace after stripped quotation too. text = text[1:-1].strip() return text label = label and unquote(label.strip()) or '' target = unquote(target.strip()) query = target # Pop realms from query expression. all_realms = self.tag_system.get_taggable_realms(formatter.perm) realms = query_realms(target, all_realms) if realms: kwargs = dict((realm, 'on') for realm in realms) target = re.sub('(^|\W)realm:\S+(\W|$)', ' ', target).strip() else: kwargs = {} tag_res = Resource('tag', target) if 'TAGS_VIEW' not in formatter.perm(tag_res): return tag.span(label, class_='forbidden tags', title=_("no permission to view tags")) context = formatter.context href = self.tag_system.get_resource_url(tag_res, context.href, kwargs) if all_realms and (target in self.tag_system.get_all_tags( formatter.req) or not iter_is_empty( self.tag_system.query(formatter.req, query))): # At least one tag provider is available and tag exists or # tags query yields at least one match. if label: return tag.a(label, href=href) return render_resource_link(self.env, context, tag_res) return tag.a(label + '?', href=href, class_='missing tags', rel='nofollow')
def expand_macro(self, formatter, name, content, args=None): style_args = { 'fg': 'color', 'bg': 'background-color', 'size': 'font-size' } style_values = {'color': '', 'background-color': '', 'font-size': ''} space_start = '' space_end = '' if args: text = content for k in args.keys(): style = style_args[k] if k in style_args else k style_values[style] = args.get(k) html = format_to_html(self.env, formatter.context, text) else: args = content.split(',') text = ','.join(args[:-1]) args = args[-1].split('/') + [''] * 3 style_values['color'] = args.pop(0).strip() # background color is optional arg = args.pop(0).strip() if len(arg) > 0 and arg[0].isdigit(): style_values['font-size'] = arg else: style_values['background-color'] = arg style_values['font-size'] = args.pop(0).strip() html = format_to_oneliner(self.env, formatter.context, text) if text.startswith(u' '): space_start = Markup(' ') if text.endswith(u' '): space_end = Markup(' ') if style_values['font-size'].isdigit(): style_values['font-size'] = '%s%%' % style_values['font-size'] style = ';'.join('%s:%s' % (k, v) for (k, v) in style_values.iteritems() if v) span = sanitize_attrib(self.env, tag.span(style=style)) span(space_start, html, space_end) return span
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 = req.args.getint('limit', self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: if path == '/': raise TracError( _("No repository specified and no default" " repository configured.")) else: raise ResourceNotFound( _("Repository '%(repo)s' not found", repo=reponame or path.strip('/'))) 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. if revs: revranges = RevRanges(repos, revs, resolve=True) rev = revranges.b else: revranges = None rev = repos.normalize_rev(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(self.realm) show_graph = False curr_revrange = [] 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: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets \ and len(revranges) == 1 def history(): separator = False for a, b in reversed(revranges.pairs): curr_revrange[:] = (a, b) node = get_existing_node(req, repos, path, b) for p, rev, chg in node.get_history(): if repos.rev_older_than(rev, a): break if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): separator = True yield p, rev, chg else: separator = False if separator: yield p, rev, 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 history_remaining = True 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 else: history_remaining = False if not 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=repos.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 history_remaining 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 curr_revrange: new_revrange = (curr_revrange[0], next_rev) \ if info[-1]['change'] else None next_revranges = revranges.truncate(curr_revrange, new_revrange) next_revranges = unicode(next_revranges) or None 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=repos.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': repos.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, {'content_type': 'text/plain'} elif format == 'rss': data['context'] = web_context(req, 'source', path, parent=repos.resource, absurls=True) return ('revisionlog.rss', data, { 'content_type': '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
def render_property(self, name, mode, context, props): def sha_link(sha, label=None): # sha is assumed to be a non-abbreviated 40-chars sha id try: reponame = context.resource.parent.id repos = RepositoryManager(self.env).get_repository(reponame) cset = repos.get_changeset(sha) if label is None: label = repos.display_rev(sha) return tag.a(label, class_='changeset', title=shorten_line(cset.message), href=context.href.changeset(sha, repos.reponame)) except Exception as e: return tag.a(sha, class_='missing changeset', title=to_unicode(e), rel='nofollow') if name == 'Branches': branches = props[name] # simple non-merge commit return tag(*intersperse(', ', (sha_link(rev, label) for label, rev in branches))) elif name in ('Parents', 'Children'): revs = props[name] # list of commit ids if name == 'Parents' and len(revs) > 1: # we got a merge... current_sha = context.resource.id reponame = context.resource.parent.id parent_links = intersperse(', ', ((sha_link(rev), ' (', tag.a(_("diff"), title=_("Diff against this parent (show the " "changes merged from the other parents)"), href=context.href.changeset(current_sha, reponame, old=rev)), ')') for rev in revs)) return tag(list(parent_links), tag.br(), tag.span(Markup(_("Note: this is a <strong>merge" "</strong> changeset, the " "changes displayed below " "correspond to the merge " "itself.")), class_='hint'), tag.br(), tag.span(Markup(_("Use the <code>(diff)</code> " "links above to see all the " "changes relative to each " "parent.")), class_='hint')) # simple non-merge commit return tag(*intersperse(', ', map(sha_link, revs))) elif name in ('git-committer', 'git-author'): user_, time_ = props[name] _str = "%s (%s)" % ( Chrome(self.env).format_author(context.req, user_), format_datetime(time_, tzinfo=context.req.tz)) return unicode(_str) raise TracError(_("Internal error"))