コード例 #1
0
def do_upgrade(env, version, cursor):
    """Replace list of repositories in [trac] repository_sync_per_request
    with boolean values [repositories] <repos>.sync_per_request.
    """
    repos_sync_per_request = \
        env.config.getlist('trac', 'repository_sync_per_request')
    if not repos_sync_per_request:
        return

    backup_config_file(env, '.db32.bak')
    rm = RepositoryManager(env)
    db_provider = env[DbRepositoryProvider]

    trac_ini_repo_names = []
    for name, _ in rm.get_repositories():
        trac_ini_repo_names.append(name)
    for repos in rm.get_all_repositories().values():
        sync_per_request = (repos['name'] or '(default)') \
                           in repos_sync_per_request
        if sync_per_request:
            if repos['name'] in trac_ini_repo_names:
                env.config.set('repositories',
                               repos['name'] + '.sync_per_request', 'true')
            else:
                changes = {'sync_per_request': sync_per_request}
                db_provider.modify_repository(repos['name'], changes)
    env.log.info(
        "Removed [trac] repository_sync_per_request option and "
        "enabled sync_per_request for the following "
        "repositories: %s", ', '.join(repos_sync_per_request))
    env.config.remove('trac', 'repository_sync_per_request')
    env.config.save()
コード例 #2
0
    def process_request(self, req):
        req.perm.require('BROWSER_VIEW')

        presel = req.args.get('preselected')
        if presel and (presel + '/').startswith(req.href.browser() + '/'):
            req.redirect(presel)

        path = req.args.get('path', '/')
        rev = req.args.get('rev', '')
        if rev in ('', 'HEAD'):
            rev = None
        order = req.args.get('order', 'name').lower()
        desc = req.args.has_key('desc')
        xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest'

        rm = RepositoryManager(self.env)
        all_repositories = rm.get_all_repositories()
        reponame, repos, path = rm.get_repository_by_path(path)

        # Repository index
        show_index = not reponame and path == '/'
        if show_index:
            if repos and (as_bool(all_repositories[''].get('hidden'))
                          or not repos.can_view(req.perm)):
                repos = None

        if not repos and reponame:
            raise ResourceNotFound(
                _("Repository '%(repo)s' not found", repo=reponame))

        if reponame and reponame != repos.reponame:  # Redirect alias
            qs = req.query_string
            req.redirect(
                req.href.browser(repos.reponame or None, path) +
                (qs and '?' + qs or ''))
        reponame = repos and repos.reponame or None

        # Find node for the requested path/rev
        context = Context.from_request(req)
        node = None
        display_rev = lambda rev: rev
        if repos:
            try:
                if rev:
                    rev = repos.normalize_rev(rev)
                # If `rev` is `None`, we'll try to reuse `None` consistently,
                # as a special shortcut to the latest revision.
                rev_or_latest = rev or repos.youngest_rev
                node = get_existing_node(req, repos, path, rev_or_latest)
            except NoSuchChangeset, e:
                raise ResourceNotFound(e.message,
                                       _('Invalid changeset number'))

            context = context(
                repos.resource.child('source', path, version=rev_or_latest))
            display_rev = repos.display_rev
コード例 #3
0
    def get_psf(self):
        """ parse attached "team project set" for Eclipse IDE
            returns a-list as [ Eclipse project name -> repository URL ] """
        if self._psf is None:
            self._psf = {}
            psfResource = Resource('wiki', 'TeamProjectSet').child(
                'attachment', 'projectSet.psf')
            if (self.compmgr[AttachmentModule].resource_exists(psfResource)):
                psf = Attachment(self.env, psfResource)

                def startElement(name, attrs):
                    if name == 'project':
                        attr = attrs.get('reference', "").split(',')
                        self._psf[attr[2]] = urlparse(
                            attr[1]).path  # trim leading scheme/port

                reader = expat.ParserCreate()
                reader.StartElementHandler = startElement
                reader.ParseFile(psf.open())
                # specify checkout dir in server subversion directory
                rm = RepositoryManager(self.env)
                repos = rm.get_all_repositories()
                for projectname in self._psf.keys():
                    path = self._psf.get(projectname) + '/.project'
                    for reponame in repos:
                        repo = rm.get_repository(reponame)
                        if not repo:
                            continue
                        for npath in self.iter_lstrip(path):
                            if not repo.has_node(npath, None):
                                continue
                            self._psf[projectname] = npath[:-9]

                            # search .classpath here
                            npath = npath[:-9] + '/.classpath'
                            if not repo.has_node(npath, None):
                                continue
                            node = repo.get_node(npath, None)
                            srcpathes = self.parse_classpath(
                                node.get_content())
                            self._srcpathes[repo.reponame] = map(
                                lambda x: npath[:-10] + x, srcpathes)
            else:  # TeamProjectSet not found
                for repo in self.config.getlist('wiki', 'source_path',
                                                sep=';'):
                    # expected: "svn: trunk/theproject/src/main/java trunk/theproject/src/test/java;"
                    repo = repo.split(':')
                    repo, srcpaths = len(repo) < 2 and (
                        "", repo[0]
                    ) or repo  # no leading reponame, use default repo
                    self._srcpathes[repo] = self._srcpathes.get(repo, [])
                    self._srcpathes[repo].extend([
                        s.rstrip('/') + '/' for s in srcpaths.split(' ') if s
                    ])
        return self._psf  # { project_name: repository_url, ... }
コード例 #4
0
ファイル: browser.py プロジェクト: zjj/trac_hack
    def process_request(self, req):
        req.perm.require('BROWSER_VIEW')

        presel = req.args.get('preselected')
        if presel and (presel + '/').startswith(req.href.browser() + '/'):
            req.redirect(presel)

        path = req.args.get('path', '/')
        rev = req.args.get('rev', '')
        if rev in ('', 'HEAD'):
            rev = None
        order = req.args.get('order', 'name').lower()
        desc = req.args.has_key('desc')
        xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest'
        
        rm = RepositoryManager(self.env)
        all_repositories = rm.get_all_repositories()
        reponame, repos, path = rm.get_repository_by_path(path)

        # Repository index
        show_index = not reponame and path == '/'
        if show_index:
            if repos and (as_bool(all_repositories[''].get('hidden'))
                          or not repos.can_view(req.perm)):
                repos = None

        if not repos and reponame:
            raise ResourceNotFound(_("Repository '%(repo)s' not found",
                                     repo=reponame))

        if reponame and reponame != repos.reponame: # Redirect alias
            qs = req.query_string
            req.redirect(req.href.browser(repos.reponame or None, path)
                         + (qs and '?' + qs or ''))
        reponame = repos and repos.reponame or None
        
        # Find node for the requested path/rev
        context = Context.from_request(req)
        node = None
        display_rev = lambda rev: rev
        if repos:
            try:
                if rev:
                    rev = repos.normalize_rev(rev)
                # If `rev` is `None`, we'll try to reuse `None` consistently,
                # as a special shortcut to the latest revision.
                rev_or_latest = rev or repos.youngest_rev
                node = get_existing_node(req, repos, path, rev_or_latest)
            except NoSuchChangeset, e:
                raise ResourceNotFound(e.message,
                                       _('Invalid changeset number'))

            context = context(repos.resource.child('source', path,
                                                   version=rev_or_latest))
            display_rev = repos.display_rev
