def _format_link(self, formatter, ns, match, label, fullmatch=None): if ns == 'log1': groups = fullmatch.groupdict() it_log = groups.get('it_log') revs = groups.get('log_revs') path = groups.get('log_path') or '/' target = '%s%s@%s' % (it_log, path, revs) # prepending it_log is needed, as the helper expects it there intertrac = formatter.shorthand_intertrac_helper( 'log', target, label, fullmatch) if intertrac: return intertrac path, query, fragment = formatter.split_link(path) else: assert ns in ('log', 'log2') if ns == 'log': match, query, fragment = formatter.split_link(match) else: query = fragment = '' match = ''.join(reversed(match.split('/', 1))) path = match revs = '' if self.LOG_LINK_RE.match(match): indexes = [sep in match and match.index(sep) for sep in ':@'] idx = min([i for i in indexes if i is not False]) path, revs = match[:idx], match[idx+1:] rm = RepositoryManager(self.env) try: reponame, repos, path = rm.get_repository_by_path(path) if not reponame: reponame = rm.get_default_repository(formatter.context) if reponame is not None: repos = rm.get_repository(reponame) if repos: if 'LOG_VIEW' in formatter.perm(repos.resource): reponame = repos.reponame or None path = path or '/' revranges = RevRanges(repos, revs) if revranges.has_ranges(): href = formatter.href.log(reponame, path, revs=unicode(revranges)) else: # try to resolve if single rev repos.normalize_rev(revs) href = formatter.href.log(reponame, path, rev=revs or None) if query and '?' in href: query = '&' + query[1:] return tag.a(label, class_='source', href=href + query + fragment) errmsg = _("No permission to view change log") elif reponame: errmsg = _("Repository '%(repo)s' not found", repo=reponame) else: errmsg = _("No default repository defined") except TracError as e: errmsg = to_unicode(e) return tag.a(label, class_='missing source', title=errmsg)
def process_request(self, req): if req.method != 'POST': msg = u'Method not allowed (%s)\n' % req.method req.send(msg.encode('utf-8'), 'text/plain', 405) if req.args.get('token') != self.token: msg = u'Invalid token (%s)\n' % req.args.get('token') req.send(msg.encode('utf-8'), 'text/plain', 403) path = req.args.get('path', '/') rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) output = u'Running hook on %s\n' % (reponame or '(default)') if self.autofetch: git = repos.git.repo output += u'* Running git fetch\n' output += git.fetch() output += u'* Updating references\n' remote_refs = git.for_each_ref( "--format=%(refname)", "refs/remotes/origin").split() for remote_ref in remote_refs: local_ref = remote_ref.replace('remotes/origin', 'heads', 1) output += git.update_ref(local_ref, remote_ref) data = req.args.get('payload') if data: revs = [commit['id'] for commit in json.loads(data)['commits']] if revs: output += u'* Adding changesets %s\n' % u', '.join(revs) rm.notify('changeset_added', reponame, revs) req.send(output.encode('utf-8'), 'text/plain', 200 if output else 204)
def _get_source(self, formatter, source_obj, dest_format): repos_mgr = RepositoryManager(self.env) try: #0.12+ repos_name, repos, source_obj = repos_mgr.get_repository_by_path( source_obj) except AttributeError, e: #0.11 repos = repos_mgr.get_repository(formatter.req.authname)
def _read_source_from_repos(self, formatter, src_path): repos_mgr = RepositoryManager(self.env) try: #0.12+ repos_name, repos, source_obj = repos_mgr.get_repository_by_path( src_path) except AttributeError, e: #0.11 repos = repos_mgr.get_repository(formatter.req.authname)
def _get_vcs_folders(self, req, q, dirname, prefix): rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(dirname) repo_entries = {'text': _('Suggestions'), 'children': [], } if repos: try: entries = ({'id': '/' + pathjoin(repos.reponame, e.path), 'text': '/' + pathjoin(repos.reponame, e.path), 'is_favorite': False } for e in repos.get_node(path).get_entries() if e.can_view(req.perm) and e.name.lower().startswith(prefix) and e.isdir ) repo_entries['children'].extend(entries) if q.endswith('/'): repo_entries['children'].append({'id': q, 'text': q, 'is_favorite': False } ) except NoSuchNode: pass return repo_entries
def _format_link(self, formatter, ns, match, label, fullmatch=None): if ns == 'log1': groups = fullmatch.groupdict() it_log = groups.get('it_log') revs = groups.get('log_revs') path = groups.get('log_path') or '/' target = '%s%s@%s' % (it_log, path, revs) # prepending it_log is needed, as the helper expects it there intertrac = formatter.shorthand_intertrac_helper( 'log', target, label, fullmatch) if intertrac: return intertrac path, query, fragment = formatter.split_link(path) else: assert ns in ('log', 'log2') if ns == 'log': match, query, fragment = formatter.split_link(match) else: query = fragment = '' match = ''.join(reversed(match.split('/', 1))) path = match revs = '' if self.LOG_LINK_RE.match(match): indexes = [sep in match and match.index(sep) for sep in ':@'] idx = min([i for i in indexes if i is not False]) path, revs = match[:idx], match[idx+1:] rm = RepositoryManager(self.env) try: reponame, repos, path = rm.get_repository_by_path(path) if not reponame: reponame = rm.get_default_repository(formatter.context) if reponame is not None: repos = rm.get_repository(reponame) if repos: if 'LOG_VIEW' in formatter.perm: reponame = repos.reponame or None path = path or '/' revranges = RevRanges(repos, revs) if revranges.has_ranges(): href = formatter.href.log(reponame, path, revs=unicode(revranges)) else: # try to resolve if single rev repos.normalize_rev(revs) href = formatter.href.log(reponame, path, rev=revs or None) if query and '?' in href: query = '&' + query[1:] return tag.a(label, class_='source', href=href + query + fragment) errmsg = _("No permission to view change log") elif reponame: errmsg = _("Repository '%(repo)s' not found", repo=reponame) else: errmsg = _("No default repository defined") except TracError, e: errmsg = to_unicode(e)
def _get_repository(env, req): '''From env and req identify and return (reponame, repository, path), removing reponame from path in the process. ''' path = req.args.get('path') repo_mgr = RepositoryManager(env) reponame, repos, path = repo_mgr.get_repository_by_path(path) return reponame, repos, path
def _get_source(self, formatter, source_obj, dest_format, start, end, lineno): repos_mgr = RepositoryManager(self.env) repos_name, repos, source_obj = \ repos_mgr.get_repository_by_path(source_obj) path, rev = _split_path(source_obj) try: node = repos.get_node(path, rev) except (NoSuchChangeset, NoSuchNode), e: return system_message(e), None, None, None
def _format_link(self, formatter, ns, match, label, fullmatch=None): if ns == "log1": groups = fullmatch.groupdict() it_log = groups.get("it_log") revs = groups.get("log_revs") path = groups.get("log_path") or "/" target = "%s%s@%s" % (it_log, path, revs) # prepending it_log is needed, as the helper expects it there intertrac = formatter.shorthand_intertrac_helper("log", target, label, fullmatch) if intertrac: return intertrac path, query, fragment = formatter.split_link(path) else: assert ns in ("log", "log2") if ns == "log": match, query, fragment = formatter.split_link(match) else: query = fragment = "" match = "".join(reversed(match.split("/", 1))) path = match revs = "" if self.LOG_LINK_RE.match(match): indexes = [sep in match and match.index(sep) for sep in ":@"] idx = min([i for i in indexes if i is not False]) path, revs = match[:idx], match[idx + 1 :] rm = RepositoryManager(self.env) try: reponame, repos, path = rm.get_repository_by_path(path) if not reponame: reponame = rm.get_default_repository(formatter.context) if reponame is not None: repos = rm.get_repository(reponame) if repos: if "LOG_VIEW" in formatter.perm: reponame = repos.reponame or None path = path or "/" revranges = RevRanges(repos, revs) if revranges.has_ranges(): href = formatter.href.log(reponame, path, revs=unicode(revranges)) else: # try to resolve if single rev repos.normalize_rev(revs) href = formatter.href.log(reponame, path, rev=revs or None) if query and "?" in href: query = "&" + query[1:] return tag.a(label, class_="source", href=href + query + fragment) errmsg = _("No permission to view change log") elif reponame: errmsg = _("Repository '%(repo)s' not found", repo=reponame) else: errmsg = _("No default repository defined") except TracError as e: errmsg = to_unicode(e) return tag.a(label, class_="missing source", title=errmsg)
def 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 _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 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 process_request(self, req): if req.method != 'POST': msg = u'Method not allowed (%s)\n' % req.method self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 405) path = req.args['path'] rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if path != '/': msg = u'No such repository (%s)\n' % path self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) output = u'Running hook on %s\n' % (reponame or '(default)') output += u'* Updating clone\n' repos.git.repo.remote('update', '--prune') output += u'* Synchronizing with clone\n' repos.git.sync() try: payload = json.loads(req.args['payload']) revs = [ commit['id'] for commit in payload['commits'] if commit['distinct'] ] except (ValueError, KeyError): msg = u'Invalid payload\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) branches = self.get_branches(reponame) added, skipped, unknown = classify_commits(revs, repos, branches) if added: output += u'* Adding %s\n' % describe_commits(added) # This is where Trac gets notified of the commits in the changeset rm.notify('changeset_added', reponame, added) if skipped: output += u'* Skipping %s\n' % describe_commits(skipped) if unknown: output += u'* Unknown %s\n' % describe_commits(unknown) self.log.error(u'Payload contains unknown %s', describe_commits(unknown)) for line in output.splitlines(): self.log.debug(line) req.send(output.encode('utf-8'), 'text/plain', 200 if output else 204)
def _get_source(self, formatter, source_obj, dest_format): repos_mgr = RepositoryManager(self.env) try: # 0.12+ repos_name, repos, source_obj = \ repos_mgr.get_repository_by_path(source_obj) except AttributeError: # 0.11 repos = repos_mgr.get_repository(formatter.req.authname) path, rev = _split_path(source_obj) try: node = repos.get_node(path, rev) except (NoSuchChangeset, NoSuchNode), e: return system_message(e), None, None
def process_request(self, req): rev = req.args.get("rev") path = req.args.get("path") rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) gh_repo = self.get_gh_repo(reponame) try: rev = repos.normalize_rev(rev) except NoSuchChangeset, e: raise ResourceNotFound(e.message, _("Invalid Changeset Number"))
def _get_link_info(self, path, rev, href, perm): rm = RepositoryManager(self.env) node = raw_href = title = None try: reponame, repos, npath = rm.get_repository_by_path(path) node = get_allowed_node(repos, npath, rev, perm) if node is not None: raw_href = self._get_download_href(href, repos, node, rev) title = _("Download") if node.isfile else _("Download as Zip archive") except TracError: pass return (node, raw_href, title)
def process_request(self, req): if req.method != 'POST': msg = u'Method not allowed (%s)\n' % req.method self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 405) path = req.args['path'] rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if path != '/': msg = u'No such repository (%s)\n' % path self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) output = u'Running hook on %s\n' % (reponame or '(default)') output += u'* Updating clone\n' repos.git.repo.remote('update', '--prune') output += u'* Synchronizing with clone\n' repos.git.sync() try: payload = json.loads(req.args['payload']) revs = [commit['id'] for commit in payload['commits'] if commit['distinct']] except (ValueError, KeyError): msg = u'Invalid payload\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) branches = self.get_branches(reponame) added, skipped, unknown = classify_commits(revs, repos, branches) if added: output += u'* Adding %s\n' % describe_commits(added) # This is where Trac gets notified of the commits in the changeset rm.notify('changeset_added', reponame, added) if skipped: output += u'* Skipping %s\n' % describe_commits(skipped) if unknown: output += u'* Unknown %s\n' % describe_commits(unknown) self.log.error(u'Payload contains unknown %s', describe_commits(unknown)) for line in output.splitlines(): self.log.debug(line) req.send(output.encode('utf-8'), 'text/plain', 200 if output else 204)
def _get_link_info(self, path, rev, href, perm): rm = RepositoryManager(self.env) node = raw_href = title = None try: reponame, repos, npath = rm.get_repository_by_path(path) node = get_allowed_node(repos, npath, rev, perm) if node is not None: raw_href = self._get_download_href(href, repos, node, rev) title = _("Download") if node.isfile \ else _("Download as Zip archive") except TracError: pass return node, raw_href, title
def process_request(self, req): if not self.gh_repo: return super(GitHubBrowser, self).process_request(req) rev = req.args.get('rev') path = req.args.get('path') rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) try: rev = repos.normalize_rev(rev) except NoSuchChangeset, e: raise ResourceNotFound(e.message, _('Invalid Changeset Number'))
def process_request(self, req): if req.method != "POST": msg = u"Method not allowed (%s)\n" % req.method self.log.warning(msg.rstrip("\n")) req.send(msg.encode("utf-8"), "text/plain", 405) path = req.args["path"] rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if path != "/": msg = u"No such repository (%s)\n" % path self.log.warning(msg.rstrip("\n")) req.send(msg.encode("utf-8"), "text/plain", 400) output = u"Running hook on %s\n" % (reponame or "(default)") output += u"* Updating clone\n" output += repos.git.repo.remote("update", "--prune") try: payload = json.loads(req.args["payload"]) revs = [commit["id"] for commit in payload["commits"] if commit["distinct"]] except (ValueError, KeyError): msg = u"Invalid payload\n" self.log.warning(msg.rstrip("\n")) req.send(msg.encode("utf-8"), "text/plain", 400) branches = self.get_branches(reponame) added_revs, skipped_revs = [], [] for rev in revs: if rev_in_branches(repos.get_changeset(rev), branches): added_revs.append(rev) else: skipped_revs.append(rev) if added_revs: output += u"* Adding %s\n" % describe_commits(added_revs) # This is where Trac gets notified of the commits in the changeset rm.notify("changeset_added", reponame, added_revs) if skipped_revs: output += u"* Skipping %s\n" % describe_commits(skipped_revs) for line in output.splitlines(): self.log.debug(line) req.send(output.encode("utf-8"), "text/plain", 200 if output else 204)
def process_request(self, req): rev = req.args.get('rev') path = req.args.get('path') rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) gh_repo = self.get_gh_repo(reponame) rev = repos.normalize_rev(rev) if path and path != '/': path = path.lstrip('/') # GitHub will s/blob/tree/ if the path is a directory url = 'https://github.com/%s/blob/%s/%s' % (gh_repo, rev, path) else: url = 'https://github.com/%s/commit/%s' % (gh_repo, rev) req.redirect(url)
def process_request(self, req): if not self.repository: # pragma: no cover return super(GitHubBrowser, self).process_request(req) rev = req.args.get('rev') path = req.args.get('path') rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) key = 'repository' if is_default(reponame) else '%s.repository' % reponame gh_repo = self.config.get('github', key) try: rev = repos.normalize_rev(rev) except NoSuchChangeset, e: raise ResourceNotFound(e.message, _('Invalid Changeset Number'))
def expand_macro(self, formatter, name, text, args=None): rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path( "/AROS/trunk/AROS/workbench/system/AboutAROS/db/credits") node = repos.get_node(path, repos.youngest_rev) stream = node.get_content() content = stream.read(10000) content = unicode(content, "iso8859-15") content = content.split("\n") result = "" listopen = False for line in content: if ":" in line: if listopen: result = result + "</ul>\n" result = result + "<h2> " + line + "</h2>" + "\n<ul>\n" listopen = True elif line.strip() != "": result = result + "<li>" + line + "</li>\n" if listopen: result = result + "</ul>\n" return result
def process_request(self, req): path = req.args['path'] rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if repos is None or path != '/': msg = u'No such repository (%s)\n' % path self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) if req.method != 'POST': msg = u'Endpoint is ready to accept GitHub notifications.\n' self.log.warning(u'Method not allowed (%s)', req.method) req.send(msg.encode('utf-8'), 'text/plain', 405) # Verify the event's signature reqdata = req.read() signature = req.get_header('X-Hub-Signature') if not self._verify_webhook_signature(signature, reqdata): msg = u'Webhook signature verification failed\n' self.log.warning(msg.rstrip('\n')) # pylint: disable=no-member req.send(msg.encode('utf-8'), 'text/plain', 403) event = req.get_header('X-GitHub-Event') if event == 'ping': payload = json.loads(reqdata) req.send(payload['zen'].encode('utf-8'), 'text/plain', 200) elif event != 'push': msg = u'Only ping and push are supported\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) output = u'Running hook on %s\n' % (reponame or '(default)') output += u'* Updating clone\n' try: git = repos.git.repo # GitRepository except AttributeError: git = repos.repos.git.repo # GitCachedRepository git.remote('update', '--prune') # Ensure that repos.get_changeset can find the new changesets. output += u'* Synchronizing with clone\n' repos.sync() try: payload = json.loads(reqdata) revs = [commit['id'] for commit in payload['commits'] if commit['distinct']] except (ValueError, KeyError): msg = u'Invalid payload\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) branches = self.get_branches(reponame) added, skipped, unknown = classify_commits(revs, repos, branches) if added: output += u'* Adding %s\n' % describe_commits(added) # This is where Trac gets notified of the commits in the changeset rm.notify('changeset_added', reponame, added) if skipped: output += u'* Skipping %s\n' % describe_commits(skipped) if unknown: output += u'* Unknown %s\n' % describe_commits(unknown) self.log.error(u'Payload contains unknown %s', describe_commits(unknown)) status = 200 git_dir = git.rev_parse('--git-dir').rstrip('\n') hook = os.path.join(git_dir, 'hooks', 'trac-github-update') if os.path.isfile(hook): output += u'* Running trac-github-update hook\n' try: p = Popen(hook, cwd=git_dir, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=trac.util.compat.close_fds) except Exception as e: output += u'Error: hook execution failed with exception\n%s' % (traceback.format_exc(),) status = 500 else: hookoutput = p.communicate(input=reqdata)[0] output += hookoutput.decode('utf-8') if p.returncode != 0: output += u'Error: hook failed with exit code %d\n' % (p.returncode,) status = 500 for line in output.splitlines(): self.log.debug(line) if status == 200 and not output: status = 204 req.send(output.encode('utf-8'), 'text/plain', status)
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 _format_name(self, req, url): linkname = url name = "" missing = False path_info = url query_string = '' idx = path_info.find('?') if idx >= 0: path_info, query_string = path_info[:idx], path_info[idx:] href = req.href(path_info) + query_string args = arg_list_to_args(parse_arg_list(query_string.lstrip('?'))) version = args.get('version', False) path = path_info.strip('/').split('/') realm = path[0] class_ = realm if len(path) > 1: resource = Resource(realm, path[1]) if resource: if realm == 'ticket': linkname = get_resource_shortname(self.env, resource) try: name = get_resource_summary(self.env, resource) except ResourceNotFound: missing = True else: from trac.ticket.model import Ticket class_ = Ticket(self.env, resource.id)['status'] + \ ' ' + class_ elif realm == 'milestone': linkname = get_resource_name(self.env, resource) elif realm == 'wiki': resource = Resource(realm, '/'.join(path[1:]), version) linkname = get_resource_shortname(self.env, resource) if version: linkname += '@' + version elif realm == 'report': linkname = "{%s}" % path[1] name = self._format_report_name(path[1]) elif realm == 'changeset': rev = path[1] parent = Resource('source', '/'.join(path[2:])) resource = Resource(realm, rev, False, parent) linkname = "[%s]" % rev name = get_resource_description(self.env, resource) elif realm == 'browser': rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path('/'.join( path[1:])) parent = Resource('source', reponame) resource = Resource('source', path, False, parent) linkname = get_resource_description(self.env, resource) name = get_resource_summary(self.env, resource) elif realm == 'attachment': # Assume a file and check existence parent = Resource(path[1], '/'.join(path[2:-1])) resource = Resource(realm, path[-1], parent=parent) linkname = get_resource_name(self.env, resource) if not resource_exists(self.env, resource): # Assume an attachment list page and check existence parent = Resource(path[1], '/'.join(path[2:])) if resource_exists(self.env, parent): resource = Resource(realm, parent=parent) linkname = get_resource_name(self.env, resource) if not query_string: # Trailing slash needed for Trac < 1.0, t:#10280 href += '/' else: # Assume it's a missing attachment missing = True else: linkname = get_resource_shortname(self.env, resource) name = get_resource_summary(self.env, resource) elif len(path) == 1 and path[0] and path[0] != 'wiki': linkname = path[0].capitalize() else: class_ = 'wiki' linkname = 'WikiStart' if missing: href = None class_ = 'missing ' + realm return { 'class_': class_, 'href': href, 'linkname': linkname, 'name': name, 'delete': req.href.bookmark('delete_in_page', url), }
def process_request(self, req): path = req.args['path'] rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if repos is None or path != '/': msg = u'No such repository (%s)\n' % path self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) if req.method != 'POST': msg = u'Endpoint is ready to accept GitHub notifications.\n' self.log.warning(u'Method not allowed (%s)' % req.method) req.send(msg.encode('utf-8'), 'text/plain', 405) event = req.get_header('X-GitHub-Event') if event == 'ping': payload = json.loads(req.read()) req.send(payload['zen'].encode('utf-8'), 'text/plain', 200) elif event != 'push': msg = u'Only ping and push are supported\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) output = u'Running hook on %s\n' % (reponame or '(default)') output += u'* Updating clone\n' try: git = repos.git.repo # GitRepository except AttributeError: git = repos.repos.git.repo # GitCachedRepository git.remote('update', '--prune') # Ensure that repos.get_changeset can find the new changesets. output += u'* Synchronizing with clone\n' repos.sync() try: payload = json.loads(req.read()) revs = [ commit['id'] for commit in payload['commits'] if commit['distinct'] ] except (ValueError, KeyError): msg = u'Invalid payload\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) branches = self.get_branches(reponame) added, skipped, unknown = classify_commits(revs, repos, branches) if added: output += u'* Adding %s\n' % describe_commits(added) # This is where Trac gets notified of the commits in the changeset rm.notify('changeset_added', reponame, added) if skipped: output += u'* Skipping %s\n' % describe_commits(skipped) if unknown: output += u'* Unknown %s\n' % describe_commits(unknown) self.log.error(u'Payload contains unknown %s', describe_commits(unknown)) for line in output.splitlines(): self.log.debug(line) req.send(output.encode('utf-8'), 'text/plain', 200 if output else 204)
def process_request(self, req): req.perm.require('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') revs = req.args.get('revs') format = req.args.get('format') verbose = req.args.get('verbose') limit = int(req.args.get('limit') or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.log(repos.reponame or None, path) + ('?' + qs if qs else '')) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. revranges = None if revs: try: revranges = Ranges(revs) rev = revranges.b except ValueError: pass rev = unicode(repos.normalize_rev(rev)) display_rev = repos.display_rev # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child('changeset') show_graph = False if mode == 'path_history': def history(): for h in repos.get_path_history(path, rev): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h elif revranges: def history(): prevpath = path expected_next_item = None ranges = list(revranges.pairs) ranges.reverse() for (a, b) in ranges: a = repos.normalize_rev(a) b = repos.normalize_rev(b) while not repos.rev_older_than(b, a): node = get_existing_node(req, repos, prevpath, b) node_history = list(node.get_history(2)) p, rev, chg = node_history[0] if repos.rev_older_than(rev, a): break # simply skip, no separator if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): if expected_next_item: # check whether we're continuing previous range np, nrev, nchg = expected_next_item if rev != nrev: # no, we need a separator yield (np, nrev, None) yield node_history[0] prevpath = node_history[-1][0] # follow copy b = repos.previous_rev(rev) if len(node_history) > 1: expected_next_item = node_history[-1] else: expected_next_item = None if expected_next_item: yield (expected_next_item[0], expected_next_item[1], None) else: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'path': old_path, 'rev': old_rev, 'existing_rev': old_rev, 'change': old_chg, 'depth': depth, } if old_chg == Changeset.DELETE: item['existing_rev'] = repos.previous_rev(old_rev, old_path) if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and \ not (mode == 'path_history' and old_path == normpath): depth += 1 item['depth'] = depth item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break elif mode == 'path_history': depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path if info == []: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError(_("The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any previous revision.", path=path, rev=display_rev(rev)), _('Nonexistent path')) # Generate graph data graph = {} if show_graph: threads, vertices, columns = \ make_log_graph(repos, (item['rev'] for item in info)) graph.update(threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1) add_script(req, 'common/js/excanvas.js', ie_if='IE') add_script(req, 'common/js/log_graph.js') add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] next_revranges = None if revranges: next_revranges = str(revranges.truncate(next_rev)) if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link(req, 'next', older_revisions_href, _('Revision Log (restarting at %(path)s, rev. %(rev)s)', path=next_path, rev=display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None revisions = [i['rev'] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == 'changelog': for rev in revisions: changeset = changes[rev] cs = {} cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs['files'] = files cs['actions'] = actions extra_changes[rev] = cs data = { 'context': web_context(req, 'source', path, parent=repos.resource), 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': display_rev, 'revranges': revranges, 'mode': mode, 'verbose': verbose, 'limit' : limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'graph': graph, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages') } if format == 'changelog': return 'revisionlog.txt', data, 'text/plain' elif format == 'rss': data['email_map'] = Chrome(self.env).get_email_map() data['context'] = web_context(req, 'source', path, parent=repos.resource, absurls=True) return 'revisionlog.rss', data, 'application/rss+xml' item_ranges = [] range = [] for item in info: if item['change'] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data['item_ranges'] = item_ranges add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/browser.css') path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data['path_links'] = path_links if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) rss_href = make_log_href(path, format='rss', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) if 'next' in req.chrome['links']: next = req.chrome['links']['next'][0] add_ctxtnav(req, tag.span(tag.a(_('Older Revisions'), href=next['href']), Markup(' →'))) return 'revisionlog.html', data, None
def expand_macro(self, formatter, name, content): req = formatter.req args, kwargs = parse_args(content) args += [None, None] path, limit = args[:2] limit = kwargs.pop('limit', limit) package = kwargs.pop('package', None) if 'CHANGESET_VIEW' not in req.perm: return Markup('<i>Releases not available</i>') rm = RepositoryManager(self.env) reponame, repo, path = rm.get_repository_by_path(path) rev = repo.get_youngest_rev() rev = repo.normalize_rev(rev) path = repo.normalize_path(path) if limit is None: limit = 20 else: limit = int(limit) releases = self.get_releases(repo, path, rev) # limit the releases after they have been sorted releases = releases[:1 + limit] items = [] releases = [None] + releases + [None] # some extra checks to avoid using double-slashes if reponame == '': if path == '/': path = '' else: path = '/' + path elif path == '/': path = '/' + reponame.rstrip('/') else: path = '/' + reponame.rstrip('/') + '/' + path.lstrip('/') if not package: package = path.split("/")[-1] for i in xrange(len(releases) - 2): prev, cur, next = releases[i:i + 3] if prev == None and next == None: # no releases yet, just show trunk items.append(" * " " [/browser%(path)s/trunk trunk]" " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " (" "[/log%(path)s/trunk changes]" " [/changeset?new_path=%(path)s/trunk diffs]" ")" % { 'reponame': reponame, 'path': path, 'rev': cur['rev'], }) elif prev == None: # first entry = trunk items.append( " * " " [/browser%(path)s/trunk trunk]" " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " (" "[/log%(path)s/trunk?revs=%(stop_rev)s-%(rev)s changes]" " [/changeset?old_path=%(path)s/tags/%(old_tag)s&new_path=%(path)s/trunk diffs]" ")" % { 'reponame': reponame, 'path': path, 'rev': cur['rev'], 'old_tag': next['version'], 'stop_rev': next['rev'], }) elif next != None: # regular releases release_page = 'release/%s-%s' % (package, cur['version']) page = WikiPage(self.env, release_page) if page.exists: release_link = " [wiki:%s release notes]" % (release_page) else: release_link = "" items.append( " * '''%(date)s'''" " [/log%(path)s/tags/%(new_tag)s %(new_tag)s] " " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " by %(author)s" " (" "[/log%(path)s/trunk?revs=%(stop_rev)s-%(rev)s changes]" " [/changeset?old_path=%(path)s/tags/%(old_tag)s&new_path=%(path)s/tags/%(new_tag)s diffs]" "%(release_link)s" ")" % { 'reponame': reponame, 'path': path, 'date': cur['time'].strftime('%Y-%m-%d'), 'rev': cur['rev'], 'stop_rev': next['rev'], 'old_tag': next['version'], 'new_tag': cur['version'], 'author': cur['author'], 'release_link': release_link, }) url = self.specurl_annotate(cur) if url != None: annotate = " spec: [%s annotate]" % url items.append(annotate) # check also diff link url = self.specurl_diff(cur, next) if url != None: annotate = " [%s diff]" % url items.append(annotate) else: # last release items.append( " * '''%(date)s'''" " [/log%(path)s/tags/%(new_tag)s?rev=%(rev)s&mode=follow_copy %(new_tag)s]" " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " by %(author)s" % { 'reponame': reponame, 'path': path, 'date': cur['time'].strftime('%Y-%m-%d'), 'rev': cur['rev'], 'new_tag': cur['version'], 'author': cur['author'], }) return '<div class="releases">\n' + to_unicode( wiki_to_html("\n".join(items), self.env, req)) + '</div>\n'
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
def _get_source(self, formatter, source_obj, dest_format): repos_mgr = RepositoryManager(self.env) try: # 0.12+ repos_name, repos, source_obj = repos_mgr.get_repository_by_path(source_obj) except AttributeError, e: # 0.11 repos = repos_mgr.get_repository(formatter.req.authname)
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
def process_request(self, req): if req.method != 'POST': msg = u'Method not allowed (%s)\n' % req.method self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 405) path = req.args['path'] rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) key = 'branches' if is_default(reponame) else '%s.branches' % reponame branches = self.config.getlist('github', key, sep=' ') if path != '/': msg = u'No such repository (%s)\n' % path self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) output = u'Running hook on %s\n' % (reponame or '(default)') output += u'* Updating clone\n' output += repos.git.repo.remote('update', '--prune') try: payload = json.loads(req.args['payload']) revs = [commit['id'] for commit in payload['commits']] except (ValueError, KeyError): msg = u'Invalid payload\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) if branches: added_revs, skipped_revs = [], [] for rev in revs: if allow_revision(rev, repos, branches): added_revs.append(rev) else: skipped_revs.append(rev) else: added_revs, skipped_revs = revs, [] if added_revs: if len(added_revs) == 1: output += u'* Adding commit %s\n' % added_revs[0] else: output += u'* Adding commits %s\n' % u', '.join(added_revs) # This is where Trac gets notified of the commits in the changeset rm.notify('changeset_added', reponame, added_revs) if skipped_revs: if len(skipped_revs) == 1: output += u'* Skipping commit %s\n' % skipped_revs[0] else: output += u'* Skipping commits %s\n' % u', '.join(skipped_revs) for line in output.splitlines(): self.log.debug(line) req.send(output.encode('utf-8'), 'text/plain', 200 if output else 204)
def process_request(self, req): path = req.args['path'] rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if repos is None or path != '/': msg = u'No such repository (%s)\n' % path self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) if req.method != 'POST': msg = u'Endpoint is ready to accept GitHub notifications.\n' self.log.warning(u'Method not allowed (%s)' % req.method) req.send(msg.encode('utf-8'), 'text/plain', 405) event = req.get_header('X-GitHub-Event') if event == 'ping': payload = json.loads(req.read()) req.send(payload['zen'].encode('utf-8'), 'text/plain', 200) elif event != 'push': msg = u'Only ping and push are supported\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) output = u'Running hook on %s\n' % (reponame or '(default)') output += u'* Updating clone\n' try: git = repos.git.repo # GitRepository except AttributeError: git = repos.repos.git.repo # GitCachedRepository git.remote('update', '--prune') # Ensure that repos.get_changeset can find the new changesets. output += u'* Synchronizing with clone\n' repos.sync() try: payload = json.loads(req.read()) revs = [commit['id'] for commit in payload['commits'] if commit['distinct']] except (ValueError, KeyError): msg = u'Invalid payload\n' self.log.warning(msg.rstrip('\n')) req.send(msg.encode('utf-8'), 'text/plain', 400) branches = self.get_branches(reponame) added, skipped, unknown = classify_commits(revs, repos, branches) if added: output += u'* Adding %s\n' % describe_commits(added) # This is where Trac gets notified of the commits in the changeset rm.notify('changeset_added', reponame, added) if skipped: output += u'* Skipping %s\n' % describe_commits(skipped) if unknown: output += u'* Unknown %s\n' % describe_commits(unknown) self.log.error(u'Payload contains unknown %s', describe_commits(unknown)) for line in output.splitlines(): self.log.debug(line) req.send(output.encode('utf-8'), 'text/plain', 200 if output else 204)
def process_request(self, req): req.perm.require("LOG_VIEW") mode = req.args.get("mode", "stop_on_copy") path = req.args.get("path", "/") rev = req.args.get("rev") stop_rev = req.args.get("stop_rev") revs = req.args.get("revs") format = req.args.get("format") verbose = req.args.get("verbose") limit = int(req.args.get("limit") or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: if path == "/": raise TracError(_("No repository specified and no default" " repository configured.")) else: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame or path.strip("/"))) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.log(repos.reponame or None, path) + ("?" + qs if qs else "")) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. if revs: revranges = RevRanges(repos, revs, resolve=True) rev = revranges.b else: revranges = None rev = repos.normalize_rev(rev) # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child(self.realm) show_graph = False curr_revrange = [] if mode == "path_history": def history(): for h in repos.get_path_history(path, rev): if "CHANGESET_VIEW" in req.perm(cset_resource(id=h[1])): yield h elif revranges: show_graph = path == "/" and not verbose and not repos.has_linear_changesets and len(revranges) == 1 def history(): separator = False for a, b in reversed(revranges.pairs): curr_revrange[:] = (a, b) node = get_existing_node(req, repos, path, b) for p, rev, chg in node.get_history(): if repos.rev_older_than(rev, a): break if "CHANGESET_VIEW" in req.perm(cset_resource(id=rev)): separator = True yield p, rev, chg else: separator = False if separator: yield p, rev, None else: show_graph = path == "/" and not verbose and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if "CHANGESET_VIEW" in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 history_remaining = True for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = {"path": old_path, "rev": old_rev, "existing_rev": old_rev, "change": old_chg, "depth": depth} if old_chg == Changeset.DELETE: item["existing_rev"] = repos.previous_rev(old_rev, old_path) if not (mode == "path_history" and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and not (mode == "path_history" and old_path == normpath): depth += 1 item["depth"] = depth item["copyfrom_path"] = old_path if mode == "stop_on_copy": break elif mode == "path_history": depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path else: history_remaining = False if not info: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError( _( "The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any " "previous revision.", path=path, rev=repos.display_rev(rev), ), _("Nonexistent path"), ) # Generate graph data graph = {} if show_graph: threads, vertices, columns = make_log_graph(repos, (item["rev"] for item in info)) graph.update( threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1, ) add_script(req, "common/js/excanvas.js", ie_if="IE") add_script(req, "common/js/log_graph.js") add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {"rev": link_rev, "mode": mode, "limit": limit} params.update(args) if verbose: params["verbose"] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ("rss", "changelog"): info = [i for i in info if i["change"]] # drop separators if info and count > limit: del info[-1] elif info and history_remaining and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]["rev"] next_path = info[-1]["path"] next_revranges = None if curr_revrange: new_revrange = (curr_revrange[0], next_rev) if info[-1]["change"] else None next_revranges = revranges.truncate(curr_revrange, new_revrange) next_revranges = unicode(next_revranges) or None if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link( req, "next", older_revisions_href, _( "Revision Log (restarting at %(path)s, rev. " "%(rev)s)", path=next_path, rev=repos.display_rev(next_rev), ), ) # only show fully 'limit' results, use `change == None` as a marker info[-1]["change"] = None revisions = [i["rev"] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == "changelog": for rev in revisions: changeset = changes[rev] cs = {} cs["message"] = wrap(changeset.message, 70, initial_indent="\t", subsequent_indent="\t") files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs["files"] = files cs["actions"] = actions extra_changes[rev] = cs data = { "context": web_context(req, "source", path, parent=repos.resource), "reponame": repos.reponame or None, "repos": repos, "path": path, "rev": rev, "stop_rev": stop_rev, "display_rev": repos.display_rev, "revranges": revranges, "mode": mode, "verbose": verbose, "limit": limit, "items": info, "changes": changes, "extra_changes": extra_changes, "graph": graph, "wiki_format_messages": self.config["changeset"].getbool("wiki_format_messages"), } if format == "changelog": return "revisionlog.txt", data, "text/plain" elif format == "rss": data["context"] = web_context(req, "source", path, parent=repos.resource, absurls=True) return "revisionlog.rss", data, "application/rss+xml" item_ranges = [] range = [] for item in info: if item["change"] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data["item_ranges"] = item_ranges add_stylesheet(req, "common/css/diff.css") add_stylesheet(req, "common/css/browser.css") path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data["path_links"] = path_links if path != "/": add_link(req, "up", path_links[-2]["href"], _("Parent directory")) rss_href = make_log_href(path, format="rss", revs=revs, stop_rev=stop_rev) add_link(req, "alternate", auth_link(req, rss_href), _("RSS Feed"), "application/rss+xml", "rss") changelog_href = make_log_href(path, format="changelog", revs=revs, stop_rev=stop_rev) add_link(req, "alternate", changelog_href, _("ChangeLog"), "text/plain") add_ctxtnav(req, _("View Latest Revision"), href=req.href.browser(repos.reponame or None, path)) if "next" in req.chrome["links"]: next = req.chrome["links"]["next"][0] add_ctxtnav(req, tag.span(tag.a(_("Older Revisions"), href=next["href"]), Markup(" →"))) return "revisionlog.html", data, None
def _read_source_from_repos(self, formatter, src_path): repos_mgr = RepositoryManager(self.env) try: #0.12+ repos_name, repos, source_obj = repos_mgr.get_repository_by_path(src_path) except AttributeError, e: #0.11 repos = repos_mgr.get_repository(formatter.req.authname)
def process_request(self, req): req.perm.require('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') revs = req.args.get('revs') format = req.args.get('format') verbose = req.args.get('verbose') limit = int(req.args.get('limit') or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: raise ResourceNotFound( _("Repository '%(repo)s' not found", repo=reponame)) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect( req.href.log(repos.reponame or None, path) + ('?' + qs if qs else '')) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. revranges = None if revs: try: revranges = Ranges(revs) rev = revranges.b except ValueError: pass rev = unicode(repos.normalize_rev(rev)) display_rev = repos.display_rev # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child('changeset') show_graph = False if mode == 'path_history': def history(): for h in repos.get_path_history(path, rev): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h elif revranges: def history(): prevpath = path expected_next_item = None ranges = list(revranges.pairs) ranges.reverse() for (a, b) in ranges: a = repos.normalize_rev(a) b = repos.normalize_rev(b) while not repos.rev_older_than(b, a) and b != a: node = get_existing_node(req, repos, prevpath, b) node_history = list(node.get_history(2)) p, rev, chg = node_history[0] if repos.rev_older_than(rev, a): break # simply skip, no separator if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): if expected_next_item: # check whether we're continuing previous range np, nrev, nchg = expected_next_item if rev != nrev: # no, we need a separator yield (np, nrev, None) yield node_history[0] prevpath = node_history[-1][0] # follow copy b = repos.previous_rev(rev) if len(node_history) > 1: expected_next_item = node_history[-1] else: expected_next_item = None if expected_next_item: yield (expected_next_item[0], expected_next_item[1], None) else: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'path': old_path, 'rev': old_rev, 'existing_rev': old_rev, 'change': old_chg, 'depth': depth, } if old_chg == Changeset.DELETE: item['existing_rev'] = repos.previous_rev(old_rev, old_path) if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and \ not (mode == 'path_history' and old_path == normpath): depth += 1 item['depth'] = depth item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break elif mode == 'path_history': depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path if info == []: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError( _( "The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any previous revision.", path=path, rev=display_rev(rev)), _('Nonexistent path')) # Generate graph data graph = {} if show_graph: threads, vertices, columns = \ make_log_graph(repos, (item['rev'] for item in info)) graph.update(threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1) add_script(req, 'common/js/excanvas.js', ie_if='IE') add_script(req, 'common/js/log_graph.js') add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] next_revranges = None if revranges: next_revranges = str(revranges.truncate(next_rev)) if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link( req, 'next', older_revisions_href, _('Revision Log (restarting at %(path)s, rev. %(rev)s)', path=next_path, rev=display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None revisions = [i['rev'] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == 'changelog': for rev in revisions: changeset = changes[rev] cs = {} cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs['files'] = files cs['actions'] = actions extra_changes[rev] = cs data = { 'context': web_context(req, 'source', path, parent=repos.resource), 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': display_rev, 'revranges': revranges, 'mode': mode, 'verbose': verbose, 'limit': limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'graph': graph, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages') } if format == 'changelog': return 'revisionlog.txt', data, 'text/plain' elif format == 'rss': data['email_map'] = Chrome(self.env).get_email_map() data['context'] = web_context(req, 'source', path, parent=repos.resource, absurls=True) return 'revisionlog.rss', data, 'application/rss+xml' item_ranges = [] range = [] for item in info: if item['change'] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data['item_ranges'] = item_ranges add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/browser.css') path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data['path_links'] = path_links if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) rss_href = make_log_href(path, format='rss', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) if 'next' in req.chrome['links']: next = req.chrome['links']['next'][0] add_ctxtnav( req, tag.span(tag.a(_('Older Revisions'), href=next['href']), Markup(' →'))) return 'revisionlog.html', data, None
def expand_macro(self, formatter, name, content): req = formatter.req args, kwargs = parse_args(content) args += [None, None] path, limit = args[:2] limit = kwargs.pop('limit', limit) package = kwargs.pop('package', None) if 'CHANGESET_VIEW' not in req.perm: return Markup('<i>Releases not available</i>') rm = RepositoryManager(self.env) reponame, repo, path = rm.get_repository_by_path(path); rev = repo.get_youngest_rev() rev = repo.normalize_rev(rev) path = repo.normalize_path(path) if limit is None: limit = 20 else: limit = int(limit) releases = self.get_releases(repo, path, rev) # limit the releases after they have been sorted releases = releases[:1 + limit] items = [] releases = [None] + releases + [None] # some extra checks to avoid using double-slashes if reponame == '': if path == '/': path = '' else: path = '/' + path elif path == '/': path = '/' + reponame.rstrip('/') else: path = '/' + reponame.rstrip('/') + '/' + path.lstrip('/') if not package: package = path.split("/")[-1] for i in xrange(len(releases) - 2): prev, cur, next = releases[i : i + 3] if prev == None and next == None: # no releases yet, just show trunk items.append( " * " " [/browser%(path)s/trunk trunk]" " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " (" "[/log%(path)s/trunk changes]" " [/changeset?new_path=%(path)s/trunk diffs]" ")" % { 'reponame' : reponame, 'path': path, 'rev': cur['rev'], }) elif prev == None: # first entry = trunk items.append( " * " " [/browser%(path)s/trunk trunk]" " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " (" "[/log%(path)s/trunk?revs=%(stop_rev)s-%(rev)s changes]" " [/changeset?old_path=%(path)s/tags/%(old_tag)s&new_path=%(path)s/trunk diffs]" ")" % { 'reponame' : reponame, 'path': path, 'rev' : cur['rev'], 'old_tag' : next['version'], 'stop_rev' : next['rev'], }) elif next != None: # regular releases release_page = 'release/%s-%s' % (package, cur['version']) page = WikiPage(self.env, release_page) if page.exists: release_link = " [wiki:%s release notes]" % (release_page) else: release_link = "" items.append( " * '''%(date)s'''" " [/log%(path)s/tags/%(new_tag)s %(new_tag)s] " " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " by %(author)s" " (" "[/log%(path)s/trunk?revs=%(stop_rev)s-%(rev)s changes]" " [/changeset?old_path=%(path)s/tags/%(old_tag)s&new_path=%(path)s/tags/%(new_tag)s diffs]" "%(release_link)s" ")" % { 'reponame' : reponame, 'path': path, 'date': cur['time'].strftime('%Y-%m-%d'), 'rev' : cur['rev'], 'stop_rev' : next['rev'], 'old_tag' : next['version'], 'new_tag' : cur['version'], 'author': cur['author'], 'release_link' : release_link, }) url = self.specurl_annotate(cur); if url != None: annotate = " spec: [%s annotate]" % url items.append(annotate) # check also diff link url = self.specurl_diff(cur, next); if url != None: annotate = " [%s diff]" % url items.append(annotate) else: # last release items.append( " * '''%(date)s'''" " [/log%(path)s/tags/%(new_tag)s?rev=%(rev)s&mode=follow_copy %(new_tag)s]" " @[changeset:%(rev)s/%(reponame)s %(rev)s]" " by %(author)s" % { 'reponame' : reponame, 'path': path, 'date': cur['time'].strftime('%Y-%m-%d'), 'rev' : cur['rev'], 'new_tag' : cur['version'], 'author': cur['author'], }) return '<div class="releases">\n' + to_unicode(wiki_to_html("\n".join(items), self.env, req)) + '</div>\n'