def displayFormattedText(db, req, user, source): document = htmlutils.Document(req) document.setBase(None) document.addExternalStylesheet("resource/tutorial.css") document.addInternalStylesheet( "div.main table td.text { %s }" % user.getPreference(db, "style.tutorialFont")) html = document.html() head = html.head() body = html.body() generateHeader(body, db, user) if isinstance(source, basestring): lines = source.splitlines() else: lines = source import textformatting textformatting.renderFormatted(db, user, body.div("main").table("paleyellow"), source, toc=True) generateFooter(body, db, user) return document
def process(req, db, user, repository_id, commit_ids, reviewfilters, applyfilters, applyparentfilters): reviewfilters = parseReviewFilters(db, reviewfilters) repository = gitutils.Repository.fromId(db, repository_id) commits = [ gitutils.Commit.fromId(db, repository, commit_id) for commit_id in commit_ids ] all_reviewers, all_watchers = getReviewersAndWatchers( db, repository, commits, reviewfilters=reviewfilters, applyfilters=applyfilters, applyparentfilters=applyparentfilters) document = htmlutils.Document(req) generateReviewersAndWatchersTable( db, repository, document, all_reviewers, all_watchers, applyfilters=applyfilters, applyparentfilters=applyparentfilters) return OperationResult(html=document.render(plain=True))
def renderNews(req, db, user): item_id = req.getParameter("item", None, filter=int) display = req.getParameter("display", "unread") document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() cursor = db.cursor() def renderButtons(target): if user.hasRole(db, "newswriter"): if item_id is not None: target.button("editnewsitem").text("Edit Item") target.button("addnewsitem").text("Add News Item") page.utils.generateHeader(body, db, user, current_page="news", generate_right=renderButtons) document.addExternalStylesheet("resource/tutorial.css") document.addExternalStylesheet("resource/comment.css") document.addExternalStylesheet("resource/news.css") document.addExternalScript("resource/news.js") document.addInternalStylesheet( "div.main table td.text { %s }" % user.getPreference(db, "style.tutorialFont")) target = body.div("main") if item_id: cursor.execute("SELECT text, date FROM newsitems WHERE id=%s", (item_id, )) text, date = cursor.fetchone() document.addInternalScript("var news_item_id = %d;" % item_id) document.addInternalScript("var news_text = %s;" % htmlutils.jsify(text)) renderNewsItem(db, user, target, text, date.isoformat()) if not user.isAnonymous() and user.name == req.user: cursor.execute("SELECT 1 FROM newsread WHERE item=%s AND uid=%s", (item_id, user.id)) if not cursor.fetchone(): cursor.execute( "INSERT INTO newsread (item, uid) VALUES (%s, %s)", (item_id, user.id)) db.commit() else: renderNewsItems(db, user, target, display in ("unread", "all"), display in ("read", "all")) return document
def generate(self, page, req, db, user): self.setup(page, req, db, user) self.document = htmlutils.Document(req) self.html = self.document.html() self.head = self.html.head() self.body = self.html.body() self._generateHeader() self.generateContent() self._generateFooter() return self.document
def displayMessage(db, req, user, title, review=None, message=None, page_title=None, is_html=False): document = htmlutils.Document(req) if page_title: document.setTitle(page_title) document.addExternalStylesheet("resource/message.css") html = document.html() head = html.head() body = html.body() if review: import reviewing.utils as review_utils def generateRight(target): review_utils.renderDraftItems(db, user, review, target) back_to_review = ("r/%d" % review.id, "Back to Review") document.addInternalScript(review.getJS()) generateHeader(body, db, user, generate_right=generateRight, extra_links=[back_to_review]) else: generateHeader(body, db, user) target = body.div("message paleyellow") if message: target.h1("title").text(title) if callable(message): message(target) elif is_html: target.innerHTML(message) else: target.p().text(message) else: target.h1("center").text(title) return document
def renderShowComment(req, db, user): chain_id = req.getParameter("chain", filter=int) context_lines = req.getParameter("context", user.getPreference(db, "comment.diff.contextLines"), filter=int) default_compact = "yes" if user.getPreference(db, "commit.diff.compactMode") else "no" compact = req.getParameter("compact", default_compact) == "yes" default_tabify = "yes" if user.getPreference(db, "commit.diff.visualTabs") else "no" tabify = req.getParameter("tabify", default_tabify) == "yes" original = req.getParameter("original", "no") == "yes" chain = review_comment.CommentChain.fromId(db, chain_id, user) if chain is None or chain.state == "empty": raise page.utils.DisplayMessage("Invalid comment chain ID: %d" % chain_id) review = chain.review document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() document.setTitle("%s in review %s" % (chain.title(False), review.branch.name)) def renderHeaderItems(target): review_utils.renderDraftItems(db, user, review, target) target.div("buttons").span("buttonscope buttonscope-global") page.utils.generateHeader(body, db, user, renderHeaderItems, extra_links=[("r/%d" % review.id, "Back to Review")]) document.addExternalScript("resource/showcomment.js") document.addInternalScript(user.getJS(db)) document.addInternalScript(review.repository.getJS()) document.addInternalScript(review.getJS()) document.addInternalScript("var contextLines = %d;" % context_lines) document.addInternalScript("var keyboardShortcuts = %s;" % (user.getPreference(db, "ui.keyboardShortcuts") and "true" or "false")) if not user.isAnonymous() and user.name == req.user: document.addInternalScript("$(function () { markChainsAsRead([%d]); });" % chain_id) review_html.renderCommentChain(db, body.div("main"), user, review, chain, context_lines=context_lines, compact=compact, tabify=tabify, original=original, linkify=linkify.Context(db=db, request=req, review=review)) if user.getPreference(db, "ui.keyboardShortcuts"): page.utils.renderShortcuts(body, "showcomment", review=review) yield document.render(pretty=not compact)
def renderTutorial(req, db, user): item = req.getParameter("item", None) document = htmlutils.Document(req) document.setBase(None) document.setTitle("Tutorials") html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page=None if item else "tutorial") document.addExternalStylesheet("resource/tutorial.css") document.addExternalScript("resource/tutorial.js") document.addInternalStylesheet( "div.main table td.text { %s }" % user.getPreference(db, "style.tutorialFont")) target = body.div("main") items = { "request": "requesting", "review": "reviewing", "filters": "filters", "archival": "archival", "viewer": "repository", "rebase": "rebasing", "reconfigure": "reconfiguring", "checkbranch": "checkbranch", "administration": "administration", "customization": "customization", "search": "search", "external-authentication": "external-authentication", "extensions": "extensions", "extensions-api": "extensions-api" } if item in items: renderFromFile(db, user, target, items[item]) else: renderSections(db, user, target) return document
def renderLogin(req, db, user): target_url = req.getParameter("target", "/") if not user.isAnonymous(): raise page.utils.MovedTemporarily(target_url or "/", True) document = htmlutils.Document(req) document.setTitle("Login") html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page="login") document.addExternalStylesheet("resource/login.css") document.addExternalScript("resource/login.js") def render(target): form = target.form(name="login", method="POST", action="redirect?" + urllib.urlencode({"target": target_url})) table = form.table("login", align="center") row = table.tr("status disabled") row.td(colspan=2).text() row = table.tr("username") row.td("key").text("Username:"******"value").input("username", name="username", autofocus="autofocus") row = table.tr("password") row.td("key").text("Password:"******"value").input("password", name="password", type="password") row = table.tr("login") row.td(colspan=2).input("login", type="submit", value="Sign in") paleyellow = page.utils.PaleYellowTable(body, "Sign in") paleyellow.addCentered(render) return document
def renderServices(req, db, user): req.content_type = "text/html; charset=utf-8" document = htmlutils.Document(req) document.setTitle("Services") html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page="services") document.addExternalStylesheet("resource/services.css") document.addExternalScript("resource/services.js") document.addInternalScript(user.getJS()) delay = 0.5 connected = False while not connected and delay <= 10: connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # This loop is for the case where we just restarted the service manager # via the /services UI. The client-side script immediately reloads the # page after restart, which typically leads to us trying to connect to # the service manager while it's in the process of restarting. So just # try a couple of times if at first the connection fails. try: connection.connect( configuration.services.SERVICEMANAGER["address"]) connected = True except socket.error, error: if error[0] in (errno.ENOENT, errno.ECONNREFUSED): time.sleep(delay) delay += delay else: raise
def renderTutorial(db, user, source): document = htmlutils.Document() document.addExternalStylesheet("resource/tutorial.css") document.addExternalScript("resource/tutorial.js") document.addInternalStylesheet( "div.main table td.text { %s }" % user.getPreference(db, "style.tutorialFont")) html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user) table = body.div("main").table("paleyellow", align="center") textformatting.renderFormatted(db, user, table, source.splitlines(), toc=True) return str(document)
def renderSearch(req, db, user): document = htmlutils.Document(req) document.setTitle("Review Search") html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page="search") document.addExternalStylesheet("resource/search.css") document.addExternalScript("resource/search.js") document.addExternalScript("resource/autocomplete.js") document.addInternalScript(user.getJS()) cursor = db.cursor() cursor.execute("SELECT name, fullname FROM users") users = dict(cursor) document.addInternalScript("var users = %s;" % textutils.json_encode(users)) def renderQuickSearch(target): wrap = target.div("quicksearch callout") wrap.p().text("""A Quick Search dialog can be opened from any page using the "F" keyboard shortcut.""") wrap.p().a(href="/tutorial?item=search").text("More information") def renderInput(target, label, name, placeholder=""): fieldset = target.fieldset("search-" + name) fieldset.label("input-label").text(label) fieldset.input(type="text", name=name, placeholder=placeholder) def renderInputWithOptions(target, label, name, options, placeholder=""): fieldset = target.fieldset("search-" + name) fieldset.label("input-label").text(label) checkGroup = fieldset.div("input-options checkbox-group") for option in options: opt_label = checkGroup.label() opt_label.input(type="checkbox", name=option["name"], checked="checked" if "checked" in option else None) opt_label.text(option["label"]) fieldset.input(type="text", name=name, placeholder=placeholder) def renderFreetext(target): options = [{ "name": "freetextSummary", "label": "Summary", "checked": True }, { "name": "freetextDescription", "label": "Description", "checked": True }] renderInputWithOptions(target, label="Search term", name="freetext", placeholder="free text search", options=options) def renderState(target): state = target.fieldset("search-state") state.label("input-label").text("State") select = state.select(name="state") select.option(value="", selected="selected").text("Any state") select.option(value="open").text("Open") select.option(value="pending").text("Pending") select.option(value="accepted").text("Accepted") select.option(value="closed").text("Finished") select.option(value="dropped").text("Dropped") def renderUser(target): options = [{ "name": "userOwner", "label": "Owner", "checked": True }, { "name": "userReviewer", "label": "Reviewer" }] renderInputWithOptions(target, label="User", name="user", placeholder="user name(s)", options=options) def renderRepository(target): fieldset = target.fieldset("search-repository") fieldset.label("input-label").text("Repository") page.utils.generateRepositorySelect(db, user, fieldset, name="repository", selected=False, placeholder_text="Any repository", allow_selecting_none=True) section = body.section("paleyellow section") section.h1("section-heading").text("Review Search") url_terms = [] for name, value in urlparse.parse_qsl(req.query): if name == "q": url_terms.append(value) elif name.startswith("q"): url_terms.append("%s:%s" % (name[1:], value)) wrap = section.div("flex") search = wrap.form("search", name="search") if url_terms: row = search.div("flex") query = row.fieldset("search-query") query.label("input-label").text("Search query") query.input(type="text", name="query", value=" ".join(url_terms)) result = section.div("search-result", style="display: none") result.h2().text("Search result") result.div("callout") else: row = search.div("flex") renderFreetext(row) renderState(row) renderUser(search) row = search.div("flex") renderRepository(row) renderInput(row, "Branch", "branch") renderInput(search, "Path", "path") buttons = search.div("search-buttons") if url_terms: buttons.button(type="submit").text("Search again") buttons.a("button", href="/search").text("Show full search form") else: buttons.button(type="submit").text("Search") renderQuickSearch(wrap) return document
def renderShowBranch(req, db, user): branch_name = req.getParameter("branch") base_name = req.getParameter("base", None) repository = gitutils.Repository.fromParameter( db, req.getParameter("repository", user.getPreference(db, "defaultRepository"))) cursor = db.cursor() cursor.execute( "SELECT id, type, base, head, tail FROM branches WHERE name=%s AND repository=%s", (branch_name, repository.id)) try: branch_id, branch_type, base_id, head_id, tail_id = cursor.fetchone() except: return page.utils.displayMessage( db, req, user, "'%s' doesn't name a branch!" % branch_name) branch = dbutils.Branch.fromName(db, repository, branch_name) rebased = False if base_name: base = dbutils.Branch.fromName(db, repository, base_name) if base is None: return page.utils.displayMessage( db, req, user, "'%s' doesn't name a branch!" % branch) old_count, new_count, base_old_count, base_new_count = branch.rebase( db, base) if base_old_count is not None: new_base_base_name = base.base.name else: new_base_base_name = None rebased = True document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() document.addExternalStylesheet("resource/showbranch.css") def renderCreateReview(target): if not user.isAnonymous( ) and branch and branch.review is None and not rebased: target.button( onclick="location.href = " + htmlutils.jsify("createreview?repository=%d&branch=%s" % (repository.id, branch_name))).text( "Create Review") page.utils.generateHeader(body, db, user, renderCreateReview) document.addInternalScript(branch.getJS()) title_right = None if rebased: def renderPerformRebase(db, target): target.button( "perform", onclick="rebase(%s, %s, %s, %s, %s, %s, %s)" % tuple( map(htmlutils.jsify, [ branch_name, base_name, new_base_base_name, old_count, new_count, base_old_count, base_new_count ]))).text("Perform Rebase") title_right = renderPerformRebase elif base_id is not None: bases = [] base = branch.base if base: if base.type == "review": bases.append("master") else: base = base.base while base: bases.append(base.name) base = base.base cursor.execute("SELECT name FROM branches WHERE base=%s", (branch.id, )) for (name, ) in cursor: bases.append(name) def renderSelectBase(db, target): select = target.select("base") select.option(value="*").text("Select new base") select.option(value="*").text("---------------") for name in bases: select.option("base", value=name.split(" ")[0]).text(name) if not bases and branch.base: cursor.execute("SELECT commit FROM reachable WHERE branch=%s", (branch.id, )) commit_ids = cursor.fetchall() body.comment(repr(commit_ids)) for commit_id in commit_ids: cursor.execute( "SELECT 1 FROM reachable WHERE branch=%s AND commit=%s", (branch.base.id, commit_id)) if cursor.fetchone(): bases.append("%s (trim)" % branch.base.name) break if bases: title_right = renderSelectBase target = body.div("main") if branch_type == 'normal': cursor.execute("SELECT COUNT(*) FROM reachable WHERE branch=%s", (branch_id, )) commit_count = cursor.fetchone()[0] if commit_count > configuration.limits.MAXIMUM_REACHABLE_COMMITS: offset = req.getParameter("offset", default=0, filter=int) limit = req.getParameter("limit", default=200, filter=int) head = gitutils.Commit.fromId(db, repository, head_id) tail = gitutils.Commit.fromId(db, repository, tail_id) if tail_id else None sha1s = repository.revlist([head], [tail] if tail else [], "--skip=%d" % offset, "--max-count=%d" % limit) commits = [ gitutils.Commit.fromSHA1(db, repository, sha1) for sha1 in sha1s ] def moreCommits(db, target): target.a(href="/log?branch=%s&offset=%d&limit=%d" % (branch_name, offset + limit, limit)).text("More commits...") log_html.renderList(db, target, branch.name, commits, title_right=title_right, bottom_right=moreCommits) return document branch.loadCommits(db) log_html.render(db, target, branch.name, branch=branch, title_right=title_right) return document
def renderCreateReview(req, db, user): if user.isAnonymous(): raise page.utils.NeedLogin(req) repository = req.getParameter("repository", filter=gitutils.Repository.FromParameter(db), default=None) applyparentfilters = req.getParameter("applyparentfilters", "yes" if user.getPreference(db, 'review.applyUpstreamFilters') else "no") == "yes" cursor = db.cursor() if req.method == "POST": data = json_decode(req.read()) summary = data.get("summary") description = data.get("description") review_branch_name = data.get("review_branch_name") commit_ids = data.get("commit_ids") commit_sha1s = data.get("commit_sha1s") else: summary = req.getParameter("summary", None) description = req.getParameter("description", None) review_branch_name = req.getParameter("reviewbranchname", None) commit_ids = None commit_sha1s = None commits_arg = req.getParameter("commits", None) remote = req.getParameter("remote", None) upstream = req.getParameter("upstream", "master") branch_name = req.getParameter("branch", None) if commits_arg: try: commit_ids = map(int, commits_arg.split(",")) except: commit_sha1s = [repository.revparse(ref) for ref in commits_arg.split(",")] elif branch_name: cursor.execute("""SELECT commit FROM reachable JOIN branches ON (branch=id) WHERE repository=%s AND name=%s""", (repository.id, branch_name)) commit_ids = [commit_id for (commit_id,) in cursor] if len(commit_ids) > configuration.limits.MAXIMUM_REVIEW_COMMITS: raise page.utils.DisplayMessage( "Too many commits!", (("<p>The branch <code>%s</code> contains %d commits. Reviews can" "be created from branches that contain at most %d commits.</p>" "<p>This limit can be adjusted by modifying the system setting" "<code>configuration.limits.MAXIMUM_REVIEW_COMMITS</code>.</p>") % (htmlutils.htmlify(branch_name), len(commit_ids), configuration.limits.MAXIMUM_REVIEW_COMMITS)), html=True) else: return renderSelectSource(req, db, user) req.content_type = "text/html; charset=utf-8" if commit_ids: commits = [gitutils.Commit.fromId(db, repository, commit_id) for commit_id in commit_ids] elif commit_sha1s: commits = [gitutils.Commit.fromSHA1(db, repository, commit_sha1) for commit_sha1 in commit_sha1s] else: commits = [] if not commit_ids: commit_ids = [commit.getId(db) for commit in commits] if not commit_sha1s: commit_sha1s = [commit.sha1 for commit in commits] if summary is None: if len(commits) == 1: summary = commits[0].summary() else: summary = "" if review_branch_name: invalid_branch_name = "false" default_branch_name = review_branch_name else: invalid_branch_name = htmlutils.jsify(user.name + "/") default_branch_name = user.name + "/" match = re.search("(?:^|[Ff]ix(?:e[ds])?(?: +for)?(?: +bug)? +)([A-Z][A-Z0-9]+-[0-9]+)", summary) if match: invalid_branch_name = "false" default_branch_name = htmlutils.htmlify(match.group(1)) changesets = [] changeset_utils.createChangesets(db, repository, commits) for commit in commits: changesets.extend(changeset_utils.createChangeset(db, None, repository, commit, do_highlight=False)) changeset_ids = [changeset.id for changeset in changesets] all_reviewers, all_watchers = reviewing.utils.getReviewersAndWatchers( db, repository, changesets=changesets, applyparentfilters=applyparentfilters) document = htmlutils.Document(req) html = document.html() head = html.head() document.addInternalScript(user.getJS(db)) if branch_name: document.addInternalScript("var fromBranch = %s;" % htmlutils.jsify(branch_name)) if remote: document.addInternalScript("var trackedbranch = { remote: %s, name: %s };" % (htmlutils.jsify(remote), htmlutils.jsify(branch_name))) head.title().text("Create Review") body = html.body(onload="document.getElementById('branch_name').focus()") page.utils.generateHeader(body, db, user, lambda target: target.button(onclick="submitReview();").text("Submit Review")) document.addExternalStylesheet("resource/createreview.css") document.addExternalScript("resource/createreview.js") document.addExternalScript("resource/reviewfilters.js") document.addExternalScript("resource/autocomplete.js") document.addInternalScript(""" var invalid_branch_name = %s; var review_data = { commit_ids: %r, commit_sha1s: %r, changeset_ids: %r };""" % (invalid_branch_name, commit_ids, commit_sha1s, changeset_ids)) document.addInternalScript(repository.getJS()) main = body.div("main") table = main.table("basic paleyellow", align="center") table.tr().td("h1", colspan=3).h1().text("Create Review") row = table.tr("line") row.td("heading").text("Branch Name:") row.td("value").text("r/").input("value", id="branch_name", value=default_branch_name) row.td("status") row = table.tr() if not remote: row.td("help", colspan=3).div().text("""\ This is the main identifier of the review. It will be created in the review repository to contain the commits below. Reviewers can fetch it from there, and additional commits can be added to the review later by pushing them to this branch in the review repository.""") else: row.td("help", colspan=3).div().text("""\ This is the main identifier of the review. It will be created in the review repository to contain the commits below, and reviewers can fetch it from there.""") if remote: row = table.tr("line") row.td("heading").text("Tracked Branch:") value = row.td("value") value.code("branch inset").text(branch_name, linkify=linkify.Context(remote=remote)) value.text(" in ") value.code("remote inset").text(remote, linkify=linkify.Context()) row.td("status") row = table.tr() row.td("help", colspan=3).div().text("""\ Rather than pushing directly to the review branch in Critic's repository to add commits to the review, you will be pushing to this branch (in a separate repository,) from which Critic will fetch commits and add them to the review automatically.""") row = table.tr("line") row.td("heading").text("Summary:") row.td("value").input("value", id="summary", value=summary) row.td("status") row = table.tr() row.td("help", colspan=3).div().text("""\ The summary should be a short summary of the changes in the review. It will appear in the subject of all emails sent about the review. """) row = table.tr("line description") row.td("heading").text("Description:") textarea = row.td("value").textarea(id="description", rows=12) textarea.preformatted() if description: textarea.text(description) row.td("status") row = table.tr() row.td("help", colspan=3).div().text("""\ The description should describe the changes to be reviewed. It is usually fine to leave the description empty, since the commit messages are also available in the review. """) generateReviewersAndWatchersTable(db, repository, main, all_reviewers, all_watchers, applyparentfilters=applyparentfilters) row = table.tr("line recipients") row.td("heading").text("Recipient List:") cell = row.td("value", colspan=2).preformatted() cell.span("mode").text("Everyone") cell.span("users") cell.text(".") buttons = cell.div("buttons") buttons.button(onclick="editRecipientList();").text("Edit Recipient List") row = table.tr() row.td("help", colspan=3).div().text("""\ The basic recipient list for e-mails sent about the review. """) log.html.render(db, main, "Commits", commits=commits) return document
def renderSelectSource(req, db, user): cursor = db.cursor() document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page="createreview") document.addExternalStylesheet("resource/createreview.css") document.addExternalScript("resource/createreview.js") document.addExternalScript("resource/autocomplete.js") document.addInternalScript(user.getJS(db)) document.setTitle("Create Review") target = body.div("main") table = page.utils.PaleYellowTable(target, "Create Review") table.titleRight.innerHTML("Step 1") default_repository = user.getPreference(db, "defaultRepository") default_remotes = {} default_branches = {} def renderLocalRepository(target): page.utils.generateRepositorySelect(db, user, target) cursor.execute("""SELECT repositories.id, repositories.name, repositories.path FROM repositories ORDER BY repositories.id""") for repository_id, name, path in cursor.fetchall(): def findRemote(local_name): cursor.execute("""SELECT remote FROM trackedbranches WHERE repository=%s AND local_name=%s""", (repository_id, local_name)) row = cursor.fetchone() if row: return row[0] repository = gitutils.Repository.fromId(db, repository_id) remote = branch_name = None for branch in repository.getSignificantBranches(db): remote = findRemote(branch.name) if remote: branch_name = branch.name break if not remote: remote = findRemote("*") default_remotes[name] = remote default_branches[name] = branch_name document.addInternalScript("var default_remotes = %s;" % json_encode(default_remotes)) document.addInternalScript("var default_branches = %s;" % json_encode(default_branches)) def renderRemoteRepository(target): target.input("remote", value=default_remotes.get(default_repository)) def renderWorkBranch(target): target.text("refs/heads/") target.input("workbranch") def renderUpstreamCommit(target): default_branch = default_branches.get(default_repository) target.input("upstreamcommit", value=("refs/heads/%s" % default_branch) if default_branch else "") table.addItem("Local Repository", renderLocalRepository, "Critic repository to create review in.") table.addItem("Remote Repository", renderRemoteRepository, "Remote repository to fetch commits from.") table.addItem("Work Branch", renderWorkBranch, "Work branch (in remote repository) containing commits to create review of.") table.addItem("Upstream Commit", renderUpstreamCommit, "Upstream commit from which the work branch was branched.") def renderButtons(target): target.button("fetchbranch").text("Fetch Branch") table.addCentered(renderButtons) return document
def renderSelectSource(req, db, user): cursor = db.cursor() document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page="createreview") document.addExternalStylesheet("resource/createreview.css") document.addExternalScript("resource/createreview.js") document.addInternalScript(user.getJS(db)) document.setTitle("Create Review") target = body.div("main") table = page.utils.PaleYellowTable(target, "Create Review") table.titleRight.innerHTML("Step 1") default_repository = user.getPreference(db, "defaultRepository") default_remotes = {} default_branches = {} def renderLocalRepository(target): repositories = target.select("repository") cursor.execute( """SELECT repositories.id, repositories.name, repositories.path, branches.name FROM repositories LEFT OUTER JOIN branches ON (branches.id=repositories.branch) ORDER BY id""") for repository_id, name, path, branch_name in cursor: option = repositories.option( "repository", value=name, selected="selected" if name == default_repository else None) option.text("%s [%s:%s]" % (name, configuration.base.HOSTNAME, path)) local_names = ["*"] if branch_name: local_names.append(branch_name) cursor.execute( """SELECT remote FROM trackedbranches WHERE repository=%s AND local_name=ANY (%s) ORDER BY local_name LIMIT 1""", (repository_id, local_names)) def splitRemote(remote): if remote.startswith("git://"): host, path = remote[6:].split("/", 1) host = "git://" + host else: host, path = remote.split(":", 1) return host, path row = cursor.fetchone() if row: default_remotes[name] = splitRemote(row[0]) else: default_remotes[name] = None default_branches[name] = branch_name document.addInternalScript("var default_remotes = %s;" % json_encode(default_remotes)) document.addInternalScript("var default_branches = %s;" % json_encode(default_branches)) def renderRemoteRepository(target): host = target.p("remotehost") host.text("Host: ") hosts = host.select("remotehost") cursor.execute("SELECT name, path FROM knownhosts ORDER BY id") default_remote = default_remotes.get(default_repository) for name, path in cursor: option = hosts.option("remotehost", value=name, critic_default_path=path, selected="selected" if default_remote and default_remote[0] == name else None) option.text(name) path = target.p("remotepath") path.text("Path: ") path.input("remotepath", value=default_remote[1] if default_remote else None) def renderWorkBranch(target): target.input("workbranch") def renderUpstreamCommit(target): default_branch = default_branches.get(default_repository) target.input("upstreamcommit", value=("refs/heads/%s" % default_branch) if default_branch else "") table.addItem("Local Repository", renderLocalRepository, "Critic repository to create review in.") table.addItem("Remote Repository", renderRemoteRepository, "Remote repository to fetch commits from.") table.addItem( "Work Branch", renderWorkBranch, "Work branch (in remote repository) containing commits to create review of." ) table.addItem("Upstream Commit", renderUpstreamCommit, "Upstream commit from which the work branch was branched.") def renderButtons(target): target.button("fetchbranch").text("Fetch Branch") table.addCentered(renderButtons) return document
def renderServices(req, db, user): req.content_type = "text/html; charset=utf-8" document = htmlutils.Document(req) document.setTitle("Services") html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page="services") document.addExternalStylesheet("resource/services.css") document.addExternalScript("resource/services.js") document.addInternalScript(user.getJS()) delay = 0.5 connected = False while not connected and delay <= 10: connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # This loop is for the case where we just restarted the service manager # via the /services UI. The client-side script immediately reloads the # page after restart, which typically leads to us trying to connect to # the service manager while it's in the process of restarting. So just # try a couple of times if at first the connection fails. try: connection.connect( configuration.services.SERVICEMANAGER["address"]) connected = True except socket.error as error: if error[0] in (errno.ENOENT, errno.ECONNREFUSED): time.sleep(delay) delay += delay else: raise if not connected: raise page.utils.DisplayMessage("Service manager not responding!") connection.send(textutils.json_encode({"query": "status"})) connection.shutdown(socket.SHUT_WR) data = "" while True: received = connection.recv(4096) if not received: break data += received result = textutils.json_decode(data) if result["status"] == "error": raise page.utils.DisplayMessage(result["error"]) paleyellow = page.utils.PaleYellowTable(body, "Services") def render(target): table = target.table("services callout") headings = table.tr("headings") headings.th("name").text("Name") headings.th("module").text("Module") headings.th("pid").text("PID") headings.th("rss").text("RSS") headings.th("cpu").text("CPU") headings.th("uptime").text("Uptime") headings.th("commands").text() table.tr("spacer").td("spacer", colspan=4) def formatUptime(seconds): def inner(seconds): if seconds < 60: return "%d seconds" % seconds elif seconds < 60 * 60: return "%d minutes" % (seconds / 60) elif seconds < 60 * 60 * 24: return "%d hours" % (seconds / (60 * 60)) else: return "%d days" % (seconds / (60 * 60 * 24)) return inner(int(seconds)).replace(" ", " ") def formatRSS(bytes): if bytes < 1024: return "%d B" % bytes elif bytes < 1024**2: return "%.1f kB" % (float(bytes) / 1024) elif bytes < 1024**3: return "%.1f MB" % (float(bytes) / 1024**2) else: return "%.1f GB" % (float(bytes) / 1024**3) def formatCPU(seconds): minutes = int(seconds / 60) seconds = seconds - minutes * 60 seconds = "%2.2f" % seconds if seconds.find(".") == 1: seconds = "0" + seconds return "%d:%s" % (minutes, seconds) def getProcessData(pid): try: items = open("/proc/%d/stat" % pid).read().split() return { "cpu": formatCPU( float(int(items[13]) + int(items[14])) / os.sysconf("SC_CLK_TCK")), "rss": formatRSS(int(items[23]) * os.sysconf("SC_PAGE_SIZE")) } except: return {"cpu": "N/A", "rss": "N/A"} for service_name, service_data in sorted(result["services"].items()): process_data = getProcessData(service_data["pid"]) row = table.tr("service") row.td("name").text(service_name) row.td("module").text(service_data["module"]) row.td("pid").text(service_data["pid"] if service_data["pid"] != -1 else "(not running)") row.td("rss").text(process_data["rss"]) row.td("cpu").text(process_data["cpu"]) row.td("uptime").innerHTML(formatUptime(service_data["uptime"])) commands = row.td("commands") commands.a(href="javascript:void(restartService(%s));" % htmlutils.jsify(service_name)).text("[restart]") commands.a(href="javascript:void(getServiceLog(%s));" % htmlutils.jsify(service_name)).text("[log]") for index, pid in enumerate( os.listdir(configuration.paths.WSGI_PIDFILE_DIR)): startup = float( open(os.path.join(configuration.paths.WSGI_PIDFILE_DIR, pid)).read()) uptime = time.time() - startup process_data = getProcessData(int(pid)) row = table.tr("service") row.td("name").text("wsgi:%d" % index) row.td("module").text() row.td("pid").text(pid) row.td("rss").text(process_data["rss"]) row.td("cpu").text(process_data["cpu"]) row.td("uptime").innerHTML(formatUptime(uptime)) commands = row.td("commands") commands.a(href="javascript:void(restartService('wsgi'));").text( "[restart]") paleyellow.addCentered(render) return document
def renderShowCommit(req, db, user): profiler = profiling.Profiler() file_ids = req.getParameter("file", None) if file_ids: file_ids = set(map(int, filter(None, file_ids.split(",")))) review_id = req.getParameter("review", None, filter=int) review_filter = req.getParameter("filter", None) context = req.getParameter("context", None, int) style = req.getParameter("style", "horizontal", str) rescan = req.getParameter("rescan", "no", str) == "yes" reanalyze = req.getParameter("reanalyze", None) wrap = req.getParameter("wrap", "yes", str) == "yes" conflicts = req.getParameter("conflicts", "no") == "yes" moves = req.getParameter("moves", "no") == "yes" full = req.getParameter("full", "no") == "yes" default_tabify = "yes" if user.getPreference(db, "commit.diff.visualTabs") else "no" tabify = req.getParameter("tabify", default_tabify) == "yes" if user.getPreference(db, "commit.diff.compactMode"): default_compact = "yes" else: default_compact = "no" compact = req.getParameter("compact", default_compact) == "yes" if moves: move_source_file_ids = req.getParameter("sourcefiles", None) move_target_file_ids = req.getParameter("targetfiles", None) if move_source_file_ids: move_source_file_ids = set(map(int, move_source_file_ids.split(","))) if move_target_file_ids: move_target_file_ids = set(map(int, move_target_file_ids.split(","))) all_commits = None listed_commits = None first_sha1 = None last_sha1 = None repository = None document = htmlutils.Document(req) if review_id is None: review = None else: review = dbutils.Review.fromId(db, review_id) if not review: raise page.utils.DisplayMessage, "Invalid review ID: %d" % review_id branch = review.branch repository = review.repository title = "" if review: title += "[r/%d] " % review.id if review_filter == "pending": title += "Pending: " elif review_filter == "reviewable": title += "Reviewable: " elif review_filter == "relevant": title += "Relevant: " if not repository: parameter = req.getParameter("repository", None) if parameter: repository = gitutils.Repository.fromParameter(db, parameter) if not repository: yield page.utils.displayMessage(db, req, user, "'%s' is not a valid repository!" % repository.name, review=review) return cursor = db.cursor() def expand_sha1(sha1): if review and re.match("^[0-9a-f]+$", sha1): cursor.execute("""SELECT sha1 FROM commits JOIN changesets ON (changesets.child=commits.id) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id) WHERE reviewchangesets.review=%s AND commits.sha1 LIKE %s""", (review.id, sha1 + "%")) try: return cursor.fetchone()[0] except: pass if len(sha1) == 40: return sha1 else: return repository.revparse(sha1) sha1 = req.getParameter("sha1", None, filter=expand_sha1) if sha1 is None: from_sha1 = req.getParameter("from", None, filter=expand_sha1) to_sha1 = req.getParameter("to", None, filter=expand_sha1) if (from_sha1 is None) != (to_sha1 is None): raise page.utils.DisplayMessage, "invalid parameters; one of 'from'/'to' specified but not both" if from_sha1 is None: first_sha1 = req.getParameter("first", None, filter=expand_sha1) last_sha1 = req.getParameter("last", None, filter=expand_sha1) if (first_sha1 is None) != (last_sha1 is None): raise page.utils.DisplayMessage, "invalid parameters; one of 'first'/'last' specified but not both" if first_sha1 is None: if review_id and review_filter: from_sha1, to_sha1, all_commits, listed_commits = commitRangeFromReview(db, user, review, review_filter, file_ids) if from_sha1 is None: sha1 = to_sha1 to_sha1 = None else: raise page.utils.DisplayMessage, "invalid parameters; need 'sha1', 'from'/'to' or 'first'/'last'" else: from_sha1 = None to_sha1 = None if context is None: context = user.getPreference(db, "commit.diff.contextLines") one_sha1 = filter(None, (sha1, from_sha1, to_sha1, first_sha1, last_sha1))[0] if repository: if not repository.iscommit(one_sha1): yield page.utils.displayMessage(db, req, user, "'%s' is not a valid commit in the repository '%s'!" % (one_sha1, repository.name), review=review) return else: default = user.getPreference(db, "defaultRepository") if default: repository = gitutils.Repository.fromName(db, default) if repository and not repository.iscommit(one_sha1): repository = None if not repository: repository = gitutils.Repository.fromSHA1(db, one_sha1) if not repository: yield page.utils.displayMessage(db, req, user, "'%s' is not a valid commit in any repository!" % one_sha1, review=review) return if first_sha1 is not None: try: first_commit = gitutils.Commit.fromSHA1(db, repository, first_sha1) except gitutils.GitError, error: raise page.utils.DisplayMessage("Invalid SHA-1", "%s is not a commit in %s" % (error.sha1, repository.path)) if len(first_commit.parents) != 1: yield page.utils.displayMessage(db, req, user, "Invalid parameters; 'first' must be a commit with a single parent.", review=review) return from_sha1 = first_commit.parents[0] to_sha1 = last_sha1
def renderFilterChanges(req, db, user): review_id = page.utils.getParameter(req, "review", filter=int) first_sha1 = page.utils.getParameter(req, "first", None) last_sha1 = page.utils.getParameter(req, "last", None) cursor = db.cursor() review = dbutils.Review.fromId(db, review_id) root_directories = {} root_files = {} def processFile(file_id): components = dbutils.describe_file(db, file_id).split("/") directories, files = root_directories, root_files for directory_name in components[:-1]: directories, files = directories.setdefault( directory_name, ({}, {})) files[components[-1]] = file_id if first_sha1 and last_sha1: sha1 = last_sha1 changesets = [] cursor.execute( """SELECT commits.sha1 FROM commits JOIN changesets ON (changesets.child=commits.id) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id) WHERE reviewchangesets.review=%s""", (review.id, )) first_commit = gitutils.Commit.fromSHA1(db, review.repository, first_sha1) last_commit = gitutils.Commit.fromSHA1(db, review.repository, last_sha1) if len(first_commit.parents) != 1: raise page.utils.DisplayMessage( "Filtering failed!", "First selected commit is a merge commit. Please go back and select a different range of commits.", review=review) from_commit = gitutils.Commit.fromSHA1(db, review.repository, first_commit.parents[0]) to_commit = last_commit commits = log.commitset.CommitSet.fromRange(db, from_commit, to_commit) if not commits: raise page.utils.DisplayMessage( "Filtering failed!", "The range of commits selected includes merges with ancestors not included in the range. Please go back and select a different range of commits.", review=review) cursor.execute( """SELECT DISTINCT reviewfiles.file FROM reviewfiles JOIN changesets ON (changesets.id=reviewfiles.changeset) JOIN commits ON (commits.id=changesets.child) WHERE reviewfiles.review=%s AND commits.sha1=ANY (%s)""", (review.id, [commit.sha1 for commit in commits])) else: cursor.execute("SELECT DISTINCT file FROM reviewfiles WHERE review=%s", (review.id, )) for (file_id, ) in cursor: processFile(file_id) document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() page.utils.generateHeader( body, db, user, lambda target: review_utils.renderDraftItems(db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review", True)]) document.addExternalStylesheet("resource/filterchanges.css") document.addExternalScript("resource/filterchanges.js") document.addInternalScript(user.getJS()) document.addInternalScript(review.getJS()) if first_sha1 and last_sha1: document.addInternalScript( "var commitRange = { first: %s, last: %s };" % (htmlutils.jsify(first_sha1), htmlutils.jsify(last_sha1))) else: document.addInternalScript("var commitRange = null;") target = body.div("main") basic = target.table('filter paleyellow', align='center', cellspacing=0) basic.col(width='10%') basic.col(width='60%') basic.col(width='30%') row = basic.tr("header") row.td('h1', colspan=2).h1().text("Filter Changes") row.td('h1 button').button("display").text("Display Diff") row = basic.tr("headings") row.td("select").text("Include") row.td("path").text("Path") row.td().text() def outputDirectory(base, name, directories, files): if name: level = base.count("/") row = basic.tr("directory", critic_level=level) row.td("select").input(type="checkbox") if level > 1: row.td("path").preformatted().innerHTML((" " * (len(base) - 2)) + "…/" + name + "/") else: row.td("path").preformatted().innerHTML(base + name + "/") row.td().text() else: level = 0 for directory_name in sorted(directories.keys()): outputDirectory(base + name + "/" if name else "", directory_name, directories[directory_name][0], directories[directory_name][1]) for file_name in sorted(files.keys()): row = basic.tr("file", critic_file_id=files[file_name], critic_level=level + 1) row.td("select").input(type="checkbox") row.td("path").preformatted().innerHTML( (" " * (len(base + name) - 1)) + "…/" + htmlutils.htmlify(file_name)) row.td().text() outputDirectory("", "", root_directories, root_files) row = basic.tr("footer") row.td('spacer', colspan=3) row = basic.tr("footer") row.td('button', colspan=3).button("display").text("Display Diff") if user.getPreference(db, "ui.keyboardShortcuts"): page.utils.renderShortcuts(body, "filterchanges") return document
def renderManageExtensions(req, db, user): if not configuration.extensions.ENABLED: administrators = dbutils.getAdministratorContacts(db, as_html=True) raise page.utils.DisplayMessage( title="Extension support not enabled", body=(("<p>This Critic system does not support extensions.</p>" "<p>Contact %s to have it enabled, or see the " "<a href='/tutorial?item=administration#extensions'>" "section on extensions</a> in the system administration " "tutorial for more information.</p>") % administrators), html=True) cursor = db.cursor() what = req.getParameter("what", "available") selected_versions = page.utils.json_decode(req.getParameter("select", "{}")) focused = req.getParameter("focus", None) if what == "installed": title = "Installed Extensions" listed_extensions = [] for extension_id, _, _, _ in Extension.getInstalls(db, user): try: listed_extensions.append(Extension.fromId(db, extension_id)) except ExtensionError as error: listed_extensions.append(error) else: title = "Available Extensions" listed_extensions = Extension.find(db) req.content_type = "text/html; charset=utf-8" document = htmlutils.Document(req) document.setTitle("Manage Extensions") html = document.html() head = html.head() body = html.body() def generateRight(target): target.a("button", href="tutorial?item=extensions").text("Tutorial") target.text(" ") target.a("button", href="tutorial?item=extensions-api").text("API Documentation") page.utils.generateHeader(body, db, user, current_page="extensions", generate_right=generateRight) document.addExternalStylesheet("resource/manageextensions.css") document.addExternalScript("resource/manageextensions.js") document.addInternalScript(user.getJS()) table = page.utils.PaleYellowTable(body, title) def addTitleRightLink(url, label): if user.name != req.user: url += "&user=%s" % user.name table.titleRight.text(" ") table.titleRight.a(href=url).text("[" + label + " extensions]") if what != "installed" or focused: addTitleRightLink("/manageextensions?what=installed", "installed") if what != "available" or focused: addTitleRightLink("/manageextensions?what=available", "available") for item in listed_extensions: if isinstance(item, ExtensionError): extension_error = item extension = item.extension else: extension_error = None extension = item if focused and extension.getKey() != focused: continue extension_path = extension.getPath() if extension.isSystemExtension(): hosting_user = None else: hosting_user = extension.getAuthor(db) selected_version = selected_versions.get(extension.getKey(), False) installed_sha1, installed_version = extension.getInstalledVersion(db, user) universal_sha1, universal_version = extension.getInstalledVersion(db, None) installed_upgradeable = universal_upgradeable = False if extension_error is None: if installed_sha1: current_sha1 = extension.getCurrentSHA1(installed_version) installed_upgradeable = installed_sha1 != current_sha1 if universal_sha1: current_sha1 = extension.getCurrentSHA1(universal_version) universal_upgradeable = universal_sha1 != current_sha1 def massage_version(version): if version is None: return "live" elif version: return "version/%s" % version else: return None if selected_version is False: selected_version = installed_version if selected_version is False: selected_version = universal_version install_version = massage_version(selected_version) installed_version = massage_version(installed_version) universal_version = massage_version(universal_version) manifest = None if extension_error is None: try: if selected_version is False: manifest = extension.getManifest() else: manifest = extension.getManifest(selected_version) except ManifestError as error: pass elif installed_sha1: manifest = extension.getManifest(installed_version, installed_sha1) elif universal_sha1: manifest = extension.getManifest(universal_version, universal_sha1) if manifest: if what == "available" and manifest.hidden: # Hide from view unless the user is hosting the extension, or is # an administrator and the extension is a system extension. if extension.isSystemExtension(): if not user.hasRole(db, "administrator"): continue elif hosting_user != user: continue else: if hosting_user != user: continue extension_id = extension.getExtensionID(db, create=False) if not user.isAnonymous(): buttons = [] if extension_id is not None: cursor.execute("""SELECT 1 FROM extensionstorage WHERE extension=%s AND uid=%s""", (extension_id, user.id)) if cursor.fetchone(): buttons.append(("Clear storage", ("clearExtensionStorage(%s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()))))) if not installed_version: if manifest and install_version and install_version != universal_version: buttons.append(("Install", ("installExtension(%s, %s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()), htmlutils.jsify(install_version))))) else: buttons.append(("Uninstall", ("uninstallExtension(%s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()))))) if manifest and (install_version != installed_version or (installed_sha1 and installed_upgradeable)): if install_version == installed_version: label = "Upgrade" else: label = "Install" buttons.append(("Upgrade", ("reinstallExtension(%s, %s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()), htmlutils.jsify(install_version))))) if user.hasRole(db, "administrator"): if not universal_version: if manifest and install_version: buttons.append(("Install (universal)", ("installExtension(%s, %s, %s, true)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()), htmlutils.jsify(install_version))))) else: buttons.append(("Uninstall (universal)", ("uninstallExtension(%s, %s, true)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()))))) if manifest and (install_version != universal_version or (universal_sha1 and universal_upgradeable)): if install_version == universal_version: label = "Upgrade (universal)" else: label = "Install (universal)" buttons.append((label, ("reinstallExtension(%s, %s, %s, true)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()), htmlutils.jsify(universal_version))))) else: buttons = None def renderItem(target): target.span("name").innerHTML(extension.getTitle(db, html=True)) if hosting_user: is_author = manifest and manifest.isAuthor(db, hosting_user) is_sole_author = is_author and len(manifest.authors) == 1 else: is_sole_author = False if extension_error is None: span = target.span("details") span.b().text("Details: ") select = span.select("details", critic_author=extension.getAuthorName(), critic_extension=extension.getName()) select.option(value='', selected="selected" if selected_version is False else None).text("Select version") versions = extension.getVersions() if versions: optgroup = select.optgroup(label="Official Versions") for version in versions: optgroup.option(value="version/%s" % version, selected="selected" if selected_version == version else None).text("%s" % version.upper()) optgroup = select.optgroup(label="Development") optgroup.option(value='live', selected="selected" if selected_version is None else None).text("LIVE") if manifest: is_installed = bool(installed_version) if is_installed: target.span("installed").text(" [installed]") else: is_installed = bool(universal_version) if is_installed: target.span("installed").text(" [installed (universal)]") target.div("description").preformatted().text(manifest.description, linkify=True) if not is_sole_author: authors = target.div("authors") authors.b().text("Author%s:" % ("s" if len(manifest.authors) > 1 else "")) authors.text(", ".join(author.name for author in manifest.getAuthors())) else: is_installed = False div = target.div("description broken").preformatted() if extension_error is None: anchor = div.a(href="loadmanifest?key=%s" % extension.getKey()) anchor.text("[This extension has an invalid MANIFEST file]") else: div.text("[This extension has been deleted or has become inaccessible]") if selected_version is False: return pages = [] injects = [] processcommits = [] filterhooks = [] scheduled = [] if manifest: for role in manifest.roles: if isinstance(role, PageRole): pages.append(role) elif isinstance(role, InjectRole): injects.append(role) elif isinstance(role, ProcessCommitsRole): processcommits.append(role) elif isinstance(role, FilterHookRole): filterhooks.append(role) elif isinstance(role, ScheduledRole): scheduled.append(role) role_table = target.table("roles") if pages: role_table.tr().th(colspan=2).text("Pages") for role in pages: row = role_table.tr() url = "%s/%s" % (dbutils.getURLPrefix(db, user), role.pattern) if is_installed and "*" not in url: row.td("pattern").a(href=url).text(url) else: row.td("pattern").text(url) td = row.td("description") td.text(role.description) if injects: role_table.tr().th(colspan=2).text("Page Injections") for role in injects: row = role_table.tr() row.td("pattern").text("%s/%s" % (dbutils.getURLPrefix(db, user), role.pattern)) td = row.td("description") td.text(role.description) if processcommits: role_table.tr().th(colspan=2).text("ProcessCommits hooks") ul = role_table.tr().td(colspan=2).ul() for role in processcommits: li = ul.li() li.text(role.description) if filterhooks: role_table.tr().th(colspan=2).text("FilterHook hooks") for role in filterhooks: row = role_table.tr() row.td("title").text(role.title) row.td("description").text(role.description) if scheduled: role_table.tr().th(colspan=2).text("Scheduled hooks") for role in scheduled: row = role_table.tr() row.td("pattern").text("%s @ %s" % (role.frequency, role.at)) td = row.td("description") td.text(role.description) installed_by = "" if extension_id is not None: cursor.execute("""SELECT uid FROM extensioninstalls JOIN extensions ON (extensions.id=extensioninstalls.extension) WHERE extensions.id=%s""", (extension.getExtensionID(db, create=False),)) user_ids = set(user_id for user_id, in cursor.fetchall()) if user_ids: installed_by = " (installed" if None in user_ids: installed_by += " universally" user_ids.remove(None) if user_ids: installed_by += " and" if user_ids: installed_by += (" by %d user%s" % (len(user_ids), "s" if len(user_ids) > 1 else "")) installed_by += ")" table.addItem("Extension", renderItem, extension_path + "/" + installed_by, buttons) document.addInternalScript("var selected_versions = %s;" % page.utils.json_encode(selected_versions)) return document
def renderShowComments(req, db, user): context_lines = req.getParameter("context", user.getPreference( db, "comment.diff.contextLines"), filter=int) default_compact = "yes" if user.getPreference( db, "commit.diff.compactMode") else "no" compact = req.getParameter("compact", default_compact) == "yes" default_tabify = "yes" if user.getPreference( db, "commit.diff.visualTabs") else "no" tabify = req.getParameter("tabify", default_tabify) == "yes" original = req.getParameter("original", "no") == "yes" review_id = req.getParameter("review", filter=int) batch_id = req.getParameter("batch", None, filter=int) filter = req.getParameter("filter", "all") blame = req.getParameter("blame", None) profiler = profiling.Profiler() review = dbutils.Review.fromId(db, review_id) review.repository.enableBlobCache() cursor = db.cursor() profiler.check("create review") if blame is not None: blame_user = dbutils.User.fromName(db, blame) cursor.execute( """SELECT commentchains.id FROM commentchains JOIN commentchainlines ON (commentchainlines.chain=commentchains.id) JOIN fileversions ON (fileversions.new_sha1=commentchainlines.sha1) JOIN changesets ON (changesets.id=fileversions.changeset) JOIN commits ON (commits.id=changesets.child) JOIN gitusers ON (gitusers.id=commits.author_gituser) JOIN usergitemails USING (email) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id AND reviewchangesets.review=commentchains.review) WHERE commentchains.review=%s AND usergitemails.uid=%s AND commentchains.state!='empty' AND (commentchains.state!='draft' OR commentchains.uid=%s) ORDER BY commentchains.file, commentchainlines.first_line""", (review.id, blame_user.id, user.id)) include_chain_ids = set([chain_id for (chain_id, ) in cursor]) profiler.check("initial blame filtering") else: include_chain_ids = None if filter == "toread": query = """SELECT commentchains.id FROM commentchains JOIN comments ON (comments.chain=commentchains.id) JOIN commentstoread ON (commentstoread.comment=comments.id) LEFT OUTER JOIN commentchainlines ON (commentchainlines.chain=commentchains.id) WHERE review=%s AND commentstoread.uid=%s ORDER BY file, first_line""" cursor.execute(query, (review.id, user.id)) else: query = """SELECT commentchains.id FROM commentchains LEFT OUTER JOIN commentchainlines ON (chain=id) WHERE review=%s AND commentchains.state!='empty'""" arguments = [review.id] if filter == "issues": query += " AND type='issue' AND (commentchains.state!='draft' OR commentchains.uid=%s)" arguments.append(user.id) elif filter == "draft-issues": query += " AND type='issue' AND commentchains.state='draft' AND commentchains.uid=%s" arguments.append(user.id) elif filter == "open-issues": query += " AND type='issue' AND commentchains.state='open'" elif filter == "addressed-issues": query += " AND type='issue' AND commentchains.state='addressed'" elif filter == "closed-issues": query += " AND type='issue' AND commentchains.state='closed'" elif filter == "notes": query += " AND type='note' AND (commentchains.state!='draft' OR commentchains.uid=%s)" arguments.append(user.id) elif filter == "draft-notes": query += " AND type='note' AND commentchains.state='draft' AND commentchains.uid=%s" arguments.append(user.id) elif filter == "open-notes": query += " AND type='note' AND commentchains.state='open'" else: query += " AND (commentchains.state!='draft' OR commentchains.uid=%s)" arguments.append(user.id) if batch_id is not None: query += " AND batch=%s" arguments.append(batch_id) # This ordering is inaccurate if comments apply to the same file but # different commits, but then, in that case there isn't really a # well-defined natural order either. Two comments that apply to the # same file and commit will at least be order by line number, and that's # better than nothing. query += " ORDER BY file, first_line" cursor.execute(query, arguments) profiler.check("main query") if include_chain_ids is None: chain_ids = [chain_id for (chain_id, ) in cursor] else: chain_ids = [ chain_id for (chain_id, ) in cursor if chain_id in include_chain_ids ] profiler.check("query result") document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() document.addInternalScript(user.getJS(db)) document.addInternalScript(review.getJS()) page.utils.generateHeader( body, db, user, lambda target: review_utils.renderDraftItems(db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review")]) profiler.check("page header") target = body.div("main") if chain_ids and not user.isAnonymous() and user.name == req.user: document.addInternalScript( "$(function () { markChainsAsRead([%s]); });" % ", ".join(map(str, chain_ids))) if chain_ids: processed = set() chains = [] file_ids = set() changesets_files = {} changesets = {} if blame is not None: annotators = {} review.branch.loadCommits(db) commits = log.commitset.CommitSet(review.branch.commits) for chain_id in chain_ids: if chain_id in processed: continue else: processed.add(chain_id) chain = review_comment.CommentChain.fromId(db, chain_id, user, review=review) chains.append(chain) if chain.file_id is not None: file_ids.add(chain.file_id) parent, child = review_html.getCodeCommentChainChangeset( db, chain, original) if parent and child: changesets_files.setdefault((parent, child), set()).add(chain.file_id) profiler.check("load chains") changeset_cache = {} for (from_commit, to_commit), filtered_file_ids in changesets_files.items(): changesets[(from_commit, to_commit)] = changeset_utils.createChangeset( db, user, review.repository, from_commit=from_commit, to_commit=to_commit, filtered_file_ids=filtered_file_ids)[0] profiler.check("create changesets") if blame is not None: annotators[(from_commit, to_commit)] = operation.blame.LineAnnotator( db, from_commit, to_commit, file_ids=file_ids, commits=commits, changeset_cache=changeset_cache) profiler.check("create annotators") for chain in chains: if blame is not None and chain.file_id is not None: try: changeset = changesets[(chain.first_commit, chain.last_commit)] annotator = annotators[(chain.first_commit, chain.last_commit)] except KeyError: # Most likely a comment created via /showfile. Such a # comment could be in code that 'blame_user' modified in the # review, but for now, let's skip the comment. continue else: file_in_changeset = changeset.getFile(chain.file_id) if not file_in_changeset: continue try: offset, count = chain.lines_by_sha1[ file_in_changeset.new_sha1] except KeyError: # Probably a chain raised against the "old" side of the diff. continue else: if not annotator.annotate(chain.file_id, offset, offset + count - 1, check_user=blame_user): continue profiler.check("detailed blame filtering") if chain.file_id is not None: from_commit, to_commit = review_html.getCodeCommentChainChangeset( db, chain, original) changeset = changesets.get((from_commit, to_commit)) else: changeset = None review_html.renderCommentChain(db, target, user, review, chain, context_lines=context_lines, compact=compact, tabify=tabify, original=original, changeset=changeset, linkify=linkify.Context( db=db, request=req, review=review)) profiler.check("rendering") yield document.render( stop=target, pretty=not compact ) + "<script>console.log((new Date).toString());</script>" profiler.check("transfer") page.utils.renderShortcuts(target, "showcomments", review=review) else: target.h1(align="center").text("No comments.") profiler.output(db, user, document) yield document.render(pretty=not compact)
def renderDashboard(req, db, user): if user.isAnonymous(): default_show = "open" else: default_show = user.getPreference(db, "dashboard.defaultGroups") show = req.getParameter("show", default_show) if user.isAnonymous(): def possible(group): return group in ("open", "closed") else: def possible(group): return True showlist = filter(possible, show.split(",")) showset = set(showlist) if user.getPreference(db, "commit.diff.compactMode"): default_compact = "yes" else: default_compact = "no" repository_arg = req.getParameter("repository", None) repository = gitutils.Repository.fromParameter( db, repository_arg) if repository_arg else None compact = req.getParameter("compact", default_compact) == "yes" cursor = db.cursor() profiler = profiling.Profiler() document = htmlutils.Document(req) document.setTitle("Dashboard") html = document.html() head = html.head() body = html.body() def generateRight(target): def addLink(key, title=None): if not title: title = key if key not in showset: target.text("[") target.a(href="dashboard?show=%s" % ",".join(showlist + [key])).text("show %s" % title) target.text("]") if user.isAnonymous(): addLink("open", "open") addLink("closed") else: target.text("[") target.a(href="config?highlight=dashboard.defaultGroups").text( "configure defaults") target.text("]") addLink("owned") addLink("draft") addLink("active") addLink("watched") addLink("open", "other open") addLink("closed") page.utils.generateHeader(body, db, user, current_page="dashboard", generate_right=generateRight, profiler=profiler) document.addExternalStylesheet("resource/dashboard.css") document.addExternalScript("resource/dashboard.js") document.addInternalScript(user.getJS()) target = body.div("main") def flush(target): return document.render(stop=target, pretty=not compact) def includeReview(review_id): if repository: cursor = db.cursor() cursor.execute( "SELECT branches.repository FROM branches JOIN reviews ON (reviews.branch=branches.id) WHERE reviews.id=%s", (review_id, )) return cursor.fetchone()[0] == repository.id else: return True def sortedReviews(data): reviews = [] for review_id in sorted(data.keys()): reviews.append((review_id, data[review_id])) return reviews def isAccepted(review_ids): cursor.execute( """SELECT reviews.id, COUNT(reviewfiles.id)=0 AND COUNT(commentchains.id)=0 FROM reviews LEFT OUTER JOIN reviewfiles ON (reviewfiles.review=reviews.id AND reviewfiles.state='pending') LEFT OUTER JOIN commentchains ON (commentchains.review=reviews.id AND commentchains.type='issue' AND commentchains.state='open') WHERE reviews.id=ANY (%s) GROUP BY reviews.id""", (review_ids, )) return dict(cursor) checked_repositories = {} def accessRepository(repository_id): already_checked = checked_repositories.get(repository_id) if already_checked is not None: return already_checked is_allowed = auth.AccessControlProfile.isAllowedRepository( db.profiles, "read", repository_id) checked_repositories[repository_id] = is_allowed return is_allowed def renderReviews(target, reviews, lines_and_comments=True, links=True): cursor.execute( "SELECT id, repository, name FROM branches WHERE id=ANY (%s)", (list(branch_id for _, (_, branch_id, _, _) in reviews), )) branch_data = { branch_id: (repository_id, name) for branch_id, repository_id, name in cursor } for review_id, (summary, branch_id, lines, comments) in reviews: repository_id, branch_name = branch_data[branch_id] if not accessRepository(repository_id): continue row = target.tr("review") row.td("name").text(branch_name) row.td("title").a(href="r/%d" % review_id).text(summary) if lines_and_comments: if lines: if links: row.td("lines").a( href="showcommit?review=%d&filter=pending" % review_id).text("%d lines" % (sum(lines))) else: row.td("lines").text("%d lines" % (sum(lines))) else: row.td("lines").text() if comments: if links: row.td("comments").a( href="showcomments?review=%s&filter=toread" % review_id).text( "%d comment%s" % (comments, "s" if comments > 1 else "")) else: row.td("comments").text( "%d comment%s" % (comments, "s" if comments > 1 else "")) else: row.td("comments").text() def hidden(what): new_show = ",".join(filter(lambda item: item != what, showlist)) if new_show: return "dashboard?show=%s" % new_show else: return "dashboard" profiler.check("generate: prologue") def renderOwned(): owned_accepted = [] owned_open = [] cursor.execute( """SELECT id, summary, branch FROM reviews JOIN reviewusers ON (review=id AND reviewusers.owner) WHERE state='open' AND uid=%s ORDER BY id DESC""", (user.id, )) owned = cursor.fetchall() profiler.check("query: owned") is_accepted = isAccepted(list(review_id for review_id, _, _ in owned)) for review_id, summary, branch_id in owned: if includeReview(review_id): if is_accepted[review_id]: owned_accepted.append( (review_id, (summary, branch_id, None, None))) else: owned_open.append( (review_id, (summary, branch_id, None, None))) profiler.check("processing: owned") if owned_accepted or owned_open: table = target.table("paleyellow reviews", id="owned", align="center", cellspacing=0) table.col(width="15%") table.col(width="55%") table.col(width="15%") table.col(width="15%") header = table.tr().td("h1", colspan=4).h1() header.text("Owned By You") header.span("right").a(href=hidden("owned")).text("[hide]") if owned_accepted: table.tr(id="accepted").td("h2", colspan=4).h2().text("Accepted") renderReviews(table, owned_accepted) if owned_open: table.tr(id="open").td("h2", colspan=4).h2().text("Pending") renderReviews(table, owned_open) profiler.check("generate: owned") return True def renderDraft(): draft_changes = {} draft_comments = {} draft_both = {} cursor.execute( """SELECT reviews.id, reviews.summary, reviews.branch, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted) FROM reviews JOIN reviewfiles ON (reviewfiles.review=reviews.id) JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id) WHERE reviews.state='open' AND reviewfiles.state=reviewfilechanges.from_state AND reviewfilechanges.state='draft' AND reviewfilechanges.uid=%s GROUP BY reviews.id, reviews.summary, reviews.branch""", (user.id, )) profiler.check("query: draft lines") for review_id, summary, branch_id, deleted_count, inserted_count in cursor: if includeReview(review_id): draft_changes[review_id] = (summary, branch_id, (deleted_count, inserted_count), None) profiler.check("processing: draft lines") cursor.execute( """SELECT reviews.id, reviews.summary, reviews.branch, COUNT(comments.id) FROM reviews JOIN commentchains ON (commentchains.review=reviews.id) JOIN comments ON (comments.chain=commentchains.id) WHERE comments.state='draft' AND comments.uid=%s GROUP BY reviews.id, reviews.summary, reviews.branch""", [user.id]) profiler.check("query: draft comments") for review_id, summary, branch_id, comments_count in cursor: if includeReview(review_id): if draft_changes.has_key(review_id): draft_both[review_id] = (summary, branch_id, draft_changes[review_id][2], comments_count) del draft_changes[review_id] else: draft_comments[review_id] = (summary, branch_id, None, comments_count) profiler.check("processing: draft comments") if draft_both or draft_changes or draft_comments: table = target.table("paleyellow reviews", id="draft", align="center", cellspacing=0) table.col(width="15%") table.col(width="55%") table.col(width="15%") table.col(width="15%") header = table.tr().td("h1", colspan=4).h1() header.text("Reviews With Unsubmitted Work") header.span("right").a(href=hidden("draft")).text("[hide]") if draft_both: table.tr(id="draft-changes-comments").td( "h2", colspan=4).h2().text("Draft Changes And Comments") renderReviews(table, sortedReviews(draft_both), links=False) if draft_changes: table.tr(id="draft-changes").td( "h2", colspan=4).h2().text("Draft Changes") renderReviews(table, sortedReviews(draft_changes), links=False) if draft_comments: table.tr(id="draft-comments").td( "h2", colspan=4).h2().text("Draft Comments") renderReviews(table, sortedReviews(draft_comments), links=False) profiler.check("generate: draft") return True active = {} def fetchActive(): if not active: with_changes = {} with_comments = {} with_both = {} cursor.execute( """SELECT reviews.id, reviews.summary, reviews.branch, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted) FROM reviews JOIN reviewusers ON (reviewusers.review=reviews.id AND reviewusers.uid=%s) JOIN reviewfiles ON (reviewfiles.review=reviews.id) JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id AND reviewuserfiles.uid=%s) WHERE reviews.state='open' AND reviewfiles.state='pending' GROUP BY reviews.id, reviews.summary, reviews.branch""", (user.id, user.id)) profiler.check("query: active lines") for review_id, summary, branch_id, deleted_count, inserted_count in cursor: if includeReview(review_id): with_changes[review_id] = (summary, branch_id, (deleted_count, inserted_count), None) profiler.check("processing: active lines") cursor.execute( """SELECT reviews.id, reviews.summary, reviews.branch, unread.count FROM (SELECT commentchains.review AS review, COUNT(commentstoread.comment) AS count FROM commentchains JOIN comments ON (comments.chain=commentchains.id) JOIN commentstoread ON (commentstoread.comment=comments.id AND commentstoread.uid=%s) GROUP BY commentchains.review) AS unread JOIN reviews ON (reviews.id=unread.review) WHERE reviews.state='open'""", (user.id, )) profiler.check("query: active comments") for review_id, summary, branch_id, comments_count in cursor: if includeReview(review_id): if with_changes.has_key(review_id): with_both[review_id] = (summary, branch_id, with_changes[review_id][2], comments_count) del with_changes[review_id] else: with_comments[review_id] = (summary, branch_id, None, comments_count) profiler.check("processing: active comments") active["changes"] = with_changes active["comments"] = with_comments active["both"] = with_both def renderActive(): fetchActive() if active["both"] or active["changes"] or active["comments"]: table = target.table("paleyellow reviews", id="active", align="center", cellspacing=0) table.col(width="15%") table.col(width="55%") table.col(width="15%") table.col(width="15%") header = table.tr().td("h1", colspan=4).h1() header.text("Active Reviews") header.span("right").a(href=hidden("active")).text("[hide]") if active["both"]: review_ids = ",".join(map(str, active["both"].keys())) h2 = table.tr(id="active-changes-comments").td( "h2", colspan=4).h2().text("Has Changes And Comments") h2.a(href="javascript:void(0);", onclick="markChainsAsRead([%s]);" % review_ids).text("[mark all as read]") renderReviews(table, sortedReviews(active["both"])) if active["changes"]: table.tr(id="active-changes").td( "h2", colspan=4).h2().text("Has Changes") renderReviews(table, sortedReviews(active["changes"])) if active["comments"]: review_ids = ",".join(map(str, active["comments"].keys())) h2 = table.tr(id="active-comments").td( "h2", colspan=4).h2().text("Has Comments") h2.a(href="javascript:void(0);", onclick="markChainsAsRead([%s]);" % review_ids).text("[mark all as read]") renderReviews(table, sortedReviews(active["comments"])) profiler.check("generate: active") return True other = {} def fetchWatchedAndClosed(): if not other: if "watched" not in showset: state_filter = " WHERE reviews.state='closed'" elif "closed" not in showset: state_filter = " WHERE reviews.state='open'" else: state_filter = "" profiler.check("query: watched/closed") watched = {} owned_closed = {} other_closed = {} if "watched" in showset: fetchActive() cursor.execute( """SELECT reviews.id, reviews.summary, reviews.branch, reviews.state, reviewusers.owner, reviewusers.uid IS NULL FROM reviews LEFT OUTER JOIN reviewusers ON (reviewusers.review=reviews.id AND reviewusers.uid=%s)""" + state_filter, (user.id, )) for review_id, summary, branch_id, review_state, is_owner, not_associated in cursor: if includeReview(review_id): if review_state == 'open': if is_owner or not_associated: continue fetchActive() if active["both"].has_key( review_id) or active["changes"].has_key( review_id) or active["comments"].has_key( review_id): continue watched[review_id] = summary, branch_id, None, None elif is_owner: owned_closed[ review_id] = summary, branch_id, None, None else: other_closed[ review_id] = summary, branch_id, None, None profiler.check("processing: watched/closed") other["watched"] = watched other["owned-closed"] = owned_closed other["other-closed"] = other_closed def renderWatched(): fetchWatchedAndClosed() watched = other["watched"] accepted = [] pending = [] is_accepted = isAccepted(watched.keys()) for review_id, (summary, branch_id, lines, comments) in sortedReviews(watched): if is_accepted[review_id]: accepted.append( (review_id, (summary, branch_id, lines, comments))) else: pending.append( (review_id, (summary, branch_id, lines, comments))) if accepted or pending: table = target.table("paleyellow reviews", id="watched", align="center", cellspacing=0) table.col(width="30%") table.col(width="70%") header = table.tr().td("h1", colspan=4).h1() header.text("Watched Reviews") header.span("right").a(href=hidden("watched")).text("[hide]") if accepted: table.tr(id="active-changes-comments").td( "h2", colspan=4).h2().text("Accepted") renderReviews(table, accepted, False) if pending: table.tr(id="active-changes-comments").td( "h2", colspan=4).h2().text("Pending") renderReviews(table, pending, False) profiler.check("generate: watched") return True def renderClosed(): fetchWatchedAndClosed() owned_closed = other["owned-closed"] other_closed = other["other-closed"] if owned_closed or other_closed: table = target.table("paleyellow reviews", id="closed", align="center", cellspacing=0) table.col(width="30%") table.col(width="70%") header = table.tr().td("h1", colspan=4).h1() header.text("Closed Reviews") header.span("right").a(href=hidden("closed")).text("[hide]") if not user.isAnonymous(): if owned_closed: table.tr().td("h2", colspan=4).h2().text("Owned") renderReviews(table, sortedReviews(owned_closed), False) if other_closed: table.tr().td("h2", colspan=4).h2().text("Other") renderReviews(table, sortedReviews(other_closed), False) else: renderReviews(table, sortedReviews(other_closed), False) profiler.check("generate: closed") return True def renderOpen(): other_open = {} cursor.execute( """SELECT reviews.id, reviews.summary, reviews.branch FROM reviews LEFT OUTER JOIN reviewusers ON (reviewusers.review=reviews.id AND reviewusers.uid=%s) WHERE reviews.state='open' AND reviewusers.uid IS NULL""", [user.id]) profiler.check("query: open") for review_id, summary, branch_id in cursor: if includeReview(review_id): other_open[review_id] = summary, branch_id, None, None profiler.check("processing: open") if other_open: accepted = [] pending = [] for review_id, (summary, branch_id, lines, comments) in sortedReviews(other_open): if dbutils.Review.isAccepted(db, review_id): accepted.append( (review_id, (summary, branch_id, lines, comments))) else: pending.append( (review_id, (summary, branch_id, lines, comments))) table = target.table("paleyellow reviews", id="open", align="center", cellspacing=0) table.col(width="30%") table.col(width="70%") header = table.tr().td("h1", colspan=4).h1() header.text( "Open Reviews" if user.isAnonymous() else "Other Open Reviews") header.span("right").a(href=hidden("open")).text("[hide]") if accepted: table.tr().td("h2", colspan=4).h2().text("Accepted") renderReviews(table, accepted, False) if pending: table.tr().td("h2", colspan=4).h2().text("Pending") renderReviews(table, pending, False) profiler.check("generate: open") return True render = { "owned": renderOwned, "draft": renderDraft, "active": renderActive, "watched": renderWatched, "closed": renderClosed, "open": renderOpen } empty = True for item in showlist: if item in render: target.comment(repr(item)) if render[item](): empty = False yield flush(target) if empty: document.addExternalStylesheet("resource/message.css") body.div("message paleyellow").h1("center").text("No reviews!") profiler.output(db=db, user=user, target=document) yield flush(None)
def renderShowReviewLog(req, db, user): review_id = page.utils.getParameter(req, "review", filter=int) granularity = page.utils.getParameter(req, "granularity") unassigned = page.utils.getParameter(req, "unassigned", "no") == "yes" cursor = db.cursor() review = dbutils.Review.fromId(db, review_id) document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() head.title("Review Log: %s" % review.branch.name) page.utils.generateHeader( body, db, user, lambda target: review_utils.renderDraftItems(db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review", True)]) document.addExternalStylesheet("resource/showreviewlog.css") document.addExternalStylesheet("resource/review.css") document.addExternalScript("resource/review.js") document.addInternalScript(review.getJS()) target = body.div("main") reviewed_reviewers = review_utils.getReviewedReviewers(db, review) def formatFiles(files): paths = sorted( [dbutils.describe_file(db, file_id) for file_id in files]) if granularity == "file": return diff.File.eliminateCommonPrefixes(paths) else: modules = set() files = [] for path in paths: match = re_module.match(path) if match: modules.add(match.group(1)) else: files.append(path) return sorted(modules) + diff.File.eliminateCommonPrefixes(files) if reviewed_reviewers and not unassigned: reviewed = target.table('changes', align='center') reviewed.col(width="30%") reviewed.col(width="10%") reviewed.col(width="60%") reviewed.tr().td('h1', colspan=3).h1().text("Reviewed Changes") teams = review_utils.collectReviewTeams(reviewed_reviewers) for team in teams: row = reviewed.tr("reviewers") cell = row.td("reviewers") users = sorted([ dbutils.User.fromId(db, user_id).fullname for user_id in team ]) for user in users: cell.text(user).br() row.td("willreview").innerHTML("reviewed") cell = row.td("files") for file in formatFiles(teams[team]): cell.span("file").innerHTML(file).br() pending_reviewers = review_utils.getPendingReviewers(db, review) if pending_reviewers: pending = target.table('changes', align='center') pending.col(width="30%") pending.col(width="10%") pending.col(width="60%") pending.tr().td('h1', colspan=3).h1().text("Pending Changes") teams = review_utils.collectReviewTeams(pending_reviewers) if not unassigned: for team in teams: if team is not None: row = pending.tr("reviewers") cell = row.td("reviewers") users = sorted([ dbutils.User.fromId(db, user_id).fullname for user_id in team ]) for user in users: cell.text(user).br() row.td("willreview").innerHTML("should review") cell = row.td("files") for file in formatFiles(teams[team]): cell.span("file").innerHTML(file).br() if None in teams: row = pending.tr("reviewers") row.td("no-one", colspan=2).text("No reviewers found for changes in") cell = row.td("files") for file in formatFiles(teams[None]): cell.span("file").innerHTML(file).br() return document
def renderManageReviewers(req, db, user): review_id = req.getParameter("review", filter=int) cursor = db.cursor() review = dbutils.Review.fromId(db, review_id) root_directories = {} root_files = {} def processFile(file_id): components = dbutils.describe_file(db, file_id).split("/") directories, files = root_directories, root_files for directory_name in components[:-1]: directories, files = directories.setdefault( directory_name, ({}, {})) files[components[-1]] = file_id cursor.execute("SELECT file FROM reviewfiles WHERE review=%s", (review.id, )) for (file_id, ) in cursor: processFile(file_id) cursor.execute("SELECT name FROM users WHERE name IS NOT NULL") users = [user_name for (user_name, ) in cursor if user_name] document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() page.utils.generateHeader( body, db, user, lambda target: review_utils.renderDraftItems(db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review")]) document.addExternalStylesheet("resource/managereviewers.css") document.addExternalScript("resource/managereviewers.js") document.addInternalScript(user.getJS()) document.addInternalScript(review.getJS()) document.addInternalScript( "var users = [ %s ];" % ", ".join([htmlutils.jsify(user_name) for user_name in sorted(users)])) target = body.div("main") basic = target.table('manage paleyellow', align='center') basic.col(width='10%') basic.col(width='60%') basic.col(width='30%') basic.tr().td('h1', colspan=3).h1().text("Manage Reviewers") row = basic.tr("current") row.td("select").text("Current:") cell = row.td("value") for index, reviewer in enumerate(review.reviewers): if index != 0: cell.text(", ") cell.span("reviewer", critic_username=reviewer.name).innerHTML( htmlutils.htmlify(reviewer.fullname).replace(" ", " ")) row.td("right").text() row = basic.tr("reviewer") row.td("select").text("Reviewer:") row.td("value").input("reviewer").span("message") row.td("right").button("save").text("Save") row = basic.tr("help") row.td("help", colspan=3).text( "Enter the name of a current reviewer to edit assignments (or unassign.) Enter the name of another user to add a new reviewer." ) row = basic.tr("headings") row.td("select").text("Assigned") row.td("path").text("Path") row.td().text() def outputDirectory(base, name, directories, files): if name: level = base.count("/") row = basic.tr("directory", critic_level=level) row.td("select").input(type="checkbox") if level > 1: row.td("path").preformatted().innerHTML((" " * (len(base) - 2)) + "…/" + name + "/") else: row.td("path").preformatted().innerHTML(base + name + "/") row.td().text() else: level = 0 for directory_name in sorted(directories.keys()): outputDirectory(base + name + "/" if name else "", directory_name, directories[directory_name][0], directories[directory_name][1]) for file_name in sorted(files.keys()): row = basic.tr("file", critic_file_id=files[file_name], critic_level=level + 1) row.td("select").input(type="checkbox") row.td("path").preformatted().innerHTML( (" " * (len(base + name) - 1)) + "…/" + htmlutils.htmlify(file_name)) row.td().text() outputDirectory("", "", root_directories, root_files) return document
def renderConfirmMerge(req, db, user): confirmation_id = req.getParameter("id", filter=int) tail_sha1 = req.getParameter("tail", None) do_confirm = req.getParameter("confirm", "no") == "yes" do_cancel = req.getParameter("cancel", "no") == "yes" cursor = db.cursor() cursor.execute( "SELECT review, uid, merge, confirmed, tail FROM reviewmergeconfirmations WHERE id=%s", (confirmation_id, )) row = cursor.fetchone() if not row: raise page.utils.DisplayMessage("No pending merge with that id.") review_id, user_id, merge_id, confirmed, tail_id = row review = dbutils.Review.fromId(db, review_id) merge = gitutils.Commit.fromId(db, review.repository, merge_id) if confirmed and tail_id is not None: tail_sha1 = gitutils.Commit.fromId(db, review.repository, tail_id).sha1 cursor.execute("SELECT merged FROM reviewmergecontributions WHERE id=%s", (confirmation_id, )) merged = [ gitutils.Commit.fromId(db, review.repository, merged_id) for (merged_id, ) in cursor ] merged_set = log.commitset.CommitSet(merged) if tail_sha1 is not None: tail = gitutils.Commit.fromSHA1(db, review.repository, tail_sha1) tail_id = tail.getId(db) cut = [ gitutils.Commit.fromSHA1(db, review.repository, sha1) for sha1 in tail.parents if sha1 in merged_set ] merged_set = merged_set.without(cut) merged = list(merged_set) else: tail_id = None if do_confirm: cursor.execute( "UPDATE reviewmergeconfirmations SET confirmed=TRUE, tail=%s WHERE id=%s", (tail_id, confirmation_id)) db.commit() elif do_cancel: cursor.execute("DELETE FROM reviewmergeconfirmations WHERE id=%s", (confirmation_id, )) db.commit() document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() def renderButtons(target): if not do_confirm and not do_cancel: target.button("confirmAll").text("Confirm (merge + contributed)") target.button("confirmNone").text("Confirm (merge only)") target.button("cancel").text("Cancel") page.utils.generateHeader(body, db, user, renderButtons, extra_links=[("r/%d" % review.id, "Back to Review")]) document.addExternalStylesheet("resource/confirmmerge.css") document.addExternalScript("resource/log.js") document.addExternalScript("resource/confirmmerge.js") document.addInternalScript(user.getJS()) document.addInternalScript(review.getJS()) document.addInternalScript("var confirmation_id = %d;" % confirmation_id) document.addInternalScript("var merge_sha1 = %s;" % htmlutils.jsify(merge.sha1)) if tail_sha1 is not None: document.addInternalScript("var tail_sha1 = %s;" % htmlutils.jsify(tail_sha1)) if not do_confirm and not do_cancel: heads = merged_set.getHeads() if heads: document.addInternalScript("var automaticAnchorCommit = %s;" % htmlutils.jsify(heads.pop().sha1)) else: document.addInternalScript("var automaticAnchorCommit = null;") if do_confirm: document.addInternalScript("var confirmed = true;") else: document.addInternalScript("var confirmed = false;") target = body.div("main") basic = target.table("paleyellow") basic.col(width='15%') basic.col(width='55%') basic.col(width='30%') h1 = basic.tr().td('h1', colspan=3).h1() if do_confirm: h1.text("CONFIRMED MERGE") elif do_cancel: h1.text("CANCELLED MERGE") else: h1.text("Confirm Merge") row = basic.tr("sha1") row.td("heading").text("SHA-1:") row.td("value").preformatted().text(merge.sha1) row.td().text() row = basic.tr("message") row.td("heading").text("Message:") row.td("value").preformatted().text(merge.message) row.td().text() if not do_confirm and not do_cancel: row = basic.tr("instructions") row.td("heading").text("Instructions:") row.td("value").preformatted().text("""\ Use the top right buttons to confirm the merge with or without the contributed commits that it brings. By clicking 'Confirm (merge + contributed)' you will bring the merge commit plus all commits that it contributes into the this code review. By clicking 'Confirm (merge only)' you will bring only the merge commit itself into the code review and not the contributed commits." By clicking 'Cancel' you will abort the merge. The code review will not be modified at all from its current state.""" ) row.td().text() if merged: columns = [(10, log.html.WhenColumn()), (60, log.html.SummaryColumn()), (16, log.html.AuthorColumn())] log.html.render(db, target, "Contributed Commits", commits=merged, columns=columns) return document
def renderEditResource(req, db, user): name = page.utils.getParameter(req, "name", None) document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user) document.addExternalStylesheet("resource/editresource.css") document.addExternalScript("resource/editresource.js") target = body.div("main") table = target.table('paleyellow', align='center') table.col(width='10%') table.col(width='60%') table.tr().td('h1', colspan=2).h1().text("Resource Editor") select_row = table.tr('select') select_row.td('heading').text('Resource:') select = select_row.td('value').select() if name is None: select.option(selected="selected").text("Select resource") select.option(value="diff.css", selected="selected" if name=="diff.css" else None).text("Diff coloring") select.option(value="syntax.css", selected="selected" if name=="syntax.css" else None).text("Syntax highlighting") help_row = table.tr('help') help_row.td('help', colspan=2).text("Select the resource to edit.") is_edited = False is_reset = False source = None if name is None: document.addInternalScript("var resource_name = null;"); source = "" else: if name not in ("diff.css", "syntax.css"): raise page.utils.DisplayMessage("Invalid resource name", body="Must be one of 'diff.css' and 'syntax.css'.") document.addInternalScript("var resource_name = %s;" % htmlutils.jsify(name)); cursor = db.cursor() cursor.execute("SELECT source FROM userresources WHERE uid=%s AND name=%s ORDER BY revision DESC FETCH FIRST ROW ONLY", (user.id, name)) row = cursor.fetchone() if row: is_edited = True source = row[0] if source is None: is_reset = is_edited source = open(configuration.paths.INSTALL_DIR + "/resources/" + name).read() document.addInternalScript("var original_source = %s;" % htmlutils.jsify(source)); table.tr('value').td('value', colspan=2).textarea(rows=source.count("\n") + 10).preformatted().text(source) buttons = table.tr('buttons').td('buttons', colspan=2) buttons.button('save').text("Save changes") if is_edited and not is_reset: buttons.button('reset').text("Reset to built-in version") if is_reset: buttons.button('restore').text("Restore last edited version") return document
def renderShowTree(req, db, user): cursor = db.cursor() sha1 = req.getParameter("sha1") path = req.getParameter("path", "/") review_id = req.getParameter("review", None, filter=int) if path[0] == '/': full_path = path if path != "/": path = path[1:] else: full_path = "/" + path if not path: path = "/" if review_id is None: review = None review_arg = "" repository_arg = req.getParameter("repository", "") if repository_arg: repository = gitutils.Repository.fromParameter(db, repository_arg) else: repository = gitutils.Repository.fromSHA1(db, sha1) repository_arg = "&repository=%d" % repository.id else: review = dbutils.Review.fromId(db, review_id) review_arg = "&review=%d" % review_id repository_arg = "" repository = review.repository document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() extra_links = [] if review: extra_links.append(("r/%d" % review.id, "Back to Review", True)) page.utils.generateHeader(body, db, user, extra_links=extra_links) document.addExternalStylesheet("resource/showtree.css") #document.addExternalScript("resource/showtree.js") target = body.div("main") table = target.table("tree paleyellow", align="center", cellspacing=0) table.col(width="10%") table.col(width="60%") table.col(width="20%") thead = table.thead() h1 = thead.tr().td('h1', colspan=3).h1() if path == "/": h1.text("/") else: h1.a("root", href="showtree?sha1=%s&path=/%s%s" % (sha1, review_arg, repository_arg)).text("root") h1.span().text('/') components = path.split("/") for index, component in enumerate(components[:-1]): h1.a(href="showtree?sha1=%s&path=%s%s%s" % (sha1, "/".join(components[:index + 1]), review_arg, repository_arg)).text(component) h1.span().text('/') h1.text(components[-1]) row = thead.tr() row.td('mode').text("Mode") row.td('name').text("Name") row.td('size').text("Size") tree = gitutils.Tree.fromPath( gitutils.Commit.fromSHA1(db, repository, sha1), full_path) def compareEntries(a, b): if a.type != b.type: if a.type == "tree": return -1 else: return 1 else: return cmp(a.name, b.name) tbody = table.tbody() for entry in sorted(tree, cmp=compareEntries): if entry.type == "blob": url = "showfile?sha1=%s&path=%s%s%s" % ( sha1, os.path.join(path, entry.name), review_arg, repository_arg) else: url = "showtree?sha1=%s&path=%s%s%s" % ( sha1, os.path.join(path, entry.name), review_arg, repository_arg) row = tbody.tr(entry.type) row.td('mode').text(str(entry.mode)) if stat.S_ISLNK(entry.mode): cell = row.td('link', colspan=2) cell.span('name').text(entry.name) cell.text(' -> ') cell.span('target').text(repository.fetch(entry.sha1).data) elif entry.type == "commit": row.td('name').text("%s (%s)" % (entry.name, entry.sha1)) row.td('size').text(entry.size) else: row.td('name').a(href=url).text(entry.name) row.td('size').text(entry.size) return document
def renderShowBatch(req, db, user): batch_id = page.utils.getParameter(req, "batch", None, filter=int) review_id = page.utils.getParameter(req, "review", None, filter=int) cursor = db.cursor() if batch_id is None and review_id is None: return page.utils.displayMessage(db, req, user, "Missing argument: 'batch'") if batch_id: cursor.execute("SELECT review, uid, comment FROM batches WHERE id=%s", (batch_id, )) row = cursor.fetchone() if not row: raise page.utils.DisplayMessage("Invalid batch ID: %d" % batch_id) review_id, author_id, chain_id = row author = dbutils.User.fromId(db, author_id) else: chain_id = None author = user review = dbutils.Review.fromId(db, review_id) if chain_id: batch_chain = review_comment.CommentChain.fromId(db, chain_id, user, review=review) batch_chain.loadComments(db, user) else: batch_chain = None document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() page.utils.generateHeader( body, db, user, lambda target: review_utils.renderDraftItems(db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review")]) document.addExternalStylesheet("resource/showreview.css") document.addExternalStylesheet("resource/showbatch.css") document.addExternalStylesheet("resource/review.css") document.addExternalStylesheet("resource/comment.css") document.addExternalScript("resource/review.js") document.addExternalScript("resource/comment.js") document.addInternalScript(user.getJS()) document.addInternalScript(review.getJS()) if batch_chain: document.addInternalScript( "commentChainById[%d] = %s;" % (batch_chain.id, batch_chain.getJSConstructor())) target = body.div("main") table = target.table('paleyellow basic comments', align='center') table.col(width='10%') table.col(width='80%') table.col(width='10%') table.tr().td('h1', colspan=3).h1().text("Review by %s" % htmlify(author.fullname)) if batch_chain: batch_chain.loadComments(db, user) row = table.tr("line") row.td("heading").text("Comment:") row.td("value").preformatted().div("text").text( htmlify(batch_chain.comments[0].comment)) row.td("status").text() def renderFiles(title, cursor): files = [] for file_id, delete_count, insert_count in cursor.fetchall(): files.append( (dbutils.describe_file(db, file_id), delete_count, insert_count)) paths = [] deleted = [] inserted = [] for path, delete_count, insert_count in sorted(files): paths.append(path) deleted.append(delete_count) inserted.append(insert_count) if paths: diff.File.eliminateCommonPrefixes(paths) row = table.tr("line") row.td("heading").text(title) files_table = row.td().table("files callout") headers = files_table.thead().tr() headers.th("path").text("Changed Files") headers.th("lines", colspan=2).text("Lines") files = files_table.tbody() for path, delete_count, insert_count in zip( paths, deleted, inserted): file = files.tr() file.td("path").preformatted().innerHTML(path) file.td("lines").preformatted().text( "-%d" % delete_count if delete_count else None) file.td("lines").preformatted().text( "+%d" % insert_count if insert_count else None) row.td("status").text() def condition(table_name): if batch_id: return "%s.batch=%d" % (table_name, batch_id) else: return "review=%d AND %s.batch IS NULL AND %s.uid=%d" % ( review.id, table_name, table_name, author.id) cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted) FROM reviewfiles JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id) WHERE %s AND reviewfilechanges.to_state='reviewed' GROUP BY reviewfiles.file""" % condition("reviewfilechanges")) renderFiles("Reviewed:", cursor) cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted) FROM reviewfiles JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id) WHERE %s AND reviewfilechanges.to_state='pending' GROUP BY reviewfiles.file""" % condition("reviewfilechanges")) renderFiles("Unreviewed:", cursor) def renderChains(title, cursor, replies): all_chains = [ review_comment.CommentChain.fromId(db, chain_id, user, review=review) for (chain_id, ) in cursor ] if not all_chains: return for chain in all_chains: chain.loadComments(db, user) issue_chains = filter(lambda chain: chain.type == "issue", all_chains) draft_issues = filter(lambda chain: chain.state == "draft", issue_chains) open_issues = filter(lambda chain: chain.state == "open", issue_chains) addressed_issues = filter(lambda chain: chain.state == "addressed", issue_chains) closed_issues = filter(lambda chain: chain.state == "closed", issue_chains) note_chains = filter(lambda chain: chain.type == "note", all_chains) draft_notes = filter( lambda chain: chain.state == "draft" and chain != batch_chain, note_chains) open_notes = filter( lambda chain: chain.state == "open" and chain != batch_chain, note_chains) def renderChains(target, chains): for chain in chains: row = target.tr("comment") row.td("author").text(chain.user.fullname) row.td("title").a(href="showcomment?chain=%d" % chain.id).innerHTML(chain.leader()) row.td("when").text(chain.when()) def showcomments(filter_param): params = {"review": review.id, "filter": filter_param} if batch_id: params["batch"] = batch_id return htmlutils.URL("/showcomments", **params) if draft_issues or open_issues or addressed_issues or closed_issues: h2 = table.tr().td("h2", colspan=3).h2().text(title) if len(draft_issues) + len(open_issues) + len( addressed_issues) + len(closed_issues) > 1: h2.a(href=showcomments("issues")).text("[display all]") if draft_issues: h3 = table.tr(id="draft-issues").td( "h3", colspan=3).h3().text("Draft issues") if len(draft_issues) > 1: h3.a(href=showcomments("draft-issues")).text( "[display all]") renderChains(table, draft_issues) if batch_id is not None or replies: if open_issues: h3 = table.tr(id="open-issues").td( "h3", colspan=3).h3().text("Still open issues") if batch_id and len(open_issues) > 1: h3.a(href=showcomments("open-issues")).text( "[display all]") renderChains(table, open_issues) if addressed_issues: h3 = table.tr(id="addressed-issues").td( "h3", colspan=3).h3().text("Now addressed issues") if batch_id and len(addressed_issues) > 1: h3.a(href=showcomments("addressed-issues")).text( "[display all]") renderChains(table, addressed_issues) if closed_issues: h3 = table.tr(id="closed-issues").td( "h3", colspan=3).h3().text("Now closed issues") if batch_id and len(closed_issues) > 1: h3.a(href=showcomments("closed-issues")).text( "[display all]") renderChains(table, closed_issues) if draft_notes or open_notes: h2 = table.tr().td("h2", colspan=3).h2().text(title) if len(draft_notes) + len(open_notes) > 1: h2.a(href=showcomments("notes")).text("[display all]") if draft_notes: h3 = table.tr(id="draft-notes").td( "h3", colspan=3).h3().text("Draft notes") if len(draft_notes) > 1: h3.a( href=showcomments("draft-notes")).text("[display all]") renderChains(table, draft_notes) if open_notes: h3 = table.tr(id="notes").td("h3", colspan=3).h3().text("Notes") if batch_id and len(open_notes) > 1: h3.a(href=showcomments("open-notes")).text("[display all]") renderChains(table, open_notes) cursor.execute("SELECT id FROM commentchains WHERE %s AND type='issue'" % condition("commentchains")) renderChains("Raised issues", cursor, False) cursor.execute("""SELECT commentchains.id FROM commentchains JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id) WHERE %s AND to_state='closed'""" % condition("commentchainchanges")) renderChains("Resolved issues", cursor, False) cursor.execute("""SELECT commentchains.id FROM commentchains JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id) WHERE %s AND to_state='open'""" % condition("commentchainchanges")) renderChains("Reopened issues", cursor, False) cursor.execute("""SELECT commentchains.id FROM commentchains JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id) WHERE %s AND to_type='issue'""" % condition("commentchainchanges")) renderChains("Converted into issues", cursor, False) cursor.execute("""SELECT commentchains.id FROM commentchains JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id) WHERE %s AND to_type='note'""" % condition("commentchainchanges")) renderChains("Converted into notes", cursor, False) cursor.execute("SELECT id FROM commentchains WHERE %s AND type='note'" % condition("commentchains")) renderChains("Written notes", cursor, False) cursor.execute("""SELECT commentchains.id FROM commentchains JOIN comments ON (comments.chain=commentchains.id) WHERE %s AND comments.id!=commentchains.first_comment""" % condition("comments")) renderChains("Replied to", cursor, True) return document
def renderShowFile(req, db, user): cursor = db.cursor() sha1 = req.getParameter("sha1") path = req.getParameter("path") line = req.getParameter("line", None) review_id = req.getParameter("review", None, filter=int) default_tabify = "yes" if user.getPreference( db, "commit.diff.visualTabs") else "no" tabify = req.getParameter("tabify", default_tabify) == "yes" if line is None: first, last = None, None else: if "-" in line: first, last = map(int, line.split("-")) else: first = last = int(line) context = req.getParameter( "context", user.getPreference(db, "commit.diff.contextLines"), int) first_with_context = max(1, first - context) last_with_context = last + context if user.getPreference(db, "commit.diff.compactMode"): default_compact = "yes" else: default_compact = "no" compact = req.getParameter("compact", default_compact) == "yes" if len(path) == 0 or path[-1:] == "/": raise page.utils.DisplayMessage( title="Invalid path parameter", body= "<p>The path must be non-empty and must not end with a <code>/</code>.</p>", html=True) if path[0] == '/': full_path = path if path != "/": path = path[1:] else: full_path = "/" + path if not path: path = "/" if review_id is None: review = None repository_arg = req.getParameter("repository", "") if repository_arg: repository = gitutils.Repository.fromParameter(db, repository_arg) else: repository = gitutils.Repository.fromSHA1(db, sha1) else: review = dbutils.Review.fromId(db, review_id) repository = review.repository document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() if review: page.utils.generateHeader(body, db, user, lambda target: review_utils.renderDraftItems( db, user, review, target), extra_links=[("r/%d" % review.id, "Back to Review")]) else: page.utils.generateHeader(body, db, user) document.addExternalStylesheet("resource/showfile.css") document.addInternalStylesheet( htmlutils.stripStylesheet( user.getResource(db, "syntax.css")[1], compact)) commit = gitutils.Commit.fromSHA1(db, repository, sha1) file_sha1 = commit.getFileSHA1(full_path) file_id = dbutils.find_file(db, path=path) if file_sha1 is None: raise page.utils.DisplayMessage( title="File does not exist", body=("<p>There is no file named <code>%s</code> in the commit " "<a href='/showcommit?repository=%s&sha1=%s'>" "<code>%s</code></a>.</p>" % (htmlutils.htmlify(textutils.escape(full_path)), htmlutils.htmlify(repository.name), htmlutils.htmlify(sha1), htmlutils.htmlify(sha1[:8]))), html=True) file = diff.File(file_id, path, None, file_sha1, repository) # A new file ID might have been added to the database, so need to commit. db.commit() if file.canHighlight(): requestHighlights(repository, {file.new_sha1: (file.path, file.getLanguage())}) file.loadNewLines(True, request_highlight=True) if review: document.addInternalScript(user.getJS()) document.addInternalScript(review.getJS()) document.addInternalScript( "var changeset = { parent: { id: %(id)d, sha1: %(sha1)r }, child: { id: %(id)d, sha1: %(sha1)r } };" % { 'id': commit.getId(db), 'sha1': commit.sha1 }) document.addInternalScript( "var files = { %(id)d: { new_sha1: %(sha1)r }, %(sha1)r: { id: %(id)d, side: 'n' } };" % { 'id': file_id, 'sha1': file_sha1 }) document.addExternalStylesheet("resource/review.css") document.addExternalScript("resource/review.js") cursor.execute( """SELECT DISTINCT commentchains.id FROM commentchains JOIN commentchainlines ON (commentchainlines.chain=commentchains.id) WHERE commentchains.review=%s AND commentchains.file=%s AND commentchainlines.sha1=%s AND ((commentchains.state!='draft' OR commentchains.uid=%s) AND commentchains.state!='empty') GROUP BY commentchains.id""", (review.id, file_id, file_sha1, user.id)) comment_chain_script = "" for (chain_id, ) in cursor.fetchall(): chain = review_comment.CommentChain.fromId(db, chain_id, user, review=review) chain.loadComments(db, user) comment_chain_script += "commentChains.push(%s);\n" % chain.getJSConstructor( file_sha1) if comment_chain_script: document.addInternalScript(comment_chain_script) document.addExternalStylesheet("resource/comment.css") document.addExternalScript("resource/comment.js") document.addExternalScript("resource/showfile.js") if tabify: document.addExternalStylesheet("resource/tabify.css") document.addExternalScript("resource/tabify.js") tabwidth = file.getTabWidth() indenttabsmode = file.getIndentTabsMode() if user.getPreference(db, "commit.diff.highlightIllegalWhitespace"): document.addInternalStylesheet( user.getResource(db, "whitespace.css")[1], compact) if first is not None: document.addInternalScript( "var firstSelectedLine = %d, lastSelectedLine = %d;" % (first, last)) target = body.div("main") if tabify: target.script(type="text/javascript").text("calculateTabWidth();") table = target.table('file show expanded paleyellow', align='center', cellspacing=0) columns = table.colgroup() columns.col('edge') columns.col('linenr') columns.col('line') columns.col('middle') columns.col('middle') columns.col('line') columns.col('linenr') columns.col('edge') thead = table.thead() cell = thead.tr().td('h1', colspan=8) h1 = cell.h1() def make_url(url_path, path): params = {"sha1": sha1, "path": path} if review is None: params["repository"] = str(repository.id) else: params["review"] = str(review.id) return "%s?%s" % (url_path, urllib.urlencode(params)) h1.a("root", href=make_url("showtree", "/")).text("root") h1.span().text('/') components = path.split("/") for index, component in enumerate(components[:-1]): h1.a(href=make_url("showtree", "/".join(components[:index + 1]))).text( component, escape=True) h1.span().text('/') if first is not None: h1.a(href=make_url("showfile", "/".join(components))).text( components[-1], escape=True) else: h1.text(components[-1], escape=True) h1.span("right").a(href=("/download/%s?repository=%s&sha1=%s" % (urllib.quote(path), repository.name, file_sha1)), download=urllib.quote(path)).text("[download]") h1.span("right").a( href=("/download/%s?repository=%s&sha1=%s" % (urllib.quote(path), repository.name, file_sha1))).text("[view]") table.tbody('spacer top').tr('spacer top').td(colspan=8).text() tbody = table.tbody("lines") yield document.render(stop=tbody, pretty=not compact) for linenr, line in enumerate(file.newLines(True)): linenr = linenr + 1 highlight_class = "" if first is not None: if not (first_with_context <= linenr <= last_with_context): continue if linenr == first: highlight_class += " first-selected" if linenr == last: highlight_class += " last-selected" if tabify: line = htmlutils.tabify(line, tabwidth, indenttabsmode) line = line.replace("\r", "<i class='cr'></i>") row = tbody.tr("line context single", id="f%do%dn%d" % (file.id, linenr, linenr)) row.td("edge").text() row.td("linenr old").text(linenr) row.td("line single whole%s" % highlight_class, id="f%dn%d" % (file.id, linenr), colspan=4).innerHTML(line) row.td("linenr new").text(linenr) row.td("edge").text() if linenr % 500: yield document.render(stop=tbody, pretty=not compact) table.tbody('spacer bottom').tr('spacer bottom').td(colspan=8).text() yield document.render(pretty=not compact)
def renderHome(req, db, user): if user.isAnonymous(): raise page.utils.NeedLogin(req) profiler = profiling.Profiler() cursor = db.cursor() readonly = req.getParameter( "readonly", "yes" if user.name != req.user else "no") == "yes" repository = req.getParameter("repository", None, gitutils.Repository.FromParameter(db)) verified_email_id = req.getParameter("email_verified", None, int) if not repository: repository = user.getDefaultRepository(db) title_fullname = user.fullname if title_fullname[-1] == 's': title_fullname += "'" else: title_fullname += "'s" cursor.execute( "SELECT email FROM usergitemails WHERE uid=%s ORDER BY email ASC", (user.id, )) gitemails = ", ".join([email for (email, ) in cursor]) document = htmlutils.Document(req) html = document.html() head = html.head() body = html.body() if user.name == req.user: actual_user = None else: actual_user = req.getUser(db) def renderHeaderItems(target): if readonly and actual_user and actual_user.hasRole( db, "administrator"): target.a("button", href="/home?user=%s&readonly=no" % user.name).text("Edit") page.utils.generateHeader(body, db, user, generate_right=renderHeaderItems, current_page="home") document.addExternalStylesheet("resource/home.css") document.addExternalScript("resource/home.js") document.addExternalScript("resource/autocomplete.js") if repository: document.addInternalScript(repository.getJS()) else: document.addInternalScript("var repository = null;") if actual_user and actual_user.hasRole(db, "administrator"): document.addInternalScript("var administrator = true;") else: document.addInternalScript("var administrator = false;") document.addInternalScript(user.getJS()) document.addInternalScript("user.gitEmails = %s;" % jsify(gitemails)) document.addInternalScript( "var verifyEmailAddresses = %s;" % jsify(configuration.base.VERIFY_EMAIL_ADDRESSES)) document.setTitle("%s Home" % title_fullname) target = body.div("main") basic = target.table('paleyellow basic', align='center') basic.tr().td('h1', colspan=3).h1().text("%s Home" % title_fullname) def row(heading, value, help=None, extra_class=None): if extra_class: row_class = "line " + extra_class else: row_class = "line" main_row = basic.tr(row_class) main_row.td('heading').text("%s:" % heading) value_cell = main_row.td('value', colspan=2) if callable(value): value(value_cell) else: value_cell.text(value) basic.tr('help').td('help', colspan=3).text(help) def renderFullname(target): if readonly: target.text(user.fullname) else: target.input("value", id="user_fullname", value=user.fullname) target.span("status", id="status_fullname") buttons = target.span("buttons") buttons.button(onclick="saveFullname();").text("Save") buttons.button(onclick="resetFullname();").text("Reset") def renderEmail(target): if not actual_user or actual_user.hasRole(db, "administrator"): cursor.execute( """SELECT id, email, verified FROM useremails WHERE uid=%s ORDER BY id ASC""", (user.id, )) rows = cursor.fetchall() if rows: if len(rows) > 1: target.addClass("multiple") addresses = target.div("addresses") for email_id, email, verified in rows: checked = "checked" if email == user.email else None selected = " selected" if email == user.email else "" label = addresses.label("address inset flex" + selected, data_email_id=email_id) if len(rows) > 1: label.input(name="email", type="radio", value=email, checked=checked) label.span("value").text(email) actions = label.span("actions") if verified is False: actions.a("action unverified", href="#").text("[unverified]") elif verified is True: now = " now" if email_id == verified_email_id else "" actions.span("action verified" + now).text("[verified]") actions.a("action delete", href="#").text("[delete]") else: target.i().text("No email address") target.span("buttons").button("addemail").text("Add email address") elif user.email is None: target.i().text("No email address") elif user.email_verified is False: # Pending verification: don't show to other users. target.i().text("Email address not verified") else: target.span("inset").text(user.email) def renderGitEmails(target): if readonly: target.text(gitemails) else: target.input("value", id="user_gitemails", value=gitemails) target.span("status", id="status_gitemails") buttons = target.span("buttons") buttons.button(onclick="saveGitEmails();").text("Save") buttons.button(onclick="resetGitEmails();").text("Reset") def renderPassword(target): cursor.execute("SELECT password IS NOT NULL FROM users WHERE id=%s", (user.id, )) has_password = cursor.fetchone()[0] if not has_password: target.text("not set") else: target.text("****") if not readonly: if not has_password or (actual_user and actual_user.hasRole( db, "administrator")): target.span("buttons").button( onclick="setPassword();").text("Set password") else: target.span("buttons").button( onclick="changePassword();").text("Change password") row("User ID", str(user.id)) row("User Name", user.name) row("Display Name", renderFullname, "This is the name used when displaying commits or comments.") row("Primary Email", renderEmail, "This is the primary email address, to which emails are sent.", extra_class="email") row("Git Emails", renderGitEmails, "These email addresses are used to map Git commits to the user.") if configuration.base.AUTHENTICATION_MODE == "critic": row("Password", renderPassword, extra_class="password") cursor.execute( """SELECT provider, account FROM externalusers WHERE uid=%s""", (user.id, )) external_accounts = [(auth.PROVIDERS[provider_name], account) for provider_name, account in cursor if provider_name in auth.PROVIDERS] if external_accounts: basic.tr().td('h2', colspan=3).h2().text("External Accounts") for provider, account in external_accounts: def renderExternalAccount(target): url = provider.getAccountURL(account) target.a("external", href=url).text(account) row(provider.getTitle(), renderExternalAccount) profiler.check("user information") filters = page.utils.PaleYellowTable(body, "Filters") filters.titleRight.a("button", href="/tutorial?item=filters").text("Tutorial") cursor.execute( """SELECT repositories.id, repositories.name, repositories.path, filters.id, filters.type, filters.path, NULL, filters.delegate FROM repositories JOIN filters ON (filters.repository=repositories.id) WHERE filters.uid=%s""", (user.id, )) rows = cursor.fetchall() if configuration.extensions.ENABLED: cursor.execute( """SELECT repositories.id, repositories.name, repositories.path, filters.id, 'extensionhook', filters.path, filters.name, filters.data FROM repositories JOIN extensionhookfilters AS filters ON (filters.repository=repositories.id) WHERE filters.uid=%s""", (user.id, )) rows.extend(cursor.fetchall()) FILTER_TYPES = ["reviewer", "watcher", "ignored", "extensionhook"] def rowSortKey(row): (repository_id, repository_name, repository_path, filter_id, filter_type, filter_path, filter_name, filter_data) = row # Rows are grouped by repository first and type second, so sort by # repository name and filter type primarily. # # Secondarily sort by filter name (only for extension hook filters; is # None for regular filters) and filter path. This sorting is mostly to # achieve a stable order; it has no greater meaning. return (repository_name, FILTER_TYPES.index(filter_type), filter_name, filter_path) rows.sort(key=rowSortKey) if rows: repository = None repository_filters = None tbody_reviewer = None tbody_watcher = None tbody_ignored = None tbody_extensionhook = None count_matched_files = {} for (repository_id, repository_name, repository_path, filter_id, filter_type, filter_path, filter_name, filter_data) in rows: if not repository or repository.id != repository_id: repository = gitutils.Repository.fromId(db, repository_id) repository_url = repository.getURL(db, user) filters.addSection(repository_name, repository_url) repository_filters = filters.addCentered().table( "filters callout") tbody_reviewer = tbody_watcher = tbody_ignored = tbody_extensionhook = None if filter_type == "reviewer": if not tbody_reviewer: tbody_reviewer = repository_filters.tbody() tbody_reviewer.tr().th(colspan=5).text("Reviewer") tbody = tbody_reviewer elif filter_type == "watcher": if not tbody_watcher: tbody_watcher = repository_filters.tbody() tbody_watcher.tr().th(colspan=5).text("Watcher") tbody = tbody_watcher elif filter_type == "ignored": if not tbody_ignored: tbody_ignored = repository_filters.tbody() tbody_ignored.tr().th(colspan=5).text("Ignored") tbody = tbody_ignored else: if not tbody_extensionhook: tbody_extensionhook = repository_filters.tbody() tbody_extensionhook.tr().th( colspan=5).text("Extension hooks") tbody = tbody_extensionhook row = tbody.tr() row.td("path").text(filter_path) if filter_type != "extensionhook": delegates = row.td("delegates", colspan=2) if filter_data: delegates.i().text("Delegates: ") delegates.span("names").text(", ".join( filter_data.split(","))) else: role = extensions.role.filterhook.getFilterHookRole( db, filter_id) if role: title = row.td("title") title.text(role.title) data = row.td("data") data.text(filter_data) else: row.td(colspan=2).i().text("Invalid filter") if filter_path == "/": row.td("files").text("all files") else: href = "javascript:void(showMatchedFiles(%s, %s));" % (jsify( repository.name), jsify(filter_path)) row.td("files").a(href=href, id=("f%d" % filter_id)).text("? files") count_matched_files.setdefault(repository_id, []).append(filter_id) links = row.td("links") arguments = (jsify(repository.name), filter_id, jsify(filter_type), jsify(filter_path), jsify(filter_data)) links.a(href="javascript:void(editFilter(%s, %d, %s, %s, %s));" % arguments).text("[edit]") if filter_type != "extensionhook": links.a( href= "javascript:if (deleteFilterById(%d)) location.reload(); void(0);" % filter_id).text("[delete]") links.a(href="javascript:location.href='/config?filter=%d';" % filter_id).text("[preferences]") else: links.a( href= "javascript:if (deleteExtensionHookFilterById(%d)) location.reload(); void(0);" % filter_id).text("[delete]") document.addInternalScript("var count_matched_files = %s;" % json_encode(count_matched_files.values())) else: filters.addCentered().p().b().text("No filters") # Additionally check if there are in fact no repositories. cursor.execute("SELECT 1 FROM repositories") if not cursor.fetchone(): document.addInternalScript("var no_repositories = true;") if not readonly: filters.addSeparator() filters.addCentered().button( onclick="editFilter();").text("Add filter") profiler.check("filters") hidden = body.div("hidden", style="display: none") if configuration.extensions.ENABLED: filterhooks = extensions.role.filterhook.listFilterHooks(db, user) else: filterhooks = [] with hidden.div("filterdialog") as dialog: paragraph = dialog.p() paragraph.b().text("Repository:") paragraph.br() page.utils.generateRepositorySelect(db, user, paragraph, name="repository") paragraph = dialog.p() paragraph.b().text("Filter type:") paragraph.br() filter_type = paragraph.select(name="type") filter_type.option(value="reviewer").text("Reviewer") filter_type.option(value="watcher").text("Watcher") filter_type.option(value="ignored").text("Ignored") for extension, manifest, roles in filterhooks: optgroup = filter_type.optgroup(label=extension.getTitle(db)) for role in roles: option = optgroup.option( value="extensionhook", data_extension_id=extension.getExtensionID(db), data_filterhook_name=role.name) option.text(role.title) paragraph = dialog.p() paragraph.b().text("Path:") paragraph.br() paragraph.input(name="path", type="text") paragraph.span("matchedfiles") regular_div = dialog.div("regular") paragraph = regular_div.p() paragraph.b().text("Delegates:") paragraph.br() paragraph.input(name="delegates", type="text") paragraph = regular_div.p() label = paragraph.label() label.input(name="apply", type="checkbox", checked="checked") label.b().text("Apply to existing reviews") for extension, manifest, roles in filterhooks: for role in roles: if not role.data_description: continue filterhook_id = "%d_%s" % (extension.getExtensionID(db), role.name) extensionhook_div = dialog.div("extensionhook " + filterhook_id, style="display: none") extensionhook_div.innerHTML(role.data_description) paragraph = extensionhook_div.p() paragraph.b().text("Data:") paragraph.br() paragraph.input(type="text") profiler.output(db, user, document) return document
def renderBranches(req, db, user): offset = req.getParameter("offset", 0, filter=int) count = req.getParameter("count", 50, filter=int) cursor = db.cursor() repository = req.getParameter("repository", None, gitutils.Repository.FromParameter(db)) if not repository: repository = user.getDefaultRepository(db) all_branches = [] commit_times = [] if repository: cursor.execute( """SELECT branches.id, branches.name, commits.commit_time FROM branches JOIN repositories ON (repositories.id=branches.repository) JOIN commits ON (commits.id=branches.head) WHERE branches.type='normal' AND branches.name NOT LIKE 'replay/%%' AND repositories.id=%s ORDER BY commits.commit_time DESC LIMIT %s""", (repository.id, count)) for branch_id, branch_name, commit_time in cursor.fetchall(): all_branches.append(branch_id) commit_times.append(commit_time.timetuple()) document = htmlutils.Document(req) if repository: document.setTitle("Branches in %s" % repository.name) else: document.setTitle("Branches") html = document.html() head = html.head() body = html.body() page.utils.generateHeader(body, db, user, current_page="branches") document.addExternalScript("resource/branches.js") if repository: document.addInternalScript(repository.getJS()) extraColumns = [ ExtraColumn( "when", "When", lambda target, index: log.html.renderWhen( target, commit_times[index])) ] render(db, body.div("main"), "All Branches", repository, all_branches[offset:], extraColumns=extraColumns) return document