コード例 #5
0
    def expand_macro(self, formatter, name, args):
        path = unicode(args)

        rm = RepositoryManager(self.env)

        for repo_name in rm.get_all_repositories():
            repo = rm.get_repository(repo_name)

            if repo.has_node(path):
                return self.get_javadoc(repo, repo_name, path)
        return "No file found for %s" % (path)
コード例 #6
0
    def expand_macro(self, formatter, name, content):
        args, kwargs = parse_args(content)
        format = kwargs.get('format', 'compact')
        glob = kwargs.get('glob', '*')
        order = kwargs.get('order')
        desc = as_bool(kwargs.get('desc', 0))

        rm = RepositoryManager(self.env)
        all_repos = dict(rdata for rdata in rm.get_all_repositories().items()
                         if fnmatchcase(rdata[0], glob))

        if format == 'table':
            repo = self._render_repository_index(formatter.context, all_repos,
                                                 order, desc)

            add_stylesheet(formatter.req, 'common/css/browser.css')
            wiki_format_messages = self.config['changeset'] \
                                       .getbool('wiki_format_messages')
            data = {'repo': repo, 'order': order, 'desc': 1 if desc else None,
                    'reponame': None, 'path': '/', 'stickyrev': None,
                    'wiki_format_messages': wiki_format_messages}
            from trac.web.chrome import Chrome
            return Chrome(self.env).render_template(
                    formatter.req, 'repository_index.html', data, None,
                    fragment=True)

        def get_repository(reponame):
            try:
                return rm.get_repository(reponame)
            except TracError:
                return

        all_repos = [(reponame, get_repository(reponame))
                     for reponame in all_repos]
        all_repos = sorted(((reponame, repos) for reponame, repos in all_repos
                            if repos
                            and not as_bool(repos.params.get('hidden'))
                            and repos.is_viewable(formatter.perm)),
                           reverse=desc)

        def repolink(reponame, repos):
            label = reponame or _('(default)')
            return Markup(tag.a(label,
                          title=_('View repository %(repo)s', repo=label),
                          href=formatter.href.browser(repos.reponame or None)))

        if format == 'list':
            return tag.dl([
                tag(tag.dt(repolink(reponame, repos)),
                    tag.dd(repos.params.get('description')))
                for reponame, repos in all_repos])
        else: # compact
            return Markup(', ').join([repolink(reponame, repos)
                                      for reponame, repos in all_repos])
コード例 #7
0
ファイル: browser.py プロジェクト: zjj/trac_hack
    def expand_macro(self, formatter, name, content):
        args, kwargs = parse_args(content)
        format = kwargs.get('format', 'compact')
        glob = kwargs.get('glob', '*')
        order = kwargs.get('order')
        desc = as_bool(kwargs.get('desc', 0))

        rm = RepositoryManager(self.env)
        all_repos = dict(rdata for rdata in rm.get_all_repositories().items()
                         if fnmatchcase(rdata[0], glob))

        if format == 'table':
            repo = self._render_repository_index(formatter.context, all_repos,
                                                 order, desc)

            add_stylesheet(formatter.req, 'common/css/browser.css')
            wiki_format_messages = self.config['changeset'] \
                                       .getbool('wiki_format_messages')
            data = {'repo': repo, 'order': order, 'desc': desc and 1 or None,
                    'reponame': None, 'path': '/', 'stickyrev': None,
                    'wiki_format_messages': wiki_format_messages}
            from trac.web.chrome import Chrome
            return Chrome(self.env).render_template(
                    formatter.req, 'repository_index.html', data, None,
                    fragment=True)

        def get_repository(reponame):
            try:
                return rm.get_repository(reponame)
            except TracError:
                return

        all_repos = [(reponame, get_repository(reponame))
                     for reponame in all_repos]
        all_repos = sorted(((reponame, repos) for reponame, repos in all_repos
                            if repos
                            and not as_bool(repos.params.get('hidden'))
                            and repos.can_view(formatter.perm)),
                           reverse=desc)

        def repolink(reponame, repos):
            label = reponame or _('(default)')
            return Markup(tag.a(label, 
                          title=_('View repository %(repo)s', repo=label),
                          href=formatter.href.browser(repos.reponame or None)))

        if format == 'list':
            return tag.dl([
                tag(tag.dt(repolink(reponame, repos)),
                    tag.dd(repos.params.get('description')))
                for reponame, repos in all_repos])
        else: # compact
            return Markup(', ').join([repolink(reponame, repos)
                                      for reponame, repos in all_repos])
コード例 #8
0
    def _suggest_source(self, req, term):
        def suggest_revs(repos, node, search_rev):
            if search_rev:
                for category, names, path, rev in repos.get_quickjump_entries(
                        None):
                    if path and path != '/':
                        # skip jumps to other paths
                        # (like SVN's 'trunk', 'branches/...', 'tags/...' folders)
                        continue
                    # Multiple Mercurial tags on same revision are comma-separated:
                    for name in names.split(', '):
                        if ' ' in name:
                            # use first token, e.g. '1.0' from '1.0 (tip)'
                            name = name.split(' ', 1)[0]
                        if name.startswith(search_rev):
                            yield name
            for r in node.get_history(10):
                rev = repos.short_rev(r[1])
                if str(rev).startswith(search_rev):
                    yield rev

        rm = RepositoryManager(self.env)
        if term.find('/') == -1 and term.find('@') == -1:
            lower_term = term.lower()
            completions = sorted(
                reponame + '/' for reponame in rm.get_all_repositories()
                if reponame.lower().startswith(lower_term)
                and 'BROWSER_VIEW' in req.perm('repository', reponame))
        else:
            pos = term.find('/')
            if pos == -1:
                pos = term.find('@')
            reponame, path = term[:pos], term[pos:]
            repos = rm.get_repository(reponame)
            completions = []
            if repos is not None:
                if path.find('@') != -1:
                    path, search_rev = path.rsplit('@', 1)
                    node = repos.get_node(path, repos.youngest_rev)
                    if node.can_view(req.perm):
                        completions.extend(
                            '%s%s@%s' % (reponame, path, rev)
                            for rev in suggest_revs(repos, node, search_rev))
                else:
                    dir, filename = path.rsplit('/', 1)
                    node = repos.get_node(dir or '/', repos.youngest_rev)
                    completions = sorted(
                        '%s/%s%s' %
                        (reponame, n.path.lstrip('/'), '/' if n.isdir else '')
                        for n in node.get_entries() if
                        n.name.startswith(filename) and n.can_view(req.perm))
        return completions
