def renderHeaderItems(target): has_draft_items = review_utils.renderDraftItems(db, user, review, target) target = target.div("buttons") if not has_draft_items: if review.state == "open": if review.accepted(db): target.button(id="closeReview", onclick="closeReview();").text("Close Review") else: if user in review.owners or user.getPreference(db, "review.pingAnyReview"): target.button(id="pingReview", onclick="pingReview();").text("Ping Review") if user in review.owners or user.getPreference(db, "review.dropAnyReview"): target.button(id="dropReview", onclick="dropReview();").text("Drop Review") if user in review.owners and not review.description: target.button(id="writeDescription", onclick="editDescription();").text("Write Description") else: target.button(id="reopenReview", onclick="reopenReview();").text("Reopen Review") target.span("buttonscope buttonscope-global")
def generateRight(target): review_utils.renderDraftItems(db, user, review, target)
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") else: target.h1(align="center").text("No comments.") profiler.output(db, user, document) yield document.render(pretty=not compact)
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 renderHeaderItems(target): review_utils.renderDraftItems(db, user, review, target) target.div("buttons").span("buttonscope buttonscope-global")
def renderFilterChanges(req, db, user): review_id = req.getParameter("review", filter=int) first_sha1 = req.getParameter("first", None) last_sha1 = req.getParameter("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: 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( title="Filtering failed!", body=("First selected commit is a merge commit. Please go back " "and select a different range of commits."), review=review) if first_commit.parents: from_commit = gitutils.Commit.fromSHA1(db, review.repository, first_commit.parents[0]) else: from_commit = None to_commit = last_commit commits = log.commitset.CommitSet.fromRange(db, from_commit, to_commit) if not commits: raise page.utils.DisplayMessage( title="Filtering failed!", body=("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")]) 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: row = basic.tr("directory", critic_level=-1) row.td("select").input(type="checkbox") row.td("path").preformatted().i().text("Everything") row.td().text() level = -1 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") if level > -1: row.td("path").preformatted().innerHTML((" " * (len(base + name) - 1)) + "…/" + htmlutils.htmlify(file_name)) else: row.td("path").preformatted().innerHTML(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", review=review) 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 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")]) 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 generateButtons(target): review_utils.renderDraftItems(db, user, review, target) buttons = target.div("buttons") if user.getPreference(db, "debug.extensions.customProcessCommits"): buttons.button(onclick='customProcessCommits();').text("Process Commits") buttons.span("buttonscope buttonscope-global")
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")]) 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("paleyellow") 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("paleyellow") 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