Example #1
0
 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
Example #2
0
 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
Example #3
0
 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)
Example #5
0
 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
Example #6
0
 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
     }
Example #7
0
File: log.py Project: t2y/trac
 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
Example #8
0
 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]
Example #9
0
 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')
Example #10
0
 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')
Example #11
0
    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
Example #12
0
    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'))
Example #13
0
    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
Example #14
0
File: log.py Project: t2y/trac
    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)
Example #15
0
    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)))
Example #16
0
    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