コード例 #9
0
ファイル: api.py プロジェクト: ebouaziz/TracRevTreePlugin
    def get_revtree(self, repos, req):
        rm = RepositoryManager(self.env)
        reps = rm.get_all_repositories()
        for repo in reps:
            rtype = reps[repo].get('type', None) or rm.default_repository_type
            if rtype == 'svn':
                break
        else:
            raise TracError("Revtree only supports Subversion repositories")

        self.env.log.debug("Enhancers: %s" % self.enhancers)

        return SvgRevtree(self.env, repos, req.href(),
                          self.enhancers, self.optimizer)
コード例 #10
0
 def _do_list_unmanaged(self):
     rm = RepositoryManager(self.env)
     trm = TracRepositoryManager(self.env)
     values = []
     for (reponame, info) in sorted(trm.get_all_repositories().iteritems()):
         alias = ''
         if 'alias' in info:
             alias = info['alias'] or '(default)'
         try:
             repos = rm.get_repository(reponame, True)
         except:
             values.append(
                 (reponame
                  or '(default)', info.get('type',
                                           ''), alias, info.get('dir', '')))
     print_table(values, [_('Name'), _('Type'), _('Alias'), _('Directory')])
コード例 #11
0
ファイル: web_ui.py プロジェクト: castorinop/proxydav
    def help(self, req):

        rm = RepositoryManager(self.env)
        all_repositories = rm.get_all_repositories()
        page = _('= Repositories =') + '\n'

        for reponame, repoinfo in all_repositories.iteritems():
          path = '%s/dav/%s' % (req.base_path, reponame)
          url = '%s://%s@%s:%s%s' % (req.scheme, req.remote_user, req.server_name, req.server_port, path)
          url = urlparse.urlparse(url)
          self.log.info('repo %r', repoinfo)
          page += ' == %s == \r\n {{{\n %s %s \n}}}\n\n' % (reponame, self.helper_vcs(repoinfo['type']), url.geturl())

        data = {
            'proxydav_title': _('Repo access'), 
            'proxydav_page': self.format_to_html(req, page)
        }
        return 'proxydav.html', data, None
コード例 #12
0
 def _format_link(self, formatter, ns, target, label, fullmatch=None):
     """ returns a tag for
         Resource: Projectname/path/to/src/org/example/package/Class.java Line: 123 """
     # search repository
     rm = RepositoryManager(self.env)
     line = fullmatch.group('line')
     line = line and '#L%s' % line or ""
     # option 1: search with unmodified path
     path = fullmatch.group('path')
     reponame, repos, npath = rm.get_repository_by_path(
         path)  # @UnusedVariable
     node = get_allowed_node(repos, npath, None, formatter.perm)
     if node:
         return tag.a(label,
                      href=formatter.href.browser(path) + line,
                      class_="source")
     # option 2: search with "/trunk/" + path
     path = "/trunk/" + fullmatch.group('path')
     reponame, repos, npath = rm.get_repository_by_path(
         path)  # @UnusedVariable
     node = get_allowed_node(repos, npath, None, formatter.perm)
     if node:
         return tag.a(label,
                      href=formatter.href.browser(path) + line,
                      class_="source")
     # option 3: heuristic search in repositories for subversion
     projectname, trailing = fullmatch.group('path').lstrip('/').split(
         '/', 1)
     psf = self.get_psf()
     if projectname in psf:
         # subversion can checkout in the middle of repository
         path = psf[projectname] + '/' + trailing
         repos = rm.get_all_repositories()
         for npath, reponame in product(self.iter_lstrip(path), repos):
             repo = rm.get_repository(reponame)
             node = get_allowed_node(repo, npath, None, formatter.perm)
             if node:
                 return tag.a(label,
                              class_="source",
                              href=formatter.href.browser(repo.reponame +
                                                          '/' + node.path) +
                              line)
     return tag.a(label, class_='missing source')
コード例 #13
0
 def _do_list_managed(self):
     rm = RepositoryManager(self.env)
     trm = TracRepositoryManager(self.env)
     values = []
     for (reponame, info) in sorted(trm.get_all_repositories().iteritems()):
         alias = ''
         if 'alias' in info:
             alias = info['alias'] or '(default)'
         try:
             repos = rm.get_repository(reponame, True)
             values.append((reponame or '(default)', info.get('type', ''),
                            alias, repos.owner, info.get('dir', '')))
         except:
             pass
     print_table(
         values,
         [_("Name"),
          _("Type"),
          _("Alias"),
          _("Owner"),
          _("Directory")])
