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()
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
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, ... }
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
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)
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])
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])
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
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)
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')])
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
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')
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")])
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()
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])
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
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
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:
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])
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