コード例 #14
0
    def process_request(self, req):
        strategy = req.args.get('strategy')
        term = req.args.get('q')

        if strategy == 'linkresolvers':
            wiki = WikiSystem(self.env)
            completions = []
            for provider in wiki.syntax_providers:
                for name, resolver in provider.get_link_resolvers():
                    if name.startswith(term):
                        completions.append(name)
            self._send_json(req, completions)

        elif strategy == 'ticket':
            with self.env.db_query as db:
                rows = db("""
                    SELECT id, summary
                    FROM ticket
                    WHERE %s %s
                    ORDER BY changetime DESC
                    LIMIT 10
                    """ % (db.cast('id', 'text'), db.prefix_match()),
                    (db.prefix_match_value(term), ))
            completions = [{
                'id': row[0],
                'summary': row[1],
                } for row in rows
                if 'TICKET_VIEW' in req.perm(Resource('ticket', row[0]))]
            self._send_json(req, completions)

        elif strategy == 'wikipage':
            with self.env.db_query as db:
                rows = db("""
                    SELECT name
                    FROM wiki
                    WHERE name %s
                    GROUP BY name
                    ORDER BY name
                    LIMIT 10
                    """ % db.prefix_match(),
                    (db.prefix_match_value(term), ))
            completions = [row[0] for row in rows
                           if 'WIKI_VIEW' in req.perm(Resource('wiki', row[0]))]
            self._send_json(req, completions)

        elif strategy == 'macro':
            resource = Resource()
            context = web_context(req, resource)
            wiki = WikiSystem(self.env)
            macros = []
            for provider in wiki.macro_providers:
                names = list(provider.get_macros() or [])
                for name in names:
                    if name.startswith(term):
                        macros.append((name, provider))
            completions = []
            if len(macros) == 1:
                name, provider = macros[0]
                descr = provider.get_macro_description(name)
                if isinstance(descr, (tuple, list)):
                    descr = dgettext(descr[0], to_unicode(descr[1]))
                descr = format_to_html(self.env, context, descr)
                completions.append({
                    'name': name,
                    'description': descr,
                })
            else:
                for name, provider in macros:
                    descr = provider.get_macro_description(name)
                    if isinstance(descr, (tuple, list)):
                        descr = dgettext(descr[0], to_unicode(descr[1]))
                    descr = format_to_oneliner(self.env, context, descr, shorten=True)
                    completions.append({
                        'name': name,
                        'description': descr,
                    })
            self._send_json(req, completions)

        elif strategy == 'source':
            rm = RepositoryManager(self.env)
            completions = []
            if term.find('/') == -1:
                for reponame, repoinfo in rm.get_all_repositories().iteritems():
                    if 'BROWSER_VIEW' in req.perm(Resource('repository', reponame)):
                        if len(term) == 0 or reponame.lower().startswith(term.lower()):
                            completions.append(reponame+'/')
            else:
                reponame, path = term.split('/', 1)
                repos = rm.get_repository(reponame)
                if repos is not None:
                    if path.find('@') != -1:
                        path, search_rev = path.rsplit('@', 1)
                        node = repos.get_node(path, repos.youngest_rev)
                        if node.can_view(req.perm):
                            for r in node.get_history(10):
                                if str(r[1]).startswith(search_rev):
                                    completions.append('%s/%s@%s' % (reponame, path, r[1]))
                    else:
                        if path.find('/') != -1:
                            dir, filename = path.rsplit('/', 1)
                        else:
                            dir, filename = '/', path
                        node = repos.get_node(dir, repos.youngest_rev)
                        completions = ['%s/%s%s' % (reponame, n.path, '/' if n.isdir else '')
                                       for n in node.get_entries()
                                       if n.can_view(req.perm) and n.name.startswith(filename)]
            self._send_json(req, completions)

        raise TracError()
コード例 #15
0
def convert_managed_repository(env, repo):
    """Convert a given repository into a `ManagedRepository`."""
    class ManagedRepository(repo.__class__):
        """A repository managed by the new `RepositoryManager`.

        This repository class inherits from the original class of the
        given repository and adds fields needed by the manager.

        Trying to convert a repository that was added via the original
        `RepositoryAdminPanel` raises an exception and can therefore
        be used to easily check if we are working with a manageable
        repository.
        """

        id = None
        owner = None
        type = None
        description = None
        is_fork = False
        is_forkable = False
        directory = None
        _owner_is_maintainer = False
        _maintainers = set()
        _writers = set()
        _readers = set()

        def maintainers(self):
            if self._owner_is_maintainer:
                return self._maintainers | set([self.owner])
            return self._maintainers

        def writers(self):
            return self._writers | set([self.owner])

        def readers(self):
            return self._readers | set([self.owner])

    class ForkedRepository(ManagedRepository):
        """A local fork of a `ManagedRepository`.

        This repository class inherits from the original class of the
        given repository and adds fields and methods needed by the
        manager and for e.g. pull requests.
        """

        origin = None
        inherit_readers = False

        def get_youngest_common_ancestor(self, rev):
            """Goes back in the repositories history starting from
            `rev` until it finds a revision that also exists in the
            origin of this fork.
            """
            nodes = [rev]
            while len(nodes):
                node = nodes.pop(0)

                try:
                    self.origin.get_changeset(node)
                except:
                    pass
                else:
                    return node

                for ancestor in self.parent_revs(node):
                    nodes.append(ancestor)

            return None

        def readers(self):
            readers = ManagedRepository.readers(self)
            if self.inherit_readers:
                return readers | self.origin.maintainers()
            return readers

    def _get_role(db, role):
        """Get the set of users that have the given `role` on this
        repository.
        """
        result = db("""SELECT value FROM repository
                       WHERE name = '%s' AND id = %d
                       """ % (role + 's', repo.id))[0][0]
        if result:
            return set(result.split(','))
        return set()

    if repo.__class__ is not ManagedRepository:
        trac_rm = TracRepositoryManager(env)
        repo.id = trac_rm.get_repository_id(repo.reponame)
        rm = RepositoryManager(env)
        with env.db_transaction as db:
            result = db("""SELECT value FROM repository
                           WHERE name = 'owner' AND id = %d
                           """ % repo.id)
            if not result:
                raise TracError(_("Not a managed repository"))

            repo.__class__ = ManagedRepository
            repo.owner = result[0][0]
            for role in rm.roles:
                role_attr = '_' + role + 's'
                setattr(repo, role_attr,
                        getattr(repo, role_attr) | _get_role(db, role))
        repo._owner_is_maintainer = rm.owner_as_maintainer

        info = trac_rm.get_all_repositories().get(repo.reponame)
        repo.type = info['type']
        repo.description = info.get('description')
        repo.is_forkable = repo.type in rm.get_forkable_types()
        repo.directory = info['dir']

        with env.db_transaction as db:
            result = db("""SELECT value FROM repository
                           WHERE name = 'name' AND
                                 id = (SELECT value FROM repository
                                       WHERE name = 'origin' AND id = %d)
                           """ % repo.id)
            if not result:
                return

            repo.__class__ = ForkedRepository
            repo.is_fork = True
            repo.origin = rm.get_repository(result[0][0], True)
            if repo.origin is None:
                raise TracError(
                    _("Origin of previously forked repository "
                      "does not exist anymore"))
            result = db("""SELECT value FROM repository
                           WHERE name = 'inherit_readers' AND id = %d
                           """ % repo.id)
            repo.inherit_readers = as_bool(result[0][0])
コード例 #16
0
ファイル: web_ui.py プロジェクト: castorinop/proxydav
 def get_repo(self, req):
     reponame = self.get_repo_name(req)
     self.log.info('repo %r', reponame)
     rm = RepositoryManager(self.env)
     repoinfo = rm.get_all_repositories().get(reponame, {})
     return repoinfo
コード例 #17
0
    def process_request(self, req):
        presel = req.args.get('preselected')
        if presel and (presel + '/').startswith(req.href.browser() + '/'):
            req.redirect(presel)

        path = req.args.get('path', '/')
        rev = req.args.get('rev', '')
        if rev.lower() in ('', 'head'):
            rev = None
        format = req.args.get('format')
        order = req.args.get('order', 'name').lower()
        desc = 'desc' in req.args

        rm = RepositoryManager(self.env)
        all_repositories = rm.get_all_repositories()
        reponame, repos, path = rm.get_repository_by_path(path)

        # Repository index
        show_index = not reponame and path == '/'
        if show_index:
            if repos and (as_bool(all_repositories[''].get('hidden'))
                          or not repos.is_viewable(req.perm)):
                repos = None

        if not repos and reponame:
            raise ResourceNotFound(_("Repository '%(repo)s' not found",
                                     repo=reponame))

        if reponame and reponame != repos.reponame: # Redirect alias
            qs = req.query_string
            req.redirect(req.href.browser(repos.reponame or None, path)
                         + ('?' + qs if qs else ''))
        reponame = repos.reponame if repos else None

        # Find node for the requested path/rev
        context = web_context(req)
        node = None
        changeset = None
        display_rev = lambda rev: rev
        if repos:
            try:
                if rev:
                    rev = repos.normalize_rev(rev)
                # If `rev` is `None`, we'll try to reuse `None` consistently,
                # as a special shortcut to the latest revision.
                rev_or_latest = rev or repos.youngest_rev
                node = get_existing_node(req, repos, path, rev_or_latest)
            except NoSuchChangeset as e:
                raise ResourceNotFound(e, _('Invalid changeset number'))
            if node:
                try:
                    # use changeset instance to retrieve branches and tags
                    changeset = repos.get_changeset(node.rev)
                except NoSuchChangeset:
                    pass

            context = context.child(repos.resource.child(self.realm, path,
                                                   version=rev_or_latest))
            display_rev = repos.display_rev

        # Prepare template data
        path_links = get_path_links(req.href, reponame, path, rev,
                                    order, desc)

        repo_data = dir_data = file_data = None
        if show_index:
            repo_data = self._render_repository_index(
                                        context, all_repositories, order, desc)
        if node:
            if not node.is_viewable(req.perm):
                raise PermissionError('BROWSER_VIEW' if node.isdir else
                                      'FILE_VIEW', node.resource, self.env)
            if node.isdir:
                if format in ('zip',): # extension point here...
                    self._render_zip(req, context, repos, node, rev)
                    # not reached
                dir_data = self._render_dir(req, repos, node, rev, order, desc)
            elif node.isfile:
                file_data = self._render_file(req, context, repos, node, rev)

        if not repos and not (repo_data and repo_data['repositories']):
            # If no viewable repositories, check permission instead of
            # repos.is_viewable()
            req.perm.require('BROWSER_VIEW')
            if show_index:
                raise ResourceNotFound(_("No viewable repositories"))
            else:
                raise ResourceNotFound(_("No node %(path)s", path=path))

        quickjump_data = properties_data = None
        if node and not req.is_xhr:
            properties_data = self.render_properties(
                    'browser', context, node.get_properties())
            quickjump_data = list(repos.get_quickjump_entries(rev))

        data = {
            'context': context, 'reponame': reponame, 'repos': repos,
            'repoinfo': all_repositories.get(reponame or ''),
            'path': path, 'rev': node and node.rev, 'stickyrev': rev,
            'display_rev': display_rev, 'changeset': changeset,
            'created_path': node and node.created_path,
            'created_rev': node and node.created_rev,
            'properties': properties_data,
            'path_links': path_links,
            'order': order, 'desc': 1 if desc else None,
            'repo': repo_data, 'dir': dir_data, 'file': file_data,
            'quickjump_entries': quickjump_data,
            'wiki_format_messages':
                self.config['changeset'].getbool('wiki_format_messages'),
        }
        if req.is_xhr: # render and return the content only
            return 'dir_entries.html', data

        if dir_data or repo_data:
            add_script(req, 'common/js/expand_dir.js')
            add_script(req, 'common/js/keyboard_nav.js')

        # Links for contextual navigation
        if node:
            if node.isfile:
                prev_rev = repos.previous_rev(rev=node.created_rev,
                                              path=node.created_path)
                if prev_rev:
                    href = req.href.browser(reponame,
                                            node.created_path, rev=prev_rev)
                    add_link(req, 'prev', href,
                             _('Revision %(num)s', num=display_rev(prev_rev)))
                if rev is not None:
                    add_link(req, 'up', req.href.browser(reponame,
                                                         node.created_path))
                next_rev = repos.next_rev(rev=node.created_rev,
                                          path=node.created_path)
                if next_rev:
                    href = req.href.browser(reponame, node.created_path,
                                            rev=next_rev)
                    add_link(req, 'next', href,
                             _('Revision %(num)s', num=display_rev(next_rev)))
                prevnext_nav(req, _('Previous Revision'), _('Next Revision'),
                             _('Latest Revision'))
            else:
                if path != '/':
                    add_link(req, 'up', path_links[-2]['href'],
                             _('Parent directory'))
                add_ctxtnav(req, tag.a(_('Last Change'),
                            href=req.href.changeset(node.created_rev, reponame,
                                                    node.created_path)))
            if node.isfile:
                annotate = data['file']['annotate']
                if annotate:
                    add_ctxtnav(req, _('Normal'),
                                title=_('View file without annotations'),
                                href=req.href.browser(reponame,
                                                      node.created_path,
                                                      rev=rev))
                if annotate != 'blame':
                    add_ctxtnav(req, _('Blame'),
                                title=_('Annotate each line with the last '
                                        'changed revision '
                                        '(this can be time consuming...)'),
                                href=req.href.browser(reponame,
                                                      node.created_path,
                                                      rev=rev,
                                                      annotate='blame'))
            add_ctxtnav(req, _('Revision Log'),
                        href=req.href.log(reponame, path, rev=rev))
            path_url = repos.get_path_url(path, rev)
            if path_url:
                if path_url.startswith('//'):
                    path_url = req.scheme + ':' + path_url
                add_ctxtnav(req, _('Repository URL'), href=path_url)

        add_stylesheet(req, 'common/css/browser.css')
        return 'browser.html', data
コード例 #18
0
class RepositoryManager(Component):
    """Adds creation, modification and deletion of repositories.

    This class extends Trac's `RepositoryManager` and adds some
    capabilities that allow users to create and manage repositories.
    The original `RepositoryManager` *just* allows adding and removing
    existing repositories from Trac's database, which means that still
    someone must do some shell work on the server.

    To work nicely together with manually created and added repositories
    a new `ManagedRepository` class is used to mark the ones that can be
    handled by this module. It also implements forking, if the connector
    supports that, which creates instances of `ForkedRepository`.
    """

    base_dir = Option('repository-manager',
                      'base_dir',
                      'repositories',
                      doc="""The base folder in which repositories will be
                             created.
                             """)
    owner_as_maintainer = BoolOption('repository-manager',
                                     'owner_as_maintainer',
                                     True,
                                     doc="""If true, the owner will have the
                                            role of a maintainer, too.
                                            Otherwise, he will only act as an
                                            administrator for his repositories.
                                            """)

    connectors = ExtensionPoint(IAdministrativeRepositoryConnector)

    manager = None

    roles = ('maintainer', 'writer', 'reader')

    def __init__(self):
        self.manager = TracRepositoryManager(self.env)

    def get_supported_types(self):
        """Return the list of supported repository types."""
        types = set(type for connector in self.connectors
                    for (type, prio) in connector.get_supported_types() or []
                    if prio >= 0)
        return list(types & set(self.manager.get_supported_types()))

    def get_forkable_types(self):
        """Return the list of forkable repository types."""
        return list(type for type in self.get_supported_types()
                    if self.can_fork(type))

    def can_fork(self, type):
        """Return whether the given repository type can be forked."""
        return self._get_repository_connector(type).can_fork(type)

    def can_delete_changesets(self, type):
        """Return whether the given repository type can delete changesets."""
        return self._get_repository_connector(type).can_delete_changesets(type)

    def can_ban_changesets(self, type):
        """Return whether the given repository type can ban changesets."""
        return self._get_repository_connector(type).can_ban_changesets(type)

    def get_forkable_repositories(self):
        """Return a dictionary of repository information, indexed by
        name and including only repositories that can be forked."""
        repositories = self.manager.get_all_repositories()
        result = {}
        for key in repositories:
            if repositories[key]['type'] in self.get_forkable_types():
                result[key] = repositories[key]['name']
        return result

    def get_managed_repositories(self):
        """Return the list of existing managed repositories."""
        repositories = self.manager.get_all_repositories()
        result = {}
        for key in repositories:
            try:
                self.get_repository(repositories[key]['name'], True)
                result[key] = repositories[key]['name']
            except:
                pass
        return result

    def get_repository(self, name, convert_to_managed=False):
        """Retrieve the appropriate repository for the given name.

        Converts the found repository into a `ManagedRepository`, if
        requested. In that case, expect an exception if the found
        repository was not created using this `RepositoryManager`.
        """
        repo = self.manager.get_repository(name)
        if repo and convert_to_managed:
            convert_managed_repository(self.env, repo)
        return repo

    def get_repository_by_id(self, id, convert_to_managed=False):
        """Retrieve a matching `Repository` for the given id."""
        repositories = self.manager.get_all_repositories()
        for name, info in repositories.iteritems():
            if info['id'] == int(id):
                return self.get_repository(name, convert_to_managed)
        return None

    def get_repository_by_path(self, path):
        """Retrieve a matching `Repository` for the given path."""
        return self.manager.get_repository_by_path(path)

    def get_base_directory(self, type):
        """Get the base directory for the given repository type."""
        return os.path.join(self.env.path, self.base_dir, type)

    def create(self, repo):
        """Create a new empty repository.

         * Checks if the new repository can be created and added
         * Prepares the filesystem
         * Uses an appropriate connector to create and initialize the
           repository
         * Postprocesses the filesystem (modes)
         * Inserts everything into the database and synchronizes Trac
        """
        if self.get_repository(repo['name']) or os.path.lexists(repo['dir']):
            raise TracError(_("Repository or directory already exists."))

        self._prepare_base_directory(repo['dir'])

        self._get_repository_connector(repo['type']).create(repo)

        self._adjust_modes(repo['dir'])

        with self.env.db_transaction as db:
            id = self.manager.get_repository_id(repo['name'])
            roles = list((id, role + 's', '') for role in self.roles)
            db.executemany(
                "INSERT INTO repository (id, name, value) VALUES (%s, %s, %s)",
                [(id, 'dir', repo['dir']), (id, 'type', repo['type']),
                 (id, 'owner', repo['owner'])] + roles)
            self.manager.reload_repositories()
        self.manager.get_repository(repo['name']).sync(None, True)
        self.update_auth_files()

    def fork_local(self, repo):
        """Fork a local repository.

         * Checks if the new repository can be created and added
         * Checks if the origin exists and can be forked
         * The filesystem is obviously already prepared
         * Uses an appropriate connector to fork the repository
         * Postprocesses the filesystem (modes)
         * Inserts everything into the database and synchronizes Trac
        """
        if self.get_repository(repo['name']) or os.path.lexists(repo['dir']):
            raise TracError(_("Repository or directory already exists."))

        origin = self.get_repository(repo['origin'], True)
        if not origin:
            raise TracError(_("Origin for local fork does not exist."))
        if origin.type != repo['type']:
            raise TracError(
                _("Fork of local repository must have same type "
                  "as origin."))
        repo.update({'origin_url': 'file://' + origin.directory})

        self._prepare_base_directory(repo['dir'])

        self._get_repository_connector(repo['type']).fork(repo)

        self._adjust_modes(repo['dir'])

        with self.env.db_transaction as db:
            id = self.manager.get_repository_id(repo['name'])
            roles = list((id, role + 's', '') for role in self.roles)
            db.executemany(
                "INSERT INTO repository (id, name, value) VALUES (%s, %s, %s)",
                [(id, 'dir', repo['dir']), (id, 'type', repo['type']),
                 (id, 'owner', repo['owner']),
                 (id, 'description', origin.description),
                 (id, 'origin', origin.id),
                 (id, 'inherit_readers', True)] + roles)
            self.manager.reload_repositories()
        self.manager.get_repository(repo['name']).sync(None, True)
        self.update_auth_files()

    def modify(self, repo, data):
        """Modify an existing repository."""
        convert_managed_repository(self.env, repo)
        if repo.directory != data['dir']:
            shutil.move(repo.directory, data['dir'])
        with self.env.db_transaction as db:
            db.executemany(
                "UPDATE repository SET value = %s WHERE id = %s AND name = %s",
                [(data[key], repo.id, key) for key in data])
            self.manager.reload_repositories()
        if repo.directory != data['dir']:
            repo = self.get_repository(data['name'])
            repo.sync(clean=True)
        self.update_auth_files()

    def remove(self, repo, delete):
        """Remove an existing repository.

        Depending on the parameter delete this method also removes the
        repository from the filesystem. This can not be undone.
        """
        convert_managed_repository(self.env, repo)
        if delete:
            shutil.rmtree(repo.directory)
        with self.env.db_transaction as db:
            db("DELETE FROM repository WHERE id = %d" % repo.id)
            db("DELETE FROM revision WHERE repos = %d" % repo.id)
            db("DELETE FROM node_change WHERE repos = %d" % repo.id)
        self.manager.reload_repositories()
        self.update_auth_files()

    def delete_changeset(self, repo, rev, ban):
        """Delete a changeset from a managed repository, if supported.

        Depending on the parameter ban this method also marks the
        changeset to be kept out of the repository. That features needs
        special support by the used scm.
        """
        convert_managed_repository(self.env, repo)
        self._get_repository_connector(repo.type).delete_changeset(
            repo, rev, ban)

    def add_role(self, repo, role, subject):
        """Add a role for the given repository."""
        assert role in self.roles
        convert_managed_repository(self.env, repo)
        role_attr = '_' + role + 's'
        setattr(repo, role_attr, getattr(repo, role_attr) | set([subject]))
        self._update_roles_in_db(repo)

    def revoke_roles(self, repo, roles):
        """Revoke a list of `role, subject` pairs."""
        convert_managed_repository(self.env, repo)
        for role, subject in roles:
            role_attr = '_' + role + 's'
            config = getattr(repo, role_attr)
            config = config - set([subject])
            setattr(repo, role_attr, getattr(repo, role_attr) - set([subject]))
        self._update_roles_in_db(repo)

    def update_auth_files(self):
        """Rewrites all configured auth files for all managed
        repositories.
        """
        types = self.get_supported_types()
        all_repositories = []
        for repo in self.manager.get_real_repositories():
            try:
                convert_managed_repository(self.env, repo)
                all_repositories.append(repo)
            except:
                pass
        for type in types:
            repos = [repo for repo in all_repositories if repo.type == type]
            self._get_repository_connector(type).update_auth_files(repos)

        authz_source_file = AuthzSourcePolicy(self.env).authz_file
        if authz_source_file:
            authz_source_path = os.path.join(self.env.path, authz_source_file)

            authz = ConfigParser()

            groups = set()
            for repo in all_repositories:
                groups |= {
                    name
                    for name in repo.maintainers() if name[0] == '@'
                }
                groups |= {name for name in repo.writers() if name[0] == '@'}
                groups |= {name for name in repo.readers() if name[0] == '@'}

            authz.add_section('groups')
            for group in groups:
                members = expand_user_set(self.env, [group])
                authz.set('groups', group[1:], ', '.join(sorted(members)))
            authenticated = sorted({u[0] for u in self.env.get_known_users()})
            authz.set('groups', 'authenticated', ', '.join(authenticated))

            for repo in all_repositories:
                section = repo.reponame + ':/'
                authz.add_section(section)
                r = repo.maintainers() | repo.writers() | repo.readers()

                def apply_user_list(users, action):
                    if not users:
                        return
                    if 'anonymous' in users:
                        authz.set(section, '*', action)
                        return
                    if 'authenticated' in users:
                        authz.set(section, '@authenticated', action)
                        return
                    for user in sorted(users):
                        authz.set(section, user, action)

                apply_user_list(r, 'r')

            self._prepare_base_directory(authz_source_path)
            with open(authz_source_path, 'wb') as authz_file:
                authz.write(authz_file)
            try:
                modes = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP
                os.chmod(authz_source_path, modes)
            except:
                pass

    ### Private methods
    def _get_repository_connector(self, repo_type):
        """Get the matching connector with maximum priority."""
        return max(((connector, type, prio) for connector in self.connectors
                    for (type, prio) in connector.get_supported_types()
                    if prio >= 0 and type == repo_type),
                   key=lambda x: x[2])[0]

    def _prepare_base_directory(self, directory):
        """Create the base directories and set the correct modes."""
        base = os.path.dirname(directory)
        original_umask = os.umask(0)
        try:
            os.makedirs(base, stat.S_IRWXU | stat.S_IRWXG)
        except OSError, e:
            if e.errno == errno.EEXIST and os.path.isdir(base):
                pass
            else:
                raise
        finally:
コード例 #19
0
ファイル: browser.py プロジェクト: pkdevbox/trac
    def expand_macro(self, formatter, name, content):
        args, kwargs = parse_args(content)
        format = kwargs.get("format", "compact")
        glob = kwargs.get("glob", "*")
        order = kwargs.get("order")
        desc = as_bool(kwargs.get("desc", 0))

        rm = RepositoryManager(self.env)
        all_repos = dict(rdata for rdata in rm.get_all_repositories().items() if fnmatchcase(rdata[0], glob))

        if format == "table":
            repo = self._render_repository_index(formatter.context, all_repos, order, desc)

            add_stylesheet(formatter.req, "common/css/browser.css")
            wiki_format_messages = self.config["changeset"].getbool("wiki_format_messages")
            data = {
                "repo": repo,
                "order": order,
                "desc": 1 if desc else None,
                "reponame": None,
                "path": "/",
                "stickyrev": None,
                "wiki_format_messages": wiki_format_messages,
            }
            from trac.web.chrome import Chrome

            return Chrome(self.env).render_template(formatter.req, "repository_index.html", data, None, fragment=True)

        def get_repository(reponame):
            try:
                return rm.get_repository(reponame)
            except TracError:
                return

        all_repos = [(reponame, get_repository(reponame)) for reponame in all_repos]
        all_repos = sorted(
            (
                (reponame, repos)
                for reponame, repos in all_repos
                if repos and not as_bool(repos.params.get("hidden")) and repos.is_viewable(formatter.perm)
            ),
            reverse=desc,
        )

        def repolink(reponame, repos):
            label = reponame or _("(default)")
            return Markup(
                tag.a(
                    label,
                    title=_("View repository %(repo)s", repo=label),
                    href=formatter.href.browser(repos.reponame or None),
                )
            )

        if format == "list":
            return tag.dl(
                [
                    tag(tag.dt(repolink(reponame, repos)), tag.dd(repos.params.get("description")))
                    for reponame, repos in all_repos
                ]
            )
        else:  # compact
            return Markup(", ").join([repolink(reponame, repos) for reponame, repos in all_repos])
コード例 #20
0
ファイル: browser.py プロジェクト: pkdevbox/trac
    def process_request(self, req):
        presel = req.args.get("preselected")
        if presel and (presel + "/").startswith(req.href.browser() + "/"):
            req.redirect(presel)

        path = req.args.get("path", "/")
        rev = req.args.get("rev", "")
        if rev.lower() in ("", "head"):
            rev = None
        format = req.args.get("format")
        order = req.args.get("order", "name").lower()
        desc = "desc" in req.args

        rm = RepositoryManager(self.env)
        all_repositories = rm.get_all_repositories()
        reponame, repos, path = rm.get_repository_by_path(path)

        # Repository index
        show_index = not reponame and path == "/"
        if show_index:
            if repos and (as_bool(all_repositories[""].get("hidden")) or not repos.is_viewable(req.perm)):
                repos = None

        if not repos and reponame:
            raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame))

        if reponame and reponame != repos.reponame:  # Redirect alias
            qs = req.query_string
            req.redirect(req.href.browser(repos.reponame or None, path) + ("?" + qs if qs else ""))
        reponame = repos.reponame if repos else None

        # Find node for the requested path/rev
        context = web_context(req)
        node = None
        changeset = None
        display_rev = lambda rev: rev
        if repos:
            try:
                if rev:
                    rev = repos.normalize_rev(rev)
                # If `rev` is `None`, we'll try to reuse `None` consistently,
                # as a special shortcut to the latest revision.
                rev_or_latest = rev or repos.youngest_rev
                node = get_existing_node(req, repos, path, rev_or_latest)
            except NoSuchChangeset as e:
                raise ResourceNotFound(e, _("Invalid changeset number"))
            if node:
                try:
                    # use changeset instance to retrieve branches and tags
                    changeset = repos.get_changeset(node.rev)
                except NoSuchChangeset:
                    pass

            context = context.child(repos.resource.child(self.realm, path, version=rev_or_latest))
            display_rev = repos.display_rev

        # Prepare template data
        path_links = get_path_links(req.href, reponame, path, rev, order, desc)

        repo_data = dir_data = file_data = None
        if show_index:
            repo_data = self._render_repository_index(context, all_repositories, order, desc)
        if node:
            if not node.is_viewable(req.perm):
                raise PermissionError("BROWSER_VIEW" if node.isdir else "FILE_VIEW", node.resource, self.env)
            if node.isdir:
                if format in ("zip",):  # extension point here...
                    self._render_zip(req, context, repos, node, rev)
                    # not reached
                dir_data = self._render_dir(req, repos, node, rev, order, desc)
            elif node.isfile:
                file_data = self._render_file(req, context, repos, node, rev)

        if not repos and not (repo_data and repo_data["repositories"]):
            # If no viewable repositories, check permission instead of
            # repos.is_viewable()
            req.perm.require("BROWSER_VIEW")
            if show_index:
                raise ResourceNotFound(_("No viewable repositories"))
            else:
                raise ResourceNotFound(_("No node %(path)s", path=path))

        quickjump_data = properties_data = None
        if node and not req.is_xhr:
            properties_data = self.render_properties("browser", context, node.get_properties())
            quickjump_data = list(repos.get_quickjump_entries(rev))

        data = {
            "context": context,
            "reponame": reponame,
            "repos": repos,
            "repoinfo": all_repositories.get(reponame or ""),
            "path": path,
            "rev": node and node.rev,
            "stickyrev": rev,
            "display_rev": display_rev,
            "changeset": changeset,
            "created_path": node and node.created_path,
            "created_rev": node and node.created_rev,
            "properties": properties_data,
            "path_links": path_links,
            "order": order,
            "desc": 1 if desc else None,
            "repo": repo_data,
            "dir": dir_data,
            "file": file_data,
            "quickjump_entries": quickjump_data,
            "wiki_format_messages": self.config["changeset"].getbool("wiki_format_messages"),
            "xhr": req.is_xhr,  # Remove in 1.3.1
        }
        if req.is_xhr:  # render and return the content only
            return "dir_entries.html", data, None

        if dir_data or repo_data:
            add_script(req, "common/js/expand_dir.js")
            add_script(req, "common/js/keyboard_nav.js")

        # Links for contextual navigation
        if node:
            if node.isfile:
                prev_rev = repos.previous_rev(rev=node.created_rev, path=node.created_path)
                if prev_rev:
                    href = req.href.browser(reponame, node.created_path, rev=prev_rev)
                    add_link(req, "prev", href, _("Revision %(num)s", num=display_rev(prev_rev)))
                if rev is not None:
                    add_link(req, "up", req.href.browser(reponame, node.created_path))
                next_rev = repos.next_rev(rev=node.created_rev, path=node.created_path)
                if next_rev:
                    href = req.href.browser(reponame, node.created_path, rev=next_rev)
                    add_link(req, "next", href, _("Revision %(num)s", num=display_rev(next_rev)))
                prevnext_nav(req, _("Previous Revision"), _("Next Revision"), _("Latest Revision"))
            else:
                if path != "/":
                    add_link(req, "up", path_links[-2]["href"], _("Parent directory"))
                add_ctxtnav(
                    req, tag.a(_("Last Change"), href=req.href.changeset(node.created_rev, reponame, node.created_path))
                )
            if node.isfile:
                annotate = data["file"]["annotate"]
                if annotate:
                    add_ctxtnav(
                        req,
                        _("Normal"),
                        title=_("View file without annotations"),
                        href=req.href.browser(reponame, node.created_path, rev=rev),
                    )
                if annotate != "blame":
                    add_ctxtnav(
                        req,
                        _("Blame"),
                        title=_(
                            "Annotate each line with the last " "changed revision " "(this can be time consuming...)"
                        ),
                        href=req.href.browser(reponame, node.created_path, rev=rev, annotate="blame"),
                    )
            add_ctxtnav(req, _("Revision Log"), href=req.href.log(reponame, path, rev=rev))
            path_url = repos.get_path_url(path, rev)
            if path_url:
                if path_url.startswith("//"):
                    path_url = req.scheme + ":" + path_url
                add_ctxtnav(req, _("Repository URL"), href=path_url)

        add_stylesheet(req, "common/css/browser.css")
        return "browser.html", data, None